dsh-report-ledger 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["Fragment"],"sources":["../src/client/timeline-model.ts","../src/client/ReportsView.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * The timeline's data model: turning a payload into rows, and filtering them.\n *\n * Deliberately a plain module with no React and no DOM so the decisions a user\n * actually feels — which rows appear, what a search matches, how the status\n * chips count — are pinned by deterministic tests instead of being inferred from\n * a browser. The view imports this; it does not re-implement it.\n *\n * @module dsh-report-ledger/client/timeline-model\n */\n\nimport type { ReportFrontMatter, ReportStatus, TimelinePayload } from '../shared/wire.ts'\n\n/** One rendered timeline row: a session branching, or a report appearing. */\nexport type Row =\n | {\n readonly kind: 'session'\n readonly at: number\n readonly id: string\n readonly depth: number\n readonly title?: string\n readonly delegated: boolean\n readonly live: boolean\n }\n | {\n readonly kind: 'report'\n readonly at: number\n readonly depth: number\n readonly front: ReportFrontMatter\n }\n\n/** Which lifecycle states the status chips currently admit. */\nexport type StatusFilter = 'all' | ReportStatus\n\n/** How a report is placed on the time axis. */\nexport type ReportTimeBasis = 'created' | 'updated'\n\n/** Filters the view applies to the assembled rows. */\nexport interface RowFilter {\n /** Lifecycle filter; applies to report rows only. */\n readonly status: StatusFilter\n /** Free text; applies to every row. */\n readonly text: string\n}\n\n/**\n * Build the merged, time-ordered row list.\n *\n * Sessions are placed at their creation time and reports at theirs, so the list\n * reads as the collaboration actually unfolded: a session appears where it was\n * branched, a report where it was opened. Report depth comes from the session\n * that authored it, which is what keeps the indentation meaningful even when the\n * session row itself is filtered out.\n * @param payload - the endpoint payload.\n * @param basis - whether to place a report at its creation or its last activity.\n * @returns the rows, oldest first.\n */\nexport function buildRows(payload: TimelinePayload, basis: ReportTimeBasis = 'created'): Row[] {\n const depth = new Map<string, number>()\n const rows: Row[] = []\n for (const node of payload.sessions) {\n depth.set(node.id, node.depth)\n rows.push({\n kind: 'session',\n at: node.createdAt ?? 0,\n id: node.id,\n depth: node.depth,\n delegated: node.delegated,\n live: node.live,\n ...(node.title === undefined ? {} : { title: node.title }),\n })\n }\n for (const front of payload.reports) {\n rows.push({\n kind: 'report',\n at: basis === 'created' ? front.created : front.updated,\n depth: depth.get(front.from) ?? 0,\n front,\n })\n }\n rows.sort((left, right) => left.at - right.at\n || (left.kind === right.kind ? 0 : left.kind === 'session' ? -1 : 1))\n return rows\n}\n\n/** Every string a report row can be found by. */\nfunction reportHaystack(front: ReportFrontMatter): string {\n return [\n front.report,\n front.subject,\n front.from,\n front.fromName ?? '',\n ...front.to,\n ...front.cc,\n ...front.authors,\n front.task ?? '',\n ...front.artifacts,\n ].join('\\n').toLowerCase()\n}\n\n/** Every string a session row can be found by. */\nfunction sessionHaystack(row: Extract<Row, { kind: 'session' }>): string {\n return `${row.title ?? ''}\\n${row.id}`.toLowerCase()\n}\n\n/**\n * Whether one row survives the current filter.\n * @param row - the candidate row.\n * @param filter - the active filter.\n * @returns true when the row should be rendered.\n */\nexport function rowMatches(row: Row, filter: RowFilter): boolean {\n const needle = filter.text.trim().toLowerCase()\n if (row.kind === 'session') {\n return needle === '' || sessionHaystack(row).includes(needle)\n }\n if (filter.status !== 'all' && row.front.status !== filter.status) return false\n return needle === '' || reportHaystack(row.front).includes(needle)\n}\n\n/**\n * Apply a filter to the row list.\n * @param rows - the assembled rows.\n * @param filter - the active filter.\n * @returns the surviving rows, in their original order.\n */\nexport function filterRows(rows: readonly Row[], filter: RowFilter): Row[] {\n return rows.filter((row) => rowMatches(row, filter))\n}\n\n/** Counts per lifecycle state, plus the total. */\nexport interface StatusCounts {\n /** Every report. */\n readonly all: number\n /** Reports awaiting action or awareness. */\n readonly open: number\n /** Reports whose recipients have acknowledged. */\n readonly acked: number\n /** Reports the owners have concluded. */\n readonly closed: number\n}\n\n/**\n * Count reports by lifecycle state.\n *\n * Counted from the whole subtree payload rather than the filtered rows, so the\n * chips keep showing the full picture while one of them is applied — a filter\n * whose own label changed when you used it would make it impossible to see how\n * much you had excluded.\n * @param reports - every report in the payload.\n * @returns the counts.\n */\nexport function statusCounts(reports: readonly ReportFrontMatter[]): StatusCounts {\n const counts = { all: reports.length, open: 0, acked: 0, closed: 0 }\n for (const front of reports) {\n if (front.status === 'open') counts.open += 1\n else if (front.status === 'acked') counts.acked += 1\n else if (front.status === 'closed') counts.closed += 1\n }\n return counts\n}\n\n/**\n * Whether a filter would hide anything.\n * @param filter - the active filter.\n * @returns true when a status or text constraint is in force.\n */\nexport function isFiltering(filter: RowFilter): boolean {\n return filter.status !== 'all' || filter.text.trim() !== ''\n}\n\n/**\n * Count how many report rows are visible under a filter.\n * @param rows - every assembled row.\n * @param filter - the active filter.\n * @returns the number of visible report rows.\n */\nexport function visibleReports(rows: readonly Row[], filter: RowFilter): number {\n let count = 0\n for (const row of filterRows(rows, filter)) if (row.kind === 'report') count += 1\n return count\n}\n\n/**\n * The report ids present in one payload.\n *\n * A thread link can point outside the subtree — a report answering one opened by\n * an unrelated session, for instance — and the detail endpoint is ledger-scoped,\n * so such a report still opens. Knowing which ids are in the list is what lets\n * the view say so instead of silently showing a card that is not in the timeline.\n * @param payload - the endpoint payload.\n * @returns the ids of every report in the payload.\n */\nexport function reportIds(payload: TimelinePayload): ReadonlySet<string> {\n const ids = new Set<string>()\n for (const front of payload.reports) ids.add(front.report)\n return ids\n}\n\n/** Body length above which the detail panel shows only a head. */\nexport const BODY_DISPLAY_LIMIT = 4000\n\n/** How much of an over-long body is shown. */\nexport const BODY_HEAD_LIMIT = 1200\n\n/**\n * Trim a body for the detail panel.\n *\n * Mirrors what `report_read` already does for the model: the full text stays in\n * the ledger file, and both readers get a bounded view plus a pointer rather than\n * an unbounded paste. Keeping the human view and the model view bounded the same\n * way means neither can be surprised by what the other sees.\n * @param body - the raw body.\n * @param limit - length above which truncation applies.\n * @param head - how many characters to keep when truncating.\n * @returns the display text and whether it was cut.\n */\nexport function displayBody(\n body: string,\n limit: number = BODY_DISPLAY_LIMIT,\n head: number = BODY_HEAD_LIMIT,\n): { readonly text: string; readonly truncated: boolean } {\n if (body.length <= limit) return { text: body, truncated: false }\n return { text: body.slice(0, head), truncated: true }\n}\n","/**\n * The report timeline view — the `conversation.view` tab this plugin registers.\n *\n * It renders one top-to-bottom timeline of the collaboration: the session\n * subtree as it branched, interleaved with the reports those sessions produced\n * or were copied on. Rows carry only the digest, so a long-running tree stays\n * scannable; clicking a report opens its transfer path and body on demand.\n *\n * Data comes from this plugin's own read-only endpoint rather than a generated\n * remote: the host assembles the subtree and filters the ledger to the sessions\n * in it, which the browser cannot do on its own because a report's transfer path\n * is a host-side file, not session state.\n *\n * A blank session never reaches this component — the conversation slot is\n * omitted entirely in that state.\n *\n * @module dsh-report-ledger/client/ReportsView\n */\n\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport type { ReportDetail, TimelinePayload } from '../shared/wire.ts'\nimport {\n buildRows,\n displayBody,\n filterRows,\n isFiltering,\n reportIds,\n statusCounts,\n visibleReports,\n type ReportTimeBasis,\n type Row,\n type StatusFilter,\n} from './timeline-model.ts'\nimport type { ReportLedgerKey } from './locales.ts'\n\n/** Translate one key, substituting `{name}` placeholders. */\nexport type Translate = (key: ReportLedgerKey, params?: Record<string, string | number>) => string\n\n/** Props the registration passes through, plus the locale seat. */\nexport interface ReportsViewProps {\n /** The session the view is bound to. */\n sessionId?: string\n /** Injected by the registration closure from the plugin's locale namespace. */\n t: Translate\n}\n\nconst TIMELINE_URL = '/api/report-ledger/timeline'\nconst REPORT_URL = '/api/report-ledger/report'\n\n/** Read one endpoint and unwrap its result envelope. */\nasync function getJson<T>(url: string): Promise<T> {\n const response = await fetch(url, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n let body: unknown\n try {\n body = await response.json()\n } catch {\n throw new Error(`HTTP ${response.status}`)\n }\n const envelope = body as { ok?: boolean; code?: string; value?: T }\n if (envelope.ok !== true) throw new Error(envelope.code ?? `HTTP ${response.status}`)\n return envelope.value as T\n}\n\n/** Substitute `{name}` placeholders. */\nfunction fill(template: string, params: Record<string, string | number>): string {\n return template.replace(/\\{(\\w+)\\}/g, (whole, name: string) =>\n Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : whole)\n}\n\n/** Format one timestamp for a timeline row. */\nfunction stamp(at: number | undefined): string {\n if (at === undefined || at <= 0) return ''\n return new Date(at).toLocaleString()\n}\n\n/** Colour a status chip from the theme's state tokens. */\nfunction statusStyle(status: string): Record<string, string> {\n if (status === 'acked') {\n return {\n color: 'var(--dsw-alias-state-success-label, #1a7f37)',\n background: 'var(--dsw-alias-state-success-bg, rgba(26,127,55,0.10))',\n }\n }\n if (status === 'closed') {\n return {\n color: 'var(--dsw-alias-label-tertiary, #888)',\n background: 'var(--dsw-alias-bg-layer-2, rgba(0,0,0,0.04))',\n }\n }\n return {\n color: 'var(--dsw-alias-state-warn-label, #9a6700)',\n background: 'var(--dsw-alias-state-warn-bg, rgba(154,103,0,0.10))',\n }\n}\n\n/** Storage key for the remembered status filter. */\nconst FILTER_KEY = 'dsh.reportLedger.filter.v1'\n\n/** Read the remembered status filter, tolerating absent or damaged storage. */\nfunction rememberedStatus(): StatusFilter {\n try {\n const raw = globalThis.localStorage?.getItem(FILTER_KEY)\n if (typeof raw === 'string') {\n const parsed = JSON.parse(raw) as { status?: unknown }\n if (parsed.status === 'open' || parsed.status === 'acked' || parsed.status === 'closed') return parsed.status\n }\n } catch {\n // A restricted or full storage just means the filter is not remembered.\n }\n return 'all'\n}\n\n/**\n * Render the report timeline.\n * @param props - the session binding and the locale seat.\n * @returns the tab content.\n */\nexport function ReportsView(props: ReportsViewProps): ReactElement {\n const { sessionId, t } = props\n const [payload, setPayload] = useState<TimelinePayload | undefined>(undefined)\n const [error, setError] = useState<string | undefined>(undefined)\n const [nonce, setNonce] = useState(0)\n const [openReport, setOpenReport] = useState<string | undefined>(undefined)\n const [detail, setDetail] = useState<ReportDetail | undefined>(undefined)\n const [detailError, setDetailError] = useState<string | undefined>(undefined)\n const [status, setStatus] = useState<StatusFilter>(rememberedStatus)\n const [text, setText] = useState('')\n const [basis, setBasis] = useState<ReportTimeBasis>('created')\n\n useEffect(() => {\n try {\n globalThis.localStorage?.setItem(FILTER_KEY, JSON.stringify({ status }))\n } catch {\n // Remembering the filter is a convenience, never a requirement.\n }\n }, [status])\n\n useEffect(() => {\n if (sessionId === undefined || sessionId === '') {\n setError('no-session')\n return\n }\n let cancelled = false\n setError(undefined)\n getJson<TimelinePayload>(`${TIMELINE_URL}?root=${encodeURIComponent(sessionId)}`)\n .then((value) => { if (!cancelled) setPayload(value) })\n .catch((cause: unknown) => { if (!cancelled) setError(cause instanceof Error ? cause.message : String(cause)) })\n return () => { cancelled = true }\n }, [sessionId, nonce])\n\n useEffect(() => {\n if (openReport === undefined) return\n let cancelled = false\n setDetail(undefined)\n setDetailError(undefined)\n getJson<ReportDetail>(`${REPORT_URL}?id=${encodeURIComponent(openReport)}`)\n .then((value) => { if (!cancelled) setDetail(value) })\n .catch((cause: unknown) => { if (!cancelled) setDetailError(cause instanceof Error ? cause.message : String(cause)) })\n return () => { cancelled = true }\n }, [openReport, nonce])\n\n const rows = useMemo<Row[]>(() => (payload === undefined ? [] : buildRows(payload, basis)), [payload, basis])\n const counts = useMemo(() => statusCounts(payload?.reports ?? []), [payload])\n const inList = useMemo(() => (payload === undefined ? new Set<string>() : reportIds(payload)), [payload])\n const shownBody = useMemo(() => displayBody(detail?.body ?? ''), [detail])\n const filter = useMemo(() => ({ status, text }), [status, text])\n const visible = useMemo<Row[]>(() => {\n const kept = filterRows(rows, filter)\n // The detail panel is anchored to a report row, so the open report must HAVE a\n // row. Two cases would otherwise leave the panel with nowhere to render: a\n // thread link pointing at a report outside this tree's payload, and any filter\n // applied while a report is open. Both are real user paths, and in the first\n // the panel is the only place the \"not in this tree\" notice can appear — so the\n // row is synthesized from the fetched detail instead of the panel being hoisted\n // out of the list.\n if (openReport === undefined || detail === undefined) return kept\n if (kept.some((row) => row.kind === 'report' && row.front.report === openReport)) return kept\n return [...kept, { kind: 'report', at: detail.front.created, depth: 0, front: detail.front }]\n }, [rows, filter, openReport, detail])\n // Reports surviving the FILTER, as opposed to `visible`, which also carries a\n // synthesized row for the open report. Keeping the two apart is what lets the\n // panel say which of the two reasons applies: outside this tree, or hidden by\n // the current filter. Claiming \"another line\" for a filtered report would be\n // simply wrong.\n const filteredReportIds = useMemo(() => {\n const ids = new Set<string>()\n for (const row of filterRows(rows, filter)) if (row.kind === 'report') ids.add(row.front.report)\n return ids\n }, [rows, filter])\n const filtering = isFiltering(filter)\n // Counted from the unfiltered row set so a synthesized out-of-tree row cannot\n // inflate the summary.\n const visibleReportCount = useMemo(() => visibleReports(rows, filter), [rows, filter])\n\n const refresh = useCallback(() => { setNonce((value) => value + 1) }, [])\n\n const shell: Record<string, string | number> = {\n padding: '16px 20px',\n overflowY: 'auto',\n height: '100%',\n font: 'var(--dsw-font-xs-13, 13px/1.6 system-ui, sans-serif)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n }\n const head: Record<string, string | number> = {\n display: 'flex',\n alignItems: 'baseline',\n gap: '12px',\n marginBottom: '12px',\n }\n const summary: Record<string, string | number> = {\n color: 'var(--dsw-alias-label-tertiary, #888)',\n fontSize: 'var(--dsw-font-xxs-12, 12)',\n flex: '1 1 auto',\n }\n const button: Record<string, string | number> = {\n font: 'inherit',\n color: 'var(--dsw-alias-label-secondary, inherit)',\n background: 'var(--dsw-alias-bg-layer-2, transparent)',\n border: '1px solid var(--dsw-alias-border-l2, rgba(0,0,0,0.12))',\n borderRadius: '6px',\n padding: '3px 10px',\n cursor: 'pointer',\n }\n const chip = (extra: Record<string, string>): Record<string, string | number> => ({\n display: 'inline-block',\n padding: '0 6px',\n borderRadius: '999px',\n fontSize: 'var(--dsw-font-xxxs-11, 11)',\n lineHeight: '17px',\n marginRight: '6px',\n ...extra,\n })\n const tools: Record<string, string | number> = {\n display: 'flex',\n alignItems: 'center',\n flexWrap: 'wrap',\n gap: '6px',\n marginBottom: '10px',\n }\n const time: Record<string, string | number> = {\n color: 'var(--dsw-alias-label-caption, #999)',\n fontSize: 'var(--dsw-font-xxxs-11, 11)',\n whiteSpace: 'nowrap',\n }\n\n if (error !== undefined) {\n return (\n <div style={shell}>\n <div style={{ color: 'var(--dsw-alias-state-error-label, #b00)' }}>{t('view.error', { code: error })}</div>\n <div style={{ marginTop: '8px' }}>\n <button type=\"button\" style={button} onClick={refresh}>{t('view.retry')}</button>\n </div>\n </div>\n )\n }\n\n if (payload === undefined) {\n return <div style={shell}>{t('view.loading')}</div>\n }\n\n const statusLabel: Record<StatusFilter, ReportLedgerKey> = {\n all: 'filter.all',\n open: 'filter.open',\n acked: 'filter.acked',\n closed: 'filter.closed',\n }\n const statusCount: Record<StatusFilter, number> = {\n all: counts.all,\n open: counts.open,\n acked: counts.acked,\n closed: counts.closed,\n }\n const chips: StatusFilter[] = ['all', 'open', 'acked', 'closed']\n const searchInput: Record<string, string | number> = {\n flex: '1 1 200px',\n minWidth: '140px',\n padding: '3px 8px',\n font: 'inherit',\n color: 'var(--dsw-alias-label-primary, inherit)',\n background: 'var(--dsw-alias-bg-base, rgba(0,0,0,0.02))',\n border: '1px solid var(--dsw-alias-border-l2, rgba(0,0,0,0.12))',\n borderRadius: '6px',\n }\n\n return (\n <div style={shell}>\n <div style={head}>\n <strong>{t('view.tab')}</strong>\n <span style={summary}>\n {filtering\n ? t('filter.showing', { visible: visibleReportCount, total: counts.all })\n : t('view.summary', { sessions: payload.sessions.length, reports: payload.reports.length })}\n </span>\n <button type=\"button\" style={button} onClick={refresh}>{t('view.refresh')}</button>\n </div>\n\n <div style={tools} title={t('filter.hint')}>\n {chips.map((candidate) => {\n const active = status === candidate\n return (\n <button\n key={candidate}\n type=\"button\"\n aria-pressed={active}\n onClick={() => { setStatus(candidate) }}\n style={{\n ...button,\n ...(active\n ? {\n color: 'var(--dsw-alias-label-primary, inherit)',\n background: 'var(--dsw-alias-interactive-bg-active, rgba(0,0,0,0.08))',\n borderColor: 'var(--dsw-alias-border-l3, rgba(0,0,0,0.20))',\n }\n : {}),\n }}\n >\n {`${t(statusLabel[candidate])} ${statusCount[candidate]}`}\n </button>\n )\n })}\n <input\n type=\"search\"\n value={text}\n placeholder={t('filter.search')}\n aria-label={t('filter.search')}\n onChange={(event) => { setText(event.target.value) }}\n style={searchInput}\n />\n {filtering ? (\n <button\n type=\"button\"\n style={button}\n onClick={() => { setStatus('all'); setText('') }}\n >\n {t('filter.clear')}\n </button>\n ) : null}\n <button\n type=\"button\"\n style={button}\n title={t('basis.hint')}\n onClick={() => { setBasis(basis === 'created' ? 'updated' : 'created') }}\n >\n {t(basis === 'created' ? 'basis.created' : 'basis.updated')}\n </button>\n </div>\n\n {rows.length === 0 ? <div style={{ color: 'var(--dsw-alias-label-tertiary, #888)' }}>{t('view.empty')}</div> : null}\n {rows.length > 0 && visible.length === 0 ? (\n <div style={{ color: 'var(--dsw-alias-label-tertiary, #888)' }}>{t('filter.none')}</div>\n ) : null}\n\n {visible.map((row, index) => {\n const pad = 8 + row.depth * 18\n if (row.kind === 'session') {\n return (\n <div key={`s-${row.id}-${index}`} style={{ display: 'flex', gap: '8px', padding: `4px 0 4px ${pad}px`, alignItems: 'baseline' }}>\n <span style={{ color: 'var(--dsw-alias-label-tertiary, #999)' }}>●</span>\n <span style={{ flex: '1 1 auto' }}>\n {row.delegated ? <span style={chip({ color: 'var(--dsw-alias-state-business-label, #0969da)', background: 'var(--dsw-alias-state-business-bg, rgba(9,105,218,0.10))' })}>{t('view.delegated')}</span> : null}\n {row.live ? <span style={chip({ color: 'var(--dsw-alias-state-success-label, #1a7f37)', background: 'var(--dsw-alias-state-success-bg, rgba(26,127,55,0.10))' })}>{t('view.live')}</span> : null}\n {row.title ?? row.id}\n </span>\n <span style={time}>{stamp(row.at)}</span>\n </div>\n )\n }\n const front = row.front\n const expanded = openReport === front.report\n return (\n <div key={`r-${front.report}`} style={{ paddingLeft: `${pad}px` }}>\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => { setOpenReport(expanded ? undefined : front.report) }}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n setOpenReport(expanded ? undefined : front.report)\n }\n }}\n style={{\n display: 'flex',\n gap: '8px',\n alignItems: 'baseline',\n padding: '5px 8px',\n margin: '2px 0',\n borderRadius: '6px',\n cursor: 'pointer',\n background: 'var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.02))',\n border: '1px solid var(--dsw-alias-border-l1, rgba(0,0,0,0.06))',\n }}\n >\n <span style={{ color: 'var(--dsw-alias-label-tertiary, #999)' }}>{expanded ? '▾' : '▸'}</span>\n <span style={{ flex: '1 1 auto' }}>\n <span style={chip(statusStyle(front.status))}>{front.status}</span>\n <strong>{front.report}</strong> {front.subject}\n {/* The task chip is a filter shortcut, not decoration: clicking it\n reuses the search box, so grouping by collaboration needs no\n state of its own. It stops the click from also toggling the\n detail panel. */}\n {front.task === undefined ? null : (\n <button\n type=\"button\"\n title={fill(t('row.filterByTask'), { task: front.task })}\n onClick={(event) => { event.stopPropagation(); setText(front.task as string) }}\n style={{\n ...chip({\n color: 'var(--dsw-alias-state-business-label, #0969da)',\n background: 'var(--dsw-alias-state-business-bg, rgba(9,105,218,0.10))',\n }),\n border: 0,\n cursor: 'pointer',\n font: 'inherit',\n }}\n >\n {front.task}\n </button>\n )}\n <div style={{ ...time, marginTop: '2px' }}>\n {`from=${front.from}`}\n {front.to.length > 0 ? ` → ${front.to.join(', ')}` : ''}\n {front.cc.length > 0 ? ` cc ${front.cc.join(', ')}` : ''}\n {` · ${fill(t('row.hops'), { count: front.hops })}`}\n {front.authors.length > 1 ? ` · ${fill(t('row.authors'), { count: front.authors.length })}` : ''}\n {front.last === undefined ? '' : ` · last=${front.last.action}`}\n </div>\n </span>\n <span style={time}>{stamp(front.updated)}</span>\n </div>\n\n {expanded ? (\n <div style={{\n margin: '4px 0 10px 24px',\n padding: '10px 12px',\n borderRadius: '6px',\n background: 'var(--dsw-alias-bg-layer-2, rgba(0,0,0,0.03))',\n border: '1px solid var(--dsw-alias-border-l1, rgba(0,0,0,0.06))',\n }}>\n {detailError !== undefined ? <div style={{ color: 'var(--dsw-alias-state-error-label, #b00)' }}>{t('view.error', { code: detailError })}</div> : null}\n {detail === undefined && detailError === undefined ? <div style={time}>{t('view.detail.loading')}</div> : null}\n {detail === undefined ? null : (\n <>\n <div style={{ marginBottom: '6px' }}><strong>{t('view.detail.route')}</strong></div>\n {detail.hops.length === 0 ? <div style={time}>{t('view.detail.routeEmpty')}</div> : (\n <div style={{ display: 'grid', gridTemplateColumns: 'auto auto auto 1fr', gap: '2px 10px', marginBottom: '8px' }}>\n {detail.hops.map((hop, hopIndex) => (\n <Fragment key={`h-${hopIndex}`}>\n <span style={time}>{stamp(hop.at)}</span>\n <span><strong>{hop.action}</strong></span>\n <span style={time}>{hop.actor}</span>\n <span style={time}>{hop.to.length === 0 ? (hop.note ?? '') : `→ ${hop.to.join(', ')}${hop.note === undefined ? '' : ` (${hop.note})`}`}</span>\n </Fragment>\n ))}\n </div>\n )}\n {detail.pending.length === 0 ? null : (\n <div style={{ ...time, color: 'var(--dsw-alias-state-warn-label, #9a6700)' }}>\n {fill(t('view.detail.pending'), { targets: detail.pending.join(', ') })}\n </div>\n )}\n\n {(() => {\n const parent = detail.front.parent\n const children = detail.front.children\n if (parent === undefined && children.length === 0) return null\n const link = (target: string): ReactElement => (\n <button\n key={target}\n type=\"button\"\n title={fill(t('detail.openLinked'), { report: target })}\n onClick={() => { setOpenReport(target) }}\n style={{ ...button, marginRight: '6px', font: 'var(--dsw-font-xxxs-11, 11px/1.4 ui-monospace, monospace)' }}\n >\n {target}\n </button>\n )\n return (\n <div style={{ marginBottom: '8px' }}>\n <strong>{t('detail.thread')}</strong>\n {parent === undefined ? null : (\n <div style={{ marginTop: '2px' }}>\n <span style={time}>{`${t('detail.parent')}: `}</span>{link(parent)}\n </div>\n )}\n {children.length === 0 ? null : (\n <div style={{ marginTop: '2px' }}>\n <span style={time}>{`${t('detail.children')}: `}</span>{children.map(link)}\n </div>\n )}\n </div>\n )\n })()}\n\n {/* Kept OUT of the thread block above: that block returns early\n when a report has no thread links, which is the common case\n for exactly the reports this notice is about. */}\n {inList.has(detail.front.report) ? null : (\n <div style={{ ...time, marginBottom: '8px' }}>\n {fill(t('detail.outside'), { report: detail.front.report })}\n {' '}\n <button type=\"button\" style={button} onClick={() => { setOpenReport(undefined) }}>\n {t('detail.backToList')}\n </button>\n </div>\n )}\n {inList.has(detail.front.report) && !filteredReportIds.has(detail.front.report) ? (\n <div style={{ ...time, marginBottom: '8px' }}>\n {fill(t('detail.filteredOut'), { report: detail.front.report })}\n </div>\n ) : null}\n\n <div style={{ margin: '8px 0 4px' }}><strong>{t('view.detail.body')}</strong></div>\n <pre style={{\n margin: 0,\n padding: '8px 10px',\n maxHeight: '320px',\n overflow: 'auto',\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n background: 'var(--dsw-alias-bg-base, rgba(0,0,0,0.02))',\n border: '1px solid var(--dsw-alias-border-l1, rgba(0,0,0,0.06))',\n borderRadius: '4px',\n font: 'var(--dsw-font-xs-13, 13px/1.5 ui-monospace, monospace)',\n }}>{shownBody.text.trim()}</pre>\n {shownBody.truncated ? (\n <div style={{ ...time, marginTop: '4px' }}>\n {fill(t('detail.bodyTruncated'), { chars: shownBody.text.trim().length })}\n </div>\n ) : null}\n <div style={{ ...time, marginTop: '6px', wordBreak: 'break-all' }}>{t('view.detail.path')}: {detail.path}</div>\n </>\n )}\n </div>\n ) : null}\n </div>\n )\n })}\n </div>\n )\n}\n","/**\n * Dictionary for the report timeline tab.\n *\n * Both shipped locales are required by the client locale service, and the key\n * union drives the typed `t()` the view uses, so a missing translation is a\n * type error rather than an empty label.\n *\n * @module dsh-report-ledger/client/locales\n */\n\n/** Chinese dictionary — the source of truth for the key set. */\nexport const zh = {\n 'view.tab': '汇报',\n 'view.loading': '正在读取账本…',\n 'view.summary': '{sessions} 个会话 · {reports} 份汇报',\n 'view.empty': '这个会话树下还没有汇报。代理用 report_author 开一份汇报后,这里会出现它的缩略卡。',\n 'view.refresh': '刷新',\n 'view.retry': '重试',\n 'view.error': '读取失败:{code}',\n 'view.root': '本会话',\n 'view.live': '在线',\n 'view.delegated': '子代理',\n 'view.detail.loading': '正在读取传递路径…',\n 'view.detail.route': '传递路径',\n 'view.detail.routeEmpty': '(还没有记录跳)',\n 'view.detail.pending': '仍在等待送达:{targets}',\n 'view.detail.body': '正文',\n 'view.detail.path': '账本文件',\n 'row.hops': '{count} 跳',\n 'row.authors': '{count} 位共写',\n 'row.filterByTask': '只看任务「{task}」',\n 'filter.all': '全部',\n 'filter.open': '进行中',\n 'filter.acked': '已回执',\n 'filter.closed': '已结案',\n 'filter.search': '搜索主题、编号、会话或收发…',\n 'filter.clear': '清除',\n 'filter.showing': '显示 {visible}/{total} 份汇报',\n 'filter.none': '没有符合当前筛选的汇报。',\n 'filter.hint': '状态筛选只作用于汇报;搜索会同时匹配会话标题与编号。',\n 'basis.created': '按创建',\n 'basis.updated': '按最近活动',\n 'basis.hint': '汇报按创建时间还是按最后一次活动排入时间线;会话始终按创建时间。',\n 'detail.thread': '线程',\n 'detail.parent': '上溯',\n 'detail.children': '下递',\n 'detail.openLinked': '打开 {report}',\n 'detail.outside': '{report} 不在当前会话树的列表里(它可能属于另一条线);正文与路径仍可读取。',\n 'detail.filteredOut': '{report} 在当前筛选下被隐藏了(用「清除」恢复)。',\n 'detail.backToList': '回到列表',\n 'detail.bodyTruncated': '正文过长,此处只显示开头 {chars} 字;完整内容见账本文件。',\n} as const\n\n/** English dictionary, checked against the Chinese key set. */\nexport const en: Record<keyof typeof zh, string> = {\n 'view.tab': 'Reports',\n 'view.loading': 'Reading the ledger…',\n 'view.summary': '{sessions} sessions · {reports} reports',\n 'view.empty': 'No reports in this session tree yet. Once an agent opens one with report_author, its digest card appears here.',\n 'view.refresh': 'Refresh',\n 'view.retry': 'Retry',\n 'view.error': 'Read failed: {code}',\n 'view.root': 'this session',\n 'view.live': 'live',\n 'view.delegated': 'subagent',\n 'view.detail.loading': 'Reading the transfer path…',\n 'view.detail.route': 'Transfer path',\n 'view.detail.routeEmpty': '(no hops recorded yet)',\n 'view.detail.pending': 'Still owed to: {targets}',\n 'view.detail.body': 'Body',\n 'view.detail.path': 'Ledger file',\n 'row.hops': '{count} hops',\n 'row.authors': '{count} authors',\n 'row.filterByTask': 'Show only task \"{task}\"',\n 'filter.all': 'All',\n 'filter.open': 'Open',\n 'filter.acked': 'Acked',\n 'filter.closed': 'Closed',\n 'filter.search': 'Search subject, id, session, or people…',\n 'filter.clear': 'Clear',\n 'filter.showing': 'Showing {visible}/{total} reports',\n 'filter.none': 'No reports match the current filter.',\n 'filter.hint': 'The status filter narrows reports only; search matches every row, including session titles and ids.',\n 'basis.created': 'By creation',\n 'basis.updated': 'By activity',\n 'basis.hint': 'Whether reports are placed at creation or at their latest activity; sessions always keep their creation time.',\n 'detail.thread': 'Thread',\n 'detail.parent': 'Answers',\n 'detail.children': 'Answered by',\n 'detail.openLinked': 'Open {report}',\n 'detail.outside': '{report} is not in this session tree\\'s list (it may belong to another line); its body and path are still readable.',\n 'detail.filteredOut': '{report} is hidden by the current filter (use Clear to restore it).',\n 'detail.backToList': 'Back to the list',\n 'detail.bodyTruncated': 'Body is long, so only the first {chars} characters are shown here; the ledger file has the whole thing.',\n}\n\n/** Every key the view may translate. */\nexport type ReportLedgerKey = keyof typeof zh\n","/**\n * Browser half of dsh-report-ledger — runs inside the dsh web GUI.\n *\n * The shell serves the built `lib/client.js` from /plugins/dsh-report-ledger/client.js\n * and evaluates it as a closure-factory artifact, so this module's `require` is\n * answered by the loader's frozen module table (react, cordis, the slot\n * services) and every other dependency is inlined by the build.\n *\n * It contributes exactly one thing: a `conversation.view` entry, which the\n * conversation shell projects as a tab beside the shipped Chat and Trajectory\n * views. A fresh id is used rather than an existing one, so nothing shipped is\n * replaced.\n *\n * @module dsh-report-ledger/client\n */\n\nimport { createElement } from 'react'\nimport { ReportsView, type Translate } from './ReportsView.tsx'\nimport { en, zh } from './locales.ts'\n\n/** Locale namespace this plugin owns. */\nconst NS = 'report-ledger'\n\n/** Tab key. Distinct from the shipped `chat` / `trajectory` cells. */\nconst VIEW_ID = 'report-ledger'\n\n/** Position after the shipped views (`chat` 0, `trajectory` 10). */\nconst VIEW_ORDER = 20\n\n/** Minimal structural face of the client slot service. */\ninterface SlotsService {\n inject(key: string, callback: () => () => void): () => void\n register(options: Record<string, unknown>, component: (props: never) => unknown): () => void\n}\n\n/** Minimal structural face of the client locale service. */\ninterface LocaleService {\n register(ns: string, dicts: { zh: unknown; en: unknown }): () => void\n bind(ns: string): Translate\n}\n\n/**\n * Minimal structural face of the client plugin context.\n *\n * Deliberately local rather than imported: the client runtime's published types\n * live in packages seeded into the shell bundle, which are not part of a\n * third-party plugin's resolvable dependency set (and one of them is not even\n * installed here). The shape asserted is exactly what the registration below\n * uses, and the host-side declarations remain the source of truth for the wire.\n */\ninterface ClientContext {\n slots: SlotsService\n locale: LocaleService\n effect(callback: () => (() => void) | void, label?: string): () => void\n get(name: string): unknown\n}\n\n/** The slot and locale services must be mounted before this half can contribute. */\nexport const inject = ['slots', 'locale']\n\n/**\n * Apply the browser half.\n * @param ctx - the client plugin context (`slots`, `locale` injected).\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'report-ledger: dictionaries')\n\n const t = ctx.locale.bind(NS)\n\n ctx.slots.inject('conversation.view', () => ctx.slots.register(\n {\n name: 'conversation.view',\n id: VIEW_ID,\n order: VIEW_ORDER,\n // A thunk is re-read on every projection, so the tab label follows the\n // active locale without re-registering.\n label: () => t('view.tab'),\n locale: NS,\n },\n (props: Record<string, unknown>) => createElement(ReportsView, { ...props, t } as never) as never,\n ))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["Tooltip","Button","Fragment","StateDot","MarkdownText","IconCheckOutlineRegular","IconCopyOutlineRegular","IconChevronDownOutlineRegular","Tooltip","StateDot","Button","IconRefreshOutlineRegular","Pill","Input","Tag","IconChevronDownOutlineRegular","IconChevronRightOutlineRegular","Fragment","Button"],"sources":["../src/client/timeline-model.ts","../src/client/format.ts","../src/client/locales.ts","../src/client/ReportDetailView.tsx","../src/client/timeline-source.ts","../src/client/styles.ts","../src/client/ReportsView.tsx","../src/client/cardgraph-model.ts","../src/client/cardgraph-painter.ts","../src/client/CardgraphView.tsx","../src/client/sidebar-tab.ts","../src/client/index.ts"],"sourcesContent":["/**\n * The timeline's data model: turning a payload into rows, and filtering them.\n *\n * Deliberately a plain module with no React and no DOM so the decisions a user\n * actually feels — which rows appear, what a search matches, how the status\n * chips count — are pinned by deterministic tests instead of being inferred from\n * a browser. The view imports this; it does not re-implement it.\n *\n * @module dsh-report-ledger/client/timeline-model\n */\n\nimport type { LeaseState, ReportFrontMatter, ReportStatus, ReportWorktree, TimelinePayload, WorktreeLane } from '../shared/wire.ts'\n\n/** One rendered timeline row: a session branching, or a report appearing. */\nexport type Row =\n | {\n readonly kind: 'session'\n readonly at: number\n readonly id: string\n readonly depth: number\n readonly title?: string\n readonly delegated: boolean\n readonly live: boolean\n }\n | {\n readonly kind: 'report'\n readonly at: number\n readonly depth: number\n readonly front: ReportFrontMatter\n }\n\n/** Which lifecycle states the status chips currently admit. */\nexport type StatusFilter = 'all' | ReportStatus\n\n/** How a report is placed on the time axis. */\nexport type ReportTimeBasis = 'created' | 'updated'\n\n/** Filters the view applies to the assembled rows. */\nexport interface RowFilter {\n /** Lifecycle filter; applies to report rows only. */\n readonly status: StatusFilter\n /** Free text; applies to every row. */\n readonly text: string\n}\n\n/**\n * The timeline's ordering: by time, and at the same instant a session before a\n * report.\n *\n * Extracted so that INSERTING a row uses the same rule as SORTING rows — two\n * copies would drift, and the synthesized row would land somewhere the sort\n * disagrees with.\n * @param left - one row.\n * @param right - the other row.\n * @returns the comparison result.\n */\nfunction byTime(left: Row, right: Row): number {\n return left.at - right.at\n || (left.kind === right.kind ? 0 : left.kind === 'session' ? -1 : 1)\n}\n\n/**\n * The depth of every session in one payload, keyed by session id.\n *\n * Exported because the views need the same map for a row they synthesize rather\n * than build: the open report keeps the indentation of the session that authored\n * it, and asking the model for the map is what stops a second copy of \"depth\n * means the author's depth\" from growing in the view.\n * @param payload - the endpoint payload.\n * @returns session id to depth.\n */\nexport function sessionDepths(payload: TimelinePayload): Map<string, number> {\n const depth = new Map<string, number>()\n for (const node of payload.sessions) depth.set(node.id, node.depth)\n return depth\n}\n\n/**\n * Build the merged, time-ordered row list.\n *\n * Sessions are placed at their creation time and reports at theirs, so the list\n * reads as the collaboration actually unfolded: a session appears where it was\n * branched, a report where it was opened. Report depth comes from the session\n * that authored it, which is what keeps the indentation meaningful even when the\n * session row itself is filtered out.\n *\n * A report row's own `at` is also the timestamp its row prints, so the list can\n * never be ordered by one axis and read as sorted by another.\n * @param payload - the endpoint payload.\n * @param basis - whether to place a report at its creation or its last activity.\n * @returns the rows, oldest first.\n */\nexport function buildRows(payload: TimelinePayload, basis: ReportTimeBasis = 'created'): Row[] {\n const depth = sessionDepths(payload)\n const rows: Row[] = []\n for (const node of payload.sessions) {\n rows.push({\n kind: 'session',\n at: node.createdAt ?? 0,\n id: node.id,\n depth: node.depth,\n delegated: node.delegated,\n live: node.live,\n ...(node.title === undefined ? {} : { title: node.title }),\n })\n }\n for (const front of payload.reports) {\n rows.push({\n kind: 'report',\n at: basis === 'created' ? front.created : front.updated,\n depth: depth.get(front.from) ?? 0,\n front,\n })\n }\n rows.sort(byTime)\n return rows\n}\n\n/** How many characters of a session id a dense row shows. */\nexport const SHORT_ID_LENGTH = 8\n\n/**\n * Shorten a session id so a digest row can be read.\n *\n * A session id is 36 characters and a report's recipients are a list of them, so\n * an unabbreviated meta line is mostly uuid and carries almost no information a\n * human can use. Eight characters of the leading segment stay unique within one\n * collaboration and, unlike a session title, are bounded: titles are the first\n * prompt of a session (\"You are doing READ-ONLY research\"), so swapping ids for\n * titles trades one unreadable line for another.\n *\n * This is a DENSE-ROW display only. The detail panel keeps full ids, because\n * that is the view somebody copies an id out of, and the tooltip on every\n * shortened id carries the full value.\n * @param id - the session id.\n * @param length - how many characters to keep.\n * @returns the shortened id.\n */\nexport function shortId(id: string, length: number = SHORT_ID_LENGTH): string {\n const bare = id.startsWith('session-') ? id.slice('session-'.length) : id\n return bare.length <= length ? bare : bare.slice(0, length)\n}\n\n/**\n * Display titles for the sessions in one payload, keyed by session id.\n *\n * Titles are resolved only for the subtree, so an id from another tree simply\n * has none — which is why every caller must fall back to the shortened id\n * rather than assuming a name exists.\n * @param payload - the endpoint payload.\n * @returns the titles that were resolved.\n */\nexport function sessionTitles(payload: TimelinePayload): Map<string, string> {\n const titles = new Map<string, string>()\n for (const node of payload.sessions) {\n if (typeof node.title === 'string' && node.title !== '') titles.set(node.id, node.title)\n }\n return titles\n}\n\n/**\n * Make sure the open report has a row, and put it where it belongs.\n *\n * The detail panel is anchored to a report row, so the open report must HAVE\n * one. Two real user paths would otherwise leave the panel with nowhere to\n * render: a thread link pointing at a report outside this tree's payload, and\n * any filter applied while a report is open. In the first case the panel is also\n * the only place the \"not in this tree\" notice can appear, so the panel is not\n * hoisted out of the list — a row is synthesized instead.\n *\n * The synthesized row is INSERTED at its own place on the time axis rather than\n * appended. Appending made a report created at 11:12 appear below one created at\n * 14:30, which reads as a broken sort at exactly the moment the user is trying\n * to understand why something is missing.\n *\n * The depth is passed in rather than assumed: the caller has the payload and can\n * ask `sessionDepths` for the author's depth, and a row that loses its\n * indentation when a filter hides it would be a second, quieter broken sort.\n * Reports whose author is outside the subtree are at the root (depth 0).\n * @param kept - the rows that survived the filter.\n * @param front - the digest of the open report.\n * @param basis - the active time basis, so the row moves with the rest.\n * @param depth - the author's depth from the payload, or 0 outside the subtree.\n * @returns the rows, with the open report present exactly once.\n */\nexport function withSynthesizedRow(\n kept: readonly Row[],\n front: ReportFrontMatter,\n basis: ReportTimeBasis,\n depth: number,\n): Row[] {\n if (kept.some((row) => row.kind === 'report' && row.front.report === front.report)) return [...kept]\n const row: Row = { kind: 'report', at: basis === 'created' ? front.created : front.updated, depth, front }\n const index = kept.findIndex((candidate) => byTime(row, candidate) < 0)\n if (index < 0) return [...kept, row]\n return [...kept.slice(0, index), row, ...kept.slice(index)]\n}\n\n/** Every string a report row can be found by. */\nfunction reportHaystack(front: ReportFrontMatter): string {\n return [\n front.report,\n front.subject,\n front.from,\n front.fromName ?? '',\n ...front.to,\n ...front.cc,\n ...front.authors,\n front.task ?? '',\n ...front.artifacts,\n // The bound checkout is searchable for the same reason the task label is:\n // the chip that names it is a filter shortcut, so the text it sets has to be\n // text the search actually looks at.\n front.worktree?.branch ?? '',\n front.worktree?.path ?? '',\n ].join('\\n').toLowerCase()\n}\n\n/** Every string a session row can be found by. */\nfunction sessionHaystack(row: Extract<Row, { kind: 'session' }>): string {\n return `${row.title ?? ''}\\n${row.id}`.toLowerCase()\n}\n\n/**\n * Whether one row survives the current filter.\n * @param row - the candidate row.\n * @param filter - the active filter.\n * @returns true when the row should be rendered.\n */\nexport function rowMatches(row: Row, filter: RowFilter): boolean {\n const needle = filter.text.trim().toLowerCase()\n if (row.kind === 'session') {\n return needle === '' || sessionHaystack(row).includes(needle)\n }\n if (filter.status !== 'all' && row.front.status !== filter.status) return false\n return needle === '' || reportHaystack(row.front).includes(needle)\n}\n\n/**\n * Apply a filter to the row list.\n * @param rows - the assembled rows.\n * @param filter - the active filter.\n * @returns the surviving rows, in their original order.\n */\nexport function filterRows(rows: readonly Row[], filter: RowFilter): Row[] {\n return rows.filter((row) => rowMatches(row, filter))\n}\n\n/** Counts per lifecycle state, plus the total. */\nexport interface StatusCounts {\n /** Every report. */\n readonly all: number\n /** Reports awaiting action or awareness. */\n readonly open: number\n /** Reports whose recipients have acknowledged. */\n readonly acked: number\n /** Reports the owners have concluded. */\n readonly closed: number\n}\n\n/**\n * Count reports by lifecycle state.\n *\n * Counted from the whole subtree payload rather than the filtered rows, so the\n * chips keep showing the full picture while one of them is applied — a filter\n * whose own label changed when you used it would make it impossible to see how\n * much you had excluded.\n * @param reports - every report in the payload.\n * @returns the counts.\n */\nexport function statusCounts(reports: readonly ReportFrontMatter[]): StatusCounts {\n const counts = { all: reports.length, open: 0, acked: 0, closed: 0 }\n for (const front of reports) {\n if (front.status === 'open') counts.open += 1\n else if (front.status === 'acked') counts.acked += 1\n else if (front.status === 'closed') counts.closed += 1\n }\n return counts\n}\n\n/**\n * Whether a filter would hide anything.\n * @param filter - the active filter.\n * @returns true when a status or text constraint is in force.\n */\nexport function isFiltering(filter: RowFilter): boolean {\n return filter.status !== 'all' || filter.text.trim() !== ''\n}\n\n/**\n * Index the payload's working-tree lanes by report id.\n *\n * The lanes carry the lease state, which the digests do not: liveness is derived\n * from the hop stream, and the host derives it once rather than each half of the\n * plugin deriving it separately.\n * @param payload - the endpoint payload.\n * @returns report id to lane.\n */\nexport function laneIndex(payload: TimelinePayload): Map<string, WorktreeLane> {\n return new Map(payload.worktrees.map((lane) => [lane.report, lane]))\n}\n\n/**\n * How a lease state should read at a glance.\n *\n * A live claim is the only state that means someone is working *now*, so it is\n * the only one that earns a positive tone. A cleared checkout is deliberately\n * plain rather than alarming: the ledger tidying up after itself is not a\n * problem to be surfaced, and giving it a warning tone would teach a reader to\n * ignore warnings.\n */\nexport const LEASE_TONE: Record<LeaseState, 'success' | 'info' | 'neutral'> = {\n held: 'success',\n released: 'info',\n reclaimed: 'neutral',\n none: 'neutral',\n}\n\n/** The last path segment, for naming a checkout that has no branch. */\nfunction lastSegment(path: string): string {\n const parts = path.split(/[\\\\/]/)\n return parts[parts.length - 1] || path\n}\n\n/**\n * The short name a checkout is shown under.\n *\n * Taken from the binding rather than from the lane, because a report opened by\n * id can be rendered before it is in the payload's lane list — the binding is\n * always on the row, the lane is not.\n * @param binding - the row's binding, when it has one.\n * @returns the label, or `undefined` when the report is not bound.\n */\nexport function worktreeLabel(binding: ReportWorktree | undefined): string | undefined {\n if (binding === undefined) return undefined\n return binding.branch ?? lastSegment(binding.path)\n}\n\n/**\n * Count how many report rows are visible under a filter.\n * @param rows - every assembled row.\n * @param filter - the active filter.\n * @returns the number of visible report rows.\n */\nexport function visibleReports(rows: readonly Row[], filter: RowFilter): number {\n let count = 0\n for (const row of filterRows(rows, filter)) if (row.kind === 'report') count += 1\n return count\n}\n\n/** Which \"there is nothing here to show\" message a payload and filter call for. */\nexport type EmptyState = 'none' | 'no-reports' | 'filtered-out'\n\n/**\n * Decide which empty-state message applies.\n *\n * An empty ledger and a filter that matched nothing look identical on screen but\n * mean opposite things: one says \"nothing has happened in this tree yet\", the\n * other says \"you are looking at a subset of what happened\". Saying the wrong\n * one makes a working ledger look broken, so the choice is made here — where it\n * is pinned by tests — rather than inline in the view.\n *\n * Counted on REPORT rows only. Session rows always survive a status chip, so\n * counting them would let a tree full of sessions report itself as \"you have\n * filtered everything out\" while the actual reason is that no report exists.\n *\n * The `no-reports` case deliberately wins over the filter: when the ledger holds\n * nothing, a filter cannot be the reason nothing is on screen, and blaming it\n * would send the reader looking for a filter to clear that they never set.\n * @param payload - the endpoint payload.\n * @param rows - every assembled row.\n * @param filter - the active filter.\n * @returns which message the view should show, if any.\n */\nexport function emptyState(payload: TimelinePayload, rows: readonly Row[], filter: RowFilter): EmptyState {\n if (payload.reports.length === 0) return 'no-reports'\n if (!isFiltering(filter)) return 'none'\n return visibleReports(rows, filter) === 0 ? 'filtered-out' : 'none'\n}\n\n/**\n * The report ids present in one payload.\n *\n * A thread link can point outside the subtree — a report answering one opened by\n * an unrelated session, for instance — and the detail endpoint is ledger-scoped,\n * so such a report still opens. Knowing which ids are in the list is what lets\n * the view say so instead of silently showing a card that is not in the timeline.\n * @param payload - the endpoint payload.\n * @returns the ids of every report in the payload.\n */\nexport function reportIds(payload: TimelinePayload): ReadonlySet<string> {\n const ids = new Set<string>()\n for (const front of payload.reports) ids.add(front.report)\n return ids\n}\n\n/** Body length above which the detail panel shows only a head. */\nexport const BODY_DISPLAY_LIMIT = 4000\n\n/** How much of an over-long body is shown. */\nexport const BODY_HEAD_LIMIT = 1200\n\n/**\n * Trim a body for the detail panel.\n *\n * Mirrors what `report_read` already does for the model: the full text stays in\n * the ledger file, and both readers get a bounded view plus a pointer rather than\n * an unbounded paste. Keeping the human view and the model view bounded the same\n * way means neither can be surprised by what the other sees.\n * @param body - the raw body.\n * @param limit - length above which truncation applies.\n * @param head - how many characters to keep when truncating.\n * @returns the display text and whether it was cut.\n */\nexport function displayBody(\n body: string,\n limit: number = BODY_DISPLAY_LIMIT,\n head: number = BODY_HEAD_LIMIT,\n): { readonly text: string; readonly truncated: boolean } {\n if (body.length <= limit) return { text: body, truncated: false }\n return { text: body.slice(0, head), truncated: true }\n}\n\n/**\n * Close a code fence left hanging by truncation.\n *\n * Cutting Markdown at a character count is not the same operation as cutting\n * plain text: the result can be a document that does not parse as written. The\n * one construct that changes the meaning of everything after it is a code fence,\n * so a body cut inside a fenced block hands the renderer an unterminated one and\n * leaves it to decide where the block ends.\n *\n * The renderer here does cope (measured: a body cut mid-fence renders as one\n * closed code block with no trailing artifact), which is exactly why this is\n * cheap insurance rather than a fix for something visibly broken: the renderer is\n * a shell platform module this plugin does not own, and its leniency is not a\n * contract. Balancing the document costs one line and makes the plugin's output\n * valid Markdown no matter who renders it.\n *\n * Balanced text is returned untouched, so this only ever adds the fence the cut\n * removed. Only backtick fences are counted; `~~~` fences are rare enough in\n * report bodies that guessing which convention the author used would be worse\n * than leaving them alone.\n *\n * A fence is a RUN, not a token: CommonMark lets an opener use four backticks\n * precisely so the body can contain a literal three-backtick line, and requires\n * the closer to be at least as long as the opener. Counting every line that\n * merely starts with three backticks therefore gets exactly the documents this\n * function exists for backwards — the nested block would look balanced while its\n * real (longer) fence stayed open. So the opener's length is remembered and only\n * a same-character run of at least that length closes it.\n * @param text - the already-truncated head.\n * @returns the head, with an unclosed fence closed.\n */\nexport function closeOpenFence(text: string): string {\n let open: number | undefined\n for (const line of text.split('\\n')) {\n const stripped = line.trimStart()\n if (!stripped.startsWith('```')) continue\n let run = 0\n while (stripped[run] === '`') run += 1\n if (open === undefined) open = run\n else if (run >= open) open = undefined\n }\n return open === undefined ? text : `${text}\\n${'`'.repeat(open)}`\n}\n","/**\n * The two text helpers both report surfaces need.\n *\n * Tiny and pure on purpose: `fill` is the `{name}` substitution every dictionary\n * template is written in, and `stamp` is the one timestamp format the ledger\n * uses. They live outside the views because there are now two of them — the\n * conversation tab and the right column's canvas tab — and a second copy of\n * either would be a second answer to the same question.\n *\n * @module dsh-report-ledger/client/format\n */\n\n/** Substitute `{name}` placeholders, leaving unknown names alone. */\nexport function fill(template: string, params: Record<string, string | number>): string {\n return template.replace(/\\{(\\w+)\\}/g, (whole, name: string) =>\n Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : whole)\n}\n\n/** Format one timestamp for a timeline row or a hop. */\nexport function stamp(at: number | undefined): string {\n if (at === undefined || at <= 0) return ''\n return new Date(at).toLocaleString()\n}\n\n/** How long the path's copy control shows its \"copied\" state. */\nexport const COPIED_HOLD_MS = 1200\n","/**\n * Dictionary for the report timeline tab.\n *\n * Both shipped locales are required by the client locale service, and the key\n * union drives the typed `t()` the view uses, so a missing translation is a\n * type error rather than an empty label.\n *\n * @module dsh-report-ledger/client/locales\n */\n\nimport type { HopAction, LeaseState, ReportStatus } from '../shared/wire.ts'\nimport type { CardgraphEdgeKind } from './cardgraph-model.ts'\n\n/** Chinese dictionary — the source of truth for the key set. */\nexport const zh = {\n 'view.tab': '汇报',\n 'view.loading': '正在读取账本…',\n 'view.summary': '{sessions} 个会话 · {reports} 份汇报',\n 'view.empty': '这个会话树下还没有汇报。代理用 report_author 开一份汇报后,这里会出现它的缩略卡。',\n 'view.refresh': '刷新',\n 'view.retry': '重试',\n 'view.error': '读取失败:{code}',\n 'view.live': '在线',\n 'view.delegated': '子代理',\n 'view.detail.loading': '正在读取传递路径…',\n 'view.detail.route': '传递路径',\n 'view.detail.routeEmpty': '(还没有记录跳)',\n 'view.detail.pending': '仍在等待送达:{targets}',\n 'view.detail.body': '正文',\n 'view.detail.path': '账本文件',\n 'view.copyPath': '复制账本路径',\n 'row.hops': '{count} 跳',\n 'row.authors': '{count} 位共写',\n 'row.filterByTask': '只看任务「{task}」',\n 'row.filterByWorktree': '只看检出「{worktree}」',\n 'row.leaseHeld': '工作区:正在被这份汇报占用',\n 'row.leaseReleased': '工作区:占用已结束,检出仍留在磁盘上',\n 'row.leaseReclaimed': '工作区:已被账本回收清除',\n 'row.leaseNone': '工作区:绑定已记录,但从未被占用',\n 'row.expand': '展开 {report}',\n 'row.collapse': '收起 {report}',\n 'row.last': '最后',\n 'hop.authored': '发起',\n 'hop.contributed': '共写',\n 'hop.sent': '主送',\n 'hop.delivered': '送达',\n 'hop.cc': '抄送',\n 'hop.forwarded': '转呈',\n 'hop.copied': '副本',\n 'hop.read': '阅读',\n 'hop.acked': '回执',\n 'hop.leased': '认领工作区',\n 'hop.released': '释放工作区',\n 'hop.reclaimed': '回收工作区',\n 'hop.amended': '修正',\n 'hop.closed': '结案',\n 'hop.reopened': '重开',\n 'filter.all': '全部',\n 'filter.open': '进行中',\n 'filter.acked': '已回执',\n 'filter.closed': '已结案',\n 'filter.search': '搜索主题、编号、会话或收发…',\n 'filter.clear': '清除',\n 'filter.showing': '显示 {visible}/{total} 份汇报',\n 'filter.none': '没有符合当前筛选的汇报。',\n 'filter.hint': '状态筛选只作用于汇报;搜索会同时匹配会话标题与编号,以及任务标签和检出名。',\n 'basis.created': '按创建',\n 'basis.updated': '按最近活动',\n 'basis.hint': '汇报按创建时间还是按最后一次活动排入时间线;会话始终按创建时间。',\n 'detail.thread': '线程',\n 'detail.parent': '上溯',\n 'detail.children': '下递',\n 'detail.openLinked': '打开 {report}',\n 'detail.outside': '{report} 不在当前会话树的列表里(它可能属于另一条线);正文与路径仍可读取。',\n 'detail.filteredOut': '{report} 在当前筛选下被隐藏了(用「清除」恢复)。',\n 'detail.backToList': '回到列表',\n 'detail.bodyTruncated': '正文过长,此处只显示开头 {chars} 字;完整内容见账本文件。',\n 'cardgraph.external': '树外',\n 'cardgraph.zoomIn': '放大',\n 'cardgraph.zoomOut': '缩小',\n 'cardgraph.zoom': '{percent}%',\n 'cardgraph.fit': '填满宽度',\n 'cardgraph.summary': '{frames} 个框 · {cards} 份汇报',\n 'cardgraph.empty': '没有汇报',\n 'cardgraph.gestureHint': '滚轮平移 · Ctrl+滚轮缩放',\n // The right-Sidebar tab this plugin contributes: the capsule that makes it\n // discoverable on the guide page, and the chip an open tab shows.\n 'sidebar.guideTitle': '汇报画布',\n 'sidebar.guideDescription': '把拓扑画布停靠进右侧栏',\n 'sidebar.chip': '汇报画布',\n 'body.copy': '复制',\n 'body.copied': '已复制',\n 'body.footnotes': '脚注',\n} as const\n\n/** English dictionary, checked against the Chinese key set. */\nexport const en: Record<keyof typeof zh, string> = {\n 'view.tab': 'Reports',\n 'view.loading': 'Reading the ledger…',\n 'view.summary': '{sessions} sessions · {reports} reports',\n 'view.empty': 'No reports in this session tree yet. Once an agent opens one with report_author, its digest card appears here.',\n 'view.refresh': 'Refresh',\n 'view.retry': 'Retry',\n 'view.error': 'Read failed: {code}',\n 'view.live': 'live',\n 'view.delegated': 'subagent',\n 'view.detail.loading': 'Reading the transfer path…',\n 'view.detail.route': 'Transfer path',\n 'view.detail.routeEmpty': '(no hops recorded yet)',\n 'view.detail.pending': 'Still owed to: {targets}',\n 'view.detail.body': 'Body',\n 'view.detail.path': 'Ledger file',\n 'view.copyPath': 'Copy the ledger path',\n 'row.hops': '{count} hops',\n 'row.authors': '{count} authors',\n 'row.filterByTask': 'Show only task \"{task}\"',\n 'row.filterByWorktree': 'Show only checkout \"{worktree}\"',\n 'row.leaseHeld': 'Checkout: currently claimed by this report',\n 'row.leaseReleased': 'Checkout: claim ended, the checkout is still on disk',\n 'row.leaseReclaimed': 'Checkout: cleared away by the ledger',\n 'row.leaseNone': 'Checkout: bound but never claimed',\n 'row.expand': 'Expand {report}',\n 'row.collapse': 'Collapse {report}',\n 'row.last': 'last',\n 'hop.authored': 'authored',\n 'hop.contributed': 'contributed',\n 'hop.sent': 'sent',\n 'hop.delivered': 'delivered',\n 'hop.cc': 'cc',\n 'hop.forwarded': 'forwarded',\n 'hop.copied': 'copied',\n 'hop.read': 'read',\n 'hop.acked': 'acked',\n 'hop.leased': 'leased',\n 'hop.released': 'released',\n 'hop.reclaimed': 'reclaimed',\n 'hop.amended': 'amended',\n 'hop.closed': 'closed',\n 'hop.reopened': 'reopened',\n 'filter.all': 'All',\n 'filter.open': 'Open',\n 'filter.acked': 'Acked',\n 'filter.closed': 'Closed',\n 'filter.search': 'Search subject, id, session, or people…',\n 'filter.clear': 'Clear',\n 'filter.showing': 'Showing {visible}/{total} reports',\n 'filter.none': 'No reports match the current filter.',\n 'filter.hint': 'The status filter narrows reports only; search matches every row, including session titles and ids, task labels and checkout names.',\n 'basis.created': 'By creation',\n 'basis.updated': 'By activity',\n 'basis.hint': 'Whether reports are placed at creation or at their latest activity; sessions always keep their creation time.',\n 'detail.thread': 'Thread',\n 'detail.parent': 'Answers',\n 'detail.children': 'Answered by',\n 'detail.openLinked': 'Open {report}',\n 'detail.outside': '{report} is not in this session tree\\'s list (it may belong to another line); its body and path are still readable.',\n 'detail.filteredOut': '{report} is hidden by the current filter (use Clear to restore it).',\n 'detail.backToList': 'Back to the list',\n 'detail.bodyTruncated': 'Body is long, so only the first {chars} characters are shown here; the ledger file has the whole thing.',\n 'cardgraph.external': 'outside',\n 'cardgraph.zoomIn': 'Zoom in',\n 'cardgraph.zoomOut': 'Zoom out',\n 'cardgraph.zoom': '{percent}%',\n 'cardgraph.fit': 'Fill width',\n 'cardgraph.summary': '{frames} frames · {cards} reports',\n 'cardgraph.empty': 'no reports',\n 'cardgraph.gestureHint': 'Wheel pans · Ctrl+wheel zooms',\n 'sidebar.guideTitle': 'Report canvas',\n 'sidebar.guideDescription': 'Dock the topology canvas into the right sidebar',\n 'sidebar.chip': 'Report canvas',\n 'body.copy': 'Copy',\n 'body.copied': 'Copied',\n 'body.footnotes': 'Footnotes',\n}\n\n/** Every key the view may translate. */\nexport type ReportLedgerKey = keyof typeof zh\n\n/**\n * Lifecycle state → dictionary key.\n *\n * Deliberately the SAME keys the toolbar's filter chips use. A card's status\n * chip and the filter that selects it name one state, so giving them two\n * vocabularies (raw `open` on the card, 进行中 in the toolbar) made the reader\n * build the mapping themselves — and the mapping was the bug, not the words.\n */\nexport const STATUS_LABEL: Record<ReportStatus, ReportLedgerKey> = {\n open: 'filter.open',\n acked: 'filter.acked',\n closed: 'filter.closed',\n}\n\n/**\n * Lifecycle state → the shell's chip tone.\n *\n * Colour-coded status is the point of the chip, and the shell's palette already\n * has the three tones this needs — so the states ride `Tag` instead of a\n * hand-mixed background. `open` reads as the one that wants attention. The tone\n * names are the ones the shell's CSS actually defines (`[data-tone=…]`), which\n * is a longer list than the shipped views happen to use.\n */\nexport const STATUS_TONE: Record<ReportStatus, 'warning' | 'success' | 'neutral'> = {\n open: 'warning',\n acked: 'success',\n closed: 'neutral',\n}\n\n/**\n * Edge kind → the word the drawing calls it.\n *\n * The drawing borrows the transfer path's own vocabulary rather than inventing a\n * second one: an arrow in the canvas and a row in the panel name the same\n * relation, so they say the same thing.\n */\nexport const EDGE_LABEL: Record<CardgraphEdgeKind, ReportLedgerKey> = {\n to: 'hop.sent',\n cc: 'hop.cc',\n author: 'hop.contributed',\n thread: 'detail.thread',\n}\n\n/** Lease-state description, for the checkout chip's tooltip. */\nexport const LEASE_HINT: Record<LeaseState, ReportLedgerKey> = {\n held: 'row.leaseHeld',\n released: 'row.leaseReleased',\n reclaimed: 'row.leaseReclaimed',\n none: 'row.leaseNone',\n}\n\n/**\n * Transfer-path hop action → dictionary key.\n *\n * These are ledger vocabulary: the model and the ledger files keep the raw\n * tokens (`authored`, `delivered`, …), and only the human view speaks a\n * language. Keyed by the full action union so a new hop action cannot ship\n * without a word for it.\n */\nexport const HOP_LABEL: Record<HopAction, ReportLedgerKey> = {\n authored: 'hop.authored',\n contributed: 'hop.contributed',\n sent: 'hop.sent',\n delivered: 'hop.delivered',\n cc: 'hop.cc',\n forwarded: 'hop.forwarded',\n copied: 'hop.copied',\n read: 'hop.read',\n acked: 'hop.acked',\n leased: 'hop.leased',\n released: 'hop.released',\n reclaimed: 'hop.reclaimed',\n amended: 'hop.amended',\n closed: 'hop.closed',\n reopened: 'hop.reopened',\n}\n","/**\n * One report's detail: its transfer path, its thread links, and its body.\n *\n * Shared by the two surfaces that show a report — the conversation tab, where it\n * opens in place under its row, and the right column's canvas tab, where it opens\n * under the drawing. One implementation on purpose: the two are read side by\n * side, and the same report must not read differently in them.\n *\n * The two variants differ in exactly two ways, both about who owns the space:\n * a row's panel grows with the timeline's own scroll (so it has no inner scroll\n * box — a nested scroller would only steal the wheel), while a docked panel hands\n * over a finite height and the detail takes its share of it and scrolls inside.\n *\n * @module dsh-report-ledger/client/ReportDetailView\n */\n\nimport { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport {\n Button,\n IconCheckOutlineRegular,\n IconChevronDownOutlineRegular,\n IconCopyOutlineRegular,\n MarkdownText,\n StateDot,\n Tooltip,\n writeClipboard,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { ReportDetail } from '../shared/wire.ts'\nimport { closeOpenFence, displayBody } from './timeline-model.ts'\nimport { COPIED_HOLD_MS, fill, stamp } from './format.ts'\nimport { HOP_LABEL } from './locales.ts'\nimport type { Translate } from './ReportsView.tsx'\n\n/** Props for one report's detail. */\nexport interface ReportDetailViewProps {\n /** The report being shown. */\n readonly report: string\n /** The read result; `undefined` while the read is still in flight. */\n readonly detail: ReportDetail | undefined\n /** The failure code of the read, when it failed. */\n readonly error: string | undefined\n readonly t: Translate\n /** Full id plus session title, for the hover hints on session ids. */\n readonly idHint: (id: string) => string\n /** `domId` for a row's `aria-controls`; omitted inside a panel. */\n readonly id?: string\n /** How the box is dressed: a list row's inline panel, or a docked pane's detail. */\n readonly variant: 'row' | 'pane'\n /** Open another report: a thread link, or `undefined` for \"back to the list\". */\n readonly onOpenReport: (report: string | undefined) => void\n /** Whether this report belongs to the rendered list at all. */\n readonly inList: boolean\n /** Whether the current filter admits it; only meaningful when `inList`. */\n readonly visibleInFilter: boolean\n /** Show a collapse control in the pane's own header. */\n readonly onClose?: () => void\n}\n\nconst time: Record<string, string | number> = {\n color: 'var(--dsw-alias-label-caption, #999)',\n fontSize: 'var(--dsw-font-xxxs-11, 11)',\n whiteSpace: 'nowrap',\n}\n\n/**\n * The same caption look, for content that must be allowed to WRAP.\n *\n * `time` sets `white-space: nowrap`, which is right for a timestamp and wrong for\n * anything whose length the ledger decides: a nowrap element's min-content width\n * is its whole unbreakable line, so inside a `1fr` grid track it grows the track\n * past its container. Recipient lists and hop notes are exactly that — which is\n * how the transfer path used to push the whole tab into a horizontal scrollbar.\n */\nconst wrap: Record<string, string | number> = {\n ...time,\n whiteSpace: 'normal',\n minWidth: 0,\n overflowWrap: 'anywhere',\n}\n\n/** The frame the rendered body sits in — a block, not a scroll box. */\nconst bodyFrame: Record<string, string | number> = {\n padding: '8px 10px',\n background: 'var(--dsw-alias-bg-base, rgba(0,0,0,0.02))',\n border: '1px solid var(--dsw-alias-border-l1, rgba(0,0,0,0.06))',\n borderRadius: '4px',\n}\n\n/** The footer line: ledger path plus its copy control. */\nconst pathRow: Record<string, string | number> = {\n ...wrap,\n marginTop: '6px',\n display: 'flex',\n alignItems: 'baseline',\n gap: '6px',\n}\n\n/** A row's inline panel: indented under its row, growing with the page. */\nconst rowBox: Record<string, string | number> = {\n margin: '4px 0 10px 24px',\n padding: '10px 12px',\n borderRadius: '6px',\n background: 'var(--dsw-alias-bg-layer-2, rgba(0,0,0,0.03))',\n border: '1px solid var(--dsw-alias-border-l1, rgba(0,0,0,0.06))',\n}\n\n/** A docked panel's share: a column that owns its height and scrolls inside it. */\nconst pane: Record<string, string | number> = {\n display: 'flex',\n flexDirection: 'column',\n height: '100%',\n minHeight: 0,\n padding: '8px 12px 10px',\n borderTop: '1px solid var(--dsw-alias-border-l1, rgba(0,0,0,0.08))',\n background: 'var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.02))',\n}\n\nconst paneHead: Record<string, string | number> = {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n marginBottom: '6px',\n}\n\nconst paneBody: Record<string, string | number> = {\n flex: '1 1 auto',\n minHeight: 0,\n overflow: 'auto',\n}\n\n/**\n * Render one report: route, thread, body and path.\n * @param props - the read result, the copy seat, and how the box is dressed.\n * @returns the detail block.\n */\nexport function ReportDetailView(props: ReportDetailViewProps): ReactElement {\n const { report, detail, error, t, idHint, id, variant, onOpenReport, inList, visibleInFilter, onClose } = props\n const [copiedPath, setCopiedPath] = useState(false)\n const copyTimer = useRef<ReturnType<typeof globalThis.setTimeout> | undefined>(undefined)\n useEffect(() => () => {\n if (copyTimer.current !== undefined) globalThis.clearTimeout(copyTimer.current)\n }, [])\n const shownBody = useMemo(() => displayBody(detail?.body ?? ''), [detail])\n // The text the renderer gets. A cut body can end mid-fence, and an unterminated\n // fence would turn the rest of the report into a code block — see `closeOpenFence`.\n // Only applied when the body was actually cut, so a whole body renders as written.\n const bodyText = useMemo(() => {\n const text = shownBody.text.trim()\n return shownBody.truncated ? closeOpenFence(text) : text\n }, [shownBody])\n /** Strings for the prose renderer's own controls, from this plugin's dictionary. */\n const markdownLabels = useMemo(\n () => ({ code: { copyLabel: t('body.copy'), copiedLabel: t('body.copied') }, footnotes: t('body.footnotes') }),\n [t],\n )\n /**\n * Copy the ledger path.\n *\n * Uses the shell's `writeClipboard` rather than `navigator.clipboard`, so the\n * plugin inherits whatever fallback chain the rest of the GUI relies on.\n */\n const copyPath = useCallback((path: string) => {\n void writeClipboard(path).then((ok) => {\n if (!ok) return\n setCopiedPath(true)\n if (copyTimer.current !== undefined) globalThis.clearTimeout(copyTimer.current)\n copyTimer.current = globalThis.setTimeout(() => { setCopiedPath(false) }, COPIED_HOLD_MS)\n })\n }, [])\n\n const link = (target: string): ReactElement => (\n <span key={target} style={{ marginRight: '6px' }}>\n <Tooltip label={fill(t('detail.openLinked'), { report: target })} side=\"bottom\" delayMs={400}>\n <Button variant=\"outline\" size=\"sm\" onClick={() => { onOpenReport(target) }}>\n {target}\n </Button>\n </Tooltip>\n </span>\n )\n\n const content = (\n <>\n {error !== undefined ? <div style={{ color: 'var(--dsw-alias-state-error-primary, #b00)' }}>{t('view.error', { code: error })}</div> : null}\n {detail === undefined && error === undefined ? <div style={time}>{t('view.detail.loading')}</div> : null}\n {detail === undefined ? null : (\n <>\n <div style={{ marginBottom: '6px' }}><strong>{t('view.detail.route')}</strong></div>\n {detail.hops.length === 0 ? <div style={time}>{t('view.detail.routeEmpty')}</div> : (\n <div style={{ display: 'grid', gridTemplateColumns: 'auto auto auto 1fr', gap: '2px 10px', marginBottom: '8px' }}>\n {detail.hops.map((hop, hopIndex) => (\n <Fragment key={`h-${hopIndex}`}>\n <span style={time}>{stamp(hop.at)}</span>\n <span><strong>{t(HOP_LABEL[hop.action])}</strong></span>\n <span style={wrap}><Tooltip label={idHint(hop.actor)} side=\"bottom\" delayMs={400}><span>{hop.actor}</span></Tooltip></span>\n <span style={wrap}>{hop.to.length === 0 ? (hop.note ?? '') : `→ ${hop.to.join(', ')}${hop.note === undefined ? '' : ` (${hop.note})`}`}</span>\n </Fragment>\n ))}\n </div>\n )}\n {/* \"Still owed\" is a state, so it gets the shell's state dot instead of a\n colour mixed by hand. */}\n {detail.pending.length === 0 ? null : (\n <div style={{ ...wrap, display: 'flex', alignItems: 'baseline', gap: '6px' }}>\n <StateDot state=\"warning\" size={8} />\n <span>{fill(t('view.detail.pending'), { targets: detail.pending.join(', ') })}</span>\n </div>\n )}\n\n {(() => {\n const parent = detail.front.parent\n const children = detail.front.children\n if (parent === undefined && children.length === 0) return null\n return (\n <div style={{ marginBottom: '8px' }}>\n <strong>{t('detail.thread')}</strong>\n {parent === undefined ? null : (\n <div style={{ marginTop: '2px' }}>\n <span style={time}>{`${t('detail.parent')}: `}</span>{link(parent)}\n </div>\n )}\n {children.length === 0 ? null : (\n <div style={{ marginTop: '2px' }}>\n <span style={time}>{`${t('detail.children')}: `}</span>{children.map(link)}\n </div>\n )}\n </div>\n )\n })()}\n\n {/* Kept OUT of the thread block above: that block returns early when a\n report has no thread links, which is the common case for exactly the\n reports this notice is about. */}\n {inList ? null : (\n <div style={{ ...wrap, marginBottom: '8px' }}>\n {fill(t('detail.outside'), { report: detail.front.report })}\n {' '}\n <Button variant=\"outline\" onClick={() => { onOpenReport(undefined) }}>\n {t('detail.backToList')}\n </Button>\n </div>\n )}\n {inList && !visibleInFilter ? (\n <div style={{ ...wrap, marginBottom: '8px' }}>\n {fill(t('detail.filteredOut'), { report: detail.front.report })}\n </div>\n ) : null}\n\n <div style={{ margin: '8px 0 4px' }}><strong>{t('view.detail.body')}</strong></div>\n {/* Rendered with the shell's own prose renderer — the same one the Chat\n and Trajectory views use — so a report body reads like everything\n else in the GUI instead of like a raw file. */}\n <div style={bodyFrame}>\n <MarkdownText text={bodyText} labels={markdownLabels} />\n </div>\n {shownBody.truncated ? (\n <div style={{ ...wrap, marginTop: '4px' }}>\n {fill(t('detail.bodyTruncated'), { chars: shownBody.text.trim().length })}\n </div>\n ) : null}\n {/* The ledger path is the longest string here and the only one with no\n spaces to break at, so it wraps mid-segment on purpose — and it has a\n copy control, because \"see the file\" is only useful if you can get\n there. */}\n <div style={pathRow}>\n <StateDot state=\"idle\" size={6} />\n <span style={{ wordBreak: 'break-all' }}>{t('view.detail.path')}: {detail.path}</span>\n <Tooltip label={copiedPath ? t('body.copied') : t('view.copyPath')} side=\"bottom\" delayMs={300}>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n icon={copiedPath ? <IconCheckOutlineRegular size={16} /> : <IconCopyOutlineRegular size={16} />}\n aria-label={copiedPath ? t('body.copied') : t('view.copyPath')}\n onClick={() => { copyPath(detail.path) }}\n />\n </Tooltip>\n </div>\n </>\n )}\n </>\n )\n\n if (variant === 'pane') {\n return (\n <div style={pane}>\n <div style={paneHead}>\n <strong>{report}</strong>\n {onClose === undefined ? null : (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n icon={<IconChevronDownOutlineRegular size={14} />}\n aria-label={fill(t('row.collapse'), { report })}\n onClick={onClose}\n />\n )}\n </div>\n <div style={paneBody}>{content}</div>\n </div>\n )\n }\n return <div id={id} style={rowBox}>{content}</div>\n}\n","/**\n * The ledger's browser-side read path: the two endpoints, the poll cadence, and\n * the one way any view reads them.\n *\n * Two surfaces now draw the same ledger for the same session — the conversation\n * tab and the canvas tab in the right column. Keeping the envelope unwrapping\n * and the polling rule here is what stops the second surface from growing a\n * second opinion about either; that kind of drift does not fail loudly, it just\n * leaves one of the two views quietly rendering nothing.\n *\n * @module dsh-report-ledger/client/timeline-source\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { ReportDetail, TimelinePayload } from '../shared/wire.ts'\n\n/** The timeline read endpoint, addressed by the tree's root session. */\nexport const TIMELINE_URL = '/api/report-ledger/timeline'\n\n/** One report's detail: its transfer path and its body. */\nexport const REPORT_URL = '/api/report-ledger/report'\n\n/**\n * How often a mounted view re-reads the timeline.\n *\n * The ledger is written by other agents in the background, so a view that loads\n * once goes stale precisely when it matters — the human is watching for a report\n * that has not been written yet. Ten seconds is quiet enough not to matter on a\n * large ledger and still fast enough to watch a collaboration happen.\n */\nexport const POLL_INTERVAL_MS = 10_000\n\n/** Read one endpoint and unwrap its result envelope. */\nexport async function getJson<T>(url: string): Promise<T> {\n const response = await fetch(url, { credentials: 'same-origin', headers: { accept: 'application/json' } })\n let body: unknown\n try {\n body = await response.json()\n } catch {\n throw new Error(`HTTP ${response.status}`)\n }\n const envelope = body as { ok?: boolean; code?: string; value?: T }\n if (envelope.ok !== true) throw new Error(envelope.code ?? `HTTP ${response.status}`)\n return envelope.value as T\n}\n\n/** What one view needs to draw a session tree's ledger. */\nexport interface TimelineRead {\n /** The payload once it has arrived; `undefined` while loading or after a failure. */\n readonly payload: TimelinePayload | undefined\n /** The failure code of the last read, or `undefined` while reads are succeeding. */\n readonly error: string | undefined\n /** Re-read now, without waiting for the next poll. */\n readonly refresh: () => void\n}\n\n/**\n * Follow one session tree's timeline for as long as the caller is mounted.\n *\n * Reads on mount and on every session change, then on the poll cadence. Polling\n * stops while the page is hidden and a returning page catches up immediately, so\n * a background panel costs nothing and a foreground one is never behind.\n * @param sessionId - the tree's root session; an empty value reads nothing.\n * @returns the latest payload, its error, and a manual refresh.\n */\nexport function useTimeline(sessionId: string): TimelineRead {\n const [payload, setPayload] = useState<TimelinePayload | undefined>(undefined)\n const [error, setError] = useState<string | undefined>(undefined)\n const [nonce, setNonce] = useState(0)\n\n useEffect(() => {\n if (sessionId === '') {\n setError('no-session')\n return undefined\n }\n let cancelled = false\n setError(undefined)\n getJson<TimelinePayload>(`${TIMELINE_URL}?root=${encodeURIComponent(sessionId)}`)\n .then((value) => { if (!cancelled) setPayload(value) })\n .catch((cause: unknown) => { if (!cancelled) setError(cause instanceof Error ? cause.message : String(cause)) })\n return () => { cancelled = true }\n }, [sessionId, nonce])\n\n useEffect(() => {\n if (sessionId === '') return undefined\n const tick = (): void => {\n if (globalThis.document?.visibilityState === 'hidden') return\n setNonce((value) => value + 1)\n }\n const timer = globalThis.setInterval(tick, POLL_INTERVAL_MS)\n const onVisibility = (): void => { if (globalThis.document?.visibilityState !== 'hidden') tick() }\n globalThis.document?.addEventListener('visibilitychange', onVisibility)\n return () => {\n globalThis.clearInterval(timer)\n globalThis.document?.removeEventListener('visibilitychange', onVisibility)\n }\n }, [sessionId])\n\n const refresh = useCallback(() => { setNonce((value) => value + 1) }, [])\n return { payload, error, refresh }\n}\n\n/** What one report's detail read returns. */\nexport interface ReportDetailRead {\n /** The detail for the requested report; `undefined` until its first read lands. */\n readonly detail: ReportDetail | undefined\n /** The failure code of the last read, cleared by the next success. */\n readonly error: string | undefined\n}\n\n/** One detail read, stamped with the report it was read for. */\ninterface DetailState {\n /** The report this read belongs to. */\n readonly report: string\n /** The detail once it has arrived; kept across a re-read of the same report. */\n readonly detail?: ReportDetail\n /** The failure code of the last read for this report, cleared by a success. */\n readonly error?: string\n}\n\n/**\n * Read one report's detail, for whichever surface is showing it.\n *\n * Not polled on its own: the caller owns the cadence, because the two surfaces\n * want different ones — the conversation tab re-reads it when the reader hits\n * refresh, while a docked panel passes its own timeline tick so a report being\n * written under the reader's eyes keeps up.\n *\n * A re-read of the SAME report therefore keeps the previous detail on screen\n * until the new one arrives: clearing it would turn every tick into a flash of\n * \"reading the transfer path\" over a body the reader is in the middle of. A read\n * for a DIFFERENT report is a different question, so the state is keyed by report\n * id and anything read for another report is never returned — that is what stops\n * one click from rendering the previous report's body under the new one, even for\n * a frame.\n *\n * Errors follow the same rule: a failed re-read keeps the detail and reports the\n * failure beside it, and only a successful read clears it.\n * @param report - the report to read; `undefined` clears the read.\n * @param nonce - bump to re-read the same report.\n * @returns the detail for `report` only, its error, and nothing else.\n */\nexport function useReportDetail(report: string | undefined, nonce: number): ReportDetailRead {\n const [state, setState] = useState<DetailState | undefined>(undefined)\n\n useEffect(() => {\n if (report === undefined) return undefined\n let cancelled = false\n getJson<ReportDetail>(`${REPORT_URL}?id=${encodeURIComponent(report)}`)\n .then((value) => { if (!cancelled) setState({ report, detail: value }) })\n .catch((cause: unknown) => {\n if (cancelled) return\n const message = cause instanceof Error ? cause.message : String(cause)\n // A failed re-read of the report already on screen keeps its detail and\n // merely adds the failure; a failed FIRST read has nothing to keep.\n setState((previous) => previous !== undefined && previous.report === report && previous.detail !== undefined\n ? { report, detail: previous.detail, error: message }\n : { report, error: message })\n })\n return () => { cancelled = true }\n }, [report, nonce])\n\n if (report === undefined || state === undefined || state.report !== report) {\n return { detail: undefined, error: undefined }\n }\n return { detail: state.detail, error: state.error }\n}\n","/**\n * The plugin's own stylesheet.\n *\n * The shell's primitives ship their own CSS with the shell, which is the whole\n * point of using them — a plugin gets the app's real look without carrying a\n * theme. What is left for us is the one thing only a consumer can know:\n *\n * **Layout that belongs to the consumer.** `Input` sizes itself to its content\n * (`display: inline-flex`, no width), which is right in a form and wrong in this\n * tab's toolbar, where the search seat takes the slack. The component puts the\n * `className` it is given on its own wrapper, so one rule is enough — and\n * targeting a class of our own keeps us off the hashed class names, which are\n * free to change between shell releases.\n *\n * Nothing here restyles a shell component; every library element keeps the look\n * the shell gave it. The SVG drawing rules that used to live here went with the\n * lane view they drew (R-0011): the topology is now painted by the card canvas,\n * which carries its own painter, so a stylesheet entry for a rail or an edge had\n * no consumer left.\n *\n * @module dsh-report-ledger/client/styles\n */\n\n/** Class the tab puts on the toolbar's search seat. */\nexport const SEARCH_CLASS = 'report-ledger-search'\n\n/** Element id, so a reload replaces the sheet instead of stacking one per apply. */\nconst STYLE_ID = 'dsh-report-ledger-styles'\n\n/** The adapter rules, kept in one place so the id and the text cannot drift. */\nconst CSS = `\n.${SEARCH_CLASS} { flex: 1 1 auto; min-width: 0; }\n`\n\n/**\n * Mount the adapter stylesheet.\n * @returns a disposer that removes it, owned by the plugin's effect scope.\n */\nexport function installStyles(): () => void {\n const doc = globalThis.document\n if (doc === undefined) return () => {}\n // A reload of the plugin re-runs apply without a page reload, so replace any\n // sheet a previous generation left behind rather than stacking duplicates.\n doc.getElementById(STYLE_ID)?.remove()\n const style = doc.createElement('style')\n style.id = STYLE_ID\n style.textContent = CSS\n doc.head.append(style)\n return () => { style.remove() }\n}\n","/**\n * The report timeline view — the `conversation.view` tab this plugin registers.\n *\n * It renders one top-to-bottom timeline of the collaboration: the session\n * subtree as it branched, interleaved with the reports those sessions produced\n * or were copied on. Rows carry only the digest, so a long-running tree stays\n * scannable; clicking a report opens its transfer path and body on demand.\n *\n * Data comes from this plugin's own read-only endpoint rather than a generated\n * remote: the host assembles the subtree and filters the ledger to the sessions\n * in it, which the browser cannot do on its own because a report's transfer path\n * is a host-side file, not session state.\n *\n * A blank session never reaches this component — the conversation slot is\n * omitted entirely in that state.\n *\n * @module dsh-report-ledger/client/ReportsView\n */\n\nimport { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport {\n Button,\n IconChevronDownOutlineRegular,\n IconChevronRightOutlineRegular,\n IconRefreshOutlineRegular,\n Input,\n Pill,\n StateDot,\n Tag,\n Tooltip,\n} from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { ReportDetail, TimelinePayload, WorktreeLane } from '../shared/wire.ts'\nimport {\n buildRows,\n emptyState,\n filterRows,\n isFiltering,\n laneIndex,\n LEASE_TONE,\n reportIds,\n sessionDepths,\n sessionTitles,\n shortId,\n statusCounts,\n visibleReports,\n withSynthesizedRow,\n worktreeLabel,\n type ReportTimeBasis,\n type Row,\n type StatusFilter,\n} from './timeline-model.ts'\nimport { ReportDetailView } from './ReportDetailView.tsx'\nimport { getJson, POLL_INTERVAL_MS, TIMELINE_URL, useReportDetail } from './timeline-source.ts'\nimport { fill, stamp } from './format.ts'\nimport { HOP_LABEL, LEASE_HINT, STATUS_LABEL, STATUS_TONE, type ReportLedgerKey } from './locales.ts'\nimport { SEARCH_CLASS } from './styles.ts'\n\n/** Translate one key, substituting `{name}` placeholders. */\nexport type Translate = (key: ReportLedgerKey, params?: Record<string, string | number>) => string\n\n/** Props the registration passes through, plus the locale seat. */\nexport interface ReportsViewProps {\n /** The session the view is bound to. */\n sessionId?: string\n /** Injected by the registration closure from the plugin's locale namespace. */\n t: Translate\n}\n\n/** Storage key for the remembered status filter. */\nconst FILTER_KEY = 'dsh.reportLedger.filter.v1'\n\n/** Read the remembered status filter, tolerating absent or damaged storage. */\nfunction rememberedStatus(): StatusFilter {\n try {\n const raw = globalThis.localStorage?.getItem(FILTER_KEY)\n if (typeof raw === 'string') {\n const parsed = JSON.parse(raw) as { status?: unknown }\n if (parsed.status === 'open' || parsed.status === 'acked' || parsed.status === 'closed') return parsed.status\n }\n } catch {\n // A restricted or full storage just means the filter is not remembered.\n }\n return 'all'\n}\n\n/**\n * Render the report timeline.\n * @param props - the session binding and the locale seat.\n * @returns the tab content.\n */\nexport function ReportsView(props: ReportsViewProps): ReactElement {\n const { sessionId, t } = props\n const [payload, setPayload] = useState<TimelinePayload | undefined>(undefined)\n const [error, setError] = useState<string | undefined>(undefined)\n const [timelineNonce, setTimelineNonce] = useState(0)\n // Refreshing the timeline and re-reading the open detail are two different\n // things: the background poll must move the first without touching the second,\n // or the panel would blink back to \"reading the transfer path\" every interval\n // while somebody is reading it. Only the explicit refresh button advances both.\n const [detailNonce, setDetailNonce] = useState(0)\n const [openReport, setOpenReport] = useState<string | undefined>(undefined)\n // A thread link points at a row that may be filtered out or outside this tree,\n // so the row it wants may not exist yet. The request is remembered until that\n // row mounts; see the scroll effect below.\n const [scrollTarget, setScrollTarget] = useState<string | undefined>(undefined)\n /** Row containers, so a thread jump can bring its target on screen. */\n const rowRefs = useRef<Map<string, HTMLDivElement>>(new Map())\n const [status, setStatus] = useState<StatusFilter>(rememberedStatus)\n const [text, setText] = useState('')\n const [basis, setBasis] = useState<ReportTimeBasis>('created')\n // The open report's detail, read through the same hook the right column's canvas\n // tab uses — one read path, so the two surfaces cannot disagree about a report.\n const { detail, error: detailError } = useReportDetail(openReport, detailNonce)\n // Belt and braces on top of the hook's own report keying: everything below that\n // renders the panel, or synthesizes a row for it, uses only a detail that was\n // read for the report currently open.\n const shown = detail !== undefined && detail.front.report === openReport ? detail : undefined\n\n useEffect(() => {\n try {\n globalThis.localStorage?.setItem(FILTER_KEY, JSON.stringify({ status }))\n } catch {\n // Remembering the filter is a convenience, never a requirement.\n }\n }, [status])\n\n // A different session is a different tree, and everything above was read for\n // the old one: the payload, the failure, the open report and the pending jump.\n // Resetting them together is what keeps a switched-to session from rendering\n // the previous session's collaboration (or leaving its report open in it).\n useEffect(() => {\n setPayload(undefined)\n setError(undefined)\n setOpenReport(undefined)\n setScrollTarget(undefined)\n }, [sessionId])\n\n useEffect(() => {\n if (sessionId === undefined || sessionId === '') {\n setError('no-session')\n return undefined\n }\n let cancelled = false\n getJson<TimelinePayload>(`${TIMELINE_URL}?root=${encodeURIComponent(sessionId)}`)\n .then((value) => { if (!cancelled) { setPayload(value); setError(undefined) } })\n .catch((cause: unknown) => { if (!cancelled) setError(cause instanceof Error ? cause.message : String(cause)) })\n // Note the asymmetry: a new read does NOT clear the previous failure. The\n // banner below stays until a read actually succeeds, so a failing host is a\n // steady, readable statement instead of a strobe of \"failed / stale / failed\".\n return () => { cancelled = true }\n }, [sessionId, timelineNonce])\n\n // Keep an open tab honest. Polling stops while the page is hidden, and a\n // returning page catches up immediately, so a background tab costs nothing and\n // a foreground one is never behind.\n useEffect(() => {\n if (sessionId === undefined || sessionId === '') return undefined\n const tick = (): void => {\n if (globalThis.document?.visibilityState === 'hidden') return\n setTimelineNonce((value) => value + 1)\n }\n const timer = globalThis.setInterval(tick, POLL_INTERVAL_MS)\n const onVisibility = (): void => { if (globalThis.document?.visibilityState !== 'hidden') tick() }\n globalThis.document?.addEventListener('visibilitychange', onVisibility)\n return () => {\n globalThis.clearInterval(timer)\n globalThis.document?.removeEventListener('visibilitychange', onVisibility)\n }\n }, [sessionId])\n\n const rows = useMemo<Row[]>(() => (payload === undefined ? [] : buildRows(payload, basis)), [payload, basis])\n const counts = useMemo(() => statusCounts(payload?.reports ?? []), [payload])\n const inList = useMemo(() => (payload === undefined ? new Set<string>() : reportIds(payload)), [payload])\n // The lease state is not on the digest: liveness comes from the hop stream, and\n // the host derives it once so the browser never needs a second copy of the rule.\n const lanes = useMemo(() => (payload === undefined ? new Map<string, WorktreeLane>() : laneIndex(payload)), [payload])\n // The author's depth, for the row a synthesized report needs. Taken from the\n // model so the payload's depth rule has one implementation.\n const depths = useMemo(() => (payload === undefined ? new Map<string, number>() : sessionDepths(payload)), [payload])\n const filter = useMemo(() => ({ status, text }), [status, text])\n const visible = useMemo<Row[]>(() => {\n const kept = filterRows(rows, filter)\n if (openReport === undefined || shown === undefined) return kept\n // The panel is anchored to a report row, so the open report must have one —\n // see `withSynthesizedRow` for the two user paths that would otherwise leave\n // the panel with nowhere to render, and why the row is inserted in time\n // order instead of appended. The depth is the author's, so a filtered-out\n // report does not jump to the root of the tree it is rendered under.\n return withSynthesizedRow(kept, shown.front, basis, depths.get(shown.front.from) ?? 0)\n }, [rows, filter, openReport, shown, basis, depths])\n // A thread link can point at a row that does not exist yet: a report outside\n // this tree, or one the current filter hides, only gets its row once its detail\n // read arrives. So the request is not a one-shot — it waits for the row to\n // mount, and is abandoned the moment the open report moves away from it.\n useEffect(() => {\n if (scrollTarget === undefined) return\n if (openReport !== scrollTarget) {\n setScrollTarget(undefined)\n return\n }\n const element = rowRefs.current.get(scrollTarget)\n if (element === undefined) return\n setScrollTarget(undefined)\n // `scrollIntoView` is the one call here that a non-browser DOM may not have;\n // skipping it only costs the jump, never the expansion that already happened.\n if (typeof element.scrollIntoView === 'function') element.scrollIntoView({ block: 'nearest' })\n }, [scrollTarget, openReport, visible])\n // Reports surviving the FILTER, as opposed to `visible`, which also carries a\n // synthesized row for the open report. Keeping the two apart is what lets the\n // panel say which of the two reasons applies: outside this tree, or hidden by\n // the current filter. Claiming \"another line\" for a filtered report would be\n // simply wrong.\n const filteredReportIds = useMemo(() => {\n const ids = new Set<string>()\n for (const row of filterRows(rows, filter)) if (row.kind === 'report') ids.add(row.front.report)\n return ids\n }, [rows, filter])\n const filtering = isFiltering(filter)\n // Counted from the unfiltered row set so a synthesized out-of-tree row cannot\n // inflate the summary.\n const visibleReportCount = useMemo(() => visibleReports(rows, filter), [rows, filter])\n // Which of the two \"nothing to show\" sentences applies. Decided in the pure\n // model rather than inline here, so `emptyState`'s tests pin it.\n const empty = useMemo(\n () => (payload === undefined ? 'none' : emptyState(payload, rows, filter)),\n [payload, rows, filter],\n )\n // Session titles, for the hover hint on a shortened id. Deliberately NOT the\n // row label: a title is the session's first prompt, so it is unbounded, while\n // a row has to stay one scannable line.\n const titles = useMemo(\n () => (payload === undefined ? new Map<string, string>() : sessionTitles(payload)),\n [payload],\n )\n const idHint = useCallback((id: string): string => {\n const title = titles.get(id)\n return title === undefined ? id : `${title} · ${id}`\n }, [titles])\n\n /**\n * One session id the way a dense row shows it: shortened, with the full value\n * and the session title on hover. The hover is not decoration — it is where\n * the human name lives, because titles are unbounded and a row is not.\n */\n const idRef = (id: string): ReactElement => (\n <Tooltip label={idHint(id)} side=\"bottom\" delayMs={400}>\n <span>{shortId(id)}</span>\n </Tooltip>\n )\n\n const refresh = useCallback(() => {\n setTimelineNonce((value) => value + 1)\n setDetailNonce((value) => value + 1)\n }, [])\n\n /**\n * Open a report that was reached through a link rather than its own row.\n *\n * A row click expands in place — the row is already under the pointer and needs\n * no help. A thread link is different: its target can be off screen, or have no\n * row at all yet (outside this tree, or hidden by the filter), so the request to\n * bring it on screen is recorded here and satisfied by the effect above when\n * the row finally mounts.\n */\n const openLinked = useCallback((report: string | undefined) => {\n setOpenReport(report)\n setScrollTarget(report)\n }, [])\n\n const shell: Record<string, string | number> = {\n padding: '16px 20px',\n overflowY: 'auto',\n height: '100%',\n font: 'var(--dsw-font-xs-13, 13px/1.6 system-ui, sans-serif)',\n color: 'var(--dsw-alias-label-primary, inherit)',\n }\n const head: Record<string, string | number> = {\n display: 'flex',\n alignItems: 'baseline',\n gap: '12px',\n marginBottom: '12px',\n }\n const summary: Record<string, string | number> = {\n color: 'var(--dsw-alias-label-tertiary, #888)',\n fontSize: 'var(--dsw-font-xxs-12, 12)',\n flex: '1 1 auto',\n }\n /**\n * The toolbar's search seat.\n *\n * `Input` renders its own wrapper around the field, so the flex sizing lives\n * on this outer box rather than on the control itself.\n */\n const searchBox: Record<string, string | number> = {\n flex: '1 1 200px',\n minWidth: '140px',\n display: 'flex',\n }\n const tools: Record<string, string | number> = {\n display: 'flex',\n alignItems: 'center',\n flexWrap: 'wrap',\n gap: '6px',\n marginBottom: '10px',\n }\n /** Chips sit next to a label; the shell's tags carry no spacing of their own. */\n const tagRow: Record<string, string | number> = {\n display: 'inline-flex',\n gap: '4px',\n marginRight: '6px',\n verticalAlign: 'baseline',\n }\n /** Keeps the state dot on the row's text baseline. */\n const dotCell: Record<string, string | number> = {\n display: 'inline-flex',\n alignItems: 'center',\n }\n /** The caption look for a timestamp; the digest's meta line reuses its colour. */\n const time: Record<string, string | number> = {\n color: 'var(--dsw-alias-label-caption, #999)',\n fontSize: 'var(--dsw-font-xxxs-11, 11)',\n whiteSpace: 'nowrap',\n }\n /** The muted caption colour, for the empty-state sentences. */\n const muted: Record<string, string | number> = {\n color: 'var(--dsw-alias-label-tertiary, #888)',\n }\n /**\n * The strip that reports a failed re-read WITHOUT replacing the list.\n *\n * A warning, not an error: the ledger on screen is still the last thing that was\n * successfully read, and the poll keeps trying. Colouring it like a page failure\n * would teach the reader to stop believing the colour. The token is the shell's\n * warn label rather than a mixed colour, so it follows the theme.\n */\n const notice: Record<string, string | number> = {\n display: 'flex',\n alignItems: 'center',\n gap: '6px',\n marginBottom: '10px',\n color: 'var(--dsw-alias-state-warn-label, #a60)',\n }\n\n // The full-page failure is only for \"there is nothing to show yet\". Once a\n // payload has arrived, a failed re-read is a fact ABOUT the list on screen — not\n // a reason to replace it, drop the open report and lose the reader's position.\n if (payload === undefined && error !== undefined) {\n return (\n <div style={shell}>\n <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>\n <StateDot state=\"error\" size={8} />\n <span>{t('view.error', { code: error })}</span>\n </div>\n <div style={{ marginTop: '8px' }}>\n <Button variant=\"outline\" onClick={refresh}>{t('view.retry')}</Button>\n </div>\n </div>\n )\n }\n\n if (payload === undefined) {\n return <div style={shell}>{t('view.loading')}</div>\n }\n\n const statusLabel: Record<StatusFilter, ReportLedgerKey> = {\n all: 'filter.all',\n open: 'filter.open',\n acked: 'filter.acked',\n closed: 'filter.closed',\n }\n const statusCount: Record<StatusFilter, number> = {\n all: counts.all,\n open: counts.open,\n acked: counts.acked,\n closed: counts.closed,\n }\n const chips: StatusFilter[] = ['all', 'open', 'acked', 'closed']\n\n return (\n <div style={shell}>\n {/* Kept above the toolbar on purpose: the header still shows when the last\n read failed, so the failure has to be readable next to it rather than\n instead of the whole tab. */}\n {error === undefined ? null : (\n <div style={notice}>\n <StateDot state=\"warning\" size={8} />\n <span style={{ flex: '1 1 auto' }}>{t('view.error', { code: error })}</span>\n <Button variant=\"outline\" onClick={refresh}>{t('view.retry')}</Button>\n </div>\n )}\n <div style={head}>\n <strong>{t('view.tab')}</strong>\n <span style={summary}>\n {filtering\n ? t('filter.showing', { visible: visibleReportCount, total: counts.all })\n : t('view.summary', { sessions: payload.sessions.length, reports: payload.reports.length })}\n </span>\n <Button variant=\"outline\" icon={<IconRefreshOutlineRegular size={16} />} onClick={refresh}>{t('view.refresh')}</Button>\n </div>\n\n <div style={tools} title={t('filter.hint')}>\n {chips.map((candidate) => (\n <Pill\n key={candidate}\n active={status === candidate}\n aria-pressed={status === candidate}\n onClick={() => { setStatus(candidate) }}\n >\n {`${t(statusLabel[candidate])} ${statusCount[candidate]}`}\n </Pill>\n ))}\n <div style={searchBox}>\n <Input\n type=\"search\"\n className={SEARCH_CLASS}\n value={text}\n placeholder={t('filter.search')}\n aria-label={t('filter.search')}\n onChange={(event) => { setText(event.target.value) }}\n />\n </div>\n {filtering ? (\n <Button variant=\"outline\" onClick={() => { setStatus('all'); setText('') }}>{t('filter.clear')}</Button>\n ) : null}\n <Tooltip label={t('basis.hint')} side=\"bottom\" delayMs={400}>\n <Button variant=\"outline\" onClick={() => { setBasis(basis === 'created' ? 'updated' : 'created') }}>\n {t(basis === 'created' ? 'basis.created' : 'basis.updated')}\n </Button>\n </Tooltip>\n </div>\n\n {empty === 'no-reports' ? <div style={muted}>{t('view.empty')}</div> : null}\n {empty === 'filtered-out' ? <div style={muted}>{t('filter.none')}</div> : null}\n\n {visible.map((row) => {\n const pad = 8 + row.depth * 18\n if (row.kind === 'session') {\n return (\n <div key={`s-${row.id}`} style={{ display: 'flex', gap: '8px', padding: `4px 0 4px ${pad}px`, alignItems: 'baseline' }}>\n {/* The shell's state dot, not a text bullet: `ongoing` is the\n animated one, so a resident session reads as alive at a glance. */}\n <Tooltip label={idHint(row.id)} side=\"bottom\" delayMs={400}>\n <span style={dotCell}><StateDot state={row.live ? 'ongoing' : 'idle'} size={8} /></span>\n </Tooltip>\n <span style={{ flex: '1 1 auto', minWidth: 0 }}>\n {row.delegated || row.live ? (\n <span style={tagRow}>\n {row.delegated ? <Tag tone=\"info\">{t('view.delegated')}</Tag> : null}\n {row.live ? <Tag tone=\"success\">{t('view.live')}</Tag> : null}\n </span>\n ) : null}\n {row.title ?? shortId(row.id)}\n </span>\n {/* The session's own short id, so a digest row's `from=`/`to=` can be\n read back to the tree it belongs to. Titles stay the prominent\n label — this is the cross-reference, not the identity. */}\n {row.title === undefined ? null : (\n <Tooltip label={row.id} side=\"right\" delayMs={400}>\n <span style={time}>{shortId(row.id)}</span>\n </Tooltip>\n )}\n <span style={time}>{stamp(row.at)}</span>\n </div>\n )\n }\n const front = row.front\n const expanded = openReport === front.report\n const panelId = `report-ledger-panel-${front.report}`\n // The label comes off the binding, so a report opened by id and rendered\n // before it is in the payload still names its checkout; only the lease\n // state needs the lane, and a missing lane degrades to `none`.\n const wtLabel = worktreeLabel(front.worktree)\n const wtLane = lanes.get(front.report)\n const toggleLabel = fill(expanded ? t('row.collapse') : t('row.expand'), { report: front.report })\n return (\n <div\n key={`r-${front.report}`}\n // The row container (row + its panel) is what a thread jump scrolls to:\n // bringing the row on screen is what makes the expansion visible.\n ref={(element) => {\n if (element === null) rowRefs.current.delete(front.report)\n else rowRefs.current.set(front.report, element)\n }}\n style={{ paddingLeft: `${pad}px` }}\n >\n {/* The row is a plain container, not a `role=\"button\"`.\n It used to be one, and it contained a real <button> (the task\n chip) — nested interactive content. Worse, a role=\"button\" takes\n its accessible name from its whole subtree, so a screen reader\n read out a line of session uuids and then the task label twice,\n once inside the row's own name.\n The disclosure now lives on the leading control: the shell's own\n Button, whose name says what it does and which points at the panel\n it opens for as long as that panel exists. Clicking anywhere on the\n row stays as the pointer convenience, which is why the container\n keeps a click handler with no role: the keyboard path is the\n button, and the pointer path is the row. */}\n <div\n onClick={() => { setOpenReport(expanded ? undefined : front.report) }}\n style={{\n display: 'flex',\n gap: '8px',\n alignItems: 'baseline',\n padding: '5px 8px',\n margin: '2px 0',\n borderRadius: '6px',\n cursor: 'pointer',\n background: 'var(--dsw-alias-bg-layer-1, rgba(0,0,0,0.02))',\n border: '1px solid var(--dsw-alias-border-l1, rgba(0,0,0,0.06))',\n }}\n >\n <Button\n variant=\"ghost\"\n size=\"sm\"\n icon={expanded ? <IconChevronDownOutlineRegular size={14} /> : <IconChevronRightOutlineRegular size={14} />}\n aria-expanded={expanded}\n // Only a mounted panel has an id to point at; a collapsed\n // disclosure claiming `aria-controls` names an absent element and\n // gives assistive tech a relationship that does not exist.\n {...(expanded ? { 'aria-controls': panelId } : {})}\n aria-label={toggleLabel}\n title={toggleLabel}\n onClick={(event) => {\n // The row's own click handler is the same action; without this\n // the one click would toggle twice and appear to do nothing.\n event.stopPropagation()\n setOpenReport(expanded ? undefined : front.report)\n }}\n />\n <span style={{ flex: '1 1 auto', minWidth: 0 }}>\n <span style={tagRow}><Tag tone={STATUS_TONE[front.status]}>{t(STATUS_LABEL[front.status])}</Tag></span>\n <strong>{front.report}</strong> {front.subject}\n {/* The task chip is a filter shortcut, not decoration: clicking it\n reuses the search box, so grouping by collaboration needs no\n state of its own. It stops the click from also toggling the\n detail panel, and it CLEARS the status filter — its own label\n promises \"show only this task\", which stops being true the\n moment a status chip is also narrowing the list. */}\n {front.task === undefined ? null : (\n <Pill\n title={fill(t('row.filterByTask'), { task: front.task })}\n onClick={(event) => {\n event.stopPropagation()\n setStatus('all')\n setText(front.task as string)\n }}\n >\n {front.task}\n </Pill>\n )}\n {/* The checkout chip follows the task chip exactly, for the same\n reason: it reuses the search box instead of inventing a second\n filter, and it clears the status filter because its own label\n promises \"show only this checkout\". Its tone is the lease\n state, which is the one thing the digest cannot carry — a\n checkout nobody holds and one being worked in look identical\n from the binding alone. */}\n {/* The chip's handler stays on the inner span: the shell's `Tag`\n takes tone, className and children only and renders a span, so\n there is no `onClick` to hoist onto it. Promoting the chip to a\n `Pill` would be the other way — and it would turn a read-only\n status label into a second button in the row, which is a product\n change rather than a fix. The cost is the tag's own padding not\n being part of the click target. */}\n {wtLabel === undefined ? null : (\n <Tag tone={LEASE_TONE[wtLane?.state ?? 'none']}>\n <span\n title={`${fill(t('row.filterByWorktree'), { worktree: wtLabel })} — ${t(LEASE_HINT[wtLane?.state ?? 'none'])}`}\n style={{ cursor: 'pointer' }}\n onClick={(event) => {\n event.stopPropagation()\n setStatus('all')\n setText(wtLabel)\n }}\n >\n {wtLabel}\n </span>\n </Tag>\n )}\n {/* One line per report is the point of the digest, so the meta\n line is clipped rather than wrapped: a single over-connected\n report must not turn every row into a paragraph. `min-width: 0`\n on the parent is what lets the ellipsis happen at all — without\n it the flex item cannot shrink below its content, and the row\n pushes the whole tab sideways instead. */}\n <div style={{ ...time, marginTop: '2px', overflow: 'hidden', textOverflow: 'ellipsis' }}>\n {`from=`}{idRef(front.from)}\n {front.to.length > 0 ? (\n <>\n {' → '}\n {front.to.map((id, index) => (\n <Fragment key={id}>{index > 0 ? ', ' : ''}{idRef(id)}</Fragment>\n ))}\n </>\n ) : null}\n {front.cc.length > 0 ? (\n <>\n {' cc '}\n {front.cc.map((id, index) => (\n <Fragment key={id}>{index > 0 ? ', ' : ''}{idRef(id)}</Fragment>\n ))}\n </>\n ) : null}\n {` · ${fill(t('row.hops'), { count: front.hops })}`}\n {front.authors.length > 1 ? ` · ${fill(t('row.authors'), { count: front.authors.length })}` : ''}\n {/* The last hop's action is ledger vocabulary, so it is named\n with the same word the transfer path uses below. */}\n {front.last === undefined ? '' : ` · ${t('row.last')} ${t(HOP_LABEL[front.last.action])}`}\n </div>\n </span>\n {/* The row prints the value it was SORTED by, not a fixed field: a\n list ordered by creation must not read as ordered by last\n activity, and the basis toggle moves both at once. */}\n <span style={time}>{stamp(row.at)}</span>\n </div>\n\n {expanded ? (\n <ReportDetailView\n report={front.report}\n detail={shown}\n error={detailError}\n t={t}\n idHint={idHint}\n id={panelId}\n variant=\"row\"\n onOpenReport={openLinked}\n inList={inList.has(front.report)}\n visibleInFilter={filteredReportIds.has(front.report)}\n />\n ) : null}\n </div>\n )\n })}\n </div>\n )\n}\n","/**\n * The card canvas's layout: sessions as **frames**, reports as **cards**, and the\n * transfer path as **wires** between them.\n *\n * This replaced the swimlane's own model, retired together with its view. The\n * swimlane proved that the shape of a collaboration can be drawn, but its nodes\n * are chips: a report is an `R-0001` and nothing more. Here a report is a\n * rectangle with room for its subject, so the drawing carries content instead of\n * only structure — and a session becomes a **frame that contains** its reports,\n * so belonging is drawn by containment rather than inferred from a column.\n *\n * Three rules keep this module the same kind of thing as the swimlane's — pure,\n * DOM-free, byte-deterministic for one input, and pinned by assertions:\n *\n * 1. **Column = depth, vertical order = the ledger's own order.** The payload's\n * session list is already a DFS pre-order, so a frame's column is its depth\n * and its y comes from that order. No graph algorithm is involved, for the\n * same reason as before: there are no ranks to compute.\n * 2. **Card size is a level-of-detail constant, never a text measurement.** Text\n * is wrapped and elided by the painter, against whatever room the card has.\n * Measuring here would make the layout depend on a canvas context, and the\n * only claim this design has to defend — \"how many objects are on screen at\n * this zoom\" — would stop being testable. The caller picks the tier from the\n * zoom (`lodFor`) and passes it in; geometry therefore changes per tier and\n * not per zoom pixel, which is also what keeps it from jittering mid-gesture.\n * 3. **Every endpoint is known before anything is drawn.** Ends are either a\n * card edge or a **port** on a frame edge, and ports are spread by an\n * order-preserving rule (below) so two wires never land on the same pixel.\n *\n * @module dsh-report-ledger/client/cardgraph-model\n */\n\nimport type { ReportFrontMatter, TimelinePayload } from '../shared/wire.ts'\nimport { shortId } from './timeline-model.ts'\n\n/** How much of a card there is room to draw, as a function of zoom. */\nexport type CardgraphLod = 'card' | 'compact' | 'chip'\n\n/** A card's rectangle at one level of detail. */\nexport interface CardSize {\n readonly width: number\n readonly height: number\n}\n\n/** An axis-aligned rectangle, the one shape culling and hit-testing work on. */\nexport interface Rect {\n readonly x: number\n readonly y: number\n readonly width: number\n readonly height: number\n}\n\n/** Which way an end faces. */\nexport type CardgraphSide = 'left' | 'right' | 'top' | 'bottom'\n\n/** Geometry knobs. Numbers, not styles: the painter decides how to paint them. */\nexport interface CardgraphOptions {\n /** Level of detail to lay out at. Pick it with {@link lodFor}. */\n readonly lod: CardgraphLod\n /** Zoom at or above which reports are drawn as full cards. */\n readonly cardMinZoom: number\n /** Zoom at or above which reports are drawn as compact cards. */\n readonly compactMinZoom: number\n /**\n * Card size per tier.\n *\n * A whole record rather than a partial one, so \"which sizes are in play\" is one\n * value that can be replaced wholesale by a test.\n */\n readonly sizes: Readonly<Record<CardgraphLod, CardSize>>\n /** Height of a frame's title bar. */\n readonly frameHeaderHeight: number\n /** Inset from a frame's side to its cards. */\n readonly framePaddingX: number\n /** Inset from the title bar down to the first card. */\n readonly framePaddingTop: number\n /** Inset below the last card. */\n readonly framePaddingBottom: number\n /** A frame is never narrower than this, even with no cards. */\n readonly minFrameWidth: number\n /** A frame is never shorter than this, so an empty session still reads as a box. */\n readonly minFrameHeight: number\n /** Vertical gap between two stacked cards. */\n readonly cardGap: number\n /** Horizontal gap between two columns. */\n readonly columnGap: number\n /** Vertical gap between two frames in one column. */\n readonly frameGap: number\n /** Margin around the whole drawing. */\n readonly margin: number\n}\n\n/**\n * Defaults.\n *\n * A full card is 240x110 — enough for a title, a status line and two lines of\n * subject at a readable size. The chip tier is deliberately tiny: below 0.4 zoom\n * a card is a colour, and drawing readable text there would be a lie about how\n * much of the drawing the reader can actually take in.\n */\nexport const DEFAULT_CARDGRAPH_OPTIONS: CardgraphOptions = {\n lod: 'card',\n cardMinZoom: 0.8,\n compactMinZoom: 0.4,\n sizes: {\n card: { width: 240, height: 110 },\n compact: { width: 160, height: 36 },\n chip: { width: 60, height: 14 },\n },\n frameHeaderHeight: 26,\n framePaddingX: 12,\n framePaddingTop: 10,\n framePaddingBottom: 12,\n minFrameWidth: 180,\n minFrameHeight: 96,\n cardGap: 8,\n columnGap: 48,\n frameGap: 24,\n margin: 24,\n}\n\n/** How far out the drawing may be zoomed, in screen pixels per drawing unit. */\nexport const MIN_ZOOM = 0.25\n/** How far in the drawing may be zoomed. */\nexport const MAX_ZOOM = 2.5\n/** One step of the zoom controls, in screen pixels per drawing unit. */\nexport const ZOOM_STEP = 1.25\n/**\n * The most a fit may magnify.\n *\n * Filling the width on a drawing that holds one card would otherwise ask for\n * several times the design's size: one card stretched across the whole box,\n * larger than any reading needs and carrying no more in it, because a tier is\n * drawn at a fixed size and zooming past 100% only magnifies. One control step\n * is the ceiling, so the fit may lean in but never stretch.\n */\nexport const MAX_FIT_ZOOM = ZOOM_STEP\n\n/** One session's frame. */\nexport interface CardgraphFrame {\n /** Session id, absent for the shared frame of out-of-tree sessions. */\n readonly id?: string\n /** Position in {@link CardgraphLayout.frames}; also how wires name a frame. */\n readonly index: number\n /** Shortened session id for the title bar. */\n readonly shortId: string\n /** Resolved title, when the payload carried one. */\n readonly title?: string\n /** Depth in the subtree, which is also the column. */\n readonly depth: number\n /** Column this frame sits in. */\n readonly column: number\n /** Whether the session is currently resident. */\n readonly live: boolean\n /** Whether this is the shared frame for sessions outside the subtree. */\n readonly external: boolean\n /** How many cards it holds. */\n readonly cards: number\n readonly bounds: Rect\n}\n\n/** One column of the drawing: all frames of one subtree depth. */\nexport interface CardgraphColumn {\n /** The depth this column holds. */\n readonly depth: number\n readonly index: number\n readonly bounds: Rect\n}\n\n/** One report, as a rectangle. */\nexport interface CardgraphCard {\n readonly report: string\n readonly subject: string\n readonly status: ReportFrontMatter['status']\n readonly task?: string\n /** Index of the frame that contains it. */\n readonly frame: number\n /** Column it sits in, i.e. the depth of its author's session. */\n readonly column: number\n /** Stacking index inside its frame, counting from the top. */\n readonly slot: number\n /** The tier this card was laid out at, so the painter knows what fits. */\n readonly lod: CardgraphLod\n readonly bounds: Rect\n}\n\n/** What a wire means. */\nexport type CardgraphEdgeKind = 'to' | 'cc' | 'author' | 'thread'\n\n/**\n * One end of a wire.\n *\n * `card` ends are anchored to a card's edge, at the middle of that edge. `frame`\n * ends are **ports**: a frame edge is shared by every wire that touches it, so\n * the y is assigned by the spreading pass rather than by any single wire.\n */\nexport interface CardgraphEnd {\n readonly kind: 'card' | 'frame'\n /** Report id for a card end, session id for a frame end (absent = out of tree). */\n readonly id?: string\n /** Index into `cards` for a card end, into `frames` for a frame end. */\n readonly index: number\n readonly side: CardgraphSide\n readonly x: number\n readonly y: number\n}\n\n/** One drawn relation. */\nexport interface CardgraphEdge {\n /** The report the wire belongs to. */\n readonly report: string\n readonly kind: CardgraphEdgeKind\n /** Where the wire leaves; carries the arrowhead's opposite end. */\n readonly from: CardgraphEnd\n /** Where the wire lands; the arrowhead goes here. */\n readonly to: CardgraphEnd\n /** Whether either end is the out-of-tree frame. */\n readonly external: boolean\n /**\n * The x a thread runs down between columns.\n *\n * Only thread wires have one. A thread spans frames, and drawing it straight\n * would cross whatever sits in between; routing it down a **column gutter**\n * keeps it in the margin band, where only other wires are.\n */\n readonly viaX?: number\n /**\n * The wire's bounding box: both ends, plus the gutter a thread bends through.\n *\n * Culling works on this, which is why a wire whose ends are both off-screen but\n * whose middle crosses the viewport is still drawn.\n */\n readonly bounds: Rect\n}\n\n/** The whole drawing, as numbers. */\nexport interface CardgraphLayout {\n /** The tier this drawing was laid out at. */\n readonly lod: CardgraphLod\n readonly columns: readonly CardgraphColumn[]\n readonly frames: readonly CardgraphFrame[]\n /** In frame order, then top to bottom, so the order is fully determined. */\n readonly cards: readonly CardgraphCard[]\n readonly edges: readonly CardgraphEdge[]\n /** Every frame is this wide, so the columns line up. */\n readonly frameWidth: number\n /**\n * Height of a frame's title bar.\n *\n * Carried in the layout rather than re-declared by the painter: the bar is\n * geometry (cards start below it, ports stay out of it), so a painter that\n * hardcoded the number would put the title band and the cards in the same\n * place the moment the option moved.\n */\n readonly frameHeaderHeight: number\n readonly cardWidth: number\n readonly cardHeight: number\n readonly width: number\n readonly height: number\n}\n\n/**\n * Which tier a zoom level calls for.\n *\n * Thresholds are inclusive on the low end, so 0.8 is a full card and 0.4 is a\n * compact one: a zoom that lands exactly on a step reads as the richer of the\n * two. A zoom that is not a usable number degrades to the cheapest tier instead\n * of producing an infinite or negative box.\n * @param zoom - the current scale.\n * @param options - threshold overrides.\n * @returns the tier to lay out at.\n */\nexport function lodFor(zoom: number, options: Partial<CardgraphOptions> = {}): CardgraphLod {\n const opts: CardgraphOptions = { ...DEFAULT_CARDGRAPH_OPTIONS, ...options }\n if (!Number.isFinite(zoom)) return 'chip'\n if (zoom >= opts.cardMinZoom) return 'card'\n if (zoom >= opts.compactMinZoom) return 'compact'\n return 'chip'\n}\n\n/**\n * The three tiers in the order a zoom sweep visits them, richest first.\n *\n * Exported so a painter can pre-build its text caches per tier without hardcoding\n * the list in a second place.\n */\nexport const CARDGRAPH_LODS: readonly CardgraphLod[] = ['card', 'compact', 'chip']\n\n/** @returns the card size of one tier. */\nexport function cardSize(lod: CardgraphLod, options: Partial<CardgraphOptions> = {}): CardSize {\n const opts: CardgraphOptions = { ...DEFAULT_CARDGRAPH_OPTIONS, ...options }\n return opts.sizes[lod]\n}\n\n/** Whether two rectangles overlap. Touching edges do not count as overlapping. */\nexport function intersects(a: Rect, b: Rect): boolean {\n return a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height\n}\n\n/** Whether a point is inside a rectangle. The top-left edges are inside, the bottom-right ones are not. */\nexport function contains(rect: Rect, x: number, y: number): boolean {\n return x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height\n}\n\n/** Grow a rectangle by `by` on every side. */\nexport function grow(rect: Rect, by: number): Rect {\n return { x: rect.x - by, y: rect.y - by, width: rect.width + by * 2, height: rect.height + by * 2 }\n}\n\n/**\n * Keep the items whose box meets the viewport.\n *\n * This is the card canvas's replacement for the swimlane's row window: with a\n * free 2D canvas there are no rows to count, so \"what is on screen\" is decided by\n * rectangle overlap. `bounds` is the only field it looks at, which is why cards,\n * frames and wires all expose one.\n * @param items - anything with a bounding box.\n * @param viewport - the visible rect, **in drawing coordinates**.\n * @returns the items to draw, in the order they were given.\n */\nexport function cullByBounds<T extends { readonly bounds: Rect }>(\n items: readonly T[],\n viewport: Rect,\n): readonly T[] {\n return items.filter((item) => intersects(item.bounds, viewport))\n}\n\n/**\n * The topmost item under a point.\n *\n * Free of the DOM on purpose: the canvas has no elements to hit, so picking is a\n * function from a point and an ordered list to an item — and that function is\n * testable here rather than only by clicking in a browser. The caller passes the\n * list **topmost first**, which is what makes \"a card wins over the frame holding\n * it\" a property of the order rather than of the arithmetic.\n * @param items - candidates, topmost first.\n * @param x - point, in drawing coordinates.\n * @param y - point, in drawing coordinates.\n * @returns the first hit, or `undefined`.\n */\nexport function hitTest<T extends { readonly bounds: Rect }>(\n items: readonly T[],\n x: number,\n y: number,\n): T | undefined {\n return items.find((item) => contains(item.bounds, x, y))\n}\n\n/** A pan/zoom transform plus the size of the box it paints into. */\nexport interface CardgraphViewport {\n /** Pan, in screen pixels. */\n readonly offsetX: number\n readonly offsetY: number\n /** Scale, screen pixels per drawing unit. */\n readonly scale: number\n /** Width of the paint box, in screen pixels. */\n readonly width: number\n /** Height of the paint box, in screen pixels. */\n readonly height: number\n}\n\n/**\n * The part of the drawing a viewport can see, in drawing coordinates.\n *\n * The inverse of the transform the painter applies, and the input to culling. A\n * broken transform is treated as identity rather than propagated: a scale of zero\n * or a NaN would otherwise turn the visible rect into an infinite or reversed box\n * and either cull everything or draw everything.\n * @param view - the transform and the size of the paint box.\n * @param overscan - drawing units to keep on each side, so a pan does not reveal a\n * blank band before the next frame is painted.\n * @returns the visible rect.\n */\nexport function worldViewport(view: CardgraphViewport, overscan = 0): Rect {\n const scale = Number.isFinite(view.scale) && view.scale > 0 ? view.scale : 1\n const width = Number.isFinite(view.width) ? Math.max(0, view.width) : 0\n const height = Number.isFinite(view.height) ? Math.max(0, view.height) : 0\n const offsetX = Number.isFinite(view.offsetX) ? view.offsetX : 0\n const offsetY = Number.isFinite(view.offsetY) ? view.offsetY : 0\n const rect: Rect = {\n x: -offsetX / scale,\n y: -offsetY / scale,\n width: width / scale,\n height: height / scale,\n }\n return overscan === 0 ? rect : grow(rect, overscan)\n}\n\n/** A mutable end, while ports are still being spread. */\ninterface PendingEnd {\n kind: 'card' | 'frame'\n id?: string\n index: number\n side: CardgraphSide\n x: number\n y: number\n}\n\n/** A wire before its ports have y coordinates. */\ninterface PendingEdge {\n report: string\n kind: CardgraphEdgeKind\n from: PendingEnd\n to: PendingEnd\n external: boolean\n viaX?: number\n}\n\n/**\n * Build the card graph.\n *\n * Frames come from the payload's sessions: column by subtree depth, vertical\n * order by the payload's own order (the DFS pre-order the list indents by), so a\n * session keeps its relative position across the two views. Cards are stacked\n * inside their author's frame **by time**, because a frame is one session's own\n * timeline; the reader who wants one timeline across all sessions has the list\n * below. Only the reports handed in are drawn, so this is a filtered view rather\n * than a second source of truth.\n * @param payload - the endpoint payload, for the session frames.\n * @param reports - the reports to plot; any order, since each frame sorts its own.\n * @param options - geometry and tier overrides.\n * @returns the layout; empty (and zero-sized) when there is nothing to draw.\n */\nexport function buildCardgraph(\n payload: TimelinePayload,\n reports: readonly ReportFrontMatter[],\n options: Partial<CardgraphOptions> = {},\n): CardgraphLayout {\n const opts: CardgraphOptions = { ...DEFAULT_CARDGRAPH_OPTIONS, ...options }\n const size = opts.sizes[opts.lod]\n const frameWidth = Math.max(opts.minFrameWidth, size.width + opts.framePaddingX * 2)\n\n // -------------------------------------------------------------------------\n // Frames\n // -------------------------------------------------------------------------\n const sessions = payload.sessions\n const depths = [...new Set(sessions.map((node) => node.depth))].sort((a, b) => a - b)\n const columnOfDepth = new Map<number, number>()\n for (const [index, depth] of depths.entries()) columnOfDepth.set(depth, index)\n\n const frameOf = new Map<string, number>()\n const frames: {\n id?: string\n shortId: string\n title?: string\n depth: number\n column: number\n live: boolean\n external: boolean\n cards: number\n }[] = sessions.map((node, index) => {\n frameOf.set(node.id, index)\n return {\n ...(node.title === undefined ? {} : { title: node.title }),\n id: node.id,\n shortId: shortId(node.id),\n depth: node.depth,\n column: columnOfDepth.get(node.depth) ?? 0,\n live: node.live,\n external: false,\n cards: 0,\n }\n })\n\n // One shared frame for every session this subtree does not contain, so a report\n // copied in from another line still has somewhere to land. Created on demand:\n // a payload whose reports never leave the tree gets no such frame.\n const mentions = (front: ReportFrontMatter): readonly (string | undefined)[] =>\n [front.from, ...front.to, ...front.cc, ...front.authors]\n const needsExternal = reports.some((front) => mentions(front).some((id) => id !== undefined && !frameOf.has(id)))\n const externalDepth = sessions.reduce((max, node) => Math.max(max, node.depth), -1) + 1\n if (needsExternal) {\n frames.push({\n shortId: '',\n depth: externalDepth,\n column: depths.length,\n live: false,\n external: true,\n cards: 0,\n })\n }\n const externalIndex = needsExternal ? frames.length - 1 : -1\n const frameIndexById = (id: string | undefined): number =>\n id !== undefined && frameOf.has(id) ? (frameOf.get(id) as number) : externalIndex\n\n // -------------------------------------------------------------------------\n // Cards, stacked by time inside their frame\n // -------------------------------------------------------------------------\n const buckets: ReportFrontMatter[][] = frames.map(() => [])\n for (const front of reports) {\n const target = frameIndexById(front.from)\n if (target >= 0) buckets[target]?.push(front)\n }\n // Time order, with the report id as the tie-break so two reports created in the\n // same millisecond do not swap places between builds.\n for (const bucket of buckets) {\n bucket.sort((a, b) => a.created - b.created || (a.report < b.report ? -1 : a.report > b.report ? 1 : 0))\n }\n\n // -------------------------------------------------------------------------\n // Frame geometry\n // -------------------------------------------------------------------------\n const columnX = (column: number): number => opts.margin + column * (frameWidth + opts.columnGap)\n const frameHeight = (count: number): number => Math.max(\n opts.minFrameHeight,\n opts.frameHeaderHeight + opts.framePaddingTop + opts.framePaddingBottom\n + count * size.height + Math.max(0, count - 1) * opts.cardGap,\n )\n\n // The counts have to land before the boxes do: a frame's height is a function of\n // how many cards it holds, and a height that disagreed with the stacking would\n // put a card outside its own frame.\n frames.forEach((frame, index) => { frame.cards = buckets[index]?.length ?? 0 })\n\n const cursor = new Map<number, number>()\n const fixed: Rect[] = frames.map((frame) => {\n const height = frameHeight(frame.cards)\n const y = cursor.get(frame.column) ?? opts.margin\n cursor.set(frame.column, y + height + opts.frameGap)\n return { x: columnX(frame.column), y, width: frameWidth, height }\n })\n\n const columnCount = depths.length + (needsExternal ? 1 : 0)\n const heights = Array.from({ length: columnCount }, (_unused, column) => {\n const stacked = fixed.filter((_box, index) => frames[index]?.column === column)\n return stacked.length === 0\n ? 0\n : stacked.reduce((sum, box) => sum + box.height, 0) + (stacked.length - 1) * opts.frameGap\n })\n\n const layoutFrames: CardgraphFrame[] = frames.map((frame, index) => ({\n ...(frame.id === undefined ? {} : { id: frame.id }),\n ...(frame.title === undefined ? {} : { title: frame.title }),\n index,\n shortId: frame.shortId,\n depth: frame.depth,\n column: frame.column,\n live: frame.live,\n external: frame.external,\n cards: frame.cards,\n bounds: fixed[index] as Rect,\n }))\n\n const columns: CardgraphColumn[] = Array.from({ length: columnCount }, (_unused, column) => ({\n depth: depths[column] ?? externalDepth,\n index: column,\n bounds: {\n x: columnX(column),\n y: opts.margin,\n width: frameWidth,\n height: Math.max(0, heights[column] ?? 0),\n },\n }))\n\n // -------------------------------------------------------------------------\n // Card geometry\n // -------------------------------------------------------------------------\n const cards: CardgraphCard[] = []\n const cardIndexOf = new Map<string, number>()\n layoutFrames.forEach((frame) => {\n const bucket = buckets[frame.index] ?? []\n bucket.forEach((front, slot) => {\n cardIndexOf.set(front.report, cards.length)\n cards.push({\n report: front.report,\n subject: front.subject,\n status: front.status,\n ...(front.task === undefined ? {} : { task: front.task }),\n frame: frame.index,\n column: frame.column,\n slot,\n lod: opts.lod,\n bounds: {\n x: frame.bounds.x + opts.framePaddingX,\n y: frame.bounds.y + opts.frameHeaderHeight + opts.framePaddingTop + slot * (size.height + opts.cardGap),\n width: size.width,\n height: size.height,\n },\n })\n })\n })\n\n // -------------------------------------------------------------------------\n // Wires\n // -------------------------------------------------------------------------\n const cardEnd = (index: number, side: CardgraphSide): PendingEnd => {\n const box = cards[index]?.bounds ?? { x: 0, y: 0, width: 0, height: 0 }\n return {\n kind: 'card',\n id: cards[index]?.report,\n index,\n side,\n x: side === 'right' ? box.x + box.width : side === 'left' ? box.x : box.x + box.width / 2,\n y: side === 'bottom' ? box.y + box.height : side === 'top' ? box.y : box.y + box.height / 2,\n }\n }\n const frameEnd = (index: number, side: CardgraphSide): PendingEnd => {\n const box = fixed[index] ?? { x: 0, y: 0, width: 0, height: 0 }\n return {\n kind: 'frame',\n ...(frames[index]?.id === undefined ? {} : { id: frames[index]?.id }),\n index,\n side,\n x: side === 'right' ? box.x + box.width : box.x,\n y: box.y + box.height / 2,\n }\n }\n const externalBetween = (a: number, b: number): boolean =>\n (externalIndex >= 0 && (a === externalIndex || b === externalIndex)) || false\n\n const pending: PendingEdge[] = []\n\n /**\n * The side a wire should bend through to reach another frame without crossing\n * one.\n *\n * Two sessions in the **same column** — siblings, or any two frames at one\n * depth — cannot be reached left-to-right: a straight line from the sender's\n * card to the recipient's facing edge runs back across the recipient's own box,\n * which reads as \"this wire belongs to that frame\". Such a wire bends down the\n * gutter beside the column instead: out of the card, along the gutter, into the\n * frame's edge on that same side. The gutter is on the right except in the last\n * column, where that would run along the outside of the drawing.\n */\n const gutterSide = (column: number): CardgraphSide => (column < columns.length - 1 ? 'right' : 'left')\n const gutterX = (column: number, side: CardgraphSide): number => {\n const box = columns[column]?.bounds ?? { x: 0, y: 0, width: 0, height: 0 }\n return side === 'right' ? box.x + box.width + opts.columnGap / 2 : box.x - opts.columnGap / 2\n }\n\n // Wires are walked in **card order**, not in the order the caller passed the\n // reports: the drawing is then a function of the set of reports rather than of\n // the order they arrived in, which is what makes \"same ledger, same picture\"\n // testable with a shuffled input.\n const frontByReport = new Map(reports.map((front) => [front.report, front]))\n for (const [cardIndex, card] of cards.entries()) {\n const front = frontByReport.get(card.report)\n if (front === undefined) continue\n const ownFrame = card.frame\n const ownColumn = card.column\n\n // Deliveries leave the card and land on the recipient's frame, on the side\n // facing the sender. The digest carries no per-hop times (those live in the\n // route sidecar), so the landing y is the port's business, not the wire's.\n for (const [kind, ids] of [['to', front.to], ['cc', front.cc]] as const) {\n for (const id of new Set(ids)) {\n const target = frameIndexById(id)\n // Nothing to draw for a report delivered to its own session, and nothing\n // to draw to a session this drawing does not have a frame for.\n if (target < 0 || target === ownFrame) continue\n const targetColumn = frames[target]?.column ?? 0\n const sameColumn = targetColumn === ownColumn\n const side = sameColumn\n ? gutterSide(ownColumn)\n : targetColumn > ownColumn ? 'right' : 'left'\n pending.push({\n report: front.report,\n kind,\n from: cardEnd(cardIndex, side),\n to: frameEnd(target, sameColumn ? side : side === 'right' ? 'left' : 'right'),\n external: externalBetween(ownFrame, target),\n ...(sameColumn ? { viaX: gutterX(ownColumn, side) } : {}),\n })\n }\n }\n\n // Co-authorship reads the other way round: the co-author's frame reaches the\n // card, and the card is the end with the arrowhead.\n for (const id of new Set(front.authors)) {\n if (id === front.from) continue\n const source = frameIndexById(id)\n if (source < 0 || source === ownFrame) continue\n const sourceColumn = frames[source]?.column ?? 0\n const sameColumn = sourceColumn === ownColumn\n const side = sameColumn\n ? gutterSide(ownColumn)\n : sourceColumn < ownColumn ? 'right' : 'left'\n pending.push({\n report: front.report,\n kind: 'author',\n from: frameEnd(source, side),\n to: cardEnd(cardIndex, sameColumn ? side : side === 'right' ? 'left' : 'right'),\n external: externalBetween(source, ownFrame),\n ...(sameColumn ? { viaX: gutterX(ownColumn, side) } : {}),\n })\n }\n\n // Only when both ends are plotted: a parent that was filtered out or lives in\n // another tree is the detail panel's business, not the drawing's.\n const parentIndex = front.parent === undefined || front.parent === front.report\n ? undefined\n : cardIndexOf.get(front.parent)\n const parentCard = parentIndex === undefined ? undefined : cards[parentIndex]\n if (parentIndex !== undefined && parentCard !== undefined) {\n const from = cardEnd(parentIndex, 'bottom')\n const to = cardEnd(cardIndex, 'top')\n const column = parentCard.column\n const target = card.column\n // Route down the gutter on the side the child is on; a thread inside one\n // column takes the same detour as a same-column delivery, for the same\n // reason.\n const side = target > column ? 'right' : target < column ? 'left' : gutterSide(column)\n pending.push({\n report: front.report,\n kind: 'thread',\n from,\n to,\n external: externalBetween(parentCard.frame, ownFrame),\n viaX: gutterX(column, side),\n })\n }\n }\n\n // -------------------------------------------------------------------------\n // Ports: spread every wire that shares a frame edge, in the order of the far end\n // -------------------------------------------------------------------------\n const groups = new Map<string, { end: PendingEnd; order: number; tie: string }[]>()\n for (const edge of pending) {\n for (const end of [edge.from, edge.to]) {\n if (end.kind !== 'frame') continue\n const other = end === edge.from ? edge.to : edge.from\n const key = `${end.index}|${end.side}`\n const list = groups.get(key) ?? []\n list.push({ end, order: other.y, tie: `${edge.report}|${edge.kind}` })\n groups.set(key, list)\n }\n }\n for (const list of groups.values()) {\n list.sort((a, b) => a.order - b.order || (a.tie < b.tie ? -1 : a.tie > b.tie ? 1 : 0))\n const box = fixed[list[0]?.end.index ?? 0] ?? { x: 0, y: 0, width: 0, height: 0 }\n const top = box.y + opts.frameHeaderHeight + opts.framePaddingTop\n const bottom = box.y + box.height - opts.framePaddingBottom\n const span = bottom - top\n list.forEach((entry, index) => {\n // Evenly inside the card band, which keeps every port off both the title bar\n // and the frame's own edge. A frame too short for a band puts them on its\n // middle line rather than stacking them on one another.\n entry.end.y = span > 0 ? top + ((index + 1) * span) / (list.length + 1) : box.y + box.height / 2\n })\n }\n\n const edges: CardgraphEdge[] = pending.map((edge) => {\n const xs = [edge.from.x, edge.to.x, ...(edge.viaX === undefined ? [] : [edge.viaX])]\n const ys = [edge.from.y, edge.to.y]\n const minX = Math.min(...xs)\n const minY = Math.min(...ys)\n return {\n report: edge.report,\n kind: edge.kind,\n from: { ...edge.from },\n to: { ...edge.to },\n external: edge.external,\n ...(edge.viaX === undefined ? {} : { viaX: edge.viaX }),\n bounds: {\n x: minX,\n y: minY,\n width: Math.max(...xs) - minX,\n height: Math.max(...ys) - minY,\n },\n }\n })\n\n const contentWidth = columnCount === 0\n ? 0\n : opts.margin * 2 + columnCount * frameWidth + (columnCount - 1) * opts.columnGap\n const widest = heights.reduce((max, height) => Math.max(max, height), 0)\n\n return {\n lod: opts.lod,\n columns,\n frames: layoutFrames,\n cards,\n edges,\n frameWidth,\n frameHeaderHeight: opts.frameHeaderHeight,\n cardWidth: size.width,\n cardHeight: size.height,\n width: columnCount === 0 ? 0 : contentWidth,\n height: columnCount === 0 ? 0 : opts.margin * 2 + widest,\n }\n}\n\n/** Where a fit puts the drawing, and the tier it read to get there. */\nexport interface CardgraphFit {\n /**\n * The tier the fitted scale lands in.\n *\n * Returned rather than left for the caller to re-derive: the layout the view\n * draws must be the layout the scale was measured against, and the view only\n * knows the tier through `lodFor(zoom)`.\n */\n readonly lod: CardgraphLod\n /** Screen pixels per drawing unit. */\n readonly scale: number\n /** Pan, in screen pixels. */\n readonly offsetX: number\n readonly offsetY: number\n}\n\n/** How far a fitted drawing is kept from the box's left edge, in screen pixels. */\nconst FIT_LEFT_MIN = 8\n/** Where a fitted drawing starts, in screen pixels from the box's top. */\nconst FIT_TOP = 12\n\n/**\n * The transform that fills a box's width with the drawing, at the richest tier\n * that can afford it.\n *\n * Width is the target because time runs **down** here: a frame is a column, so\n * the horizontal axis is what has to fit on screen and the vertical axis is what\n * the reader pans along. Magnification stops at {@link MAX_FIT_ZOOM}, so a\n * drawing too narrow to fill the box is left at its own size instead of\n * stretched.\n *\n * The scale and the tier are mutually dependent — the tier is derived from the\n * zoom ({@link lodFor}) and the zoom is computed from a layout — so one pass over\n * the current layout can leave the view drawing one tier while its offset was\n * centred for another. Probing every tier makes the result a function of the box\n * alone: idempotent, and independent of whichever tier the reader arrived in.\n *\n * This lives beside the geometry rather than in the view because it *is*\n * geometry: pure, byte-deterministic for one input, and the only part of the\n * fitting that can be tested without a browser.\n * @param payload - the endpoint payload, for the session frames.\n * @param reports - the reports to plot.\n * @param boxWidth - the paint box's width in screen pixels.\n * @param options - geometry, tier and threshold overrides.\n * @returns the transform, or `undefined` when there is nothing to fit into or\n * nothing to fit (an empty drawing, or a box with no width yet).\n */\nexport function fitTransform(\n payload: TimelinePayload,\n reports: readonly ReportFrontMatter[],\n boxWidth: number,\n options: Partial<CardgraphOptions> = {},\n): CardgraphFit | undefined {\n const opts: CardgraphOptions = { ...DEFAULT_CARDGRAPH_OPTIONS, ...options }\n if (!Number.isFinite(boxWidth) || boxWidth <= 0) return undefined\n // The drawing already carries a margin on both sides, so the fit leaves one of\n // them as breathing room on the right instead of filling edge to edge.\n const available = Math.max(0, boxWidth - opts.margin)\n const bands: Readonly<Record<CardgraphLod, { readonly min: number; readonly max: number }>> = {\n card: { min: opts.cardMinZoom, max: MAX_FIT_ZOOM },\n compact: { min: opts.compactMinZoom, max: opts.cardMinZoom },\n chip: { min: MIN_ZOOM, max: opts.compactMinZoom },\n }\n const probes = CARDGRAPH_LODS\n .map((tier) => ({ tier, drawing: buildCardgraph(payload, reports, { ...options, lod: tier }) }))\n .filter((probe) => probe.drawing.width > 0)\n if (probes.length === 0) return undefined\n // The richest tier the box can pay for: the scale that fills the width at that\n // tier still reaches the tier's own floor, so the zoom the reader lands on is\n // the tier that was measured. Demanding an exact `lodFor(scale) === tier` match\n // instead leaves box widths where no tier can have both — a two-column drawing\n // between 396 and 523 px, say — and a fit that finds nothing writes nothing:\n // the drawing keeps whatever transform it already had, which is exactly the\n // state dependence the tier probing exists to remove.\n const affordable = probes.find((probe) => available / probe.drawing.width >= bands[probe.tier].min)\n // Nothing can pay for its own floor only when the box is narrower than the\n // cheapest tier's minimum zoom allows: show that tier at the floor rather than\n // leaving the reader with a control that does nothing.\n const chosen = affordable ?? probes[probes.length - 1]\n const band = bands[chosen.tier]\n let scale = Math.min(Math.max(available / chosen.drawing.width, band.min), band.max)\n // A band's top edge belongs to the richer tier (the thresholds are inclusive on\n // the low end), so a scale clamped exactly onto it would be laid out one tier up\n // from the one that was measured. Step it down by a millionth: invisible to the\n // reader, and enough for `lodFor` to agree.\n if (lodFor(scale, opts) !== chosen.tier) scale *= 1 - 1e-9\n return {\n lod: chosen.tier,\n scale,\n offsetX: Math.max(FIT_LEFT_MIN, (boxWidth - chosen.drawing.width * scale) / 2),\n offsetY: FIT_TOP,\n }\n}\n","/**\n * Painting the card canvas.\n *\n * The layout decides *what* is where; this module decides what a card, a frame\n * and a wire look like, and pushes them at a 2D context. Two choices carry the\n * design:\n *\n * 1. **The painter talks to a structural context, not to a canvas.** Everything\n * it needs is the small `PaintContext` surface below — fills, paths, text,\n * one transform — so the same painter drives a canvas, a recorder in a test,\n * or an SVG surface, and nothing here can reach for the DOM. This is the part\n * of draw.io's architecture worth copying: its shapes only ever talk to an\n * `AbstractCanvas2D`, so `SvgCanvas2D` and `XmlCanvas2D` are drop-in. Ours\n * keeps the same door open for a fraction of the code.\n *\n * 2. **Text is measured here and nowhere else.** `foldText` wraps and elides\n * against a `measure` function, whose only production implementation reads the\n * context's own metrics — cached per (font, text), because every measure on a\n * canvas context is a synchronous text-layout call. The geometry never sees\n * any of it: the layout's card sizes are constants, so however long a subject\n * is, the card it sits in does not move.\n *\n * @module dsh-report-ledger/client/cardgraph-painter\n */\n\nimport type {\n CardgraphCard, CardgraphEdge, CardgraphEdgeKind, CardgraphFrame, CardgraphLayout, CardgraphViewport, Rect,\n} from './cardgraph-model.ts'\nimport { intersects, worldViewport } from './cardgraph-model.ts'\n\n/**\n * The 2D surface the painter draws through.\n *\n * A hand-picked subset of `CanvasRenderingContext2D`, which a real canvas context\n * satisfies as-is and a test can implement in twenty lines. Keeping it this small\n * is deliberate: every method here is one a hypothetical SVG painter must also\n * provide, and anything outside it would be a canvas-only shortcut.\n */\nexport interface PaintContext {\n save(): void\n restore(): void\n setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void\n clearRect(x: number, y: number, width: number, height: number): void\n fillRect(x: number, y: number, width: number, height: number): void\n beginPath(): void\n closePath(): void\n moveTo(x: number, y: number): void\n lineTo(x: number, y: number): void\n arc(x: number, y: number, radius: number, startAngle: number, endAngle: number): void\n arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void\n clip(): void\n fill(): void\n stroke(): void\n fillText(text: string, x: number, y: number): void\n measureText(text: string): { readonly width: number }\n setLineDash(segments: readonly number[]): void\n translate(x: number, y: number): void\n scale(x: number, y: number): void\n fillStyle: string\n strokeStyle: string\n lineWidth: number\n lineDashOffset: number\n globalAlpha: number\n font: string\n textAlign: CanvasTextAlign\n textBaseline: CanvasTextBaseline\n}\n\n/** One font token, resolved to what a context needs. */\nexport interface PaintFont {\n readonly size: number\n readonly weight: number\n readonly lineHeight: number\n readonly family: string\n}\n\n/**\n * The colours and fonts the painter draws with.\n *\n * Read once from the shell's `--dsw-*` tokens (`readTheme`), so the drawing\n * follows the light/dark theme without knowing it exists. Every field is a string\n * a context accepts verbatim.\n */\nexport interface PaintTheme {\n /** Page background behind the drawing. */\n readonly canvas: string\n /** Frame body. */\n readonly frame: string\n /** Frame title bar. */\n readonly frameBar: string\n /** Card body. */\n readonly surface: string\n /** Hairlines: frame borders, card borders. */\n readonly border: string\n readonly frameBorder: string\n /** Text, from loudest to quietest. */\n readonly text: string\n readonly secondary: string\n readonly caption: string\n readonly tertiary: string\n /** Accent for the focused or open card. */\n readonly accent: string\n /** Lifecycle tones, matching the tab's `Tag` tones. */\n readonly warning: string\n readonly success: string\n readonly error: string\n /** The colour threads are drawn in. */\n readonly business: string\n readonly fonts: {\n /** Frame titles. */\n readonly title: PaintFont\n /** Report ids, which want weight rather than size. */\n readonly id: PaintFont\n /** Card subjects. */\n readonly body: PaintFont\n /** Status and task, the quietest line. */\n readonly meta: PaintFont\n }\n}\n\n/** A guaranteed-visible theme, for a context with no tokens at all. */\nexport const FALLBACK_THEME: PaintTheme = {\n canvas: '#ffffff',\n frame: '#f6f7f9',\n frameBar: '#eef0f3',\n surface: '#ffffff',\n border: '#d8dbe0',\n frameBorder: '#c3c7ce',\n text: '#1a1c1f',\n secondary: '#4a4f57',\n caption: '#70757d',\n tertiary: '#9aa0a8',\n accent: '#2f6fed',\n warning: '#c77700',\n success: '#1f9254',\n error: '#d33a2c',\n business: '#2f6fed',\n fonts: {\n title: { size: 13, weight: 500, lineHeight: 18, family: 'system-ui, sans-serif' },\n id: { size: 12, weight: 600, lineHeight: 16, family: 'system-ui, sans-serif' },\n body: { size: 11, weight: 400, lineHeight: 15, family: 'system-ui, sans-serif' },\n meta: { size: 11, weight: 500, lineHeight: 15, family: 'system-ui, sans-serif' },\n },\n}\n\n/** Token name → the field it fills. Keeping it as data means one reader, not twenty. */\nconst TOKENS: readonly (readonly [keyof Omit<PaintTheme, 'fonts'>, string, string])[] = [\n ['canvas', '--dsw-alias-bg-base', FALLBACK_THEME.canvas],\n ['frame', '--dsw-alias-bg-layer-1', FALLBACK_THEME.frame],\n ['frameBar', '--dsw-alias-bg-layer-2', FALLBACK_THEME.frameBar],\n ['surface', '--dsw-alias-bg-layer-3', FALLBACK_THEME.surface],\n ['border', '--dsw-alias-border-l1', FALLBACK_THEME.border],\n ['frameBorder', '--dsw-alias-border-l2', FALLBACK_THEME.frameBorder],\n ['text', '--dsw-alias-label-primary', FALLBACK_THEME.text],\n ['secondary', '--dsw-alias-label-secondary', FALLBACK_THEME.secondary],\n ['caption', '--dsw-alias-label-caption', FALLBACK_THEME.caption],\n ['tertiary', '--dsw-alias-label-tertiary', FALLBACK_THEME.tertiary],\n ['accent', '--dsw-alias-brand-primary', FALLBACK_THEME.accent],\n ['warning', '--dsw-alias-state-warn-primary', FALLBACK_THEME.warning],\n ['success', '--dsw-alias-state-success-primary', FALLBACK_THEME.success],\n ['error', '--dsw-alias-state-error-primary', FALLBACK_THEME.error],\n ['business', '--dsw-alias-state-business-primary', FALLBACK_THEME.business],\n]\n\n/** Font token → the field it fills. Each token carries its own size, weight and family. */\nconst FONT_TOKENS: readonly (readonly [keyof PaintTheme['fonts'], string, PaintFont])[] = [\n ['title', '--dsw-font-xs-13', FALLBACK_THEME.fonts.title],\n ['id', '--dsw-font-xxs-strong-12', FALLBACK_THEME.fonts.id],\n ['body', '--dsw-font-xxxs-11', FALLBACK_THEME.fonts.body],\n ['meta', '--dsw-font-xxxs-strong-11', FALLBACK_THEME.fonts.meta],\n]\n\n/** The one token a theme change always moves, so a repaint can detect it cheaply. */\nconst CANARY = '--dsw-alias-bg-base'\n\n/** A `var()`-style read of one token, with a fallback for a shell that has none. */\ntype ReadToken = (name: string) => string\n\n/** Assemble a canvas font string. */\nexport function fontString(font: PaintFont): string {\n return `${font.weight} ${font.size}px ${font.family}`\n}\n\n/** Read one font token, taking its size/weight/line-height/family parts. */\nfunction readFont(read: ReadToken, token: string, fallback: PaintFont): PaintFont {\n const size = Number.parseFloat(read(`${token}-font-size`))\n const weight = Number.parseFloat(read(`${token}-font-weight`))\n const lineHeight = Number.parseFloat(read(`${token}-line-height`))\n const family = read(`${token}-font-family`)\n return {\n size: Number.isFinite(size) && size > 0 ? size : fallback.size,\n weight: Number.isFinite(weight) && weight > 0 ? weight : fallback.weight,\n lineHeight: Number.isFinite(lineHeight) && lineHeight > 0 ? lineHeight : fallback.lineHeight,\n family: family === '' ? fallback.family : family,\n }\n}\n\n/**\n * Read the drawing's colours and fonts out of the live theme.\n * @param read - a token reader, normally `getComputedStyle(root).getPropertyValue`.\n * @returns the theme; every field falls back so a missing token cannot blank the drawing.\n */\nexport function readTheme(read: ReadToken): PaintTheme {\n const pick = (token: string, fallback: string): string => {\n const value = read(token).trim()\n return value === '' ? fallback : value\n }\n const colours: Partial<Record<keyof Omit<PaintTheme, 'fonts'>, string>> = {}\n for (const [field, token, fallback] of TOKENS) colours[field] = pick(token, fallback)\n const fonts: Partial<Record<keyof PaintTheme['fonts'], PaintFont>> = {}\n for (const [field, token, fallback] of FONT_TOKENS) fonts[field] = readFont(read, token, fallback)\n return { ...FALLBACK_THEME, ...colours, fonts: { ...FALLBACK_THEME.fonts, ...fonts } }\n}\n\n/** A theme reader that only re-reads the tokens when the theme actually changed. */\nexport interface ThemeReader {\n /** @returns the current theme, cached until the canary token moves. */\n read(): PaintTheme\n}\n\n/** Whether the reader was handed a list of candidates rather than one element. */\nconst isElementList = (value: Element | readonly Element[]): value is readonly Element[] =>\n Array.isArray(value)\n\n/**\n * Watch the theme through one token.\n *\n * `getComputedStyle(...).getPropertyValue` forces a style read, so doing twenty of\n * them per repaint would be paying for the theme on every hover. The canary is the\n * page background: if it did not move, nothing else did, and the cached theme is\n * returned.\n *\n * **The palette lives on `body`, so the candidates are a chain.** DSH declares\n * every `--dsw-alias-*` colour and every `--dsw-font-<tier>-*` part on `body` —\n * `:root` carries only the family and the radii — and custom properties inherit\n * downwards, so a reader pointed at `document.documentElement` reads the empty\n * string for all of them and the whole drawing collapses onto the fallback theme\n * (a white canvas in a dark shell). Handing over both elements, `body` first,\n * keeps the drawing on the palette the shell actually painted it with, and still\n * works if a host ever moves the tokens up to `:root`. The first candidate whose\n * canary is non-empty is the source; the probe runs per read, not once, because a\n * host may mount `body` after this reader exists.\n * @param root - one element, or the candidates in order of preference.\n * @param win - the window to read styles from.\n * @returns the reader.\n */\nexport function createThemeReader(root: Element | readonly Element[], win: Window): ThemeReader {\n const roots: readonly Element[] = isElementList(root) ? root : [root]\n const sources = new Map<Element, ReadToken>()\n const readerOf = (element: Element): ReadToken => {\n const known = sources.get(element)\n if (known !== undefined) return known\n const style = win.getComputedStyle(element)\n const read: ReadToken = (name) => style.getPropertyValue(name)\n sources.set(element, read)\n return read\n }\n let canary: string | undefined\n let cached: PaintTheme | undefined\n return {\n read(): PaintTheme {\n let current = ''\n let source: ReadToken | undefined\n for (const element of roots) {\n const read = readerOf(element)\n const value = read(CANARY)\n if (value === '') continue\n current = value\n source = read\n break\n }\n if (cached !== undefined && current === canary) return cached\n canary = current\n // A host that declares no palette at all keeps the guaranteed-visible theme;\n // every field of it is a usable value, so nothing can blank the drawing.\n cached = readTheme(source ?? (() => ''))\n return cached\n },\n }\n}\n\n/**\n * Shorten text until it fits, ending it with an ellipsis.\n * @param text - the text to fit.\n * @param maxWidth - the room available.\n * @param measure - width of a candidate string.\n * @returns text that fits, or `'…'` when not even one character does.\n */\nexport function elide(text: string, maxWidth: number, measure: (text: string) => number): string {\n if (measure(text) <= maxWidth) return text\n for (let take = text.length - 1; take > 0; take--) {\n const candidate = `${text.slice(0, take).trimEnd()}…`\n if (measure(candidate) <= maxWidth) return candidate\n }\n return '…'\n}\n\n/**\n * Wrap text to a width, eliding whatever does not fit in the last line.\n *\n * Character-by-character, with one concession to Latin text: when a line has to\n * break and there is a space far enough back, break at the space instead of mid\n * word. Chinese has no spaces, so a word-based rule would leave a long subject on\n * one line and elide most of it; this rule wraps it, and still avoids breaking\n * \"report-ledger\" in half when it can.\n * @param text - the text to lay out.\n * @param maxWidth - the room available.\n * @param measure - width of a candidate string.\n * @param maxLines - how many lines to produce.\n * @returns the lines, possibly empty.\n */\nexport function foldText(\n text: string,\n maxWidth: number,\n measure: (text: string) => number,\n maxLines = 1,\n): readonly string[] {\n const clean = text.replace(/\\s+/g, ' ').trim()\n if (clean === '' || maxWidth <= 0 || maxLines < 1) return []\n const lines: string[] = []\n let rest = clean\n while (rest !== '' && lines.length < maxLines) {\n if (lines.length === maxLines - 1) {\n lines.push(elide(rest, maxWidth, measure))\n break\n }\n if (measure(rest) <= maxWidth) {\n lines.push(rest)\n break\n }\n let cut = 0\n for (let take = 1; take <= rest.length; take++) {\n if (measure(rest.slice(0, take)) > maxWidth) break\n cut = take\n }\n if (cut === 0) {\n lines.push(elide(rest, maxWidth, measure))\n break\n }\n // Only back off to a space if it is at least a third of the way out: on a\n // narrow card backing off further would leave the line nearly empty.\n const space = rest.lastIndexOf(' ', cut)\n if (space > cut / 3) cut = space\n lines.push(rest.slice(0, cut).trimEnd())\n rest = rest.slice(cut).trimStart()\n }\n return lines\n}\n\n/** What the pointer is on, or what the reader opened. */\nexport interface PaintFocus {\n /** Report under the pointer. */\n readonly report?: string\n /** Frame under the pointer. */\n readonly frame?: number\n /** Report whose detail panel is open. */\n readonly selected?: string\n}\n\n/** One frame's worth of drawing instructions. */\nexport interface PaintScene {\n readonly layout: CardgraphLayout\n readonly theme: PaintTheme\n readonly viewport: CardgraphViewport\n readonly focus?: PaintFocus\n /** Localised words the drawing needs, supplied by the view. */\n readonly labels: {\n /** The shared frame's title, e.g. \"树外\". */\n readonly external: string\n /** What an empty frame says. */\n readonly empty: string\n /** Lifecycle status → its label, so a card speaks the tab's language. */\n readonly status: Readonly<Record<string, string>>\n }\n /** Extra drawing units to keep beyond the viewport, so a pan is never blank. */\n readonly overscan?: number\n}\n\n/** A surface that can draw a scene. Canvas today; an SVG implementation is a swap. */\nexport interface CardgraphPainter {\n /** Size the paint box, in CSS pixels, and the device pixel ratio to paint at. */\n resize(width: number, height: number, dpr: number): void\n /** Draw the whole drawing, at full strength and with nothing focused. */\n paint(scene: PaintScene): void\n /**\n * Draw one report's path over a scrim of everything else — the interaction\n * layer.\n *\n * This is why the drawing is split across two surfaces: a hover changes the\n * scrim and a handful of wires, never the hundreds of shapes underneath, so the\n * cost of a hover is the same on a 10-report drawing and a 3000-report one.\n * Passing no focus wipes the layer.\n * @param scene - the same scene the base layer was drawn from.\n */\n paintPath(scene: PaintScene): void\n /** Forget the text-measurement cache, e.g. when the font tokens changed. */\n reset(): void\n}\n\n/** Corner radius of a card. */\nconst CARD_RADIUS = 5\n/** Corner radius of a frame. */\nconst FRAME_RADIUS = 7\n/** Inset from a card's edge to its text. */\nconst CARD_PADDING = 9\n/** Width of the status stripe down a card's left edge. */\nconst STRIPE = 3\n/** Arrowhead length and half-width. */\nconst ARROW_LENGTH = 7\nconst ARROW_HALF = 3.5\n/**\n * How far the scrim pushes everything but the focused path back.\n *\n * **Measured, then corrected.** The first version reused the swimlane's dim value\n * (0.18 left showing) and a screenshot of the real tab showed why that was wrong\n * there and wrong here: at 0.18 the frames, their titles and every other card\n * become unreadable, so tracing one path costs the reader the context the drawing\n * exists to provide. The swimlane had already learned the same thing from the other\n * side — *the relations recede, the participants stay* — and a canvas scrim is\n * blunter than a per-shape dim, so it has to be gentler: 0.45 showing keeps every\n * frame and card legible while the focused path, drawn back on top at full\n * strength, still reads as the thing being pointed at.\n */\nconst SCRIM_ALPHA = 0.55\n/** Subject lines a full card has room for. */\nconst SUBJECT_LINES = 2\n\n/** The colour a status is drawn in, matching the tab's tones. */\nfunction statusColour(status: string, theme: PaintTheme): string {\n if (status === 'open') return theme.warning\n if (status === 'acked') return theme.success\n return theme.tertiary\n}\n\n/**\n * How each kind of wire is drawn: the token its colour comes from, its dash\n * pattern, and its weight.\n *\n * Exported because the wires are canvas and the legend is DOM — the only way the\n * two cannot drift is for the legend to read this table instead of restating it in\n * CSS. The colour is a **token name**, not a value, so it resolves through the\n * theme like everything else.\n */\nexport const WIRE_TOKENS: Readonly<Record<CardgraphEdgeKind, {\n readonly colour: string\n readonly dash: readonly number[]\n readonly width: number\n}>> = {\n to: { colour: '--dsw-alias-label-secondary', dash: [], width: 1.4 },\n cc: { colour: '--dsw-alias-label-caption', dash: [2, 3], width: 1 },\n author: { colour: '--dsw-alias-label-caption', dash: [1, 3], width: 1 },\n thread: { colour: '--dsw-alias-state-business-primary', dash: [], width: 1.4 },\n}\n\n/** Reverse lookup: which theme field a token fills. */\nconst FIELD_OF_TOKEN = new Map<string, keyof Omit<PaintTheme, 'fonts'>>(\n TOKENS.map(([field, token]) => [token, field]),\n)\n\n/** How one kind of wire is drawn, resolved against a theme. */\nfunction wireStyle(kind: CardgraphEdgeKind, theme: PaintTheme): { stroke: string; dash: readonly number[]; width: number } {\n const spec = WIRE_TOKENS[kind]\n const field = FIELD_OF_TOKEN.get(spec.colour)\n return { stroke: field === undefined ? theme.secondary : theme[field], dash: spec.dash, width: spec.width }\n}\n\n/**\n * The polyline a wire is drawn as.\n *\n * A wire with a gutter bends through it in three segments — out, along, in — which\n * is what keeps a same-column wire from crossing the frame it lands on and a\n * thread from crossing the cards between its ends.\n * @param edge - the wire.\n * @returns between two and four points, first to last.\n */\nexport function wirePoints(edge: CardgraphEdge): readonly { readonly x: number; readonly y: number }[] {\n const from = { x: edge.from.x, y: edge.from.y }\n const to = { x: edge.to.x, y: edge.to.y }\n if (edge.viaX === undefined) return [from, to]\n return [from, { x: edge.viaX, y: from.y }, { x: edge.viaX, y: to.y }, to]\n}\n\n/** Trace a rounded rectangle into the current path. */\nfunction pathRoundRect(ctx: PaintContext, rect: Rect, radius: number): void {\n const r = Math.max(0, Math.min(radius, rect.width / 2, rect.height / 2))\n ctx.beginPath()\n ctx.moveTo(rect.x + r, rect.y)\n ctx.lineTo(rect.x + rect.width - r, rect.y)\n ctx.arcTo(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + r, r)\n ctx.lineTo(rect.x + rect.width, rect.y + rect.height - r)\n ctx.arcTo(rect.x + rect.width, rect.y + rect.height, rect.x + rect.width - r, rect.y + rect.height, r)\n ctx.lineTo(rect.x + r, rect.y + rect.height)\n ctx.arcTo(rect.x, rect.y + rect.height, rect.x, rect.y + rect.height - r, r)\n ctx.lineTo(rect.x, rect.y + r)\n ctx.arcTo(rect.x, rect.y, rect.x + r, rect.y, r)\n ctx.closePath()\n}\n\n/**\n * Build a painter that draws into a canvas.\n *\n * One transform carries pan and zoom; everything after that is drawn in the\n * layout's own coordinates. Coordinates are deliberately **not** snapped to\n * pixels: draw.io rounds after its transform because its canvas is 1:1 screen\n * space, while ours is scaled, so snapping would have to happen per point in\n * screen space. Antialiasing is the honest default until a measurement says the\n * blur costs readability.\n * @param canvas - the canvas to draw into.\n * @returns the painter.\n */\nexport function createCanvasPainter(canvas: HTMLCanvasElement): CardgraphPainter {\n const ctx = canvas.getContext('2d') as unknown as PaintContext | null\n if (ctx === null) throw new Error('card canvas: no 2d context')\n let width = 0\n let height = 0\n let dpr = 1\n /** Measured text widths, keyed by font and text: measuring is a layout call. */\n const widths = new Map<string, number>()\n\n const measure = (text: string): number => {\n const key = `${ctx.font}\\u0000${text}`\n const hit = widths.get(key)\n if (hit !== undefined) return hit\n const value = ctx.measureText(text).width\n widths.set(key, value)\n return value\n }\n\n const line = (x1: number, y1: number, x2: number, y2: number, colour: string, dash: readonly number[], alpha: number): void => {\n ctx.globalAlpha = alpha\n ctx.strokeStyle = colour\n ctx.setLineDash(dash)\n ctx.beginPath()\n ctx.moveTo(x1, y1)\n ctx.lineTo(x2, y2)\n ctx.stroke()\n }\n\n /** The frame's outline alone, for the interaction layer: the box is already drawn. */\n const drawFrameOutline = (frame: CardgraphFrame, scene: PaintScene): void => {\n ctx.globalAlpha = 1\n pathRoundRect(ctx, frame.bounds, FRAME_RADIUS)\n ctx.strokeStyle = scene.theme.accent\n ctx.lineWidth = 1.6\n ctx.stroke()\n }\n\n const drawFrame = (frame: CardgraphFrame, scene: PaintScene): void => {\n const { theme } = scene\n ctx.globalAlpha = 1\n pathRoundRect(ctx, frame.bounds, FRAME_RADIUS)\n ctx.fillStyle = theme.frame\n ctx.fill()\n ctx.strokeStyle = theme.frameBorder\n ctx.lineWidth = 1\n ctx.stroke()\n\n // Title bar: a band across the top, clipped to the frame so its square corners\n // do not cut across the rounded ones. Its height is the layout's, not a number\n // of the painter's own: cards start below it and ports stay out of it.\n const bar: Rect = {\n x: frame.bounds.x,\n y: frame.bounds.y,\n width: frame.bounds.width,\n height: scene.layout.frameHeaderHeight,\n }\n ctx.save()\n pathRoundRect(ctx, frame.bounds, FRAME_RADIUS)\n ctx.clip()\n ctx.globalAlpha = 1\n ctx.fillStyle = theme.frameBar\n ctx.fillRect(bar.x, bar.y, bar.width, bar.height)\n ctx.restore()\n line(bar.x, bar.y + bar.height, bar.x + bar.width, bar.y + bar.height, theme.frameBorder, [], 1)\n\n ctx.font = fontString(theme.fonts.title)\n ctx.textAlign = 'left'\n ctx.textBaseline = 'middle'\n ctx.fillStyle = theme.secondary\n const label = frame.external ? scene.labels.external : frame.title ?? frame.shortId\n const dot = 10\n ctx.fillText(elide(label, frame.bounds.width - 24 - dot, measure), bar.x + 10, bar.y + bar.height / 2 + 0.5)\n\n // One dot for a resident session, an outline for one that is not: the same\n // distinction the list's StateDot makes.\n const cx = bar.x + bar.width - 13\n const cy = bar.y + bar.height / 2\n ctx.beginPath()\n ctx.arc(cx, cy, 3.5, 0, Math.PI * 2)\n if (frame.live) {\n ctx.fillStyle = theme.success\n ctx.fill()\n } else {\n ctx.strokeStyle = theme.tertiary\n ctx.lineWidth = 1\n ctx.stroke()\n }\n\n if (frame.cards === 0) {\n ctx.font = fontString(theme.fonts.body)\n ctx.fillStyle = theme.tertiary\n ctx.fillText(scene.labels.empty, frame.bounds.x + CARD_PADDING, bar.y + bar.height + 18)\n }\n }\n\n const drawWire = (edge: CardgraphEdge, scene: PaintScene, alpha: number): void => {\n const { theme } = scene\n const style = wireStyle(edge.kind, theme)\n const points = wirePoints(edge)\n ctx.globalAlpha = alpha\n ctx.strokeStyle = style.stroke\n ctx.lineWidth = style.width\n ctx.setLineDash(style.dash)\n ctx.beginPath()\n points.forEach((point, index) => {\n if (index === 0) ctx.moveTo(point.x, point.y)\n else ctx.lineTo(point.x, point.y)\n })\n ctx.stroke()\n\n // The head turns with the last segment, so a wire that arrives through a\n // gutter points into the frame rather than along the way it came.\n const last = points[points.length - 2]\n const end = points[points.length - 1]\n if (last === undefined || end === undefined) return\n const angle = Math.atan2(end.y - last.y, end.x - last.x)\n const back = { x: end.x - Math.cos(angle) * ARROW_LENGTH, y: end.y - Math.sin(angle) * ARROW_LENGTH }\n const side = { x: -Math.sin(angle) * ARROW_HALF, y: Math.cos(angle) * ARROW_HALF }\n ctx.setLineDash([])\n ctx.fillStyle = style.stroke\n ctx.beginPath()\n ctx.moveTo(end.x, end.y)\n ctx.lineTo(back.x + side.x, back.y + side.y)\n ctx.lineTo(back.x - side.x, back.y - side.y)\n ctx.closePath()\n ctx.fill()\n }\n\n const drawCard = (card: CardgraphCard, scene: PaintScene, accent: boolean): void => {\n const { theme } = scene\n const box = card.bounds\n ctx.globalAlpha = 1\n pathRoundRect(ctx, box, CARD_RADIUS)\n ctx.fillStyle = card.lod === 'chip' ? statusColour(card.status, theme) : theme.surface\n ctx.fill()\n if (card.lod === 'chip') return\n\n ctx.strokeStyle = accent ? theme.accent : theme.border\n ctx.lineWidth = accent ? 1.6 : 1\n ctx.stroke()\n\n // Status stripe, in the tone the tab's Tag uses for the same status.\n ctx.save()\n pathRoundRect(ctx, box, CARD_RADIUS)\n ctx.clip()\n ctx.fillStyle = statusColour(card.status, theme)\n ctx.fillRect(box.x, box.y, STRIPE, box.height)\n ctx.restore()\n\n const textLeft = box.x + CARD_PADDING + STRIPE\n const textWidth = box.width - CARD_PADDING * 2 - STRIPE\n ctx.textAlign = 'left'\n ctx.textBaseline = 'top'\n\n ctx.font = fontString(theme.fonts.id)\n ctx.fillStyle = accent ? theme.accent : theme.secondary\n if (card.lod === 'compact') {\n // One line: the id, then as much subject as is left of the card. Each half is\n // measured in the font it is drawn in — folding the subject against the\n // heavier id font shortened it for no reason, and would overflow the card in\n // a theme whose body font is the larger of the two.\n const id = card.report\n const idWidth = measure(id)\n ctx.fillText(id, textLeft, box.y + box.height / 2 - theme.fonts.id.size / 2 - 1)\n ctx.font = fontString(theme.fonts.body)\n ctx.fillStyle = theme.caption\n const rest = foldText(`· ${card.subject}`, textWidth - idWidth - 4, measure, 1)[0] ?? ''\n ctx.fillText(rest, textLeft + idWidth + 4, box.y + box.height / 2 - theme.fonts.body.size / 2 - 1)\n return\n }\n\n ctx.fillText(card.report, textLeft, box.y + 8)\n ctx.font = fontString(theme.fonts.body)\n ctx.fillStyle = theme.text\n const lines = foldText(card.subject, textWidth, measure, SUBJECT_LINES)\n lines.forEach((text, index) => {\n ctx.fillText(text, textLeft, box.y + 28 + index * theme.fonts.body.lineHeight)\n })\n\n // Footer: the status, and the task chip when there is room for both.\n ctx.font = fontString(theme.fonts.meta)\n const baseline = box.y + box.height - theme.fonts.meta.lineHeight - 6\n ctx.fillStyle = statusColour(card.status, theme)\n const status = scene.labels.status[card.status] ?? card.status\n ctx.fillText(status, textLeft, baseline)\n if (card.task !== undefined) {\n const statusWidth = measure(status)\n const chipLeft = textLeft + statusWidth + 8\n const chipText = foldText(card.task, box.x + box.width - CARD_PADDING - chipLeft - 10, measure, 1)[0] ?? ''\n if (chipText !== '') {\n ctx.fillStyle = theme.frameBar\n const chipWidth = measure(chipText) + 10\n ctx.fillRect(chipLeft, baseline - 2, chipWidth, theme.fonts.meta.lineHeight + 2)\n ctx.fillStyle = theme.caption\n ctx.fillText(chipText, chipLeft + 5, baseline)\n }\n }\n }\n\n return {\n resize(w: number, h: number, ratio: number): void {\n width = Math.max(0, w)\n height = Math.max(0, h)\n dpr = Number.isFinite(ratio) && ratio > 0 ? ratio : 1\n canvas.width = Math.max(1, Math.round(width * dpr))\n canvas.height = Math.max(1, Math.round(height * dpr))\n canvas.style.width = `${width}px`\n canvas.style.height = `${height}px`\n },\n reset(): void {\n widths.clear()\n },\n paint(scene: PaintScene): void {\n const { layout, theme, viewport } = scene\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0)\n ctx.globalAlpha = 1\n ctx.setLineDash([])\n ctx.clearRect(0, 0, width, height)\n ctx.fillStyle = theme.canvas\n ctx.fillRect(0, 0, width, height)\n\n const world = worldViewport(viewport, scene.overscan ?? 0)\n const visible = (rect: Rect): boolean => intersects(rect, world)\n\n ctx.save()\n ctx.translate(viewport.offsetX, viewport.offsetY)\n ctx.scale(viewport.scale, viewport.scale)\n\n // Frames first, then wires, then cards. A wire therefore always reads on top\n // of the boxes it travels between, while a card's text is never crossed.\n for (const frame of layout.frames) {\n if (visible(frame.bounds)) drawFrame(frame, scene)\n }\n for (const edge of layout.edges) {\n if (visible(edge.bounds)) drawWire(edge, scene, 1)\n }\n for (const card of layout.cards) {\n if (!visible(card.bounds)) continue\n drawCard(card, scene, card.report === scene.focus?.selected)\n }\n ctx.globalAlpha = 1\n ctx.restore()\n },\n paintPath(scene: PaintScene): void {\n const { layout, theme, viewport } = scene\n const report = scene.focus?.report ?? scene.focus?.selected\n const frameIndex = scene.focus?.frame\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0)\n ctx.globalAlpha = 1\n ctx.setLineDash([])\n ctx.clearRect(0, 0, width, height)\n if (report === undefined && frameIndex === undefined) return\n\n ctx.save()\n ctx.translate(viewport.offsetX, viewport.offsetY)\n ctx.scale(viewport.scale, viewport.scale)\n\n if (report === undefined) {\n const frame = layout.frames[frameIndex as number]\n if (frame !== undefined) drawFrameOutline(frame, scene)\n ctx.restore()\n return\n }\n\n // The scrim: one rectangle over the whole visible world. Everything else\n // recedes at once, and — unlike dimming shapes one by one — it costs the same\n // whether the drawing has ten reports or three thousand.\n const world = worldViewport(viewport, 0)\n ctx.globalAlpha = SCRIM_ALPHA\n ctx.fillStyle = theme.canvas\n ctx.fillRect(world.x, world.y, world.width, world.height)\n ctx.globalAlpha = 1\n\n const edges = layout.edges.filter((edge) => edge.report === report)\n const touched = new Set<number>()\n for (const edge of edges) {\n if (edge.from.kind === 'frame') touched.add(edge.from.index)\n if (edge.to.kind === 'frame') touched.add(edge.to.index)\n }\n // The frames the path touches are re-outlined rather than redrawn: their\n // boxes are already underneath, and only the endpoints need to stay legible.\n for (const frame of layout.frames) {\n if (touched.has(frame.index)) drawFrameOutline(frame, scene)\n }\n for (const edge of edges) {\n if (intersects(edge.bounds, world)) drawWire(edge, scene, 1)\n }\n for (const card of layout.cards) {\n if (card.report === report) drawCard(card, scene, true)\n }\n ctx.restore()\n },\n }\n}\n","/**\n * The card canvas's view: two canvases, a pan/zoom transform, picking, and an\n * invisible button layer.\n *\n * The drawing is split across two surfaces, and that split is the whole\n * performance story:\n *\n * - the **base** canvas holds frames, wires and cards, and is repainted only when\n * the drawing itself changes (layout, transform, size, theme);\n * - the **interaction** canvas holds one report's path over a scrim, and is\n * repainted on every hover. Its cost does not grow with the ledger, because it\n * never touches the shapes underneath.\n *\n * Picking is arithmetic rather than elements: the canvas has nothing to hit, so\n * `hitTest` from the model is given the cards (topmost first) and then the frames.\n * What the canvas also cannot offer is accessibility — so every card that is on\n * screen gets a real, transparent `<button>` on top of it, which is what a keyboard\n * or a screen reader reaches, and the list below stays the full accessible path.\n *\n * @module dsh-report-ledger/client/CardgraphView\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { PointerEvent as ReactPointerEvent, ReactElement } from 'react'\nimport { Button } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type { ReportFrontMatter, TimelinePayload } from '../shared/wire.ts'\nimport { buildCardgraph, fitTransform, hitTest, lodFor, MAX_ZOOM, MIN_ZOOM, ZOOM_STEP } from './cardgraph-model.ts'\nimport type { CardgraphViewport, Rect } from './cardgraph-model.ts'\nimport {\n FALLBACK_THEME, WIRE_TOKENS, createCanvasPainter, createThemeReader,\n} from './cardgraph-painter.ts'\nimport type { CardgraphPainter, PaintScene, ThemeReader } from './cardgraph-painter.ts'\nimport { EDGE_LABEL, STATUS_LABEL } from './locales.ts'\nimport type { Translate } from './ReportsView.tsx'\n\n/**\n * Wheel-zoom exponent per pixel of delta, so that one 100px notch — a mouse\n * wheel's usual magnitude — is exactly the {@link ZOOM_STEP} the buttons take.\n * A trackpad pinch arrives as the same event with much smaller deltas, which is\n * why the zoom is exponential in the delta rather than one step per event.\n */\nconst WHEEL_ZOOM_RATE = Math.log(ZOOM_STEP) / 100\n/** How close to the box's edge a wheel-pan stops, in screen pixels. */\nconst EDGE_MARGIN = 12\n/** Drawing units to keep beyond the viewport, so a pan does not reveal a blank band. */\nconst OVERSCAN = 160\n/** How tall the canvas box is, inside the tab. */\nconst VIEW_HEIGHT = 420\n/** How far the pointer may travel before a press becomes a drag instead of a click. */\nconst DRAG_SLOP = 4\n/** The four kinds, in the order the legend lists them. */\nconst KINDS = ['to', 'cc', 'author', 'thread'] as const\n\n/** One pickable thing, shaped for the model's `hitTest`. */\ninterface PickTarget {\n readonly kind: 'card' | 'frame'\n readonly report?: string\n readonly frame?: number\n readonly bounds: Rect\n}\n\n/** What the pointer is on, or nothing. Kept apart from the base layer's scene on purpose. */\ninterface HoverTarget {\n readonly report?: string\n readonly frame?: number\n}\n\n/**\n * Whether the pointer is on the same thing.\n *\n * A pointermove over one card is not a new hover: without this the state object is\n * replaced on every move, which re-renders the drawing and repaints the overlay for\n * no change at all.\n */\nconst sameHover = (a: HoverTarget, b: HoverTarget): boolean => a.report === b.report && a.frame === b.frame\n\n/** Props for the card canvas. */\nexport interface CardgraphViewProps {\n /** The endpoint payload, for the session frames. */\n readonly payload: TimelinePayload\n /** The digests being plotted — the same filtered set the list renders. */\n readonly reports: readonly ReportFrontMatter[]\n readonly t: Translate\n /** The report whose card is open, if any. */\n readonly openReport?: string\n /** Open a report's card in the list below. */\n readonly onOpen: (report: string) => void\n /** Full id plus session title, for the frame's hover label. */\n readonly idHint: (id: string) => string\n /**\n * Fill the host's height instead of the tab's fixed measure.\n *\n * The conversation tab needs a fixed box, because the page below it scrolls and\n * an unbounded drawing would push that page around. A docked panel is the\n * opposite: it owns the height, and a fixed measure there floats the drawing in\n * a half-empty column. The box is measured either way — this only decides who\n * sets the height.\n */\n readonly fill?: boolean\n}\n\n/**\n * The legend.\n *\n * Built from the painter's own wire table (`WIRE_TOKENS`) rather than from CSS, so\n * a legend that disagreed with the lines beside it would take a change to that one\n * table — the drift the swimlane's legend avoided a different way (by sharing a\n * class name with its paths).\n * @param props - the translator.\n * @returns the legend row.\n */\nexport function CardgraphLegend({ t }: { readonly t: Translate }): ReactElement {\n return (\n <div style={{ display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap' }}>\n {KINDS.map((kind) => (\n <span\n key={kind}\n style={{ display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: 'var(--dsw-font-xxxs-11, 11px)', color: 'var(--dsw-alias-label-caption)' }}\n >\n <svg width=\"26\" height=\"8\" aria-hidden=\"true\" focusable=\"false\">\n <line\n x1=\"0\"\n y1=\"4\"\n x2=\"26\"\n y2=\"4\"\n stroke={`var(${WIRE_TOKENS[kind].colour})`}\n strokeWidth={WIRE_TOKENS[kind].width}\n {...(WIRE_TOKENS[kind].dash.length === 0 ? {} : { strokeDasharray: WIRE_TOKENS[kind].dash.join(' ') })}\n />\n </svg>\n {t(EDGE_LABEL[kind])}\n </span>\n ))}\n </div>\n )\n}\n\n/** Keep a number inside a range. */\nconst clamp = (value: number, low: number, high: number): number => Math.min(high, Math.max(low, value))\n\n/**\n * The drawing.\n * @param props - payload, the reports to plot, translations, and the open handler.\n * @returns the canvas box with its controls.\n */\nexport function CardgraphView({ payload, reports, t, openReport, onOpen, idHint, fill = false }: CardgraphViewProps): ReactElement {\n const boxRef = useRef<HTMLDivElement | null>(null)\n const baseRef = useRef<HTMLCanvasElement | null>(null)\n const pathRef = useRef<HTMLCanvasElement | null>(null)\n const painter = useRef<CardgraphPainter | null>(null)\n const pathPainter = useRef<CardgraphPainter | null>(null)\n const reader = useRef<ThemeReader | null>(null)\n const drag = useRef<{\n x: number\n y: number\n offsetX: number\n offsetY: number\n moved: boolean\n pointerId: number\n } | null>(null)\n\n const [size, setSize] = useState({ width: 0, height: VIEW_HEIGHT })\n const [zoom, setZoom] = useState(1)\n const [offset, setOffset] = useState({ x: 0, y: 0 })\n const [hover, setHover] = useState<HoverTarget>({})\n const [ready, setReady] = useState(false)\n /** Bumped when the palette changed, so the theme is read again. */\n const [themeTick, setThemeTick] = useState(0)\n /** Bumped when font metrics may have arrived, so the measured text is dropped. */\n const [fontTick, setFontTick] = useState(0)\n /** Bumped when the device pixel ratio may have changed. */\n const [dprTick, setDprTick] = useState(0)\n const fitted = useRef(false)\n /**\n * Whether the transform on screen is still the fit's own.\n *\n * While this holds, the fit follows the box; the moment the reader zooms or pans\n * the view themselves it stops following, because from then on the transform is\n * a decision rather than a consequence — see the resize effect below.\n */\n const fittedTransform = useRef(true)\n /** The last box the resize effect saw, so a re-measure is not a resize. */\n const measured = useRef({ width: 0, height: 0 })\n /** The transform as of this render, for the one listener that is attached once. */\n const zoomRef = useRef(zoom)\n const offsetRef = useRef(offset)\n zoomRef.current = zoom\n offsetRef.current = offset\n\n const lod = lodFor(zoom)\n const layout = useMemo(() => buildCardgraph(payload, reports, { lod }), [payload, reports, lod])\n /** The drawing as of this render, for that same listener: it needs its extent. */\n const layoutRef = useRef(layout)\n layoutRef.current = layout\n\n const labels = useMemo(() => {\n const status: Record<string, string> = {}\n for (const [state, key] of Object.entries(STATUS_LABEL)) status[state] = t(key)\n return { external: t('cardgraph.external'), empty: t('cardgraph.empty'), status }\n }, [t])\n\n // ---------------------------------------------------------------------------\n // Mounting: painters, theme reader, the observed size, and the two things that\n // invalidate the drawing without changing it (palette, font metrics)\n // ---------------------------------------------------------------------------\n useEffect(() => {\n const base = baseRef.current\n const path = pathRef.current\n if (base === null || path === null) return\n painter.current = createCanvasPainter(base)\n pathPainter.current = createCanvasPainter(path)\n // The palette is declared on `body`, not on `:root` — see `createThemeReader`,\n // which is why both elements are offered, `body` first. Reading the document\n // element alone returns the empty string for every token and collapses the whole\n // drawing onto the fallback theme, which is a white canvas in a dark shell.\n const doc = globalThis.document\n reader.current = createThemeReader([doc.body, doc.documentElement], globalThis.window)\n // The drawing has to follow the palette it was painted with. The shell switches\n // it by toggling this attribute on `body`, and nothing else in the drawing would\n // repaint when it does.\n const palette = new MutationObserver(() => { setThemeTick((tick) => tick + 1) })\n palette.observe(doc.body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] })\n // Measured text is only as good as the font it was measured against: a family\n // that arrives after the first paint would keep the widths the first paint\n // measured until something else emptied the cache.\n void doc.fonts.ready.then(() => {\n painter.current?.reset()\n pathPainter.current?.reset()\n setFontTick((tick) => tick + 1)\n })\n setReady(true)\n return () => {\n palette.disconnect()\n painter.current = null\n pathPainter.current = null\n reader.current = null\n setReady(false)\n }\n }, [])\n\n useEffect(() => {\n const box = boxRef.current\n if (box === null) return\n const measure = (): void => {\n setSize({ width: box.clientWidth, height: box.clientHeight })\n }\n measure()\n const observer = new ResizeObserver(measure)\n observer.observe(box)\n return () => { observer.disconnect() }\n }, [])\n\n // A window moved between monitors changes the device pixel ratio without changing\n // the box, so the backing store has to be resized for the same reason a resize is.\n useEffect(() => {\n const win = globalThis.window\n const bump = (): void => { setDprTick((tick) => tick + 1) }\n win.addEventListener('resize', bump)\n return () => { win.removeEventListener('resize', bump) }\n }, [])\n\n const viewport = useMemo<CardgraphViewport>(\n () => ({ offsetX: offset.x, offsetY: offset.y, scale: zoom, width: size.width, height: size.height }),\n [offset, zoom, size],\n )\n /**\n * The theme the drawing is painted in.\n *\n * Read through the reader (which caches until the canary token moves) and keyed by\n * `ready` so the first real read happens once the reader exists, and by\n * `themeTick` so a palette switch repaints without waiting for the next poll.\n */\n const theme = useMemo(\n () => (ready ? reader.current?.read() ?? FALLBACK_THEME : FALLBACK_THEME),\n [ready, themeTick],\n )\n /**\n * The base layer's scene.\n *\n * Carries the open report — the selected card is accented there — but deliberately\n * **not** the hover: the bottom layer is repainted only when the drawing itself\n * changes, and a pointer moving over the canvas is not a change to the drawing.\n * The hover travels in {@link overlayScene}, which is what the second canvas is for.\n */\n const baseScene = useMemo<PaintScene>(() => ({\n layout,\n theme,\n viewport,\n labels,\n overscan: OVERSCAN,\n focus: openReport === undefined ? {} : { selected: openReport },\n }), [layout, theme, viewport, labels, openReport])\n /** The overlay's scene: the base scene plus what the pointer and the panel point at. */\n const overlayScene = useMemo<PaintScene>(() => ({\n ...baseScene,\n focus: {\n ...(openReport === undefined ? {} : { selected: openReport }),\n ...(hover.report === undefined ? {} : { report: hover.report }),\n ...(hover.frame === undefined ? {} : { frame: hover.frame }),\n },\n }), [baseScene, openReport, hover])\n\n // ---------------------------------------------------------------------------\n // Painting\n // ---------------------------------------------------------------------------\n // Sizing the backing store is its own effect: it reallocates the canvas, so it\n // belongs to a box change (or a device pixel ratio change), never to a repaint.\n // It is declared before the two paint effects so that, within one commit, the\n // canvas is the right size before anything is drawn into it.\n useEffect(() => {\n if (!ready) return\n const ratio = globalThis.devicePixelRatio || 1\n painter.current?.resize(size.width, size.height, ratio)\n pathPainter.current?.resize(size.width, size.height, ratio)\n }, [ready, size, dprTick])\n\n useEffect(() => {\n const target = painter.current\n if (!ready || target === null) return\n target.paint(baseScene)\n }, [ready, baseScene, fontTick])\n\n useEffect(() => {\n const target = pathPainter.current\n if (!ready || target === null) return\n target.paintPath(overlayScene)\n }, [ready, overlayScene, fontTick])\n\n // ---------------------------------------------------------------------------\n // Picking\n // ---------------------------------------------------------------------------\n /** Everything under the pointer, topmost first: cards, then the frames holding them. */\n const targets = useMemo<readonly PickTarget[]>(() => [\n ...[...layout.cards].reverse().map((card) => ({ kind: 'card' as const, report: card.report, bounds: card.bounds })),\n ...[...layout.frames].reverse().map((frame) => ({ kind: 'frame' as const, frame: frame.index, bounds: frame.bounds })),\n ], [layout])\n\n const pick = useCallback((clientX: number, clientY: number): PickTarget | undefined => {\n const box = boxRef.current\n if (box === null) return undefined\n const rect = box.getBoundingClientRect()\n const x = (clientX - rect.left - offset.x) / zoom\n const y = (clientY - rect.top - offset.y) / zoom\n return hitTest(targets, x, y)\n }, [targets, offset, zoom])\n\n // ---------------------------------------------------------------------------\n // Fitting, zooming and panning\n // ---------------------------------------------------------------------------\n /**\n * Fill the width, in the richest tier that can afford to.\n *\n * The geometry itself lives in the model ({@link fitTransform}): the scale and\n * the tier are mutually dependent, so the probing belongs where it is pure,\n * byte-deterministic, and reachable by the assertions. What stays here is the\n * one thing that needs the DOM — which width to fit into — and the state write.\n */\n const fit = useCallback(() => {\n const box = boxRef.current\n if (box === null) return\n const next = fitTransform(payload, reports, box.clientWidth)\n if (next === undefined) return\n // The transform is the fit's own again, so it follows the box again.\n fittedTransform.current = true\n setZoom(next.scale)\n // Anchored to the top rather than centred vertically: a frame's cards run\n // oldest first, so a drawing taller than its box is read from the top down.\n setOffset({ x: next.offsetX, y: next.offsetY })\n }, [payload, reports])\n\n /**\n * The `+`/`−` controls.\n *\n * An explicit zoom is the reader's decision, so the fit stops following the box\n * from here — the same reason the wheel and the drag clear the flag.\n * @param factor - what to multiply the current zoom by.\n */\n const stepZoom = useCallback((factor: number) => {\n fittedTransform.current = false\n setZoom((z) => clamp(z * factor, MIN_ZOOM, MAX_ZOOM))\n }, [])\n\n /**\n * The first view is the fit — the same rule the control runs.\n *\n * It used to be a rule of its own (fit the width, never below the card tier,\n * never above 100%) because the fit of the day — the whole drawing inside the\n * box — landed around 57%, the compact tier, and opened the tab as a wall of\n * one-line cards. With the fit now filling the width in the richest tier that\n * can afford it, that special case has nothing left to correct, and one rule is\n * one fewer way for the first sight and the control to disagree.\n */\n useEffect(() => {\n if (fitted.current || size.width === 0 || layout.cards.length === 0) return\n fitted.current = true\n fit()\n }, [fit, layout, size])\n\n /**\n * Follow the box, but only while the box is what chose the view.\n *\n * A dock, a split and a fullscreen switch all change the box under a transform\n * computed for the old one. A drawing left carrying the old offset is clipped by\n * the new box — it reads as a broken drawing rather than as a preserved view —\n * so a view the fit chose is fitted again. A reader who zoomed in to read one\n * card means it, so a view they took over is left exactly where they put it.\n *\n * The box is compared by value, not by identity: the observer hands over a fresh\n * object on every measure, and a re-measure of the same box is not a resize.\n */\n useEffect(() => {\n const previous = measured.current\n measured.current = size\n if (size.width === 0 || previous.width === 0) return\n if (previous.width === size.width && previous.height === size.height) return\n if (fittedTransform.current) fit()\n }, [fit, size])\n\n // A real, non-passive wheel listener: React's `onWheel` is passive at the root,\n // where `preventDefault` is a no-op and the page scrolls under the drawing.\n //\n // **The wheel pans; Ctrl/⌘-wheel zooms.** The drawing is read top to bottom — a\n // frame is a column of its session's own timeline, oldest card first — so the\n // wheel is the reader's way down the drawing, which is what every other scroll\n // surface on the page does with it; zoom moves to the modifier, the way editors\n // split the two. Shift is the browser's own \"horizontally\", and a mouse reports\n // that on the vertical delta.\n //\n // An axis only moves while the drawing overflows it, so a wheel over a drawing\n // that fits cannot nudge it off-centre, and the travel stops one margin short of\n // the edge rather than scrolling the drawing out of the box.\n //\n // The listener is attached once, so it reads the current transform and extent\n // through refs rather than closing over them. The obvious alternative — computing\n // the next zoom inside a `setZoom` updater and calling `setOffset` from in there —\n // is a side effect inside an updater, which React is free to invoke twice; the\n // offset would then be shifted twice per notch and the zoom would drift away from\n // the cursor.\n useEffect(() => {\n const box = boxRef.current\n if (box === null) return\n const onWheel = (event: WheelEvent): void => {\n event.preventDefault()\n // Whatever the gesture is, the view is the reader's from here on.\n fittedTransform.current = false\n // `deltaMode` 1 is lines and 2 is pages; a mouse on Windows reports pixels.\n const unit = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? box.clientHeight : 1\n const dy = event.deltaY * unit\n const dx = event.deltaX * unit\n const previous = offsetRef.current\n if (event.ctrlKey || event.metaKey) {\n const rect = box.getBoundingClientRect()\n const px = event.clientX - rect.left\n const py = event.clientY - rect.top\n const current = zoomRef.current\n const next = clamp(current * Math.exp(-dy * WHEEL_ZOOM_RATE), MIN_ZOOM, MAX_ZOOM)\n const ratio = next / current\n // Keep the point under the cursor fixed: the offset scales about it.\n setOffset({ x: px - (px - previous.x) * ratio, y: py - (py - previous.y) * ratio })\n setZoom(next)\n return\n }\n const drawing = layoutRef.current\n const zoom = zoomRef.current\n const width = drawing.width * zoom\n const height = drawing.height * zoom\n const overflowX = width - box.clientWidth\n const overflowY = height - box.clientHeight\n const panX = event.shiftKey && dx === 0 ? dy : dx\n const panY = event.shiftKey ? 0 : dy\n setOffset({\n x: overflowX > 0 ? clamp(previous.x - panX, box.clientWidth - width - EDGE_MARGIN, EDGE_MARGIN) : previous.x,\n y: overflowY > 0 ? clamp(previous.y - panY, box.clientHeight - height - EDGE_MARGIN, EDGE_MARGIN) : previous.y,\n })\n }\n box.addEventListener('wheel', onWheel, { passive: false })\n return () => { box.removeEventListener('wheel', onWheel) }\n }, [])\n\n /**\n * Drag to pan, click to open. Distinguishing them by distance travelled.\n *\n * The pointer is captured on press, so a drag keeps following it outside the box\n * instead of being abandoned at the edge (a mouse has no implicit capture; touch\n * does). Only the primary pointer is followed, so a second finger cannot reset the\n * drag's origin halfway through a pan.\n */\n const onPointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {\n if (event.button !== 0 || !event.isPrimary) return\n event.currentTarget.setPointerCapture(event.pointerId)\n drag.current = {\n x: event.clientX,\n y: event.clientY,\n offsetX: offset.x,\n offsetY: offset.y,\n moved: false,\n pointerId: event.pointerId,\n }\n }, [offset])\n\n const onPointerMove = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {\n if (!event.isPrimary) return\n const pressing = drag.current\n if (pressing !== null) {\n const dx = event.clientX - pressing.x\n const dy = event.clientY - pressing.y\n // Squared Euclidean, so the slop means the distance the pointer travelled\n // rather than the sum of the axes (which let a 3px diagonal count as a drag).\n if (dx * dx + dy * dy > DRAG_SLOP * DRAG_SLOP) pressing.moved = true\n if (pressing.moved) {\n // A drag is the reader placing the drawing, not the box choosing it.\n fittedTransform.current = false\n setOffset({ x: pressing.offsetX + dx, y: pressing.offsetY + dy })\n return\n }\n }\n const hit = pick(event.clientX, event.clientY)\n const next: HoverTarget = hit?.kind === 'card' && hit.report !== undefined\n ? { report: hit.report }\n : hit?.kind === 'frame' && hit.frame !== undefined ? { frame: hit.frame } : {}\n // A move within the same card is not a new hover: returning the current state\n // lets React skip the render (and the overlay repaint that would follow).\n setHover((current) => (sameHover(current, next) ? current : next))\n }, [pick])\n\n const onPointerUp = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {\n if (!event.isPrimary) return\n const pressing = drag.current\n drag.current = null\n if (pressing === null || pressing.moved) return\n // Every visible card is covered by a real button, and the button's own click is\n // the activation. Opening from this bubbling pointerup as well would reach the\n // toggling `onOpen` twice for one gesture — the double toggle ReportsView guards\n // against with `stopPropagation`. The pointer path stays for a card whose\n // button is not under the pointer (a stale box, a card clipped at the edge).\n const target = event.target\n if (target instanceof Element && target.closest('button') !== null) return\n const hit = pick(event.clientX, event.clientY)\n if (hit?.kind === 'card' && hit.report !== undefined) onOpen(hit.report)\n }, [pick, onOpen])\n\n const onPointerLeave = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {\n const pressing = drag.current\n // A captured drag is still the reader's gesture even when the pointer has left\n // the box: the release is delivered here anyway, so the drag is not abandoned.\n if (pressing !== null && event.currentTarget.hasPointerCapture(pressing.pointerId)) return\n drag.current = null\n setHover((current) => (sameHover(current, {}) ? current : {}))\n }, [])\n\n /** A cancelled pointer will never deliver a release, so the drag ends here. */\n const onPointerCancel = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {\n if (!event.isPrimary) return\n drag.current = null\n }, [])\n\n // ---------------------------------------------------------------------------\n // The accessible layer: one real button per visible card\n // ---------------------------------------------------------------------------\n const visibleCards = useMemo(() => {\n const left = -offset.x / zoom\n const top = -offset.y / zoom\n const right = (size.width - offset.x) / zoom\n const bottom = (size.height - offset.y) / zoom\n return layout.cards.filter((card) => card.bounds.x < right && left < card.bounds.x + card.bounds.width\n && card.bounds.y < bottom && top < card.bounds.y + card.bounds.height)\n }, [layout, offset, zoom, size])\n\n const percent = Math.round(zoom * 100)\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '6px', ...(fill ? fillRoot : {}) }}>\n <div style={controls}>\n <Button variant=\"outline\" size=\"sm\" aria-label={t('cardgraph.zoomOut')} onClick={() => { stepZoom(1 / ZOOM_STEP) }}>−</Button>\n <span style={percentStyle}>{t('cardgraph.zoom', { percent })}</span>\n <Button variant=\"outline\" size=\"sm\" aria-label={t('cardgraph.zoomIn')} onClick={() => { stepZoom(ZOOM_STEP) }}>+</Button>\n <Button variant=\"outline\" size=\"sm\" onClick={fit}>{t('cardgraph.fit')}</Button>\n <CardgraphLegend t={t} />\n <span style={percentStyle}>{t('cardgraph.summary', { frames: layout.frames.length, cards: layout.cards.length })}</span>\n </div>\n <div\n ref={boxRef}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerCancel}\n onPointerLeave={onPointerLeave}\n title={t('cardgraph.gestureHint')}\n style={{\n ...box,\n ...(fill ? fillBox : { height: `${VIEW_HEIGHT}px` }),\n cursor: drag.current?.moved === true ? 'grabbing' : hover.report === undefined ? 'grab' : 'pointer',\n }}\n >\n <canvas ref={baseRef} role=\"img\" aria-label={t('cardgraph.summary', { frames: layout.frames.length, cards: layout.cards.length })} style={layer} />\n <canvas ref={pathRef} aria-hidden=\"true\" style={layer} />\n {visibleCards.map((card) => (\n <button\n key={card.report}\n type=\"button\"\n aria-label={`${card.report} · ${card.subject}`}\n title={idHint(card.report)}\n onClick={() => { onOpen(card.report) }}\n onFocus={() => { setHover({ report: card.report }) }}\n onBlur={() => { setHover({}) }}\n style={{\n position: 'absolute',\n left: `${card.bounds.x * zoom + offset.x}px`,\n top: `${card.bounds.y * zoom + offset.y}px`,\n width: `${card.bounds.width * zoom}px`,\n height: `${card.bounds.height * zoom}px`,\n padding: 0,\n border: 0,\n background: 'transparent',\n cursor: 'pointer',\n }}\n />\n ))}\n </div>\n </div>\n )\n}\n\nconst controls: Record<string, string> = {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n flexWrap: 'wrap',\n}\n\nconst percentStyle: Record<string, string> = {\n fontSize: 'var(--dsw-font-xxxs-11, 11px)',\n color: 'var(--dsw-alias-label-caption)',\n fontVariantNumeric: 'tabular-nums',\n}\n\nconst box: Record<string, string> = {\n position: 'relative',\n overflow: 'hidden',\n borderRadius: '6px',\n border: '1px solid var(--dsw-alias-border-l1)',\n touchAction: 'none',\n}\n\n/**\n * A filling host owns the height: the root takes the whole body and lets the\n * measured box keep whatever the toolbar left. `minHeight` is what stops a flex\n * child from refusing to shrink, which would push the toolbar off the top of a\n * short panel instead of shrinking the drawing.\n */\nconst fillRoot: Record<string, string> = { flex: '1 1 auto', height: '100%', minHeight: '0' }\nconst fillBox: Record<string, string> = { flex: '1 1 auto', minHeight: '0' }\n\nconst layer: Record<string, string> = {\n position: 'absolute',\n inset: '0',\n display: 'block',\n}\n","/**\n * The card canvas as a right-Sidebar tab — stage one and stage two of the tab\n * system.\n *\n * The right column belongs to the product\n * (`@deepseek-ai/dsh-client-ui-sidebar-right`), and a type from outside reaches\n * it through **cordis services**, never through imports: the client bundle's\n * purity gate rejects a cross-plugin value import, and that package's types are\n * not part of a third-party plugin's resolvable dependency set anyway. So the\n * two faces this module needs are asserted structurally here, exactly as\n * `client/index.ts` already asserts the `slots` and `locale` faces.\n *\n * ## Why the registry is injected rather than read\n *\n * `sidebarRightTabs` is published by that package's own `apply`. Cordis has no\n * optional-dependency form: a key on this plugin's `inject` list is a\n * REQUIREMENT, and a missing one parks this plugin's fiber — on the web boot\n * that is a white page, not a degraded view. `ctx.get` fails the other way: it\n * reads the registry at this instant, so a host that publishes it a moment later\n * would leave the tab silently unregistered forever. `ctx.inject` is the one\n * form that is both optional and late — the callback runs when the service\n * actually appears, and again if it comes back.\n *\n * ## Why the guide entry is not optional in practice\n *\n * The strip's `+` control only ever re-opens the guide page, and the guide lists\n * registered types through exactly the entries they declare. Without one, this\n * tab could only ever be opened by code.\n *\n * ## What the body draws\n *\n * The drawing the conversation tab used to carry, filling the panel instead of\n * sitting in a fixed 420px box — the panel owns its height, and it can be split\n * or taken fullscreen, which is most of the reason to put a drawing in this\n * column at all. Picking a card opens that report's detail underneath, through\n * the same view the conversation tab's rows use, so one report cannot read\n * differently in the two places.\n *\n * @module dsh-report-ledger/client/sidebar-tab\n */\n\nimport { createElement, useCallback, useMemo, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport { CardgraphView } from './CardgraphView.tsx'\nimport { ReportDetailView } from './ReportDetailView.tsx'\nimport { buildRows, reportIds, sessionTitles, shortId } from './timeline-model.ts'\nimport { useReportDetail, useTimeline } from './timeline-source.ts'\nimport type { Translate } from './ReportsView.tsx'\n\n/** This type's identity in the tab system: unique, and the key both seats use. */\nexport const TAB_ID = 'dsh-report-ledger/canvas'\n\n/**\n * What `openTab(kind)` names.\n *\n * Deliberately not derived from {@link TAB_ID}: the registry is looked up BY\n * kind, and deriving one name from the other is a mistake the ecosystem has\n * already paid for — a registration that says one thing while its opener says\n * another only fails at click time.\n */\nexport const TAB_KIND = 'ledger-canvas'\n\n/** One entry capsule the guide page offers, as this plugin declares it. */\nexport interface GuideEntry {\n /** Stable identity within this provider. */\n readonly id: string\n /** Ascending position among every registered type's entries. */\n readonly order: number\n /** The capsule's title in the current language. */\n readonly title: () => string\n /** One line under the title; shown only while the guide lists few entries. */\n readonly description?: () => string\n}\n\n/** One tab type's static face, as far as this plugin declares it. */\nexport interface TabDefinition {\n readonly id: string\n readonly kind: string\n /** Defaults to `extension` — the band for a type from outside the product. */\n readonly priority?: 'extension' | 'builtin' | 'fallback'\n /** The tab chip's initial text, captured when the tab opens. */\n readonly title: (address: string) => string\n /** Entry capsules for the guide page. Omit to stay off it. */\n readonly guide?: readonly GuideEntry[]\n}\n\n/** The tab-type registry the product publishes as `sidebarRightTabs`. */\nexport interface TabRegistry {\n /**\n * Declare one type. Purely static: the body and title are separate\n * registrations, keyed by this definition's `id`.\n * @param definition - the type's static face.\n * @returns disposer unregistering it.\n */\n register(definition: TabDefinition): () => void\n}\n\n/** The slot seats, as far as this module uses them. */\nexport interface TabSlots {\n inject(key: string, callback: () => () => void): () => void\n register(options: Record<string, unknown>, component: (props: never) => unknown): () => void\n}\n\n/** What a deferred contribution can use: the context it was started from. */\nexport interface ScopedContext {\n get(name: string): unknown\n effect(callback: () => (() => void) | void, label?: string): () => void\n}\n\n/** What this module needs from the client context. */\nexport interface SidebarTabContext {\n readonly slots: TabSlots\n inject(keys: readonly string[], callback: (scoped: ScopedContext) => void): () => void\n}\n\n/** Props a tab body receives; only what this body reads is declared. */\nexport interface TabBodyProps {\n /** The session whose column this tab lives in. */\n readonly sessionId?: string\n}\n\n/** The sentences that stand in for a drawing that cannot be made yet. */\nconst muted: Record<string, string> = {\n padding: '12px',\n color: 'var(--dsw-alias-label-caption)',\n}\n\n/**\n * The canvas over the open report's detail.\n *\n * The split is even on purpose: the drawing keeps half the panel while a report\n * is read, and all of it otherwise. A reader who wants more of either has the\n * panel's own fullscreen and split rather than a second control here.\n */\nconst stack: Record<string, string> = {\n display: 'flex',\n flexDirection: 'column',\n height: '100%',\n minHeight: '0',\n}\n/** The drawing alone: it takes whatever the panel has. */\nconst stackFull: Record<string, string> = { flex: '1 1 auto', minHeight: '0' }\nconst stackCanvas: Record<string, string> = { flex: '1 1 50%', minHeight: '0' }\nconst stackDetail: Record<string, string> = { flex: '1 1 50%', minHeight: '0', overflow: 'hidden' }\n\n/**\n * Bind the tab body to the plugin's copy.\n *\n * The body is the *second* registration and carries the runtime face: a\n * component, receiving the session it was opened in — which is the only input\n * the drawing needs, since the ledger is read from the session tree's root.\n * @param t - namespace-bound translate, read on every render.\n * @returns the tab body component.\n */\nfunction bodyFor(t: Translate): (props: TabBodyProps) => ReactElement {\n return function LedgerCanvasBody(props: TabBodyProps): ReactElement {\n const sessionId = typeof props.sessionId === 'string' ? props.sessionId : ''\n const { payload, error } = useTimeline(sessionId)\n // Which card's path is being read. Unlike the conversation tab there is no\n // list to expand a row into, so clicking a card opens its detail under the\n // drawing and clicking it again puts the drawing back on its own.\n const [openReport, setOpenReport] = useState<string | undefined>(undefined)\n // The detail rides the timeline's own tick: a report amended while the reader\n // watches it keeps up, without a second cadence to reason about.\n const { detail, error: detailError } = useReportDetail(openReport, payload?.generatedAt ?? 0)\n\n const rows = useMemo(() => (payload === undefined ? [] : buildRows(payload)), [payload])\n // Unfiltered on purpose: a docked canvas has no toolbar of its own to filter\n // with, and the conversation tab beside it is where narrowing belongs.\n const reports = useMemo(() => rows.flatMap((row) => (row.kind === 'report' ? [row.front] : [])), [rows])\n // Which reports this tree actually holds — a thread link can lead to one it\n // does not (a co-author's report, or a child living in another tree), and the\n // detail says so rather than pretending the drawing lost it.\n const inList = useMemo(() => (payload === undefined ? new Set<string>() : reportIds(payload)), [payload])\n const titles = useMemo(\n () => (payload === undefined ? new Map<string, string>() : sessionTitles(payload)),\n [payload],\n )\n const idHint = useCallback((id: string): string => {\n const title = titles.get(id)\n return title === undefined ? id : `${title} · ${id}`\n }, [titles])\n const onOpen = useCallback((report: string) => {\n setOpenReport((current) => (current === report ? undefined : report))\n }, [])\n\n if (error !== undefined) return createElement('div', { style: muted }, t('view.error', { code: error }))\n if (payload === undefined) return createElement('div', { style: muted }, t('view.loading'))\n if (reports.length === 0) return createElement('div', { style: muted }, t('view.empty'))\n\n // The canvas keeps its place in the tree whether or not a report is open: one\n // container, the drawing in the first cell, the detail in the second. Switching\n // the root between the drawing and a wrapper around it (the earlier shape)\n // unmounted and remounted `CardgraphView` on every open and close, which threw\n // away the reader's zoom, pan and fitted state — the one view the panel is\n // supposed to leave where the reader put it.\n const canvasCell = createElement(\n 'div',\n { style: openReport === undefined ? stackFull : stackCanvas },\n createElement(CardgraphView, {\n payload,\n reports,\n t,\n ...(openReport === undefined ? {} : { openReport }),\n onOpen,\n idHint,\n fill: true,\n }),\n )\n if (openReport === undefined) return createElement('div', { style: stack }, canvasCell)\n return createElement(\n 'div',\n { style: stack },\n canvasCell,\n createElement(\n 'div',\n { style: stackDetail },\n createElement(ReportDetailView, {\n report: openReport,\n detail,\n error: detailError,\n t,\n idHint,\n variant: 'pane',\n onOpenReport: setOpenReport,\n inList: inList.has(openReport),\n // The drawing plots every report of the tree, so anything reachable from\n // it is admitted by construction; the filter lives in the other surface.\n visibleInFilter: true,\n onClose: () => { setOpenReport(undefined) },\n }),\n ),\n )\n }\n}\n\n/**\n * Contribute the canvas tab: its type, its body, and its guide capsule.\n * @param ctx - the client context (slots, plus the optional registry service).\n * @param t - namespace-bound translate, read fresh on every label call.\n */\nexport function installSidebarTab(ctx: SidebarTabContext, t: Translate): void {\n ctx.inject(['sidebarRightTabs'], (scoped) => {\n const tabs = scoped.get('sidebarRightTabs') as TabRegistry | undefined\n if (tabs === undefined) return\n scoped.effect(() => {\n // Stage one: what the type IS. `extension` is stated rather than inherited\n // — it is both the default and the band that may take over a builtin kind,\n // and neither is something to leave implicit.\n const disposeType = tabs.register({\n id: TAB_ID,\n kind: TAB_KIND,\n priority: 'extension',\n title: () => t('sidebar.chip'),\n guide: [{\n id: 'canvas',\n // Between the product's files (10) and terminal (20) capsules.\n order: 15,\n title: () => t('sidebar.guideTitle'),\n description: () => t('sidebar.guideDescription'),\n }],\n })\n // Stage two: the body, keyed by the SAME implementation id.\n const disposeBody = ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register(\n { name: 'sidebar.right.pane.tab', key: TAB_ID },\n bodyFor(t) as never,\n ))\n return () => {\n disposeBody()\n disposeType()\n }\n }, 'report-ledger: canvas tab')\n })\n}\n","/**\n * Browser half of dsh-report-ledger — runs inside the dsh web GUI.\n *\n * The shell serves the built `lib/client.js` from /plugins/dsh-report-ledger/client.js\n * and evaluates it as a closure-factory artifact, so this module's `require` is\n * answered by the loader's frozen module table (react, cordis, the slot\n * services) and every other dependency is inlined by the build.\n *\n * It contributes exactly one thing: a `conversation.view` entry, which the\n * conversation shell projects as a tab beside the shipped Chat and Trajectory\n * views. A fresh id is used rather than an existing one, so nothing shipped is\n * replaced.\n *\n * @module dsh-report-ledger/client\n */\n\nimport { createElement } from 'react'\nimport { ReportsView, type Translate } from './ReportsView.tsx'\nimport { en, zh } from './locales.ts'\nimport { installStyles } from './styles.ts'\nimport { installSidebarTab, type ScopedContext } from './sidebar-tab.ts'\n\n/** Locale namespace this plugin owns. */\nconst NS = 'report-ledger'\n\n/** Tab key. Distinct from the shipped `chat` / `trajectory` cells. */\nconst VIEW_ID = 'report-ledger'\n\n/** Position after the shipped views (`chat` 0, `trajectory` 10). */\nconst VIEW_ORDER = 20\n\n/** Minimal structural face of the client slot service. */\ninterface SlotsService {\n inject(key: string, callback: () => () => void): () => void\n register(options: Record<string, unknown>, component: (props: never) => unknown): () => void\n}\n\n/** Minimal structural face of the client locale service. */\ninterface LocaleService {\n register(ns: string, dicts: { zh: unknown; en: unknown }): () => void\n bind(ns: string): Translate\n}\n\n/**\n * Minimal structural face of the client plugin context.\n *\n * Deliberately local rather than imported: the client runtime's published types\n * live in packages seeded into the shell bundle, which are not part of a\n * third-party plugin's resolvable dependency set (and one of them is not even\n * installed here). The shape asserted is exactly what the registration below\n * uses, and the host-side declarations remain the source of truth for the wire.\n */\ninterface ClientContext {\n slots: SlotsService\n locale: LocaleService\n effect(callback: () => (() => void) | void, label?: string): () => void\n get(name: string): unknown\n /**\n * Run a contribution once a service exists — and again if it comes back.\n *\n * The one form that is both optional (a missing service does not park this\n * fiber) and late (a service published after this plugin still reaches it):\n * `get` alone reads too early, and a key on `inject` above makes it a\n * requirement whose absence is a white page rather than a missing tab. See\n * client/sidebar-tab.ts for the case that needs it.\n */\n inject(keys: readonly string[], callback: (scoped: ScopedContext) => void): () => void\n}\n\n/** The slot and locale services must be mounted before this half can contribute. */\nexport const inject = ['slots', 'locale']\n\n/**\n * Apply the browser half.\n * @param ctx - the client plugin context (`slots`, `locale` injected).\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'report-ledger: dictionaries')\n\n // Layout helpers the shell cannot know about — see client/styles.ts. Registered\n // as an effect so cordis_stop/undefine takes the sheet down with the plugin.\n ctx.effect(() => installStyles(), 'report-ledger: adapter styles')\n\n const t = ctx.locale.bind(NS)\n\n // The right column is optional twice over — a host may not mount it at all, and\n // this one publishes its tab registry after this plugin in the same boot — so\n // the contribution hangs off `ctx.inject`, not the `inject` list above.\n installSidebarTab(ctx, t)\n\n ctx.slots.inject('conversation.view', () => ctx.slots.register(\n {\n name: 'conversation.view',\n id: VIEW_ID,\n order: VIEW_ORDER,\n // A thunk is re-read on every projection, so the tab label follows the\n // active locale without re-registering.\n label: () => t('view.tab'),\n locale: NS,\n },\n (props: Record<string, unknown>) => createElement(ReportsView, { ...props, t } as never) as never,\n ))\n}\n"],"mappings":"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"}