/** * The unified product's shell for this host: the ONE implementation both seats * mount (plan dsh-panel-unification Tasks 3–4). * * The right-Sidebar tab and the keyed `main` panel are the same product at two * sizes — same nav, same destination ids, same screens, same copy, same absence * decisions. Everything they share lives here, so a prop only one seat can * supply becomes a prop of this module instead of a second call site: * * - `PANEL_DESTINATIONS` is the destination set, in IA §8.2 order. * - `PanelSeat` owns the transport states and the adapter work. Its data is the * SESSION DIGEST (enforcement / knowledge / selection, plus the delivery * fallback) read once, plus two per-view reads (`workflow-detail`, `iterations`) * issued by the screen that needs them. Nothing polls. * - `PanelShell` is the pure composition (provider + nav + destination body), so * a test can render any destination without a transport. * * No product markup, styling or copy is declared here: they come from * `@mstar-harness/ui`. This module is the adapter, and nothing else. */ import * as React from 'react'; import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'; import type { SessionId } from '@deepseek-ai/dsh-session'; import { type Copy, type Locale } from '@mstar-harness/ui'; import type { IterationGateStep, WorkflowDTO } from '@mstar-harness/engine'; import type { PanelSnapshot } from '../../panel/panel-snapshot.ts'; import type { MstarWorkflowPickerClient } from './workflow-picker-client.ts'; import type { MstarPanelSnapshotClient } from './panel-snapshot-client.ts'; import { type IssueQuery } from '@mstar-harness/ui'; import { type DetailView } from './destinations.tsx'; import { type MstarDashboardViewState } from './use-mstar-dashboard-view.ts'; import { type UseSessions } from './use-mstar-workflow-picker.ts'; import type { PanelDestination } from './panel-store.ts'; import type { PanelKey } from './locale.ts'; import type { ColorScheme, HostThemeFace } from './theme.ts'; /** * The host locale face the product needs, declared structurally (the * `LocaleFace` getSnapshot/subscribe pair) so this module never value-imports a * host package. */ export type HostLocaleFace = { getSnapshot(): { readonly active: string; }; subscribe(listener: () => void): () => void; }; /** The five product destinations, in IA §8.2 order; `focus` is this host's landing. */ export declare const PANEL_DESTINATIONS: readonly ['overview', 'issues', 'workflows', 'iterations', 'projects']; /** The full nav: the session landing, then the five destinations. */ export declare const SHELL_DESTINATIONS: readonly PanelDestination[]; /** The host's locale id → the product's two locales. */ export declare function productLocale(active: string): Locale; /** The shared nav's items: the session landing first, then the five destinations. */ export declare function navItems(copy: Copy): Array<{ id: string; label: string; }>; /** The bound workflow as the shared model wants it: a three-state member. */ export type WorkflowMember = { readonly state: 'read'; readonly data: WorkflowDTO | null; } | { readonly state: 'not-applicable'; readonly reason: string; } | { readonly state: 'unavailable'; readonly reason: string; }; /** Map one per-view read of `workflow-detail` onto the member the model takes. */ export declare function workflowMemberOf(state: MstarDashboardViewState): { readonly member: WorkflowMember; readonly phase: 'loading' | 'loaded'; }; /** * The delivery line as the per-view `iterations` read proves it. * * `undefined` = no answer (the digest's iteration member is the only evidence); * `null` = the read answered and none of the bound workflow's plans is * attributed to an iteration, so the plans are delivered standalone. */ export declare function deliveryFromView(state: MstarDashboardViewState, planIds: readonly string[]): { iterationId: string; gateStep: IterationGateStep | null; } | null | undefined; /** The pure shell: provider, nav, and the active destination's body. */ export declare function PanelShell(props: { snapshot: PanelSnapshot; /** The bound workflow as the per-view `workflow-detail` read answered it. */ workflow: WorkflowMember; /** A pending workflow read: the board renders no arm for a fact not yet known. */ phase?: 'loading' | 'loaded'; /** * The session's delivery as the per-view `iterations` read proves it: a value * wins, `null` means "the bound plans are delivered standalone", and * `undefined` means the envelope's own member is the only evidence. */ delivery?: { iterationId: string; gateStep: IterationGateStep | null; } | null; destination: PanelDestination; locale: Locale; /** The host's resolved colour scheme (only the scheme is inherited). */ scheme?: ColorScheme; /** Accessible name for the nav landmark (the panel's own name). */ navLabel: string; onSelect: (destination: string) => void; select: ((workflowId: string) => void) | null; /** The ACTIVE destination's per-view read (one request, issued by the seat). */ reads?: Partial>; /** Issues owns the only interactive seat a destination has (its filter query). */ issueQuery?: IssueQuery; onIssueQueryChange?: (query: IssueQuery) => void; /** The open detail route, or null for the destination's list. */ detail?: { view: DetailView; id: string; state: MstarDashboardViewState; } | null; onBack?: () => void; /** Open one record from its destination's list (the shell owns the open record). */ onOpen?: (view: DetailView, id: string) => void; /** A project detail's scoped Iterations read. */ projectIterations?: MstarDashboardViewState; /** A project detail's scoped Issues read. */ projectIssues?: MstarDashboardViewState; }): React.ReactElement; /** The dictionary key that explains one reason code (unknown → the generic one). */ export declare function failureKey(reason: string): PanelKey; /** * The read target for an open detail. The engine has NO `project-detail` view: * a project's page is composed from the views that exist, so the project open * reads its `roadmap` scoped to the project. Every other detail is its own view. */ export declare function detailReadTarget(view: DetailView, id: string): { view: string; params?: Record; id?: string; }; /** * The view a destination reads. `iterations` reads its own list — the delivery * read is gated on a bound workflow, and a destination must answer for an * unbound (or fresh) session too. */ export declare function destinationReadView(destination: PanelDestination): string | null; /** * The parameters a destination's read carries. Always EXPLICIT: the engine's * default page is 50 rows, and a destination with no pager would silently drop * everything past that. `iterations` asks for the documented ceiling (200) and * the adapter discloses any truncation from the DTO's own `total`. */ export declare function destinationReadParams(destination: PanelDestination): Record; /** * One seat's body: the digest read, the two per-view reads, the adapter and the * shell. `visible` is the seat's own gate (a collapsed sidebar column renders * nothing; the main panel is always visible). */ export type PanelSeatProps = { /** The seat's visibility gate (`useTabInfo().tab.visible` for the sidebar; always true for the main panel). */ visible: boolean; /** The active destination, owned by the seat (entry store or component state). */ destination: PanelDestination; /** Destination selection, owned by the seat's own state. */ onSelect: (destination: string) => void; /** The seat's typed translate face. */ t: TranslateNS<'mstar-panel'>; sessionId?: SessionId; useSessions?: UseSessions; picker?: MstarWorkflowPickerClient; panelSnapshot?: MstarPanelSnapshotClient; localeFace?: HostLocaleFace; themeFace?: HostThemeFace; }; export declare function PanelSeat(props: PanelSeatProps): React.ReactElement | null;