@deepwatch/dsh-library 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":["_jsxs","_jsx","useId","useState","useMemo","useRef","useCallback"],"sources":["../../contracts/lib/presentation.js","../../brand/lib/identity.js","../../brand/lib/mark.js","../../workspace/lib/client/surface.js","../src/index-store.ts","../src/client/read-plane.ts","../src/client/search-view.tsx","../src/client/library-mode.tsx","../src/sources.ts","../src/search.ts","../src/client/components.tsx","../src/client/index.tsx"],"sourcesContent":["/**\n * Rules about how results may be presented.\n *\n * These live in the contracts package rather than in a component because they\n * are product rules, not styling choices. \"Green belongs to VERIFIED alone\" is\n * the same commitment whether it is rendered by a React card, a terminal\n * summary or a CI annotation, and keeping it here means there is one place to\n * change it and one place to test it.\n *\n * Everything below is a pure function over data that already crossed the wire.\n * No DOM, no React, no Node.\n *\n * @module @deepwatch/dsh-contracts/presentation\n */\n/**\n * The tone one verdict is rendered in.\n *\n * `INCONCLUSIVE`, `STALE`, `BLOCKED` and `UNVERIFIED` share the caution tone\n * on purpose. They are not failures, and styling them as errors teaches people\n * to dismiss them — which is how an unproven result comes to be accepted as a\n * proven one. They are also not successes, which is the more obvious half.\n */\nexport function verdictTone(verdict) {\n if (verdict === 'VERIFIED')\n return 'success';\n if (verdict === 'FAILED')\n return 'error';\n return 'caution';\n}\n/** The sentence to show when Watch Core supplied no reason of its own. */\nconst FALLBACK_REASON = {\n VERIFIED: 'Every required check passed against valid evidence.',\n FAILED: 'A required check failed.',\n UNVERIFIED: 'Nothing executable was checked, so nothing was established.',\n INCONCLUSIVE: 'The evidence conflicts, or a check could not be run.',\n STALE: 'The evidence no longer describes the current source.',\n BLOCKED: 'Policy or a missing dependency prevented verification.',\n};\n/** Every verdict the taxonomy defines, for exhaustive validation. */\nconst VERDICTS = new Set([\n 'VERIFIED', 'FAILED', 'UNVERIFIED', 'INCONCLUSIVE', 'STALE', 'BLOCKED',\n]);\n/** Wording per freshness state, so the distinction survives without colour. */\nconst FRESHNESS_LABEL = {\n current: null,\n stale: 'stale',\n gap: 'gap in capture',\n expired: 'expired',\n unavailable: 'freshness unknown',\n};\n/** How a freshness state should be labelled, or null when it needs no label. */\nexport function freshnessLabel(freshness) {\n return FRESHNESS_LABEL[freshness];\n}\n/**\n * Parse a tool result into a verdict.\n *\n * Returns null rather than guessing. A result this cannot read renders as a\n * generic row, which is honest; inventing a verdict to fill a card would not\n * be.\n */\nexport function parseVerdict(value) {\n const record = asRecord(value);\n if (record === null)\n return null;\n const verdict = record['verdict'];\n if (typeof verdict !== 'string' || !VERDICTS.has(verdict))\n return null;\n const checks = Array.isArray(record['checks']) ? record['checks'] : [];\n const reason = record['reason'];\n return {\n verdict: verdict,\n reason: typeof reason === 'string' && reason !== ''\n ? reason\n : FALLBACK_REASON[verdict],\n checks: checks.flatMap(parseCheck),\n contractDigest: typeof record['contractDigest'] === 'string' ? record['contractDigest'] : '',\n assurance: typeof record['assurance'] === 'string' ? record['assurance'] : null,\n };\n}\nfunction parseCheck(value) {\n const record = asRecord(value);\n if (record === null || typeof record['checkId'] !== 'string')\n return [];\n return [{\n checkId: record['checkId'],\n kind: typeof record['kind'] === 'string' ? record['kind'] : 'check',\n description: typeof record['description'] === 'string' ? record['description'] : null,\n passed: typeof record['passed'] === 'boolean' ? record['passed'] : null,\n detail: typeof record['detail'] === 'string' ? record['detail'] : null,\n }];\n}\n/**\n * Parse a source-query result into an answer.\n *\n * Returns null for a refusal or an unreadable payload, so a failure never\n * renders as a grounded answer with nothing behind it.\n */\nexport function parseAnswer(value) {\n const record = asRecord(value);\n if (record === null || record['ok'] !== true)\n return null;\n if (typeof record['answer'] !== 'string')\n return null;\n const evidence = Array.isArray(record['evidence']) ? record['evidence'] : [];\n const groundedness = record['groundedness'];\n return {\n answer: record['answer'],\n citations: evidence.flatMap(parseCitation),\n groundedness: groundedness === 'sufficient' || groundedness === 'insufficient'\n ? groundedness\n : null,\n };\n}\nfunction parseCitation(value) {\n const record = asRecord(value);\n if (record === null || typeof record['evidenceId'] !== 'string')\n return [];\n const range = asRecord(record['temporalRange']);\n const start = range === null ? undefined : range['startMs'];\n const freshness = record['freshness'];\n return [{\n evidenceId: record['evidenceId'],\n text: typeof record['text'] === 'string' ? record['text'] : '',\n atMs: typeof start === 'number' && Number.isFinite(start) ? start : null,\n modality: typeof record['modality'] === 'string' ? record['modality'] : 'text',\n provenance: typeof record['provenance'] === 'string' ? record['provenance'] : 'observation',\n // An unrecognized value becomes `unavailable`, never `current`. Defaulting\n // an unknown to the reassuring answer is exactly the wrong direction.\n freshness: typeof freshness === 'string' && freshness in FRESHNESS_LABEL\n ? freshness\n : 'unavailable',\n }];\n}\n/**\n * Format a media position the way a person reads one.\n *\n * @returns `m:ss` under an hour, `h:mm:ss` above it, or null when there is no\n * usable timestamp — which a caller renders as no timestamp rather than as\n * `0:00`, because those mean different things.\n */\nexport function formatTimestamp(atMs) {\n if (atMs === null || !Number.isFinite(atMs))\n return null;\n const total = Math.max(0, Math.floor(atMs / 1000));\n const seconds = String(total % 60).padStart(2, '0');\n if (total < 3600)\n return `${String(Math.floor(total / 60))}:${seconds}`;\n const minutes = String(Math.floor(total / 60) % 60).padStart(2, '0');\n return `${String(Math.floor(total / 3600))}:${minutes}:${seconds}`;\n}\n/** Narrow an unknown to a plain object, excluding null and arrays. */\nfunction asRecord(value) {\n if (typeof value !== 'object' || value === null || Array.isArray(value))\n return null;\n return value;\n}\n//# sourceMappingURL=presentation.js.map","/**\n * The Watch product identity, and the one colour rule that matters.\n *\n * Kept as data rather than as markup so the same strings reach the sidebar,\n * the conversation hero, the About panel, the window title and the release\n * notes without four copies drifting apart. Attribution in particular has to\n * be identical everywhere it appears, because it is a legal statement rather\n * than a design element.\n *\n * @module @deepwatch/dsh-client-brand/identity\n */\n/** What the product is called. Never \"DeepSeek\" anything. */\nexport const PRODUCT_NAME = 'DeepWatch';\n/** The short form, for a tab title or a cramped header. */\nexport const PRODUCT_SHORT_NAME = 'Watch';\n/**\n * The document title this product should be showing.\n *\n * The `<title>` belongs to DSH's built HTML shell, which this distribution\n * does not fork, and DSH's session layer rewrites it on every navigation as\n * `<session> — <foundation>`. Left alone that reads\n * `Say hello — DeepSeek Harness · DeepWatch`: two products named in one tab,\n * and the wrong one first.\n *\n * So both names come off before ours goes back on, and ours comes off *first*\n * — the observer that calls this fires on the change this makes, and a version\n * that stripped only the foundation appended a second `· DeepWatch` each time.\n *\n * @param current - the title as it stands right now.\n * @param foundation - the shell's own title, from before hydration.\n */\nexport function productTitle(current, foundation) {\n const ours = ` · ${PRODUCT_NAME}`;\n let text = current;\n while (text.endsWith(ours))\n text = text.slice(0, -ours.length);\n if (foundation !== '') {\n for (const separator of [' — ', ' · ', ' - ', ' | ']) {\n const suffix = `${separator}${foundation}`;\n if (text.endsWith(suffix)) {\n text = text.slice(0, -suffix.length);\n break;\n }\n }\n }\n return text === '' || text === foundation || text === PRODUCT_NAME\n ? PRODUCT_NAME\n : `${text} · ${PRODUCT_NAME}`;\n}\n/**\n * Attribution to the upstream project.\n *\n * Required, and required to be visible. Watch is built on DeepSeek Harness and\n * says so; the alternative — quietly shipping someone else's foundation — is\n * both wrong and against the MIT notice this distribution inherits.\n */\nexport const ATTRIBUTION = 'Built on DeepSeek Harness · Powered by Watch Skill';\n/**\n * The independence disclosure.\n *\n * Equally required, and for the opposite reason: attribution without it could\n * read as endorsement, and no such endorsement exists.\n */\nexport const INDEPENDENCE = 'DeepWatch and Watch Skill are independent projects and are not affiliated with or endorsed by DeepSeek.';\n/** One line for a tooltip or an empty state. */\nexport const TAGLINE = 'An agent that sees, remembers, and can prove what actually happened.';\n/** Every status the product renders, and the tone it is permitted. */\nexport const STATUS_TONE = {\n // Verification — the only place `success` is reachable.\n VERIFIED: 'success',\n FAILED: 'error',\n UNVERIFIED: 'caution',\n INCONCLUSIVE: 'caution',\n STALE: 'caution',\n BLOCKED: 'caution',\n // Agent execution — deliberately never `success`. A completed turn is a\n // statement about the agent, not about the world.\n queued: 'neutral',\n running: 'active',\n completed: 'info',\n failed: 'error',\n cancelled: 'neutral',\n // Evidence health.\n current: 'neutral',\n gap: 'caution',\n expired: 'caution',\n unavailable: 'caution',\n};\n/**\n * The tone one status is allowed.\n *\n * Anything unrecognized is `neutral`, never `success`. A new status added\n * elsewhere and not registered here renders as unremarkable rather than\n * accidentally as a win.\n */\nexport function toneFor(status) {\n return STATUS_TONE[status] ?? 'neutral';\n}\n/** Whether a status may be rendered with the success affordance. */\nexport function isSuccessTone(status) {\n return toneFor(status) === 'success';\n}\n/**\n * The semantic token a tone maps to.\n *\n * Feature packages ask for a tone and get a CSS variable. They never write a\n * hex value, which is what stops the palette from being re-invented slightly\n * differently in every panel — and what makes a theme change one edit.\n */\nexport function tokenFor(tone) {\n return `var(--watch-tone-${tone})`;\n}\n//# sourceMappingURL=identity.js.map","/**\n * The Watch mark, inlined.\n *\n * Generated by `scripts/brand-assets.mjs` from `assets/watch-orca-master.png`.\n * Do not edit by hand: the master is the brand source of truth and this is a\n * mechanical derivation of it at 64px, which covers a 32px slot at 2x.\n *\n * @module @deepwatch/dsh-client-brand/mark\n */\n/** The orca, as a transparent PNG data URI. */\nexport const WATCH_MARK_PNG = 'data:image/png;base64,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';\n/** The master artwork's own blue. Measured from it, never chosen. */\nexport const MASTER_BLUE = '#3160fc';\n//# sourceMappingURL=mark.js.map","import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from \"react/jsx-runtime\";\nimport { WATCH_MARK_PNG } from '@deepwatch/dsh-client-brand';\nconst MODE_KICKER = {\n Watch: 'Trust layer',\n Live: 'Observation',\n Memory: 'Knowledge',\n Library: 'Evidence library',\n Compare: 'Change analysis',\n};\n/** Stable global class names; their rules live with the product theme. */\nconst C = {\n root: 'watch-mode-root',\n hero: 'watch-mode-hero',\n markFrame: 'watch-mode-mark-frame',\n mark: 'watch-mode-mark',\n heroCopy: 'watch-mode-hero-copy',\n eyebrow: 'watch-mode-eyebrow',\n title: 'watch-mode-title',\n lead: 'watch-mode-lead',\n localBadge: 'watch-mode-local-badge',\n body: 'watch-mode-body',\n empty: 'watch-empty',\n emptyCopy: 'watch-empty-copy',\n sectionLabel: 'watch-section-label',\n emptyShows: 'watch-empty-shows',\n emptyWhy: 'watch-empty-why',\n nextBlock: 'watch-next-block',\n nextList: 'watch-next-list',\n panel: 'watch-panel',\n panelHeading: 'watch-panel-heading',\n facts: 'watch-facts',\n factRow: 'watch-fact-row',\n factKey: 'watch-fact-key',\n factValue: 'watch-fact-value',\n note: 'watch-note',\n noteMark: 'watch-note-mark',\n unavailable: 'watch-unavailable',\n unavailableHead: 'watch-unavailable-head',\n unavailableBadge: 'watch-unavailable-badge',\n unavailableBecause: 'watch-unavailable-because',\n requirements: 'watch-requirements',\n};\n/** The frame: a title, one sentence of what this is, then the body. */\nexport function ModeSurface({ title, lead, children }) {\n return (_jsxs(\"div\", { className: C.root, \"data-watch-mode\": title.toLowerCase(), children: [_jsxs(\"header\", { className: C.hero, children: [_jsx(\"span\", { className: C.markFrame, \"aria-hidden\": \"true\", children: _jsx(\"img\", { className: C.mark, src: WATCH_MARK_PNG, alt: \"\" }) }), _jsxs(\"div\", { className: C.heroCopy, children: [_jsx(\"span\", { className: C.eyebrow, children: `DEEPWATCH / ${MODE_KICKER[title] ?? 'Evidence workspace'}` }), _jsx(\"h2\", { className: C.title, children: title }), _jsx(\"p\", { className: C.lead, children: lead })] }), _jsx(\"span\", { className: C.localBadge, children: \"Local-first\" })] }), _jsx(\"div\", { className: C.body, children: children })] }));\n}\n/**\n * What this surface would show, why it shows nothing, and what to do.\n *\n * All three parts are required. \"No data\" on its own teaches people the product\n * is broken; naming the reason and the next action is the difference between an\n * empty state and a dead end.\n */\nexport function EmptyState({ shows, why, next }) {\n return (_jsxs(\"div\", { className: C.empty, \"data-watch-empty-state\": \"\", children: [_jsxs(\"div\", { className: C.emptyCopy, children: [_jsx(\"span\", { className: C.sectionLabel, children: \"What appears here\" }), _jsx(\"p\", { className: C.emptyShows, children: shows }), _jsx(\"p\", { className: C.emptyWhy, children: why })] }), next.length === 0\n ? null\n : (_jsxs(\"div\", { className: C.nextBlock, children: [_jsx(\"span\", { className: C.sectionLabel, children: \"Start here\" }), _jsx(\"ol\", { className: C.nextList, children: next.map(step => _jsx(\"li\", { children: step }, step)) })] }))] }));\n}\n/** A titled block of content. */\nexport function Panel({ heading, children }) {\n return (_jsxs(\"section\", { className: C.panel, \"data-watch-panel\": \"\", children: [heading === undefined ? null : _jsx(\"h3\", { className: C.panelHeading, children: heading }), children] }));\n}\n/** A key/value grid. Long values wrap rather than forcing the page sideways. */\nexport function Facts({ rows }) {\n return (_jsx(\"dl\", { className: C.facts, children: rows.map(([label, value]) => (_jsxs(\"div\", { className: C.factRow, children: [_jsx(\"dt\", { className: C.factKey, children: label }), _jsx(\"dd\", { className: C.factValue, children: value })] }, label))) }));\n}\n/** A short aside in the product's own voice, marked by the accent rule. */\nexport function Note({ children }) {\n return (_jsxs(\"aside\", { className: C.note, children: [_jsx(\"span\", { className: C.noteMark, \"aria-hidden\": \"true\", children: \"i\" }), _jsx(\"p\", { children: children })] }));\n}\n/**\n * A capability this build cannot reach, said out loud.\n *\n * Distinct from `EmptyState`: empty means nothing has happened yet, unavailable\n * means the surface could not show it even if something had. Conflating the two\n * is how a person concludes a working feature is broken, or a missing one is\n * merely quiet.\n */\nexport function Unavailable({ what, because, wouldNeed }) {\n return (_jsxs(\"div\", { className: C.unavailable, \"data-watch-unavailable\": \"\", children: [_jsxs(\"div\", { className: C.unavailableHead, children: [_jsx(\"h3\", { children: what }), _jsx(\"span\", { className: C.unavailableBadge, children: \"Not available in this build\" })] }), _jsx(\"p\", { className: C.unavailableBecause, children: because }), wouldNeed.length === 0 ? null : (_jsxs(_Fragment, { children: [_jsx(\"h4\", { className: C.panelHeading, children: \"What it would take\" }), _jsx(\"ul\", { className: C.requirements, children: wouldNeed.map(item => _jsx(\"li\", { children: item }, item)) })] }))] }));\n}\n/**\n * Read the JSON a Watch tool returned out of whatever DSH handed us.\n *\n * Returns null on anything unexpected — a running call, a failed one, a result\n * that is not JSON, a shape we do not recognise. The caller then renders its\n * empty state, which is the honest outcome: a surface that cannot read its\n * input must not draw a card implying it did.\n */\nexport function readToolResult(value) {\n if (typeof value !== 'object' || value === null)\n return null;\n const block = value;\n if (!('kind' in block) || block.isError === true)\n return null;\n if (!Array.isArray(block.content))\n return null;\n const text = block.content\n .filter((part) => typeof part === 'object' && part !== null\n && part.type === 'text'\n && typeof part.text === 'string')\n .map(part => part.text)\n .join('');\n if (text === '')\n return null;\n try {\n return JSON.parse(text);\n }\n catch {\n return null;\n }\n}\n//# sourceMappingURL=surface.js.map","/**\n * The Library's local search index.\n *\n * The evidence store is the source of truth. This is a *derived* structure: it\n * can be thrown away and rebuilt from the records at any time, and every design\n * decision here follows from that one fact. A derived index that cannot be\n * safely deleted is not derived, it is a second database with none of the\n * guarantees of the first.\n *\n * What it is:\n *\n * - **Local.** An inverted index over tokens, held in memory and serialisable\n * to plain JSON. No service, no network, no embedding model. Semantic\n * retrieval stays a future optional plugin; lexical matching is what works\n * offline on any machine, today.\n * - **Versioned.** Every serialised index carries `INDEX_VERSION` and a\n * digest of its own contents. A version it does not recognise, or a digest\n * that does not match, is a corrupt index — detected on load, reported, and\n * rebuilt rather than half-trusted.\n * - **Incremental and idempotent.** Indexing the same record twice leaves the\n * index identical. Re-indexing a changed record replaces its postings\n * rather than adding a second copy, so a document cannot accumulate ghosts\n * of its former text.\n * - **Recoverable.** Indexing records progress, so an interrupted run resumes\n * from what it completed instead of starting over or, worse, believing it\n * finished.\n *\n * Queries are bounded, paginated and cancellable by construction: a query\n * carries its own limit, and a caller can pass an `AbortSignal`. An unbounded\n * search over a large corpus is a denial of service you wrote yourself.\n *\n * @module @deepwatch/dsh-library/index-store\n */\n\nimport type { SearchHit, SearchResult } from './search.js'\nimport type { SourceKind } from './sources.js'\n\n/**\n * Bumped when the serialised shape changes.\n *\n * An index written by a newer build is refused rather than reinterpreted. A\n * structure read under the wrong assumptions produces confident wrong answers,\n * which is worse than producing none.\n */\nexport const INDEX_VERSION = 1\n\n/** How the index reports its own condition. */\nexport type IndexHealth =\n /** Never built. Not an error — nobody has indexed anything yet. */\n | 'empty'\n /** Built, current, queryable. */\n | 'ready'\n /** A build is in progress; results are partial and say so. */\n | 'indexing'\n /** Records changed after the last build. Queryable, but incomplete. */\n | 'stale'\n /** Unreadable: wrong version, failed digest, malformed. Must be rebuilt. */\n | 'corrupt'\n\n/** One indexable record. Everything is optional except the identity. */\nexport interface IndexableRecord {\n readonly recordId: string\n readonly revisionId: string\n readonly title: string\n readonly kind: SourceKind\n /** Body text: extracted text, a transcript, a description. */\n readonly text: string\n /** Where it came from, for provenance filtering. */\n readonly source: string | null\n /** The run or task it belongs to. */\n readonly runId: string | null\n /** ISO-8601. Used for range filters and for ordering. */\n readonly observedAt: string | null\n /** The verification state, when the record has one. */\n readonly verdict: string | null\n readonly tags: readonly string[]\n /** Evidence this record resolves to. */\n readonly evidenceIds: readonly string[]\n}\n\n/** What a caller may narrow a query by. */\nexport interface IndexQuery {\n readonly text: string\n readonly kinds?: readonly SourceKind[]\n readonly runIds?: readonly string[]\n readonly verdicts?: readonly string[]\n readonly tags?: readonly string[]\n readonly sources?: readonly string[]\n /** Inclusive ISO-8601 bounds. */\n readonly from?: string\n readonly to?: string\n readonly sort?: 'relevance' | 'newest' | 'oldest' | 'title'\n readonly offset?: number\n readonly limit?: number\n readonly signal?: AbortSignal\n}\n\n/** A page of results, and enough context to page through the rest. */\nexport interface IndexQueryResult {\n readonly results: readonly SearchResult[]\n /** Matches before paging. The count a person is told. */\n readonly total: number\n readonly offset: number\n readonly limit: number\n readonly health: IndexHealth\n /** Non-fatal facts about this answer: truncation, staleness, degradation. */\n readonly notes: readonly string[]\n}\n\n/** The serialised form. Plain JSON so any store can hold it. */\nexport interface SerializedIndex {\n readonly version: number\n readonly digest: string\n readonly builtAt: string\n readonly documents: readonly IndexableRecord[]\n /** Token → the record ids carrying it. */\n readonly postings: Readonly<Record<string, readonly string[]>>\n}\n\n/** The largest page anyone may ask for. */\nexport const MAX_LIMIT = 200\nconst DEFAULT_LIMIT = 25\n\n/** Han, Hiragana, Katakana — scripts written without spaces. */\nconst CJK = /[぀-ヿ㐀-䶿一-鿿]/u\n\n/**\n * Split text into searchable tokens.\n *\n * Unicode-aware on purpose. Splitting on `[a-z0-9]+` would silently drop every\n * Arabic, Chinese, Cyrillic and Greek record in the corpus — they would index\n * as nothing and return nothing, and the failure would look like an empty\n * library rather than a broken tokenizer.\n *\n * CJK has no spaces, so a run is emitted as its characters and its adjacent\n * bigrams rather than whole. Keeping the run would make it a token only an\n * exact repetition could match, and since every query term must be present,\n * that run token would then fail a query whose characters are all indexed.\n *\n * Case folding is `toLowerCase`, which is a no-op for scripts without case and\n * correct for those with it. Diacritics are deliberately *kept*: the original\n * text is the evidence, and folding \"عَلَم\" into \"علم\" would make a citation\n * resolve to something the source does not say.\n *\n * `\\p{M}` is in the continuation class for the same reason, and its absence was\n * a real bug. Arabic harakat are Unicode *Mark*, not *Letter*, so a class of\n * letters and numbers alone breaks at every vowel sign: vocalised \"عَلَم\"\n * tokenized as three separate consonants, and no query could ever match it.\n */\nexport function tokenize(text: string): readonly string[] {\n if (text === '') return []\n const tokens: string[] = []\n for (const match of text.toLowerCase().matchAll(/[\\p{L}\\p{N}][\\p{L}\\p{N}\\p{M}_'-]*/gu)) {\n const token = match[0]\n if (CJK.test(token) && token.length > 1) {\n // Characters and adjacent bigrams, and deliberately *not* the whole run.\n //\n // Emitting the run would make \"安装程序\" a token only an exact repetition\n // could match: a document containing \"安装程序报告错误\" indexes that entire\n // string, and a search for the first four characters finds nothing. Since\n // every term has to be present, the run token would then fail the query\n // even though the characters are all there. Bigrams are the standard\n // answer to a script with no spaces and no segmenter.\n for (const character of token) tokens.push(character)\n for (let at = 0; at + 1 < token.length; at += 1) tokens.push(token.slice(at, at + 2))\n continue\n }\n tokens.push(token)\n }\n return tokens\n}\n\n/** A stable digest over the index's own contents, for corruption detection. */\nfunction digestOf(documents: readonly IndexableRecord[], postings: Map<string, Set<string>>): string {\n // Order-independent: the same content must produce the same digest whatever\n // order it was added in, or every rebuild would look like corruption.\n let hash = 0x811c9dc5\n const parts = [\n ...documents.map(document => `${document.recordId}@${document.revisionId}`).sort(),\n ...[...postings.keys()].sort().map(token => `${token}:${String(postings.get(token)?.size ?? 0)}`),\n ]\n for (const part of parts) {\n for (let index = 0; index < part.length; index += 1) {\n hash ^= part.charCodeAt(index)\n hash = Math.imul(hash, 0x01000193) >>> 0\n }\n }\n return hash.toString(16).padStart(8, '0')\n}\n\n/**\n * Decode one round of percent-escapes, without ever throwing.\n *\n * `decodeURIComponent` is the obvious tool and the wrong one: it throws on a\n * malformed escape, so a file legitimately named `100%.json` would be refused\n * as hostile. This decodes only well-formed `%XX` pairs and leaves everything\n * else exactly as it arrived.\n */\nfunction decodeOnce(value: string): string {\n return value.replace(/%([0-9a-fA-F]{2})/g, (_, hex: string) =>\n String.fromCharCode(Number.parseInt(hex, 16)))\n}\n\n/**\n * Every form a path can decode to, including the one that arrived.\n *\n * A traversal survives encoding, and it survives being encoded twice. Checking\n * only the string as received missed `..%2f` — literal dots joined by an\n * encoded separator — which reads as harmless until something downstream\n * decodes it and it becomes `../`. Bounded at four rounds, which is three more\n * than anything legitimate needs.\n */\nfunction decodings(candidate: string): readonly string[] {\n const forms = [candidate]\n let current = candidate\n for (let round = 0; round < 4; round += 1) {\n const next = decodeOnce(current)\n if (next === current) break\n forms.push(next)\n current = next\n }\n return forms\n}\n\n/**\n * Is this path inside one of the roots the caller allows?\n *\n * Refusal is the safe direction, so anything ambiguous is refused. The root\n * comparison is case-sensitive: on a case-insensitive filesystem that can\n * refuse a legitimate path, which is a nuisance, but it can never admit an\n * illegitimate one.\n */\nexport function isWithinRoots(candidate: string, roots: readonly string[]): boolean {\n if (candidate === '') return false\n\n // The traversal check runs against every form, not only the one that arrived.\n for (const form of decodings(candidate)) {\n const normalized = form.replace(/\\\\/g, '/')\n if (normalized.split('/').includes('..')) return false\n if (normalized.includes('\\0')) return false\n }\n\n const normalized = candidate.replace(/\\\\/g, '/')\n return roots.some(root => {\n const base = root.replace(/\\\\/g, '/').replace(/\\/+$/, '')\n return normalized === base || normalized.startsWith(`${base}/`)\n })\n}\n\n/** The local, derived, rebuildable search index. */\nexport class LibraryIndex {\n #documents = new Map<string, IndexableRecord>()\n #postings = new Map<string, Set<string>>()\n #health: IndexHealth = 'empty'\n #builtAt: string | null = null\n #pending = new Set<string>()\n #notes: string[] = []\n\n get health(): IndexHealth {\n return this.#health\n }\n\n /**\n * One record by id, or undefined.\n *\n * A direct lookup rather than a search. The read plane's `get` was briefly\n * implemented as a search with `limit: 1` whose single result was then\n * compared to the requested id, which reports every record except the\n * first-ranked one as missing. `#documents` is already keyed by record id;\n * this is the accessor that key exists for.\n */\n record(recordId: string): IndexableRecord | undefined {\n return this.#documents.get(recordId)\n }\n\n get size(): number {\n return this.#documents.size\n }\n\n /** Ids indexing began but did not finish, so a resumed run knows where it was. */\n get pending(): readonly string[] {\n return [...this.#pending]\n }\n\n get diagnostics(): readonly string[] {\n return [...this.#notes]\n }\n\n /**\n * Add or replace one record.\n *\n * Idempotent by construction: the record's existing postings are removed\n * before the new ones are written, so re-indexing changed text cannot leave\n * the old words behind still pointing at the document. Indexing identical\n * content twice is a no-op, which is what makes an interrupted run safe to\n * simply repeat.\n */\n add(input: IndexableRecord): void {\n // Normalized at the door, exactly as `load` already does. The type says\n // every field is present, and the type is not enforced at runtime: these\n // records are built by walking tool output, which crosses a JSON boundary\n // and arrives as whatever the tool actually returned. A record missing\n // `tags` used to throw \"not iterable\" from inside the indexer, turning one\n // malformed record into a failed index.\n const record = normalizeRecord(input)\n if (record.recordId === '') return\n this.#pending.add(record.recordId)\n this.#removePostings(record.recordId)\n this.#documents.set(record.recordId, record)\n\n const haystack = [\n record.title,\n record.text,\n record.source ?? '',\n record.runId ?? '',\n record.verdict ?? '',\n ...record.tags,\n ].join(' ')\n\n for (const token of tokenize(haystack)) {\n let postings = this.#postings.get(token)\n if (postings === undefined) {\n postings = new Set()\n this.#postings.set(token, postings)\n }\n postings.add(record.recordId)\n }\n\n this.#pending.delete(record.recordId)\n this.#health = this.#documents.size === 0 ? 'empty' : 'ready'\n this.#builtAt = new Date().toISOString()\n }\n\n /** Index many, reporting progress so an interrupted run can resume. */\n addAll(records: readonly IndexableRecord[], signal?: AbortSignal): number {\n this.#health = 'indexing'\n let done = 0\n for (const record of records) {\n if (signal?.aborted ?? false) {\n this.#health = this.#documents.size === 0 ? 'empty' : 'stale'\n this.#notes.push(`indexing cancelled after ${String(done)} of ${String(records.length)}`)\n return done\n }\n this.add(record)\n done += 1\n }\n this.#health = this.#documents.size === 0 ? 'empty' : 'ready'\n return done\n }\n\n /**\n * Forget a record entirely.\n *\n * A deleted record must not survive as a search hit. Removing the document\n * without its postings would leave a token pointing at an id that no longer\n * resolves — a result that cannot be opened, which is worse than no result.\n */\n remove(recordId: string): boolean {\n if (!this.#documents.has(recordId)) return false\n this.#removePostings(recordId)\n this.#documents.delete(recordId)\n this.#pending.delete(recordId)\n if (this.#documents.size === 0) this.#health = 'empty'\n return true\n }\n\n /** Throw everything away. The point of a derived index. */\n clear(): void {\n this.#documents.clear()\n this.#postings.clear()\n this.#pending.clear()\n this.#notes = []\n this.#health = 'empty'\n this.#builtAt = null\n }\n\n /** Mark the index as behind the store, without discarding what it has. */\n markStale(reason: string): void {\n if (this.#health === 'ready') this.#health = 'stale'\n this.#notes.push(reason)\n }\n\n #removePostings(recordId: string): void {\n for (const [token, ids] of this.#postings) {\n if (ids.delete(recordId) && ids.size === 0) this.#postings.delete(token)\n }\n }\n\n /**\n * Search.\n *\n * Every term must be present — an AND over tokens. OR would return a page of\n * documents sharing one common word, which reads as the search being broken.\n *\n * The query string is never interpreted: it is tokenized exactly like indexed\n * text, so a regular expression, a glob, a SQL fragment or a path traversal\n * in the box is simply a set of words that will not be found. There is no\n * escaping to get wrong because there is nothing to escape into.\n */\n search(query: IndexQuery): IndexQueryResult {\n const notes: string[] = []\n const limit = Math.min(Math.max(1, query.limit ?? DEFAULT_LIMIT), MAX_LIMIT)\n const offset = Math.max(0, query.offset ?? 0)\n // Read through a function, not a narrowed property. TypeScript narrows\n // `aborted` after the first check and then believes it can never be true\n // again — which is exactly what a cancellation signal is for.\n const cancelled = (): boolean => query.signal?.aborted ?? false\n\n if (this.#health === 'corrupt') {\n return {\n results: [], total: 0, offset, limit, health: 'corrupt',\n notes: ['The index is unreadable and must be rebuilt.', ...this.#notes],\n }\n }\n if (cancelled()) {\n return { results: [], total: 0, offset, limit, health: this.#health, notes: ['Search cancelled.'] }\n }\n\n const terms = tokenize(query.text)\n let candidates: Set<string>\n if (terms.length === 0) {\n // An empty query lists everything the filters allow rather than nothing.\n // \"Show me the library\" is a real request.\n candidates = new Set(this.#documents.keys())\n notes.push('No search terms: showing everything the filters allow.')\n } else {\n candidates = this.#intersect(terms)\n }\n\n const matched: { record: IndexableRecord, score: number }[] = []\n for (const recordId of candidates) {\n if (cancelled()) {\n return { results: [], total: 0, offset, limit, health: this.#health, notes: ['Search cancelled.'] }\n }\n const record = this.#documents.get(recordId)\n if (record === undefined) continue\n if (!passesFilters(record, query)) continue\n matched.push({ record, score: scoreOf(record, terms) })\n }\n\n sortMatches(matched, query.sort ?? 'relevance')\n\n const total = matched.length\n const page = matched.slice(offset, offset + limit)\n if (total > offset + page.length) {\n notes.push(`Showing ${String(offset + 1)}–${String(offset + page.length)} of ${String(total)}.`)\n }\n if (this.#health === 'stale') {\n notes.push('The index is behind the store; some recent records may be missing.')\n }\n if (this.#health === 'indexing') {\n notes.push('Indexing is still running; this answer is partial.')\n }\n\n return {\n results: page.map(({ record, score }) => toResult(record, terms, score)),\n total,\n offset,\n limit,\n health: this.#health,\n notes,\n }\n }\n\n #intersect(terms: readonly string[]): Set<string> {\n let smallest: Set<string> | null = null\n for (const term of terms) {\n const postings = this.#postings.get(term)\n if (postings === undefined) return new Set()\n if (smallest === null || postings.size < smallest.size) smallest = postings\n }\n if (smallest === null) return new Set()\n const out = new Set<string>()\n for (const candidate of smallest) {\n if (terms.every(term => this.#postings.get(term)?.has(candidate) === true)) out.add(candidate)\n }\n return out\n }\n\n /** Serialise, with a digest so a later load can tell it was not damaged. */\n serialize(): SerializedIndex {\n const documents = [...this.#documents.values()]\n return {\n version: INDEX_VERSION,\n digest: digestOf(documents, this.#postings),\n builtAt: this.#builtAt ?? new Date().toISOString(),\n documents,\n postings: Object.fromEntries(\n [...this.#postings.entries()].map(([token, ids]) => [token, [...ids].sort()]),\n ),\n }\n }\n\n /**\n * Load a serialised index, refusing anything it cannot trust.\n *\n * A wrong version, a failed digest or a malformed body all produce a\n * `corrupt` index rather than a partial one. Half-loading is the failure that\n * looks like success: queries answer, and they answer wrongly.\n */\n static load(value: unknown): LibraryIndex {\n const index = new LibraryIndex()\n const fail = (reason: string): LibraryIndex => {\n index.#health = 'corrupt'\n index.#notes.push(reason)\n return index\n }\n\n if (typeof value !== 'object' || value === null) return fail('The stored index is not an object.')\n const stored = value as Partial<SerializedIndex>\n if (stored.version !== INDEX_VERSION) {\n return fail(`Index version ${String(stored.version)} cannot be read by this build (expects ${String(INDEX_VERSION)}).`)\n }\n if (!Array.isArray(stored.documents) || typeof stored.postings !== 'object' || stored.postings === null) {\n return fail('The stored index is missing its documents or postings.')\n }\n\n const documents: IndexableRecord[] = []\n for (const document of stored.documents) {\n if (typeof document !== 'object' || document === null) return fail('A stored document is malformed.')\n const record = document as Partial<IndexableRecord>\n if (typeof record.recordId !== 'string' || record.recordId === '') {\n return fail('A stored document has no id.')\n }\n documents.push(normalizeRecord(record))\n }\n\n const postings = new Map<string, Set<string>>()\n for (const [token, ids] of Object.entries(stored.postings)) {\n if (!Array.isArray(ids)) return fail(`Postings for \"${token}\" are malformed.`)\n postings.set(token, new Set(ids.filter((id): id is string => typeof id === 'string')))\n }\n\n if (digestOf(documents, postings) !== stored.digest) {\n return fail('The stored index failed its own digest — it has been modified or truncated.')\n }\n\n for (const document of documents) index.#documents.set(document.recordId, document)\n index.#postings = postings\n index.#builtAt = typeof stored.builtAt === 'string' ? stored.builtAt : null\n index.#health = documents.length === 0 ? 'empty' : 'ready'\n return index\n }\n}\n\n/** Fill in what a stored record may be missing, without inventing content. */\nfunction normalizeRecord(record: Partial<IndexableRecord>): IndexableRecord {\n return {\n recordId: record.recordId ?? '',\n revisionId: typeof record.revisionId === 'string' ? record.revisionId : '',\n title: typeof record.title === 'string' ? record.title : '',\n kind: record.kind ?? 'document',\n text: typeof record.text === 'string' ? record.text : '',\n source: typeof record.source === 'string' ? record.source : null,\n runId: typeof record.runId === 'string' ? record.runId : null,\n observedAt: typeof record.observedAt === 'string' ? record.observedAt : null,\n verdict: typeof record.verdict === 'string' ? record.verdict : null,\n tags: Array.isArray(record.tags) ? record.tags.filter((tag): tag is string => typeof tag === 'string') : [],\n evidenceIds: Array.isArray(record.evidenceIds)\n ? record.evidenceIds.filter((id): id is string => typeof id === 'string')\n : [],\n }\n}\n\nfunction passesFilters(record: IndexableRecord, query: IndexQuery): boolean {\n if (query.kinds !== undefined && query.kinds.length > 0 && !query.kinds.includes(record.kind)) return false\n if (query.runIds !== undefined && query.runIds.length > 0) {\n if (record.runId === null || !query.runIds.includes(record.runId)) return false\n }\n if (query.verdicts !== undefined && query.verdicts.length > 0) {\n if (record.verdict === null || !query.verdicts.includes(record.verdict)) return false\n }\n if (query.sources !== undefined && query.sources.length > 0) {\n if (record.source === null || !query.sources.includes(record.source)) return false\n }\n if (query.tags !== undefined && query.tags.length > 0) {\n if (!query.tags.some(tag => record.tags.includes(tag))) return false\n }\n if (query.from !== undefined && (record.observedAt === null || record.observedAt < query.from)) return false\n if (query.to !== undefined && (record.observedAt === null || record.observedAt > query.to)) return false\n return true\n}\n\n/**\n * Score a match.\n *\n * Term frequency with a title bonus, and nothing more. A more elaborate\n * relevance model would be guessing, and this one is at least explicable: a\n * record whose title contains your words outranks one that merely mentions\n * them, and more mentions outrank fewer.\n */\nfunction scoreOf(record: IndexableRecord, terms: readonly string[]): number {\n if (terms.length === 0) return 0\n const title = new Set(tokenize(record.title))\n const body = tokenize(record.text)\n let score = 0\n for (const term of terms) {\n if (title.has(term)) score += 5\n score += body.filter(token => token === term).length\n }\n return score\n}\n\nfunction sortMatches(\n matched: { record: IndexableRecord, score: number }[],\n sort: NonNullable<IndexQuery['sort']>,\n): void {\n const time = (record: IndexableRecord): string => record.observedAt ?? ''\n matched.sort((left, right) => {\n if (sort === 'newest') return time(right.record).localeCompare(time(left.record))\n if (sort === 'oldest') return time(left.record).localeCompare(time(right.record))\n if (sort === 'title') return left.record.title.localeCompare(right.record.title)\n const byScore = right.score - left.score\n // Ties break on id so the same corpus always pages identically. A stable\n // order is what makes \"page 2\" mean anything.\n return byScore !== 0 ? byScore : left.record.recordId.localeCompare(right.record.recordId)\n })\n}\n\n/**\n * Build the snippet a person reads, around the first match.\n *\n * The text is returned verbatim and un-escaped — it is evidence, and altering\n * it here would make the snippet disagree with the source. Rendering is the\n * caller's job, and React escapes by default; this deliberately produces no\n * markup for a renderer to trust.\n */\nexport function snippetFor(text: string, terms: readonly string[], radius = 90): string {\n if (text === '' || terms.length === 0) return text.slice(0, radius * 2)\n const lower = text.toLowerCase()\n let at = -1\n for (const term of terms) {\n const found = lower.indexOf(term)\n if (found >= 0 && (at < 0 || found < at)) at = found\n }\n if (at < 0) return text.slice(0, radius * 2)\n const start = Math.max(0, at - radius)\n const end = Math.min(text.length, at + radius)\n return (start > 0 ? '…' : '') + text.slice(start, end) + (end < text.length ? '…' : '')\n}\n\nfunction toResult(record: IndexableRecord, terms: readonly string[], score: number): SearchResult {\n const hit: SearchHit = {\n sourceId: record.recordId,\n sourceRevisionId: record.revisionId,\n range: null,\n text: snippetFor(record.text === '' ? record.title : record.text, terms),\n path: 'lexical',\n score,\n evidenceIds: record.evidenceIds,\n }\n return {\n sourceId: record.recordId,\n title: record.title,\n kind: record.kind,\n hits: [hit],\n current: true,\n }\n}\n","/**\n * The Library's end of the read plane: what the surface asks, and what it does\n * with the answer.\n *\n * The Host end is `@deepwatch/dsh-tools`, which registers `WatchQueryService`\n * and lets Typert generate a strict Remote from it. The Library does not import\n * that generated artifact and does not mount it. Doing either would make the\n * package that owns the Library capability depend on the package that reads it,\n * which is the cycle `@deepwatch/dsh-client-remotes` exists to remove.\n *\n * So the namespace is described here from the contracts both ends already\n * share. `@deepwatch/dsh-contracts/query/wire` is the single definition of\n * every request and response on this wire — the generated declaration imports\n * its types from exactly that module — and `RemoteResult` is upstream's own\n * envelope. Nothing below restates a shape either side owns.\n *\n * That leaves one thing a shared contract cannot prove: that the namespace\n * really is called `watchQuery` and really carries these two methods. Two\n * things hold it. `@deepwatch/dsh-client-remotes` compares this interface\n * against the generated one at compile time, so a changed signature stops the\n * build; and `tests/remote-client-mount.test.mjs` mounts the real contribution\n * through the real Gateway and calls it, so a changed *name* fails a test\n * rather than a page.\n *\n * @module @deepwatch/dsh-library/client/read-plane\n */\n\nimport type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol'\nimport type {\n LibraryGetRequest,\n LibraryGetResponse,\n LibraryRecord,\n LibraryRefreshRequest,\n CoreHealthRequest,\n CoreHealthResponse,\n LibraryRefreshResponse,\n LibrarySearchPage,\n LibrarySearchRequest,\n LibrarySearchResponse,\n ProviderTestRequest,\n ProviderTestResponse,\n RouteReadinessRequest,\n RouteReadinessResponse,\n} from '@deepwatch/dsh-contracts/query/wire'\nimport { WATCH_QUERY_WIRE_VERSION } from '@deepwatch/dsh-contracts/query/wire'\nimport type { IndexHealth, IndexQueryResult } from '../index-store.js'\n\n/**\n * The `watchQuery` namespace, as the Library calls it.\n *\n * `ctx.remote.watchQuery` is a cordis service the Gateway installs when the\n * contribution is mounted, so a surface that injects `remote.watchQuery` is\n * handed exactly this.\n */\nexport interface WatchQueryRemote {\n readonly librarySearch: (\n request: LibrarySearchRequest, signal?: AbortSignal,\n ) => Promise<RemoteResult<LibrarySearchResponse>>\n readonly libraryGet: (\n request: LibraryGetRequest, signal?: AbortSignal,\n ) => Promise<RemoteResult<LibraryGetResponse>>\n /**\n * The only method here with a side effect.\n *\n * A separate operation rather than a flag on a search: a search that might\n * re-read the corpus has a cost nobody can predict, and leaves a caller no\n * way to ask for an answer from what the host already holds.\n */\n readonly libraryRefresh: (\n request: LibraryRefreshRequest, signal?: AbortSignal,\n ) => Promise<RemoteResult<LibraryRefreshResponse>>\n /**\n * The state of Watch Core, read from the running Bridge.\n *\n * Not a Library read, and on this namespace because this is the only channel\n * the browser has to the Host. Diagnostics is its caller: before it existed\n * that panel rendered the engine's connection state and version as literals,\n * because there was nowhere to read them from.\n */\n readonly coreHealth: (\n request: CoreHealthRequest, signal?: AbortSignal,\n ) => Promise<RemoteResult<CoreHealthResponse>>\n readonly providerTest: (\n request: ProviderTestRequest, signal?: AbortSignal,\n ) => Promise<RemoteResult<ProviderTestResponse>>\n /**\n * The Host's own verdict on a route, asked without spending anything.\n *\n * Here because this is the browser's only channel to the Host, and because\n * the alternative — a tab remembering a provider test it ran — is a claim\n * about a Host it cannot see.\n */\n readonly routeReadiness: (\n request: RouteReadinessRequest, signal?: AbortSignal,\n ) => Promise<RemoteResult<RouteReadinessResponse>>\n}\n\n/** One row of results, however the surface obtained it. */\nexport interface ResultRow {\n /** Stable across a re-render of the same answer. */\n readonly key: string\n readonly recordId: string\n readonly title: string\n readonly kind: string\n /** Matched text, verbatim. Empty when the answer carried no excerpt. */\n readonly snippets: readonly string[]\n readonly evidenceCount: number\n /** False when a newer revision of the same source exists. */\n readonly current: boolean\n}\n\n/** What one completed search left on the screen. */\nexport interface SearchState {\n readonly rows: readonly ResultRow[]\n /** Matches in total, not merely on this page. */\n readonly total: number\n readonly health: IndexHealth\n /** Which index generation answered. Zero when the host tracks none. */\n readonly generation: number\n /** Non-fatal facts about this answer: truncation, staleness, refusal. */\n readonly notes: readonly string[]\n /** Whether the caller may ask for another page of the same answer. */\n readonly pageable: boolean\n}\n\n/** What the surface is asking the host for. */\nexport interface LibraryQuery {\n readonly text: string\n /** One modality, or the empty string for all of them. */\n readonly modality: string\n readonly limit: number\n readonly deadlineMs: number\n}\n\n/**\n * Correlation ids, from a counter rather than from randomness.\n *\n * The host refuses a `requestId` that is not an identifier, and a counter\n * produces one by construction. It is also what makes a failing request\n * quotable: `library-7` names a call somebody can find twice.\n */\nlet sequence = 0\n\n/** The next correlation id for a Library read. */\nexport function nextRequestId(): string {\n sequence += 1\n return `library-${String(sequence)}`\n}\n\n/** An answer that produced no rows, and says why. */\nfunction nothing(note: string, health: IndexHealth = 'stale'): SearchState {\n return { rows: [], total: 0, health, generation: 0, notes: [note], pageable: false }\n}\n\n/** The host's own vocabulary for index condition, in the surface's terms. */\nfunction healthOf(state: LibrarySearchPage['indexState']): IndexHealth {\n return state === 'rebuilding' ? 'indexing' : state\n}\n\n/** One wire record as a row. */\nfunction rowOf(record: LibraryRecord): ResultRow {\n return {\n key: `${record.recordId}@${record.revisionId}`,\n recordId: record.recordId,\n title: record.title,\n kind: record.modality,\n // The wire record carries provenance, not excerpts: the Host answers with\n // what it persisted, and inventing a snippet from a title would put text on\n // screen that no record contains.\n snippets: [],\n evidenceCount: record.evidenceIds.length,\n current: record.current,\n }\n}\n\n/**\n * Ask the host, and turn whatever comes back into something renderable.\n *\n * Every outcome is an answer the surface shows rather than an exception it\n * swallows. A refusal, an elapsed deadline and an expired cursor are different\n * facts, and a person acts differently on each, so each keeps its own sentence.\n */\nexport async function readLibraryPage(\n reads: WatchQueryRemote, query: LibraryQuery, signal: AbortSignal,\n): Promise<SearchState> {\n const answer = await reads.librarySearch({\n protocol: WATCH_QUERY_WIRE_VERSION,\n requestId: nextRequestId(),\n query: query.text,\n modalities: query.modality === '' ? [] : [query.modality],\n limit: query.limit,\n cursor: null,\n deadlineMs: query.deadlineMs,\n }, signal)\n\n // The transport envelope first. `ok: false` means the call never produced a\n // domain answer at all — no Connection, a Gateway refusal, a codec that\n // rejected the response — and reporting that as an empty library would be a\n // lie about what the workspace contains.\n if (!answer.ok) {\n return nothing(`The Library host did not answer: ${answer.error.message}`, 'corrupt')\n }\n\n const value = answer.value\n switch (value.outcome) {\n case 'page': {\n const notes = value.records.length < value.total\n ? [`Showing ${String(value.records.length)} of ${String(value.total)} matches; `\n + 'the host answered with one page and offered no cursor.']\n : []\n return {\n rows: value.records.map(rowOf),\n total: value.total,\n health: healthOf(value.indexState),\n generation: value.generation,\n notes,\n // `nextCursor` is the host's own statement about whether more remains.\n // Deriving it from `total` instead would offer a Next control the host\n // has no way to answer.\n pageable: value.nextCursor !== null,\n }\n }\n case 'rejected':\n return nothing(\n `The host refused the request (${value.reason}`\n + `${value.field === null ? '' : ` at ${value.field}`}).`,\n )\n case 'deadline_exceeded':\n return nothing(\n `The host did not answer within ${String(value.deadlineMs)}ms. Try a narrower query.`,\n )\n case 'cursor_expired':\n return nothing('That page is no longer held by the host. Search again.')\n }\n}\n\n/** A local index answer as the same view model, so the surface renders one shape. */\nexport function fromIndex(result: IndexQueryResult): SearchState {\n return {\n rows: result.results.map(entry => ({\n key: entry.sourceId,\n recordId: entry.sourceId,\n title: entry.title,\n kind: entry.kind,\n snippets: entry.hits.map(hit => hit.text),\n evidenceCount: entry.hits[0]?.evidenceIds.length ?? 0,\n current: entry.current,\n })),\n total: result.total,\n health: result.health,\n // The local index is not a host generation and does not pretend to be one.\n generation: 0,\n notes: result.notes,\n pageable: true,\n }\n}\n\n/** What a completed refresh left for the surface to say. */\nexport interface RefreshState {\n /** True only where the host swapped a new generation into service. */\n readonly refreshed: boolean\n /** The generation now answering searches. */\n readonly generation: number\n readonly recordCount: number\n /** One sentence a person can act on. Empty where there is nothing to say. */\n readonly note: string\n /** Whether the note describes a failure rather than a result. */\n readonly failed: boolean\n}\n\n/**\n * Ask the host to read its roots again.\n *\n * Every outcome is rendered, and none of them is an exception. A refusal, an\n * elapsed deadline, an abandoned rebuild and a failed one are four different\n * facts; so is a rebuild that succeeded and found nothing new. Reporting any\n * of them as \"refreshed\" would be a control that lies about what it did.\n */\nexport async function refreshLibrary(\n reads: WatchQueryRemote, deadlineMs: number, signal: AbortSignal,\n): Promise<RefreshState> {\n const answer = await reads.libraryRefresh({\n protocol: WATCH_QUERY_WIRE_VERSION,\n requestId: nextRequestId(),\n deadlineMs,\n }, signal)\n\n if (!answer.ok) {\n return failedRefresh(`The Library host did not answer: ${answer.error.message}`)\n }\n\n const value = answer.value\n switch (value.outcome) {\n case 'refreshed':\n return {\n refreshed: true,\n generation: value.index.generation,\n recordCount: value.index.recordCount,\n note: value.skipped.length === 0\n ? ''\n : `${String(value.skipped.length)} file(s) were not readable: `\n + value.skipped.slice(0, 3).join('; '),\n failed: false,\n }\n case 'refresh_cancelled':\n return {\n refreshed: false,\n generation: value.index.generation,\n recordCount: value.index.recordCount,\n note: 'The refresh was abandoned. The Library is unchanged.',\n failed: false,\n }\n case 'refresh_failed':\n return {\n refreshed: false,\n generation: value.index.generation,\n recordCount: value.index.recordCount,\n note: `The refresh failed: ${value.reason}. The previous index is still searchable.`,\n failed: true,\n }\n case 'rejected':\n return failedRefresh(`The host refused the refresh (${value.reason}).`)\n case 'deadline_exceeded':\n return failedRefresh(\n `The refresh did not finish within ${String(value.deadlineMs)}ms. `\n + 'It may still be running on the host.',\n )\n }\n}\n\n/** A refresh that produced no generation, and why. */\nfunction failedRefresh(note: string): RefreshState {\n return { refreshed: false, generation: 0, recordCount: 0, note, failed: true }\n}\n","/**\n * The Library, as a working search surface.\n *\n * It is backed by `LibraryIndex` — a local, derived, rebuildable inverted index\n * — so search works offline, needs no service and needs no embedding model.\n * Semantic retrieval stays a future optional plugin; this is what runs on any\n * machine today.\n *\n * The records come from the evidence the workspace has actually seen. Where\n * there are none the surface says so and offers a rebuild, rather than\n * presenting an empty result set as though a search had run and found nothing —\n * those are different facts and a person acts differently on each.\n *\n * Accessibility is not an afterthought here because a search box is where\n * keyboard and screen-reader behaviour is most obviously felt: the field is\n * labelled, results are a live region announcing their own count, every filter\n * is a real control, and the index's condition is announced rather than only\n * coloured.\n *\n * @module @deepwatch/dsh-library/client/search-view\n */\n\nimport type { ReactNode } from 'react'\nimport { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { LibraryIndex, MAX_LIMIT, tokenize } from '../index-store.js'\nimport type { IndexQuery, IndexableRecord } from '../index-store.js'\nimport type { SourceKind } from '../sources.js'\nimport { fromIndex, readLibraryPage, refreshLibrary } from './read-plane.js'\nimport type { RefreshState, SearchState, WatchQueryRemote } from './read-plane.js'\n\nconst PAGE = 10\n\n/**\n * How long the surface will wait for the host before it must show something.\n *\n * Shorter than the host's own ceiling on purpose: the host clamps to 30s, and a\n * search box that can appear frozen for half a minute is a search box people\n * stop trusting. The host is told this number, so an answer it cannot produce\n * in time comes back as `deadline_exceeded` — a sentence on the screen — rather\n * than as a request nobody ever cancelled.\n */\nconst DEADLINE_MS = 10_000\n\n/**\n * How long the surface will wait for a rebuild.\n *\n * Longer than a search, because it is one: reading a corpus is work a person\n * has asked for and expects to take a moment. Still bounded, and still the\n * number the host is told, so an overrun comes back as an answer rather than\n * as a control that never settles.\n */\nconst REFRESH_DEADLINE_MS = 60_000\n\nconst S = {\n root: {\n display: 'flex', flexDirection: 'column' as const, gap: '14px',\n height: '100%', minHeight: 0,\n },\n bar: { display: 'flex', gap: '10px', flexWrap: 'wrap' as const, alignItems: 'flex-end' },\n field: { display: 'flex', flexDirection: 'column' as const, gap: '4px', flex: '1 1 260px', minWidth: 0 },\n label: {\n fontSize: '11px', fontWeight: 600, letterSpacing: '.05em',\n textTransform: 'uppercase' as const, color: 'var(--dsw-alias-label-tertiary)',\n },\n input: {\n background: 'var(--dsw-alias-bg-layer-2)',\n border: '1px solid color-mix(in srgb, var(--watch-accent) 12%, var(--dsw-alias-border-l2))',\n borderRadius: '10px', padding: '9px 11px', fontSize: '13px',\n color: 'inherit', font: 'inherit', minWidth: 0, width: '100%',\n },\n select: {\n background: 'var(--dsw-alias-bg-layer-2)',\n border: '1px solid color-mix(in srgb, var(--watch-accent) 12%, var(--dsw-alias-border-l2))',\n borderRadius: '10px', padding: '9px 11px', fontSize: '13px', color: 'inherit',\n },\n button: {\n background: 'transparent', border: '1px solid var(--dsw-alias-border-l2)',\n borderRadius: '10px', padding: '9px 13px', fontSize: '13px',\n color: 'inherit', cursor: 'pointer',\n },\n status: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)', margin: 0 },\n list: { display: 'flex', flexDirection: 'column' as const, gap: '8px', margin: 0, padding: 0, listStyle: 'none' },\n hit: {\n border: '1px solid color-mix(in srgb, var(--watch-accent) 9%, var(--dsw-alias-border-l2))', borderRadius: '14px',\n padding: '14px 16px', background: 'linear-gradient(145deg, color-mix(in srgb, var(--watch-accent) 3%, var(--dsw-alias-bg-layer-2)), var(--dsw-alias-bg-base))',\n boxShadow: '0 8px 24px color-mix(in srgb, black 7%, transparent)',\n },\n title: { fontSize: '13.5px', fontWeight: 600, margin: 0 },\n snippet: {\n fontSize: '12.5px', lineHeight: 1.6, margin: '6px 0 0',\n color: 'var(--dsw-alias-label-secondary)', wordBreak: 'break-word' as const,\n },\n meta: { fontSize: '11.5px', color: 'var(--dsw-alias-label-tertiary)', marginTop: '6px', display: 'flex', gap: '10px', flexWrap: 'wrap' as const },\n}\n\n/** What the index says about itself, in words a person can act on. */\nconst HEALTH: Record<string, { readonly says: string, readonly tone: string }> = {\n empty: { says: 'Nothing indexed yet.', tone: 'var(--watch-tone-neutral)' },\n ready: { says: 'Index ready.', tone: 'var(--watch-tone-active)' },\n indexing: { says: 'Indexing — results are partial.', tone: 'var(--watch-tone-caution)' },\n stale: { says: 'Index is behind the store.', tone: 'var(--watch-tone-caution)' },\n corrupt: { says: 'Index unreadable. Rebuild required.', tone: 'var(--watch-tone-error)' },\n}\n\nconst KINDS: readonly SourceKind[] = ['video', 'audio', 'page', 'stream', 'document', 'screen_capture']\n\n/**\n * Highlight matches without building markup.\n *\n * The snippet is evidence, so it is never altered and never handed to a\n * renderer as HTML. Splitting into plain segments and marking them with React\n * elements keeps escaping the renderer's job, which is the only place it is\n * reliably done.\n */\nfunction Highlighted({ text, terms }: { readonly text: string, readonly terms: readonly string[] }): ReactNode {\n if (terms.length === 0 || text === '') return <>{text}</>\n const pattern = terms\n .map(term => term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .filter(term => term !== '')\n .join('|')\n if (pattern === '') return <>{text}</>\n const parts = text.split(new RegExp(`(${pattern})`, 'giu'))\n return (\n <>\n {parts.map((part, index) => (\n terms.includes(part.toLowerCase())\n ? <mark key={`${part}-${String(index)}`} style={{ background: 'var(--watch-wash-active)', color: 'inherit' }}>{part}</mark>\n : <span key={`${part}-${String(index)}`}>{part}</span>\n ))}\n </>\n )\n}\n\nexport interface LibrarySearchProps {\n /** The records to index. The store remains the source of truth. */\n readonly records?: readonly IndexableRecord[]\n /** Injected for tests; production builds its own. */\n readonly index?: LibraryIndex\n /**\n * The mounted `ctx.remote.watchQuery` namespace, when there is one.\n *\n * Present in a profile: the workspace's own host holds the library, and\n * answering from a browser-side copy of it would be a second index with its\n * own drift. Absent everywhere else — a test, a story, a build without the\n * Host row — and the local index answers instead.\n */\n readonly reads?: WatchQueryRemote | undefined\n}\n\n/** The Library search workflow: query, filter, sort, page, rebuild. */\nexport function LibrarySearch(\n { records = [], index: injected, reads }: LibrarySearchProps,\n): ReactNode {\n const queryId = useId()\n const kindId = useId()\n const verdictId = useId()\n const sortId = useId()\n\n const [text, setText] = useState('')\n const [kind, setKind] = useState<'' | SourceKind>('')\n const [verdict, setVerdict] = useState('')\n const [sort, setSort] = useState<NonNullable<IndexQuery['sort']>>('relevance')\n const [offset, setOffset] = useState(0)\n const [generation, setGeneration] = useState(0)\n const [state, setState] = useState<SearchState | null>(null)\n // Bounded progress. `true` only while a rebuild the host accepted is\n // outstanding, so the control cannot appear busy after the answer arrived.\n const [refreshing, setRefreshing] = useState(false)\n const [refreshed, setRefreshed] = useState<RefreshState | null>(null)\n\n const index = useMemo(() => {\n if (injected !== undefined) return injected\n const built = new LibraryIndex()\n built.addAll(records)\n return built\n }, [injected, records, generation])\n\n // Every query supersedes the one before it. Without this, a slow search over\n // a large corpus can land after a newer one and overwrite it with stale\n // results — the classic race that makes a search box feel haunted. It is what\n // carries cancellation to the host too: the Remote takes the same signal, so\n // an abandoned query is abandoned on both sides rather than only on this one.\n const inFlight = useRef<AbortController | null>(null)\n\n const run = useCallback((nextOffset: number) => {\n inFlight.current?.abort()\n const controller = new AbortController()\n inFlight.current = controller\n setOffset(nextOffset)\n\n if (reads === undefined) {\n setState(fromIndex(index.search({\n text,\n ...(kind === '' ? {} : { kinds: [kind] }),\n ...(verdict === '' ? {} : { verdicts: [verdict] }),\n sort,\n offset: nextOffset,\n limit: PAGE,\n signal: controller.signal,\n })))\n return\n }\n\n void readLibraryPage(\n reads,\n { text, modality: kind, limit: PAGE, deadlineMs: DEADLINE_MS },\n controller.signal,\n ).then(next => {\n // A superseded answer is dropped rather than rendered: the newer query\n // already owns the screen.\n if (!controller.signal.aborted) setState(next)\n })\n }, [reads, index, text, kind, verdict, sort])\n\n useEffect(() => { run(0) }, [run])\n useEffect(() => () => { inFlight.current?.abort() }, [])\n\n /**\n * Ask the host to read its roots again, then search the result.\n *\n * Two steps rather than one, and deliberately in that order: the refresh\n * reports what the host now holds, and the search that follows is what puts\n * it on the screen. Collapsing them would leave the count and the rows\n * describing two different generations.\n */\n const refresh = useCallback(() => {\n if (reads === undefined) {\n // No host to ask. The local index is derived, so discarding it and\n // building it again is the whole refresh.\n setGeneration(value => value + 1)\n return\n }\n if (refreshing) return\n setRefreshing(true)\n setRefreshed(null)\n const controller = new AbortController()\n void refreshLibrary(reads, REFRESH_DEADLINE_MS, controller.signal)\n .then(next => {\n setRefreshed(next)\n setRefreshing(false)\n // Re-read only where the host actually swapped something in. A failed\n // refresh leaves the previous index searchable and the rows on screen\n // are still correct for it.\n if (next.refreshed) setGeneration(value => value + 1)\n })\n }, [reads, refreshing])\n\n const terms = useMemo(() => tokenize(text), [text])\n // `noUncheckedIndexedAccess` is on, so an index lookup is optional even\n // with a total record type. Falling back keeps the surface renderable\n // for a health value a future build adds before this one knows it.\n const health = HEALTH[state?.health ?? (reads === undefined ? index.health : 'empty')]\n ?? { says: 'Index state unknown.', tone: 'var(--watch-tone-neutral)' }\n const rows = state?.rows ?? []\n const total = state?.total ?? 0\n const shown = rows.length\n const page = Math.floor(offset / PAGE) + 1\n // The host answers one page and offers no cursor, so it never claims more\n // pages than it can produce. Only the local index pages.\n const pages = state?.pageable === true ? Math.max(1, Math.ceil(total / PAGE)) : 1\n\n return (\n <div style={S.root}>\n <form\n style={S.bar}\n role=\"search\"\n onSubmit={event => { event.preventDefault(); run(0) }}\n >\n <div style={S.field}>\n <label htmlFor={queryId} style={S.label}>Search evidence</label>\n <input\n id={queryId}\n style={S.input}\n type=\"search\"\n value={text}\n placeholder=\"Words in a transcript, a title, a run…\"\n onChange={event => { setText(event.target.value) }}\n />\n </div>\n\n <div style={S.field}>\n <label htmlFor={kindId} style={S.label}>Type</label>\n <select id={kindId} style={S.select} value={kind} onChange={event => { setKind(event.target.value as '' | SourceKind) }}>\n <option value=\"\">Any type</option>\n {KINDS.map(value => <option key={value} value={value}>{value.replace('_', ' ')}</option>)}\n </select>\n </div>\n\n <div style={S.field}>\n <label htmlFor={verdictId} style={S.label}>Verification</label>\n {/* The host answers by query and modality and has no verdict or sort\n parameter, so both controls are disabled rather than silently\n ignored while it is answering. A filter that changes nothing is\n worse than one that is plainly unavailable. */}\n <select\n id={verdictId}\n style={S.select}\n value={verdict}\n disabled={reads !== undefined}\n onChange={event => { setVerdict(event.target.value) }}\n >\n <option value=\"\">Any state</option>\n {['VERIFIED', 'FAILED', 'UNVERIFIED', 'INCONCLUSIVE'].map(value => (\n <option key={value} value={value}>{value}</option>\n ))}\n </select>\n </div>\n\n <div style={S.field}>\n <label htmlFor={sortId} style={S.label}>Sort</label>\n <select\n id={sortId}\n style={S.select}\n value={sort}\n disabled={reads !== undefined}\n onChange={event => { setSort(event.target.value as NonNullable<IndexQuery['sort']>) }}\n >\n <option value=\"relevance\">Relevance</option>\n <option value=\"newest\">Newest first</option>\n <option value=\"oldest\">Oldest first</option>\n <option value=\"title\">Title</option>\n </select>\n </div>\n\n <button type=\"submit\" style={S.button}>Search</button>\n <button\n type=\"button\"\n style={S.button}\n disabled={refreshing}\n // Rebuilding is safe precisely because the index is derived: it can\n // be thrown away and reconstructed from the records at any time.\n //\n // It says what it does in each mode rather than one word for two\n // actions. Locally it discards the index and builds it again. Against\n // a host it asks the host to read its roots again — a real operation\n // with a real answer, which is why the label is the same verb and the\n // subject is the Library rather than a local structure.\n onClick={refresh}\n >\n {refreshing\n ? 'Refreshing…'\n : (reads === undefined ? 'Rebuild index' : 'Refresh library')}\n </button>\n </form>\n\n <p style={{ ...S.status, color: health.tone }}>\n {health.says}\n {' '}\n <span style={{ color: 'var(--dsw-alias-label-tertiary)' }}>\n {reads === undefined\n ? `${String(index.size)} record(s) indexed on this machine.`\n : 'Answered by this workspace’s own host.'}\n </span>\n </p>\n\n {/* A live region: the result count is announced, not only drawn. */}\n <p style={S.status} role=\"status\" aria-live=\"polite\">\n {total === 0\n ? (terms.length === 0 ? 'No records to list.' : `No matches for “${text}”.`)\n : `${String(total)} match${total === 1 ? '' : 'es'}, showing ${String(shown)} (page ${String(page)} of ${String(pages)}).`}\n </p>\n\n {/* What the last refresh did, kept separate from what the search found.\n They are different questions and a person acts differently on each. */}\n {refreshing\n ? (\n <p style={S.status} role=\"status\" aria-live=\"polite\">\n Reading the library again. The results below are the previous\n index until it finishes.\n </p>\n )\n : null}\n {refreshed === null || refreshing\n ? null\n : (\n <p\n style={{\n ...S.status,\n color: refreshed.failed ? 'var(--watch-tone-error)' : 'var(--dsw-alias-label-tertiary)',\n }}\n role=\"status\"\n aria-live=\"polite\"\n >\n {refreshed.refreshed\n ? `Library refreshed: ${String(refreshed.recordCount)} record(s), `\n + `generation ${String(refreshed.generation)}.`\n : refreshed.note}\n {refreshed.refreshed && refreshed.note !== '' ? ` ${refreshed.note}` : ''}\n </p>\n )}\n\n {(state?.notes ?? []).map(note => (\n <p key={note} style={S.status}>{note}</p>\n ))}\n\n {total === 0\n ? (\n <div style={{ ...S.hit, borderStyle: 'dashed' }}>\n <p style={{ ...S.snippet, margin: 0 }}>\n {reads === undefined && index.size === 0\n ? 'Nothing has been indexed yet. Evidence appears here once the workspace has recorded some — then this searches it locally, with no service and no model.'\n : 'Nothing matched. Every word has to appear in a record; try fewer words, or clear the filters.'}\n </p>\n </div>\n )\n : (\n <ul style={S.list}>\n {rows.map(entry => (\n <li key={entry.key} style={S.hit}>\n <h4 style={S.title}><Highlighted text={entry.title} terms={terms} /></h4>\n {entry.snippets.map((snippet, at) => (\n <p key={`${entry.key}-${String(at)}`} style={S.snippet}>\n <Highlighted text={snippet} terms={terms} />\n </p>\n ))}\n <div style={S.meta}>\n <span>{entry.kind}</span>\n <span data-watch-ltr>{entry.recordId}</span>\n {entry.evidenceCount > 0\n ? (\n <span data-watch-ltr>\n {`${String(entry.evidenceCount)} evidence ref(s)`}\n </span>\n )\n : null}\n </div>\n </li>\n ))}\n </ul>\n )}\n\n {pages > 1\n ? (\n <nav style={{ display: 'flex', gap: '8px' }} aria-label=\"Search results pages\">\n <button\n type=\"button\"\n style={S.button}\n disabled={offset === 0}\n onClick={() => { run(Math.max(0, offset - PAGE)) }}\n >\n Previous\n </button>\n <button\n type=\"button\"\n style={S.button}\n disabled={offset + PAGE >= total}\n onClick={() => { run(Math.min(offset + PAGE, Math.max(0, total - 1))) }}\n >\n Next\n </button>\n </nav>\n )\n : null}\n </div>\n )\n}\n\nexport { MAX_LIMIT }\n","/**\n * The Library mode body.\n *\n * It lives in the package that owns the capability rather than in the workspace\n * shell — the shell provides the scaffold every mode shares, and a mode that\n * also needed something back from its own package made the two depend on each\n * other. TypeScript refused the circular project reference, which was the right\n * answer to the wrong arrangement.\n *\n * @module @deepwatch/dsh-library/client/library-mode\n */\n\nimport type { ReactNode } from 'react'\nimport { parseVerdict } from '@deepwatch/dsh-contracts'\nimport { Facts, ModeSurface, Panel, readToolResult } from '@deepwatch/dsh-workspace/surface'\nimport type { ModeViewProps } from '@deepwatch/dsh-workspace/surface'\nimport { LibrarySearch } from './search-view.js'\nimport type { IndexableRecord } from '../index-store.js'\nimport type { WatchQueryRemote } from './read-plane.js'\n\n/** What the Library body needs beyond the standard view props. */\nexport interface LibraryModeProps extends ModeViewProps {\n /**\n * Records to index.\n *\n * The evidence store stays the source of truth; the index is derived from\n * this and can be thrown away at any time.\n */\n readonly records?: readonly IndexableRecord[]\n /**\n * The mounted `ctx.remote.watchQuery` namespace, when there is one.\n *\n * Bound by the registration in `./index.tsx`, because a `conversation.view`\n * entry receives only `{ inspect, onInspectDone }` and cannot reach a\n * service itself. Absent when this body is rendered outside a profile — in a\n * test, or a story — and the search then answers from the local index.\n */\n readonly reads?: WatchQueryRemote | undefined\n}\n\n/** The Library mode: everything recorded, and searchable. */\nexport function LibraryModeView(\n { inspect, records = [], reads }: LibraryModeProps = {},\n): ReactNode {\n const selected = parseVerdict(readToolResult(inspect))\n\n return (\n <ModeSurface\n title=\"Library\"\n lead={\n reads === undefined\n ? 'Every source and every piece of evidence this workspace has recorded. '\n + 'Search runs on this machine — no service, no model, nothing leaves it.'\n : 'Every source and every piece of evidence this workspace has recorded. '\n + 'Search runs on this workspace’s own host — no service, no model, '\n + 'nothing leaves the machine it runs on.'\n }\n >\n {selected === null\n ? null\n : (\n <Panel heading=\"Selected record\">\n <Facts\n rows={[\n ['Verdict', selected.verdict],\n ['Reason', selected.reason],\n ['Checks', String(selected.checks.length)],\n ]}\n />\n </Panel>\n )}\n\n <LibrarySearch records={records} reads={reads} />\n </ModeSurface>\n )\n}\n","/**\n * The Library: sources, their revisions, and what is still true about them.\n *\n * The Library is not memory. That separation is the first thing this module\n * exists to hold: memory is what the system believes, and the Library is what\n * it has *seen*. Conflating them produces the worst version of both — a\n * knowledge base you cannot cite and an evidence store that argues with you.\n * So nothing here has a scope, a confidence or a status; those are memory's\n * vocabulary. A source has revisions, and evidence is addressed to one of them.\n *\n * The second thing it holds is the revision rule, which is the whole reason\n * evidence ids are worth anything:\n *\n * > A source that changed is a different source revision, and evidence stays\n * > addressed to the revision it was taken from — forever.\n *\n * When a page is re-indexed, the evidence from last week does not become\n * wrong, and it does not become unreachable. It becomes *stale*: it still\n * opens, still resolves to the same frame at the same millisecond, and now\n * carries a note saying the source has moved on. Deleting it would destroy the\n * receipt; silently re-pointing it at the new revision would be worse, because\n * the citation would then be to something nobody observed.\n *\n * @module @deepwatch/dsh-library/sources\n */\n\nimport type { EvidenceRecord, Freshness, TemporalRange } from '@deepwatch/dsh-contracts'\nimport type { ScriptTag } from '@deepwatch/dsh-technology'\n\n/** What kind of thing a source is. */\nexport type SourceKind = 'video' | 'audio' | 'page' | 'stream' | 'document' | 'screen_capture'\n\n/** Where an index is in its life. */\nexport type IndexState =\n /** Known to the Library, nothing extracted. */\n | 'not_indexed'\n | 'indexing'\n /** Extracted and searchable. */\n | 'indexed'\n /** Indexed, but against an older revision than the current one. */\n | 'stale'\n | 'failed'\n\n/** One immutable version of a source. */\nexport interface SourceRevision {\n readonly sourceRevisionId: string\n readonly sourceId: string\n /** Monotonic within a source. Revision 1 is the first thing observed. */\n readonly revision: number\n /** Digest of the bytes, which is what makes \"changed\" a fact. */\n readonly contentDigest: string\n readonly observedAt: string\n readonly durationMs: number | null\n readonly indexState: IndexState\n /** Why indexing failed, when it did. */\n readonly indexError: string | null\n /** Scripts detected in this revision's text. Structural, not measured. */\n readonly scripts: readonly ScriptTag[]\n}\n\n/** A source, with every revision the Library holds. */\nexport interface Source {\n readonly sourceId: string\n readonly kind: SourceKind\n /** What it is called. Presentation only; nothing resolves from it. */\n readonly title: string\n /** URL or path. */\n readonly locator: string\n readonly revisions: readonly SourceRevision[]\n /** Collections this source belongs to. */\n readonly collections: readonly string[]\n /** Entities extracted from it, when the engine extracts any. */\n readonly entities: readonly string[]\n}\n\n/** A named group of sources. Curation, not classification. */\nexport interface Collection {\n readonly collectionId: string\n readonly name: string\n readonly sourceIds: readonly string[]\n}\n\n/**\n * The current revision of a source.\n *\n * Highest revision number, not most recently observed — a re-index of an old\n * revision must not become \"current\" because it happened last.\n */\nexport function currentRevision(source: Source): SourceRevision | null {\n let best: SourceRevision | null = null\n for (const revision of source.revisions) {\n if (best === null || revision.revision > best.revision) best = revision\n }\n return best\n}\n\n/** Find one revision by id, wherever it sits in the history. */\nexport function findRevision(\n source: Source,\n sourceRevisionId: string,\n): SourceRevision | null {\n return source.revisions.find(revision => revision.sourceRevisionId === sourceRevisionId) ?? null\n}\n\n/**\n * Whether a source revision is still the one a fresh observation would produce.\n *\n * Separated from freshness below because they are different questions: this is\n * about the *source*, and freshness is about one piece of evidence taken from\n * it. A source can be current while a specific observation from it is stale,\n * when the observation covers a range the new revision no longer contains.\n */\nexport function isCurrentRevision(source: Source, sourceRevisionId: string): boolean {\n return currentRevision(source)?.sourceRevisionId === sourceRevisionId\n}\n\n/**\n * Freshness of one evidence record, given what the Library now holds.\n *\n * The rules, in order:\n *\n * - Evidence whose source the Library does not hold is `unavailable`. Not\n * `expired` — nobody knows whether it expired; it simply cannot be checked.\n * - Evidence against the current revision keeps whatever freshness the engine\n * assigned it, including `gap`. Freshness is not the Library's to upgrade.\n * - Evidence against a superseded revision is `stale`. It still resolves; it\n * no longer describes the source.\n *\n * Note what this function never returns: `current` for something it was not\n * already told was current. A Library that could promote evidence to fresh\n * would be a Library that re-validates by assertion.\n */\nexport function freshnessOf(\n evidence: Pick<EvidenceRecord, 'sourceRevisionId' | 'freshness'>,\n sources: readonly Source[],\n): Freshness {\n const owner = sources.find(source =>\n source.revisions.some(revision => revision.sourceRevisionId === evidence.sourceRevisionId))\n if (owner === undefined) return 'unavailable'\n if (!isCurrentRevision(owner, evidence.sourceRevisionId)) return 'stale'\n return evidence.freshness\n}\n\n/**\n * Whether an evidence id can still be opened.\n *\n * Always true when the Library holds its revision, whatever the freshness. The\n * function exists to make that a stated guarantee rather than an accident of\n * whichever query happens to run: a stale citation that stopped opening would\n * turn every old receipt into a dead link.\n */\nexport function isAddressable(\n evidence: Pick<EvidenceRecord, 'sourceRevisionId'>,\n sources: readonly Source[],\n): boolean {\n return sources.some(source =>\n source.revisions.some(revision => revision.sourceRevisionId === evidence.sourceRevisionId))\n}\n\n/** A place in a source that a citation resolves to. */\nexport interface EvidenceLocation {\n readonly sourceId: string\n readonly sourceRevisionId: string\n readonly revision: number\n readonly range: TemporalRange | null\n readonly freshness: Freshness\n /** Whether the source has moved on since this was observed. */\n readonly supersededBy: string | null\n}\n\n/**\n * Resolve an evidence record to a place in the Library.\n *\n * Returns null only when the revision is not held. Everything else resolves,\n * including evidence from four revisions ago — with `supersededBy` naming what\n * replaced it, so the surface can offer \"look at the same moment in the\n * current revision\" without silently doing it.\n */\nexport function locate(\n evidence: Pick<EvidenceRecord, 'sourceRevisionId' | 'temporalRange' | 'freshness'>,\n sources: readonly Source[],\n): EvidenceLocation | null {\n for (const source of sources) {\n const revision = findRevision(source, evidence.sourceRevisionId)\n if (revision === null) continue\n const current = currentRevision(source)\n return {\n sourceId: source.sourceId,\n sourceRevisionId: revision.sourceRevisionId,\n revision: revision.revision,\n range: evidence.temporalRange,\n freshness: freshnessOf(evidence, sources),\n supersededBy: current === null || current.sourceRevisionId === revision.sourceRevisionId\n ? null\n : current.sourceRevisionId,\n }\n }\n return null\n}\n\n/**\n * Record a new revision of a source.\n *\n * Old revisions are kept, and their index state is marked `stale` rather than\n * removed. That is the mechanism behind every \"old evidence still opens\"\n * guarantee above — there is no code path that discards a revision, so there is\n * no code path that could orphan a citation.\n */\nexport function withRevision(source: Source, revision: SourceRevision): Source {\n const existing = source.revisions.filter(\n entry => entry.sourceRevisionId !== revision.sourceRevisionId)\n const superseded: readonly SourceRevision[] = existing.map(entry =>\n entry.revision < revision.revision && entry.indexState === 'indexed'\n ? { ...entry, indexState: 'stale' }\n : entry)\n return {\n ...source,\n revisions: [...superseded, revision].sort((left, right) => left.revision - right.revision),\n }\n}\n","/**\n * Library search: finding the source, and saying how it was found.\n *\n * Search here has one unusual requirement. It has to report *which retrieval\n * path produced a result*, because the two paths make different promises. A\n * lexical hit means those characters are in that source at that moment. A\n * semantic hit means something in that source was near the query in an\n * embedding space, which is a much weaker claim and occasionally a wrong one.\n *\n * A product that merged them into one ranked list with one relevance number\n * would be a product where \"it found nothing\" and \"the embedding model was not\n * installed\" look identical, and where a paraphrase match and an exact quote\n * look equally certain. So {@link searchPlan} states the path before anything\n * runs, and every hit carries the path that produced it.\n *\n * Facets are computed from the results rather than from a fixed taxonomy. A\n * facet that offers \"Arabic (0)\" on a library with no Arabic in it is a filter\n * that teaches people the filter is broken.\n *\n * @module @deepwatch/dsh-library/search\n */\n\nimport type { TemporalRange } from '@deepwatch/dsh-contracts'\nimport type { Source, SourceKind, IndexState } from './sources.js'\n\n/** How a result was retrieved. */\nexport type RetrievalPath = 'lexical' | 'semantic' | 'both'\n\n/** What the engine can actually do here. */\nexport interface SearchCapabilities {\n /** Substring and token matching over extracted text. */\n readonly lexical: boolean\n /** Embedding retrieval. Requires a bound embeddings role. */\n readonly semantic: boolean\n}\n\n/** What search will do, decided before it runs. */\nexport interface SearchPlan {\n readonly path: RetrievalPath | 'none'\n /** One sentence for the results header. Always populated. */\n readonly explanation: string\n /** What is missing, and what to do about it. Empty when nothing is. */\n readonly degradedBecause: string\n readonly fix: string\n}\n\n/**\n * Decide the retrieval path.\n *\n * Semantic-only is a real state and is reported as one rather than silently\n * treated as \"search works\". A library where exact-phrase search is\n * unavailable behaves very differently from one where it is not, and a user\n * searching for an error code needs to know which they are in.\n */\nexport function searchPlan(capabilities: SearchCapabilities): SearchPlan {\n if (capabilities.lexical && capabilities.semantic) {\n return {\n path: 'both',\n explanation: 'Hybrid search: exact matches and meaning-based matches, marked separately.',\n degradedBecause: '',\n fix: '',\n }\n }\n if (capabilities.lexical) {\n return {\n path: 'lexical',\n explanation: 'Exact matching only. A paraphrase of what was said will not be found.',\n degradedBecause: 'No embeddings role is bound, so semantic retrieval is unavailable.',\n fix: 'Bind an embeddings role in Settings to search by meaning as well.',\n }\n }\n if (capabilities.semantic) {\n return {\n path: 'semantic',\n explanation: 'Meaning-based matching only. An exact phrase may rank below a paraphrase.',\n degradedBecause: 'The lexical index is unavailable.',\n fix: 'Re-index the library to restore exact matching.',\n }\n }\n return {\n path: 'none',\n explanation: 'Search is unavailable.',\n degradedBecause: 'Neither the lexical index nor an embeddings role is available.',\n fix: 'Index a source, or bind an embeddings role in Settings.',\n }\n}\n\n/** One hit inside one source. */\nexport interface SearchHit {\n readonly sourceId: string\n readonly sourceRevisionId: string\n /** Where in the source, when the modality has a clock. */\n readonly range: TemporalRange | null\n /** The matched text, verbatim and in its original script. */\n readonly text: string\n /** Which path produced this hit. */\n readonly path: RetrievalPath\n /**\n * Score, in the producing path's own units.\n *\n * Deliberately not normalized across paths. A lexical rank and a cosine\n * similarity are not comparable, and putting them on one 0–1 scale would\n * manufacture a comparison that does not exist.\n */\n readonly score: number\n /** Evidence this hit resolves to, when the engine minted any. */\n readonly evidenceIds: readonly string[]\n}\n\n/** A result: one source, and the hits inside it. */\nexport interface SearchResult {\n readonly sourceId: string\n readonly title: string\n readonly kind: SourceKind\n readonly hits: readonly SearchHit[]\n /** Whether the hits are against the source's current revision. */\n readonly current: boolean\n}\n\n/** One facet value and how many results carry it. */\nexport interface FacetValue {\n readonly value: string\n readonly count: number\n}\n\n/** The facets computed from a result set. */\nexport interface Facets {\n readonly kind: readonly FacetValue[]\n readonly indexState: readonly FacetValue[]\n readonly collection: readonly FacetValue[]\n readonly script: readonly FacetValue[]\n readonly path: readonly FacetValue[]\n}\n\n/** What a search was narrowed to. */\nexport interface SearchFilters {\n readonly kinds?: readonly SourceKind[]\n readonly collections?: readonly string[]\n readonly indexStates?: readonly IndexState[]\n readonly scripts?: readonly string[]\n /** Only hits from the current revision of each source. */\n readonly currentOnly?: boolean\n}\n\n/** Count values, dropping the empty ones. */\nfunction tally(values: readonly string[]): readonly FacetValue[] {\n const counts = new Map<string, number>()\n for (const value of values) counts.set(value, (counts.get(value) ?? 0) + 1)\n return [...counts.entries()]\n .map(([value, count]) => ({ value, count }))\n .sort((left, right) => {\n const byCount = right.count - left.count\n return byCount !== 0 ? byCount : left.value.localeCompare(right.value)\n })\n}\n\n/**\n * Compute facets over a result set.\n *\n * Only values that actually occur. A facet list generated from the schema\n * rather than from the results offers filters that return nothing, and a\n * filter that returns nothing is indistinguishable from a broken one.\n */\nexport function facetsFor(\n results: readonly SearchResult[],\n sources: readonly Source[],\n): Facets {\n const byId = new Map(sources.map(source => [source.sourceId, source]))\n const kinds: string[] = []\n const indexStates: string[] = []\n const collections: string[] = []\n const scripts: string[] = []\n const paths: string[] = []\n\n for (const result of results) {\n kinds.push(result.kind)\n const source = byId.get(result.sourceId)\n if (source !== undefined) {\n for (const collection of source.collections) collections.push(collection)\n for (const revision of source.revisions) {\n if (!result.hits.some(hit => hit.sourceRevisionId === revision.sourceRevisionId)) continue\n indexStates.push(revision.indexState)\n for (const script of revision.scripts) scripts.push(script)\n }\n }\n for (const hit of result.hits) paths.push(hit.path)\n }\n\n return {\n kind: tally(kinds),\n indexState: tally(indexStates),\n collection: tally(collections),\n script: tally(scripts),\n path: tally(paths),\n }\n}\n\n/** Apply filters to a result set. Pure; the engine does the retrieval. */\nexport function applyFilters(\n results: readonly SearchResult[],\n sources: readonly Source[],\n filters: SearchFilters,\n): readonly SearchResult[] {\n const byId = new Map(sources.map(source => [source.sourceId, source]))\n return results.filter(result => {\n if (filters.kinds !== undefined && !filters.kinds.includes(result.kind)) return false\n if (filters.currentOnly === true && !result.current) return false\n\n const source = byId.get(result.sourceId)\n if (filters.collections !== undefined) {\n if (source === undefined) return false\n if (!filters.collections.some(collection => source.collections.includes(collection))) return false\n }\n if (filters.indexStates !== undefined) {\n if (source === undefined) return false\n const states = source.revisions\n .filter(revision => result.hits.some(hit => hit.sourceRevisionId === revision.sourceRevisionId))\n .map(revision => revision.indexState)\n if (!states.some(state => filters.indexStates?.includes(state) === true)) return false\n }\n if (filters.scripts !== undefined) {\n if (source === undefined) return false\n const present = new Set(source.revisions.flatMap(revision => revision.scripts))\n if (!filters.scripts.some(script => present.has(script as never))) return false\n }\n return true\n })\n}\n\n/**\n * Order results for display.\n *\n * Within a source, hits are ordered by path and then by time — not by score\n * across paths, because the scores are not comparable. Across sources, the\n * source with the strongest lexical evidence leads, because an exact match is\n * the strongest claim search can make.\n */\nexport function rankResults(results: readonly SearchResult[]): readonly SearchResult[] {\n const lexicalWeight = (result: SearchResult): number =>\n result.hits.filter(hit => hit.path === 'lexical' || hit.path === 'both').length\n return [...results].sort((left, right) => {\n const byLexical = lexicalWeight(right) - lexicalWeight(left)\n if (byLexical !== 0) return byLexical\n const byHits = right.hits.length - left.hits.length\n if (byHits !== 0) return byHits\n return left.sourceId.localeCompare(right.sourceId)\n })\n}\n\n/**\n * One line above the results, stating what was searched and how.\n *\n * Always says the path. \"12 results\" alone invites the reading that the library\n * was searched thoroughly, which may not be true.\n */\nexport function describeSearch(\n plan: SearchPlan,\n results: readonly SearchResult[],\n): string {\n const hits = results.reduce((total, result) => total + result.hits.length, 0)\n const count = `${String(hits)} hit(s) in ${String(results.length)} source(s)`\n return plan.degradedBecause === ''\n ? `${count} · ${plan.explanation}`\n : `${count} · ${plan.explanation} ${plan.degradedBecause}`\n}\n","/**\n * The Library surface.\n *\n * Visually and structurally separate from Memory, and that separation is\n * enforced rather than encouraged: this module imports nothing from the memory\n * packages, so a memory record cannot be rendered here even by mistake. The\n * two surfaces answer different questions — what has been seen, and what is\n * believed — and a person needs to be able to tell at a glance which one they\n * are looking at.\n *\n * Every result says how it was found and whether it is still current. A search\n * result that showed neither would be a list of claims about a library whose\n * state the reader cannot check.\n *\n * @module @deepwatch/dsh-library/components\n */\n\nimport type { ReactNode } from 'react'\nimport { toneFor, tokenFor } from '@deepwatch/dsh-client-brand'\nimport type { Freshness } from '@deepwatch/dsh-contracts'\nimport {\n currentRevision,\n type Source,\n type SourceRevision,\n} from '../sources.js'\nimport {\n describeSearch,\n type Facets,\n type SearchHit,\n type SearchPlan,\n type SearchResult,\n} from '../search.js'\n\n/** The glyph half of a freshness state, so colour is never the only signal. */\nconst FRESHNESS_GLYPH: Readonly<Record<Freshness, string>> = {\n current: '●',\n stale: '⌛',\n gap: '⌇',\n expired: '⊘',\n unavailable: '?',\n}\n\n/** Props for {@link FreshnessBadge}. */\nexport interface FreshnessBadgeProps {\n readonly freshness: Freshness\n}\n\n/** Freshness as glyph, word and tone. */\nexport function FreshnessBadge({ freshness }: FreshnessBadgeProps): ReactNode {\n return (\n <span data-watch-freshness={freshness} style={{ color: tokenFor(toneFor(freshness)) }}>\n <span aria-hidden=\"true\">{FRESHNESS_GLYPH[freshness]}</span>\n <span>{` ${freshness}`}</span>\n </span>\n )\n}\n\n/** Props for {@link RevisionHistory}. */\nexport interface RevisionHistoryProps {\n readonly source: Source\n readonly onOpen: (revision: SourceRevision) => void\n}\n\n/**\n * A source's revisions, newest last.\n *\n * Every revision is listed, including superseded ones, and every one is\n * openable. A history that showed only the current revision would make old\n * evidence unreachable through the interface even though it remains\n * addressable underneath, which is the same failure with extra steps.\n */\nexport function RevisionHistory({ source, onOpen }: RevisionHistoryProps): ReactNode {\n const current = currentRevision(source)\n return (\n <ol data-watch-revisions={source.sourceId} style={{ listStyle: 'none', margin: 0, padding: 0 }}>\n {source.revisions.map(revision => (\n <li key={revision.sourceRevisionId} data-watch-revision={revision.sourceRevisionId}>\n <button\n type=\"button\"\n data-watch-index-state={revision.indexState}\n aria-current={current?.sourceRevisionId === revision.sourceRevisionId ? 'true' : undefined}\n onClick={() => { onOpen(revision) }}\n style={{ font: 'inherit', color: 'inherit', background: 'none', border: 'none', cursor: 'pointer' }}\n >\n <span dir=\"ltr\">{`r${String(revision.revision)}`}</span>\n <span>{` ${revision.indexState}`}</span>\n <time dateTime={revision.observedAt}>{` ${revision.observedAt}`}</time>\n {current?.sourceRevisionId === revision.sourceRevisionId && <span>{' · current'}</span>}\n </button>\n {revision.indexError !== null && (\n <span data-watch-index-error=\"\">{` ${revision.indexError}`}</span>\n )}\n </li>\n ))}\n </ol>\n )\n}\n\n/** Props for {@link SearchHitRow}. */\nexport interface SearchHitRowProps {\n readonly hit: SearchHit\n readonly freshness: Freshness\n readonly onOpen: (hit: SearchHit) => void\n}\n\n/**\n * One hit.\n *\n * The retrieval path is on the row, not in a legend. A person reading a\n * semantic hit needs to know it is a semantic hit at the moment they read it,\n * because that is what decides whether they should check it.\n */\nexport function SearchHitRow({ hit, freshness, onOpen }: SearchHitRowProps): ReactNode {\n return (\n <li data-watch-hit={hit.sourceRevisionId} data-watch-path={hit.path}>\n <button\n type=\"button\"\n onClick={() => { onOpen(hit) }}\n style={{ font: 'inherit', color: 'inherit', background: 'none', border: 'none', cursor: 'pointer', textAlign: 'start' }}\n >\n {hit.range !== null && (\n <span dir=\"ltr\" style={{ fontVariantNumeric: 'tabular-nums' }}>\n {`${String(Math.floor(hit.range.startMs / 1000))}s `}\n </span>\n )}\n {/* Verbatim, in its own script and its own direction. A hit rendered\n left-to-right because the surrounding interface is would show the\n wrong text to the person best able to check it. */}\n <span dir=\"auto\">{hit.text}</span>\n <span data-watch-hit-path={hit.path}>{` (${hit.path})`}</span>\n <FreshnessBadge freshness={freshness} />\n </button>\n </li>\n )\n}\n\n/** Props for {@link FacetPanel}. */\nexport interface FacetPanelProps {\n readonly facets: Facets\n readonly onFilter: (facet: string, value: string) => void\n}\n\n/** The facet rail. Only values that actually occur are offered. */\nexport function FacetPanel({ facets, onFilter }: FacetPanelProps): ReactNode {\n const groups: readonly (readonly [string, Facets[keyof Facets]])[] = [\n ['kind', facets.kind],\n ['indexState', facets.indexState],\n ['collection', facets.collection],\n ['script', facets.script],\n ['path', facets.path],\n ]\n return (\n <aside data-watch-facets=\"\" aria-label=\"Filters\">\n {groups.map(([name, values]) => (\n values.length === 0 ? null : (\n <section key={name} data-watch-facet={name}>\n <h3 style={{ font: 'inherit', fontSize: '11px' }}>{name}</h3>\n <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>\n {values.map(value => (\n <li key={value.value}>\n <button\n type=\"button\"\n data-watch-facet-value={value.value}\n onClick={() => { onFilter(name, value.value) }}\n style={{ font: 'inherit', color: 'inherit', background: 'none', border: 'none', cursor: 'pointer' }}\n >\n {`${value.value} (${String(value.count)})`}\n </button>\n </li>\n ))}\n </ul>\n </section>\n )\n ))}\n </aside>\n )\n}\n\n/** Props for {@link LibrarySurface}. */\nexport interface LibrarySurfaceProps {\n readonly plan: SearchPlan\n readonly results: readonly SearchResult[]\n readonly facets: Facets\n readonly sources: readonly Source[]\n readonly freshnessOf: (hit: SearchHit) => Freshness\n readonly onOpenHit: (hit: SearchHit) => void\n readonly onOpenRevision: (revision: SourceRevision) => void\n readonly onFilter: (facet: string, value: string) => void\n}\n\n/** The Library mode body. */\nexport function LibrarySurface(props: LibrarySurfaceProps): ReactNode {\n const byId = new Map(props.sources.map(source => [source.sourceId, source]))\n return (\n <section data-watch-library=\"\" aria-label=\"Library\">\n <p data-watch-search-plan={props.plan.path}>{describeSearch(props.plan, props.results)}</p>\n {props.plan.fix !== '' && <p data-watch-search-fix=\"\">{props.plan.fix}</p>}\n <FacetPanel facets={props.facets} onFilter={props.onFilter} />\n {props.results.length === 0\n ? <p data-watch-library-empty=\"\">Nothing in the Library matches.</p>\n : props.results.map(result => {\n const source = byId.get(result.sourceId)\n return (\n <article key={result.sourceId} data-watch-source={result.sourceId}>\n <h3 style={{ font: 'inherit' }} dir=\"auto\">{result.title}</h3>\n <span data-watch-source-kind={result.kind}>{result.kind}</span>\n {!result.current && (\n <span data-watch-source-superseded=\"\">\n {' The source has changed since these were observed.'}\n </span>\n )}\n <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>\n {result.hits.map(hit => (\n <SearchHitRow\n key={`${hit.sourceRevisionId}:${String(hit.range?.startMs ?? 0)}:${hit.text}`}\n hit={hit}\n freshness={props.freshnessOf(hit)}\n onOpen={props.onOpenHit}\n />\n ))}\n </ul>\n {source !== undefined && (\n <RevisionHistory source={source} onOpen={props.onOpenRevision} />\n )}\n </article>\n )\n })}\n </section>\n )\n}\n","/**\n * The Library surface, registered into DSH's slots.\n *\n * @module @deepwatch/dsh-library/client\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { ReactNode } from 'react'\nimport type { ModeViewProps } from '@deepwatch/dsh-workspace/surface'\nimport { LibraryModeView } from './library-mode.js'\nimport type { WatchQueryRemote } from './read-plane.js'\n\nexport * from './components.js'\nexport * from './search-view.js'\nexport * from './library-mode.js'\nexport * from './read-plane.js'\nexport * from '../sources.js'\nexport * from '../search.js'\n\n/**\n * Services this half needs before it can register anything.\n *\n * `remote.watchQuery` as well as `remote`. The Gateway installs each mounted\n * namespace as its own cordis service under that key, so naming it is what\n * makes this plugin wait for the mount rather than load beside it: the bare\n * `remote` resolves as soon as the Gateway's browser half exists — which is\n * before any contribution is mounted — and leaves `ctx.remote` with no\n * `watchQuery` on it. Both are listed because both are read, and cordis\n * refuses a property no `inject` entry claims: reaching `ctx.remote.watchQuery`\n * on the strength of the second entry alone fails the fiber with \"cannot get\n * property \"remote\" without inject\".\n *\n * The mount itself belongs to `@deepwatch/dsh-client-remotes`. This package\n * owns the Library capability, and a package that owns a capability does not\n * also own the transport that carries it; when it did, the two depended on\n * each other.\n */\nexport const inject = ['slots', 'remote', 'remote.watchQuery']\n\n// The boot graph reads `dsh.client.inject` from package.json rather than this\n// constant, so the package that performs the mount is named there as well.\n\n/** The minimal shape of DSH's slot service this module uses. */\ninterface SlotService {\n inject(name: string, register: () => void): void\n register(entry: Record<string, unknown>, component: unknown): void\n}\n\n/**\n * Register the Library mode body, bound to the read plane it queries.\n *\n * A `conversation.view` entry is handed `{ inspect, onInspectDone }` and\n * nothing else, so a mode body has no way to reach a service on its own. The\n * binding happens here, where the context is: what gets registered is the mode\n * body with the mounted `watchQuery` namespace already supplied.\n *\n * Nothing here is defensive about `ctx.remote.watchQuery`. `inject` above means\n * cordis does not call `apply` until that service exists, so a profile without\n * the mount parks this plugin — no Library tab at all — rather than drawing a\n * tab whose search quietly answers from an empty local index.\n */\nexport function apply(ctx: Context): void {\n const slots = (ctx as unknown as { slots: SlotService }).slots\n const reads = (ctx as unknown as {\n remote: { watchQuery: WatchQueryRemote }\n }).remote.watchQuery\n\n /** The Library body, bound to the host that answers for it. */\n const BoundLibraryModeView = (props: ModeViewProps): ReactNode => (\n <LibraryModeView {...props} reads={reads} />\n )\n\n // Library is a product mode. See the note in the Live surface: registering as\n // a view means DSH renders the tab, not Watch.\n slots.inject('conversation.view', () => {\n slots.register(\n { name: 'conversation.view', id: 'library', label: 'Library', order: 50 },\n BoundLibraryModeView,\n )\n })\n}\n"],"mappings":"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"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * The Library's local search index.
3
+ *
4
+ * The evidence store is the source of truth. This is a *derived* structure: it
5
+ * can be thrown away and rebuilt from the records at any time, and every design
6
+ * decision here follows from that one fact. A derived index that cannot be
7
+ * safely deleted is not derived, it is a second database with none of the
8
+ * guarantees of the first.
9
+ *
10
+ * What it is:
11
+ *
12
+ * - **Local.** An inverted index over tokens, held in memory and serialisable
13
+ * to plain JSON. No service, no network, no embedding model. Semantic
14
+ * retrieval stays a future optional plugin; lexical matching is what works
15
+ * offline on any machine, today.
16
+ * - **Versioned.** Every serialised index carries `INDEX_VERSION` and a
17
+ * digest of its own contents. A version it does not recognise, or a digest
18
+ * that does not match, is a corrupt index — detected on load, reported, and
19
+ * rebuilt rather than half-trusted.
20
+ * - **Incremental and idempotent.** Indexing the same record twice leaves the
21
+ * index identical. Re-indexing a changed record replaces its postings
22
+ * rather than adding a second copy, so a document cannot accumulate ghosts
23
+ * of its former text.
24
+ * - **Recoverable.** Indexing records progress, so an interrupted run resumes
25
+ * from what it completed instead of starting over or, worse, believing it
26
+ * finished.
27
+ *
28
+ * Queries are bounded, paginated and cancellable by construction: a query
29
+ * carries its own limit, and a caller can pass an `AbortSignal`. An unbounded
30
+ * search over a large corpus is a denial of service you wrote yourself.
31
+ *
32
+ * @module @deepwatch/dsh-library/index-store
33
+ */
34
+ import type { SearchResult } from './search.js';
35
+ import type { SourceKind } from './sources.js';
36
+ /**
37
+ * Bumped when the serialised shape changes.
38
+ *
39
+ * An index written by a newer build is refused rather than reinterpreted. A
40
+ * structure read under the wrong assumptions produces confident wrong answers,
41
+ * which is worse than producing none.
42
+ */
43
+ export declare const INDEX_VERSION = 1;
44
+ /** How the index reports its own condition. */
45
+ export type IndexHealth =
46
+ /** Never built. Not an error — nobody has indexed anything yet. */
47
+ 'empty'
48
+ /** Built, current, queryable. */
49
+ | 'ready'
50
+ /** A build is in progress; results are partial and say so. */
51
+ | 'indexing'
52
+ /** Records changed after the last build. Queryable, but incomplete. */
53
+ | 'stale'
54
+ /** Unreadable: wrong version, failed digest, malformed. Must be rebuilt. */
55
+ | 'corrupt';
56
+ /** One indexable record. Everything is optional except the identity. */
57
+ export interface IndexableRecord {
58
+ readonly recordId: string;
59
+ readonly revisionId: string;
60
+ readonly title: string;
61
+ readonly kind: SourceKind;
62
+ /** Body text: extracted text, a transcript, a description. */
63
+ readonly text: string;
64
+ /** Where it came from, for provenance filtering. */
65
+ readonly source: string | null;
66
+ /** The run or task it belongs to. */
67
+ readonly runId: string | null;
68
+ /** ISO-8601. Used for range filters and for ordering. */
69
+ readonly observedAt: string | null;
70
+ /** The verification state, when the record has one. */
71
+ readonly verdict: string | null;
72
+ readonly tags: readonly string[];
73
+ /** Evidence this record resolves to. */
74
+ readonly evidenceIds: readonly string[];
75
+ }
76
+ /** What a caller may narrow a query by. */
77
+ export interface IndexQuery {
78
+ readonly text: string;
79
+ readonly kinds?: readonly SourceKind[];
80
+ readonly runIds?: readonly string[];
81
+ readonly verdicts?: readonly string[];
82
+ readonly tags?: readonly string[];
83
+ readonly sources?: readonly string[];
84
+ /** Inclusive ISO-8601 bounds. */
85
+ readonly from?: string;
86
+ readonly to?: string;
87
+ readonly sort?: 'relevance' | 'newest' | 'oldest' | 'title';
88
+ readonly offset?: number;
89
+ readonly limit?: number;
90
+ readonly signal?: AbortSignal;
91
+ }
92
+ /** A page of results, and enough context to page through the rest. */
93
+ export interface IndexQueryResult {
94
+ readonly results: readonly SearchResult[];
95
+ /** Matches before paging. The count a person is told. */
96
+ readonly total: number;
97
+ readonly offset: number;
98
+ readonly limit: number;
99
+ readonly health: IndexHealth;
100
+ /** Non-fatal facts about this answer: truncation, staleness, degradation. */
101
+ readonly notes: readonly string[];
102
+ }
103
+ /** The serialised form. Plain JSON so any store can hold it. */
104
+ export interface SerializedIndex {
105
+ readonly version: number;
106
+ readonly digest: string;
107
+ readonly builtAt: string;
108
+ readonly documents: readonly IndexableRecord[];
109
+ /** Token → the record ids carrying it. */
110
+ readonly postings: Readonly<Record<string, readonly string[]>>;
111
+ }
112
+ /** The largest page anyone may ask for. */
113
+ export declare const MAX_LIMIT = 200;
114
+ /**
115
+ * Split text into searchable tokens.
116
+ *
117
+ * Unicode-aware on purpose. Splitting on `[a-z0-9]+` would silently drop every
118
+ * Arabic, Chinese, Cyrillic and Greek record in the corpus — they would index
119
+ * as nothing and return nothing, and the failure would look like an empty
120
+ * library rather than a broken tokenizer.
121
+ *
122
+ * CJK has no spaces, so a run is emitted as its characters and its adjacent
123
+ * bigrams rather than whole. Keeping the run would make it a token only an
124
+ * exact repetition could match, and since every query term must be present,
125
+ * that run token would then fail a query whose characters are all indexed.
126
+ *
127
+ * Case folding is `toLowerCase`, which is a no-op for scripts without case and
128
+ * correct for those with it. Diacritics are deliberately *kept*: the original
129
+ * text is the evidence, and folding "عَلَم" into "علم" would make a citation
130
+ * resolve to something the source does not say.
131
+ *
132
+ * `\p{M}` is in the continuation class for the same reason, and its absence was
133
+ * a real bug. Arabic harakat are Unicode *Mark*, not *Letter*, so a class of
134
+ * letters and numbers alone breaks at every vowel sign: vocalised "عَلَم"
135
+ * tokenized as three separate consonants, and no query could ever match it.
136
+ */
137
+ export declare function tokenize(text: string): readonly string[];
138
+ /**
139
+ * Is this path inside one of the roots the caller allows?
140
+ *
141
+ * Refusal is the safe direction, so anything ambiguous is refused. The root
142
+ * comparison is case-sensitive: on a case-insensitive filesystem that can
143
+ * refuse a legitimate path, which is a nuisance, but it can never admit an
144
+ * illegitimate one.
145
+ */
146
+ export declare function isWithinRoots(candidate: string, roots: readonly string[]): boolean;
147
+ /** The local, derived, rebuildable search index. */
148
+ export declare class LibraryIndex {
149
+ #private;
150
+ get health(): IndexHealth;
151
+ /**
152
+ * One record by id, or undefined.
153
+ *
154
+ * A direct lookup rather than a search. The read plane's `get` was briefly
155
+ * implemented as a search with `limit: 1` whose single result was then
156
+ * compared to the requested id, which reports every record except the
157
+ * first-ranked one as missing. `#documents` is already keyed by record id;
158
+ * this is the accessor that key exists for.
159
+ */
160
+ record(recordId: string): IndexableRecord | undefined;
161
+ get size(): number;
162
+ /** Ids indexing began but did not finish, so a resumed run knows where it was. */
163
+ get pending(): readonly string[];
164
+ get diagnostics(): readonly string[];
165
+ /**
166
+ * Add or replace one record.
167
+ *
168
+ * Idempotent by construction: the record's existing postings are removed
169
+ * before the new ones are written, so re-indexing changed text cannot leave
170
+ * the old words behind still pointing at the document. Indexing identical
171
+ * content twice is a no-op, which is what makes an interrupted run safe to
172
+ * simply repeat.
173
+ */
174
+ add(input: IndexableRecord): void;
175
+ /** Index many, reporting progress so an interrupted run can resume. */
176
+ addAll(records: readonly IndexableRecord[], signal?: AbortSignal): number;
177
+ /**
178
+ * Forget a record entirely.
179
+ *
180
+ * A deleted record must not survive as a search hit. Removing the document
181
+ * without its postings would leave a token pointing at an id that no longer
182
+ * resolves — a result that cannot be opened, which is worse than no result.
183
+ */
184
+ remove(recordId: string): boolean;
185
+ /** Throw everything away. The point of a derived index. */
186
+ clear(): void;
187
+ /** Mark the index as behind the store, without discarding what it has. */
188
+ markStale(reason: string): void;
189
+ /**
190
+ * Search.
191
+ *
192
+ * Every term must be present — an AND over tokens. OR would return a page of
193
+ * documents sharing one common word, which reads as the search being broken.
194
+ *
195
+ * The query string is never interpreted: it is tokenized exactly like indexed
196
+ * text, so a regular expression, a glob, a SQL fragment or a path traversal
197
+ * in the box is simply a set of words that will not be found. There is no
198
+ * escaping to get wrong because there is nothing to escape into.
199
+ */
200
+ search(query: IndexQuery): IndexQueryResult;
201
+ /** Serialise, with a digest so a later load can tell it was not damaged. */
202
+ serialize(): SerializedIndex;
203
+ /**
204
+ * Load a serialised index, refusing anything it cannot trust.
205
+ *
206
+ * A wrong version, a failed digest or a malformed body all produce a
207
+ * `corrupt` index rather than a partial one. Half-loading is the failure that
208
+ * looks like success: queries answer, and they answer wrongly.
209
+ */
210
+ static load(value: unknown): LibraryIndex;
211
+ }
212
+ /**
213
+ * Build the snippet a person reads, around the first match.
214
+ *
215
+ * The text is returned verbatim and un-escaped — it is evidence, and altering
216
+ * it here would make the snippet disagree with the source. Rendering is the
217
+ * caller's job, and React escapes by default; this deliberately produces no
218
+ * markup for a renderer to trust.
219
+ */
220
+ export declare function snippetFor(text: string, terms: readonly string[], radius?: number): string;
221
+ //# sourceMappingURL=index-store.d.ts.map