dsh-report-ledger 0.1.0

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.
@@ -0,0 +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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"}