@deepwatch/dsh-client-settings 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,141 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The composer, closed until something is actually bound to Chat — and the
4
+ * fastest way to bind it, offered where the person is standing.
5
+ *
6
+ * A person typed a message into a composer that looked ready, pressed send,
7
+ * and the prompt was routed to a provider they had never configured. Nothing
8
+ * about the composer had told them otherwise: the model chip named a DeepSeek
9
+ * model they had not chosen, the send button was live, and the first thing
10
+ * that disagreed was a failed turn.
11
+ *
12
+ * This is the client half of preflight. It raises the block upstream provides
13
+ * for exactly this — *"Composer blocks: the one way another plugin stops a
14
+ * session's input"* — so the textarea goes inert, the send button stops
15
+ * accepting, and the placeholder says why in the words of whoever knows.
16
+ *
17
+ * **This is an affordance, and it is not the enforcement.** It lives in a
18
+ * browser tab. The Host refuses an unbound route regardless of what any client
19
+ * disables (`@deepwatch/dsh-technology/routing`), and the composed default
20
+ * names no route so the Harness's own admission boundary refuses a turn before
21
+ * one exists. The value of this layer is not that it makes refusal certain — it
22
+ * is that a person finds out *before* typing rather than after sending.
23
+ *
24
+ * **The draft survives.** Blocking is one inert textarea, never a second tree:
25
+ * the composer keeps its DOM, so whatever was typed is still there when the
26
+ * binding is fixed. That is upstream's design and this file's reason for using
27
+ * it rather than rendering a replacement.
28
+ *
29
+ * **The way out is here, not somewhere else.** The settings panel's open state
30
+ * is component-local to `SettingsRoot`, so no plugin can navigate to a section
31
+ * — and a button that names a screen it cannot open is worse than no button.
32
+ * So the fix is offered inline: the same provider-and-model picker the Role
33
+ * Bindings screen uses, writing through the same store, so the two surfaces
34
+ * cannot disagree about what a provider offers.
35
+ *
36
+ * @module @deepwatch/dsh-client-settings/chat-gate
37
+ */
38
+ import { useEffect, useState, useSyncExternalStore } from 'react';
39
+ import { PRIMARY_ROLE, ROLE_LABEL, cardForBlocker, isExecutable } from '@deepwatch/dsh-contracts';
40
+ import { StatusChip } from './components.js';
41
+ import { BindingEditor, CONTROL } from './role-bindings.js';
42
+ import { chatReadiness } from './binding-state.js';
43
+ /**
44
+ * The placeholder an inert composer carries.
45
+ *
46
+ * The blocker's own sentence, prefixed with the capability it is about. A
47
+ * placeholder reading only "Not configured" leaves a person guessing which of
48
+ * six things is missing, which is the failure the ordered blocker list exists
49
+ * to prevent.
50
+ */
51
+ export function blockReason(detail) {
52
+ return `${ROLE_LABEL[PRIMARY_ROLE]} is not ready — ${detail}`;
53
+ }
54
+ /**
55
+ * The block this snapshot calls for, or undefined when the composer may open.
56
+ *
57
+ * A pure function rather than a branch inside the effect, so the decision can
58
+ * be tested as the decision. A test that re-derived the same condition beside
59
+ * the component would agree with itself and prove nothing about what a person
60
+ * gets.
61
+ *
62
+ * `idle` and `loading` deliberately produce nothing. Blocking a composer
63
+ * because an answer has not arrived yet would make every reload look like a
64
+ * misconfiguration — and the Host refuses an unbound route regardless, so the
65
+ * safe direction here is to say nothing until something is known.
66
+ *
67
+ * @param snapshot - what the store currently knows.
68
+ * @returns the block to raise, or undefined to lift any standing one.
69
+ */
70
+ export function blockFor(snapshot) {
71
+ if (snapshot.status !== 'ready')
72
+ return undefined;
73
+ const chat = chatReadiness(snapshot);
74
+ if (chat === null || isExecutable(chat) || chat.primaryBlocker === null)
75
+ return undefined;
76
+ return { reason: blockReason(cardForBlocker(chat.primaryBlocker).detail) };
77
+ }
78
+ /**
79
+ * Raise or clear this session's composer block, and offer the way out.
80
+ *
81
+ * @param props - see {@link ChatGateProps}.
82
+ * @returns the setup card while Chat cannot run, and nothing once it can.
83
+ */
84
+ export function ChatGate({ sessionId, store, blocks }) {
85
+ const snapshot = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
86
+ const [open, setOpen] = useState(false);
87
+ // One load per mounted gate, not one per render. The store guards against a
88
+ // slow answer landing after a newer one, so a second session mounting while
89
+ // the first is loading does not produce a stale snapshot.
90
+ useEffect(() => { void store.load(); }, [store]);
91
+ const chat = chatReadiness(snapshot);
92
+ const ready = chat !== null && isExecutable(chat);
93
+ const card = chat === null || chat.primaryBlocker === null
94
+ ? null
95
+ : cardForBlocker(chat.primaryBlocker);
96
+ // The same decision function the tests assert on, so what a person gets and
97
+ // what is asserted cannot drift apart.
98
+ const block = blockFor(snapshot);
99
+ useEffect(() => {
100
+ const registry = blocks();
101
+ if (registry === undefined)
102
+ return;
103
+ registry.set(sessionId, block);
104
+ // Clearing on unmount matters: a session whose gate is gone must not keep
105
+ // a block nothing is left to lift.
106
+ return () => { registry.set(sessionId, undefined); };
107
+ }, [blocks, sessionId, block?.reason]);
108
+ if (ready || card === null || chat === null)
109
+ return null;
110
+ return (_jsxs("section", { "aria-label": `${ROLE_LABEL[PRIMARY_ROLE]} setup`, style: {
111
+ border: '1px solid color-mix(in srgb, var(--watch-accent) 68%, var(--dsw-alias-border-l2))',
112
+ borderRadius: '14px',
113
+ padding: '15px 17px',
114
+ margin: '0 0 8px',
115
+ background: 'linear-gradient(145deg, color-mix(in srgb, var(--watch-accent) 7%, var(--dsw-alias-bg-layer-2)), var(--dsw-alias-bg-base))',
116
+ boxShadow: '0 10px 30px color-mix(in srgb, var(--watch-accent) 9%, transparent)',
117
+ }, children: [_jsxs("div", { style: {
118
+ display: 'flex', alignItems: 'baseline', gap: '10px',
119
+ justifyContent: 'space-between', flexWrap: 'wrap',
120
+ }, children: [_jsx("h3", { style: { fontSize: '13px', fontWeight: 600, margin: 0 }, children: card.title }), _jsx(StatusChip, { tone: "neutral", children: chat.status === 'bound_unverified' ? 'Configured · not tested' : 'Not ready' })] }), _jsx("p", { style: {
121
+ fontSize: '12px', lineHeight: 1.55, margin: '6px 0 0',
122
+ color: 'var(--dsw-alias-label-secondary)',
123
+ }, children: card.detail }), open
124
+ ? (_jsx(BindingEditor, { row: {
125
+ role: PRIMARY_ROLE,
126
+ provider: null,
127
+ model: null,
128
+ readiness: chat,
129
+ }, providers: snapshot.providers, saving: snapshot.saving, onBind: (provider, model) => {
130
+ void store.bind(PRIMARY_ROLE, provider, model).then(() => { setOpen(false); });
131
+ }, onCancel: () => { setOpen(false); } }))
132
+ : (_jsxs("div", { style: CONTROL.row, children: [_jsx("button", { type: "button", style: CONTROL.primary, disabled: !snapshot.writable || snapshot.testingRole !== null, "aria-describedby": snapshot.writable ? undefined : 'watch-chat-gate-readonly', onClick: () => {
133
+ if (chat.primaryBlocker === 'provider_untested')
134
+ void store.testRole(PRIMARY_ROLE);
135
+ else
136
+ setOpen(true);
137
+ }, children: snapshot.testingRole === PRIMARY_ROLE ? 'Testing provider…' : card.action }), snapshot.writable
138
+ ? null
139
+ : (_jsx("span", { id: "watch-chat-gate-readonly", style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' }, children: "This Workspace cannot change settings here, so Chat cannot be bound from this screen. What is shown is still read from the running system." })), _jsx("span", { style: { fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' }, children: "Settings \u2192 Role Bindings has the full view." })] }))] }));
140
+ }
141
+ //# sourceMappingURL=chat-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-gate.js","sourceRoot":"","sources":["../../src/client/chat-gate.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAEjE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACjG,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AA6BlD;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,OAAO,GAAG,UAAU,CAAC,YAAY,CAAC,mBAAmB,MAAM,EAAE,CAAA;AAC/D,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,QAAQ,CAAC,QAAyB;IAChD,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO;QAAE,OAAO,SAAS,CAAA;IACjD,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IACpC,IAAI,IAAI,KAAK,IAAI,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IACzF,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,cAAc,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,EAAE,CAAA;AAC5E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAiB;IAClE,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;IAC5F,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEvC,4EAA4E;IAC5E,4EAA4E;IAC5E,0DAA0D;IAC1D,SAAS,CAAC,GAAG,EAAE,GAAG,KAAK,KAAK,CAAC,IAAI,EAAE,CAAA,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAE/C,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IACpC,MAAM,KAAK,GAAG,IAAI,KAAK,IAAI,IAAI,YAAY,CAAC,IAAI,CAAC,CAAA;IACjD,MAAM,IAAI,GAAG,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI;QACxD,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;IAEvC,4EAA4E;IAC5E,uCAAuC;IACvC,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAChC,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAA;QACzB,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAM;QAClC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QAC9B,0EAA0E;QAC1E,mCAAmC;QACnC,OAAO,GAAG,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA,CAAC,CAAC,CAAA;IACrD,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IAEtC,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAExD,OAAO,CACL,iCACc,GAAG,UAAU,CAAC,YAAY,CAAC,QAAQ,EAC/C,KAAK,EAAE;YACL,MAAM,EAAE,mFAAmF;YAC3F,YAAY,EAAE,MAAM;YACpB,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,SAAS;YACjB,UAAU,EAAE,4HAA4H;YACxI,SAAS,EAAE,qEAAqE;SACjF,aAED,eAAK,KAAK,EAAE;oBACV,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM;oBACpD,cAAc,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM;iBAClD,aAEC,aAAI,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,YAAG,IAAI,CAAC,KAAK,GAAM,EAE9E,KAAC,UAAU,IAAC,IAAI,EAAC,SAAS,YACvB,IAAI,CAAC,MAAM,KAAK,kBAAkB,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,WAAW,GAClE,IACT,EACN,YAAG,KAAK,EAAE;oBACR,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS;oBACrD,KAAK,EAAE,kCAAkC;iBAC1C,YAEE,IAAI,CAAC,MAAM,GACV,EAEH,IAAI;gBACH,CAAC,CAAC,CACE,KAAC,aAAa,IACZ,GAAG,EAAE;wBACH,IAAI,EAAE,YAAY;wBAClB,QAAQ,EAAE,IAAI;wBACd,KAAK,EAAE,IAAI;wBACX,SAAS,EAAE,IAAI;qBAChB,EACD,SAAS,EAAE,QAAQ,CAAC,SAAS,EAC7B,MAAM,EAAE,QAAQ,CAAC,MAAM,EACvB,MAAM,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE;wBAC1B,KAAK,KAAK,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;oBAC/E,CAAC,EACD,QAAQ,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA,CAAC,CAAC,GAClC,CACH;gBACH,CAAC,CAAC,CACE,eAAK,KAAK,EAAE,OAAO,CAAC,GAAG,aACrB,iBACE,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,OAAO,CAAC,OAAO,EACtB,QAAQ,EAAE,CAAC,QAAQ,CAAC,QAAQ,IAAI,QAAQ,CAAC,WAAW,KAAK,IAAI,sBAC3C,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,0BAA0B,EAC5E,OAAO,EAAE,GAAG,EAAE;gCACZ,IAAI,IAAI,CAAC,cAAc,KAAK,mBAAmB;oCAAE,KAAK,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;;oCAC7E,OAAO,CAAC,IAAI,CAAC,CAAA;4BACpB,CAAC,YAEA,QAAQ,CAAC,WAAW,KAAK,YAAY,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,GACnE,EACR,QAAQ,CAAC,QAAQ;4BAChB,CAAC,CAAC,IAAI;4BACN,CAAC,CAAC,CACE,eAAM,EAAE,EAAC,0BAA0B,EAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,iCAAiC,EAAE,2JAIlG,CACR,EACL,eAAM,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,iCAAiC,EAAE,iEAMpE,IACH,CACP,IACG,CACX,CAAA;AACH,CAAC"}
@@ -0,0 +1,194 @@
1
+ /**
2
+ * The Technology & Capability Center.
3
+ *
4
+ * Seven surfaces that answer one question each, and one rule that governs all
5
+ * of them: **nothing here may claim a capability works.** Every status shown is
6
+ * either read from a descriptor that says what was actually established, or
7
+ * labelled as not established. A settings page that flatters the installation
8
+ * is worse than no settings page, because it is the screen a person checks
9
+ * before trusting a result.
10
+ *
11
+ * That is why there is no "Ready" anywhere that is not backed by
12
+ * `machine_tested`, and no accuracy or speed figure at all for an engine that
13
+ * has not run on this machine.
14
+ *
15
+ * @module @deepwatch/dsh-client-settings/components
16
+ */
17
+ import type { ReactNode } from 'react';
18
+ import type { CoreHealthReport } from '@deepwatch/dsh-contracts/query/wire';
19
+ import type { BrandTone } from '@deepwatch/dsh-client-brand';
20
+ import type { RoleRow } from './binding-state.js';
21
+ /** What a settings section is handed by DSH. */
22
+ export interface SectionProps {
23
+ readonly close?: () => void;
24
+ }
25
+ /**
26
+ * The panel vocabulary every surface here shares.
27
+ *
28
+ * Exported because Role Bindings moved into its own file once it stopped being
29
+ * static copy, and two settings screens with independently-invented padding is
30
+ * how a panel starts looking like two products.
31
+ */
32
+ export declare const T: {
33
+ page: {
34
+ padding: string;
35
+ maxWidth: string;
36
+ background: string;
37
+ };
38
+ lead: {
39
+ fontSize: string;
40
+ lineHeight: number;
41
+ color: string;
42
+ margin: string;
43
+ maxWidth: string;
44
+ };
45
+ card: {
46
+ border: string;
47
+ borderRadius: string;
48
+ padding: string;
49
+ marginBottom: string;
50
+ background: string;
51
+ boxShadow: string;
52
+ };
53
+ cardHead: {
54
+ display: string;
55
+ alignItems: string;
56
+ gap: string;
57
+ justifyContent: string;
58
+ flexWrap: "wrap";
59
+ };
60
+ title: {
61
+ fontSize: string;
62
+ fontWeight: number;
63
+ margin: number;
64
+ letterSpacing: string;
65
+ };
66
+ meta: {
67
+ display: string;
68
+ gridTemplateColumns: string;
69
+ columnGap: string;
70
+ rowGap: string;
71
+ fontSize: string;
72
+ marginTop: string;
73
+ };
74
+ key: {
75
+ color: string;
76
+ };
77
+ value: {
78
+ color: string;
79
+ };
80
+ note: {
81
+ fontSize: string;
82
+ lineHeight: number;
83
+ color: string;
84
+ borderInlineStart: string;
85
+ paddingInlineStart: string;
86
+ margin: string;
87
+ };
88
+ h2: {
89
+ fontSize: string;
90
+ fontWeight: number;
91
+ letterSpacing: string;
92
+ textTransform: "uppercase";
93
+ color: string;
94
+ margin: string;
95
+ };
96
+ };
97
+ /**
98
+ * The tones a settings chip may use.
99
+ *
100
+ * `success` is deliberately not among them. Green is reserved for a VERIFIED
101
+ * verdict, and nothing on a settings page is a verdict — a configured
102
+ * capability reads as `active`, which is a different colour and a different
103
+ * claim.
104
+ */
105
+ export type ChipTone = Exclude<BrandTone, 'success'>;
106
+ /**
107
+ * A status chip.
108
+ *
109
+ * The tone vocabulary is the brand's, and `success` is deliberately absent:
110
+ * nothing in a settings page is a verification verdict, so nothing here is
111
+ * allowed to be green. A capability that is genuinely working reads as
112
+ * `active`, which is a different colour and a different claim.
113
+ */
114
+ export declare function StatusChip({ tone, children }: {
115
+ readonly tone: ChipTone;
116
+ readonly children: ReactNode;
117
+ }): ReactNode;
118
+ /**
119
+ * The empty state every surface here needs.
120
+ *
121
+ * A capability that is not configured must still render something a person can
122
+ * act on. A dead control that fails when clicked teaches people the product is
123
+ * broken; a sentence explaining what is missing and what would fix it does not.
124
+ */
125
+ export declare function NotConfigured({ what, why, fix }: {
126
+ readonly what: string;
127
+ readonly why: string;
128
+ readonly fix: string;
129
+ }): ReactNode;
130
+ /**
131
+ * Perception Engines.
132
+ *
133
+ * An Engine Runtime is not a Provider Connection, and the distinction is the
134
+ * whole point of the screen: a provider is a credential and an endpoint, an
135
+ * engine is software that runs here. Presence on disk is not readiness, so the
136
+ * lifecycle state is shown verbatim rather than collapsed into a tick.
137
+ *
138
+ * No engine shows a quality or speed number. On a machine where nothing has
139
+ * been measured, every such number would be invented.
140
+ */
141
+ export declare function EnginesSection(): ReactNode;
142
+ /**
143
+ * Sources & Devices.
144
+ *
145
+ * Nothing here asks the operating system for anything. A permission prompt on
146
+ * page load trains people to click Allow without reading, so a permission is
147
+ * requested when a capability is first used and not before — which is also why
148
+ * every row says when it would ask.
149
+ */
150
+ export declare function SourcesSection(): ReactNode;
151
+ /**
152
+ * Memory & Retrieval.
153
+ *
154
+ * The encryption row is the one that matters. The ledger is a plain file with
155
+ * the profile's permissions, and saying so is the difference between a product
156
+ * a person can calibrate their trust against and one that misleads them about
157
+ * where their data sits.
158
+ */
159
+ export declare function MemorySection(): ReactNode;
160
+ /**
161
+ * Verification.
162
+ *
163
+ * The screen leads with the distinction the whole product rests on, because
164
+ * this is where somebody configures how much proof they want and needs to know
165
+ * what a verdict does and does not mean.
166
+ */
167
+ export declare function VerificationSection(): ReactNode;
168
+ /**
169
+ * Diagnostics. What is actually running, and what is not.
170
+ *
171
+ * The capability readiness list lives here rather than in the first-run notice.
172
+ * It needs the settings panel's width; the onboarding seat is 256 pixels wide,
173
+ * and putting this there once already spilled two thousand pixels out of a
174
+ * clipped sidebar column.
175
+ */
176
+ export declare function DiagnosticsSection({ openSection, roles, health, reading, onRefresh }?: {
177
+ readonly openSection?: ((id: string) => void) | undefined;
178
+ /** Live role readiness, so this screen and Role Bindings agree. */
179
+ readonly roles?: readonly RoleRow[] | undefined;
180
+ /** The engine, as the Host last read it. Null when it could not be read. */
181
+ readonly health?: CoreHealthReport | null | undefined;
182
+ readonly reading?: boolean | undefined;
183
+ readonly onRefresh?: (() => void) | undefined;
184
+ }): ReactNode;
185
+ /**
186
+ * About.
187
+ *
188
+ * This is where the foundation becomes explicit. Watch is the product and
189
+ * DeepSeek Harness is what it is built on; both statements belong on the same
190
+ * screen, and the independence disclosure belongs beside them so the
191
+ * attribution cannot be read as an endorsement.
192
+ */
193
+ export declare function AboutSection(): ReactNode;
194
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1,273 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ATTRIBUTION, INDEPENDENCE, PRODUCT_NAME, WATCH_MARK_PNG, tokenFor } from '@deepwatch/dsh-client-brand';
3
+ // The `/descriptors` subpath, not the package root: the root re-exports the
4
+ // OCR worker, which imports `node:child_process` to supervise a real process.
5
+ // Correct on the host, fatal in a browser bundle.
6
+ import { OCR_ENGINES } from '@deepwatch/dsh-technology/descriptors';
7
+ import { ReadinessList } from './readiness.js';
8
+ import { OCR_BY_WORKLOAD, OCR_DEVICE, OCR_ENGINE, OCR_MEASURED } from '../ocr-measured.js';
9
+ /* ── shared presentation ────────────────────────────────────────────────── */
10
+ /**
11
+ * The panel vocabulary every surface here shares.
12
+ *
13
+ * Exported because Role Bindings moved into its own file once it stopped being
14
+ * static copy, and two settings screens with independently-invented padding is
15
+ * how a panel starts looking like two products.
16
+ */
17
+ export const T = {
18
+ page: {
19
+ padding: '6px 4px 30px', maxWidth: '880px',
20
+ background: 'radial-gradient(circle at 0 0, color-mix(in srgb, var(--watch-accent) 5%, transparent), transparent 34%)',
21
+ },
22
+ lead: {
23
+ fontSize: '13.5px', lineHeight: 1.65,
24
+ color: 'var(--dsw-alias-label-secondary)', margin: '0 0 20px', maxWidth: '72ch',
25
+ },
26
+ card: {
27
+ border: '1px solid color-mix(in srgb, var(--watch-accent) 9%, var(--dsw-alias-border-l2))',
28
+ borderRadius: '14px',
29
+ padding: '16px 18px',
30
+ marginBottom: '12px',
31
+ background: 'linear-gradient(145deg, color-mix(in srgb, var(--watch-accent) 3%, var(--dsw-alias-bg-layer-2)), var(--dsw-alias-bg-base))',
32
+ boxShadow: '0 10px 28px color-mix(in srgb, black 8%, transparent)',
33
+ },
34
+ cardHead: {
35
+ display: 'flex', alignItems: 'baseline', gap: '10px',
36
+ justifyContent: 'space-between', flexWrap: 'wrap',
37
+ },
38
+ title: { fontSize: '14px', fontWeight: 620, margin: 0, letterSpacing: '-0.01em' },
39
+ meta: {
40
+ display: 'grid',
41
+ gridTemplateColumns: 'max-content 1fr',
42
+ columnGap: '14px', rowGap: '4px',
43
+ fontSize: '12px', marginTop: '10px',
44
+ },
45
+ key: { color: 'var(--dsw-alias-label-tertiary)' },
46
+ value: { color: 'var(--dsw-alias-label-secondary)' },
47
+ note: {
48
+ fontSize: '12px', lineHeight: 1.55,
49
+ color: 'var(--dsw-alias-label-tertiary)',
50
+ borderInlineStart: '2px solid var(--watch-accent)',
51
+ paddingInlineStart: '10px', margin: '14px 0 0',
52
+ },
53
+ h2: { fontSize: '11px', fontWeight: 700, letterSpacing: '0.11em', textTransform: 'uppercase', color: 'var(--watch-accent)', margin: '24px 0 9px' },
54
+ };
55
+ /**
56
+ * A status chip.
57
+ *
58
+ * The tone vocabulary is the brand's, and `success` is deliberately absent:
59
+ * nothing in a settings page is a verification verdict, so nothing here is
60
+ * allowed to be green. A capability that is genuinely working reads as
61
+ * `active`, which is a different colour and a different claim.
62
+ */
63
+ export function StatusChip({ tone, children }) {
64
+ // `tokenFor` rather than a colour: a feature package asks for a tone and gets
65
+ // a custom property, which is what keeps the palette from being re-invented
66
+ // slightly differently in every panel and makes a theme change one edit.
67
+ const colour = tokenFor(tone);
68
+ return (_jsx("span", { style: {
69
+ display: 'inline-flex', alignItems: 'center', gap: '5px',
70
+ fontSize: '11px', lineHeight: 1.4, padding: '2px 8px',
71
+ borderRadius: '999px', whiteSpace: 'nowrap',
72
+ border: `1px solid color-mix(in srgb, ${colour} 60%, transparent)`, color: colour,
73
+ background: `color-mix(in srgb, ${colour} 8%, transparent)`,
74
+ }, children: children }));
75
+ }
76
+ /** A local/remote label, because where the data goes is a product fact. */
77
+ function Where({ local }) {
78
+ return _jsx(StatusChip, { tone: local ? 'active' : 'info', children: local ? 'Local' : 'Remote' });
79
+ }
80
+ function Row({ label, children }) {
81
+ return (_jsxs(_Fragment, { children: [_jsx("span", { style: T.key, children: label }), _jsx("span", { style: T.value, children: children })] }));
82
+ }
83
+ /**
84
+ * The empty state every surface here needs.
85
+ *
86
+ * A capability that is not configured must still render something a person can
87
+ * act on. A dead control that fails when clicked teaches people the product is
88
+ * broken; a sentence explaining what is missing and what would fix it does not.
89
+ */
90
+ export function NotConfigured({ what, why, fix }) {
91
+ return (_jsxs("div", { style: { ...T.card, borderStyle: 'dashed' }, children: [_jsxs("div", { style: T.cardHead, children: [_jsx("h3", { style: T.title, children: what }), _jsx(StatusChip, { tone: "neutral", children: "Not configured" })] }), _jsx("p", { style: { ...T.lead, margin: '8px 0 0' }, children: why }), _jsx("p", { style: { ...T.note, marginTop: '10px' }, children: fix })] }));
92
+ }
93
+ /* ── 2. Perception Engines ──────────────────────────────────────────────── */
94
+ /**
95
+ * Lifecycle words a person can act on, and the tone each earns.
96
+ *
97
+ * `not_tested` is the state every engine is in on a machine where no capability
98
+ * check has run, and it is what `untestedHealth` returns. It is deliberately
99
+ * not styled as an error: nothing is broken, nobody has looked.
100
+ */
101
+ function lifecycleChip(state) {
102
+ const map = {
103
+ machine_tested: { tone: 'active', text: 'Machine tested' },
104
+ ready: { tone: 'active', text: 'Ready' },
105
+ probed: { tone: 'caution', text: 'Probed, not measured' },
106
+ installed: { tone: 'caution', text: 'Installed, not tested' },
107
+ discovered: { tone: 'neutral', text: 'Not tested' },
108
+ not_tested: { tone: 'neutral', text: 'Not tested' },
109
+ not_installed: { tone: 'neutral', text: 'Not installed' },
110
+ installing: { tone: 'caution', text: 'Installing' },
111
+ degraded: { tone: 'error', text: 'Degraded' },
112
+ unavailable: { tone: 'error', text: 'Unavailable' },
113
+ incompatible: { tone: 'error', text: 'Incompatible' },
114
+ disabled: { tone: 'neutral', text: 'Disabled' },
115
+ };
116
+ const entry = map[state] ?? { tone: 'neutral', text: state };
117
+ return _jsx(StatusChip, { tone: entry.tone, children: entry.text });
118
+ }
119
+ function gigabytes(bytes) {
120
+ if (bytes === null)
121
+ return 'unknown';
122
+ return `${(bytes / 1_000_000_000).toFixed(1)} GB`;
123
+ }
124
+ /**
125
+ * Perception Engines.
126
+ *
127
+ * An Engine Runtime is not a Provider Connection, and the distinction is the
128
+ * whole point of the screen: a provider is a credential and an endpoint, an
129
+ * engine is software that runs here. Presence on disk is not readiness, so the
130
+ * lifecycle state is shown verbatim rather than collapsed into a tick.
131
+ *
132
+ * No engine shows a quality or speed number. On a machine where nothing has
133
+ * been measured, every such number would be invented.
134
+ */
135
+ export function EnginesSection() {
136
+ return (_jsxs("div", { style: T.page, children: [_jsx("p", { style: T.lead, children: "An engine runs on this machine; a provider is something you connect to. Presence on disk is not readiness, so each engine shows the furthest state actually reached. Nothing here shows an accuracy or speed figure: none has been measured on this hardware, and a figure that was not measured would be fiction." }), _jsx("h2", { style: T.h2, children: "OCR and layout" }), OCR_ENGINES.map((engine) => (_jsxs("div", { style: T.card, children: [_jsxs("div", { style: T.cardHead, children: [_jsx("h3", { style: T.title, children: engine.displayName }), _jsxs("span", { style: { display: 'flex', gap: '6px' }, children: [_jsx(Where, { local: engine.runtime !== 'remote' }), lifecycleChip('not_tested')] })] }), _jsxs("div", { style: T.meta, children: [_jsx(Row, { label: "Version", children: engine.version }), _jsx(Row, { label: "Runtime", children: engine.runtime }), _jsx(Row, { label: "Hardware", children: engine.hardware.gpu === 'required'
137
+ ? `GPU required${engine.hardware.minVramGb === null ? '' : `, ${String(engine.hardware.minVramGb)} GB VRAM`}`
138
+ : engine.hardware.gpu === 'optional' ? 'GPU optional' : 'CPU only' }), _jsx(Row, { label: "Egress", children: engine.privacy.egress === 'none'
139
+ ? 'Nothing leaves this machine'
140
+ : engine.privacy.egress === 'metadata_only' ? 'Metadata only' : 'Sends content off this machine' }), _jsx(Row, { label: "Offline", children: engine.privacy.worksOffline ? 'Works offline' : 'Requires network' }), _jsxs(Row, { label: "Install", children: [engine.install.method === 'bundled' ? 'Bundled' : engine.install.method, engine.install.downloadBytes === null ? '' : ` · ${gigabytes(engine.install.downloadBytes)} download`] }), _jsx(Row, { label: "Quality", children: OCR_MEASURED && engine.id.includes(OCR_ENGINE)
141
+ ? 'Measured on this machine — see the table below.'
142
+ : 'Not measured on this machine' }), _jsx(Row, { label: "How it would be checked", children: engine.testMethod ?? engine.probeMethod ?? 'No check is defined for this engine' })] })] }, engine.id))), OCR_MEASURED
143
+ ? (_jsxs(_Fragment, { children: [_jsx("h2", { style: T.h2, children: `Measured accuracy — ${OCR_ENGINE} on ${OCR_DEVICE}` }), _jsxs("div", { style: T.card, children: [_jsx("p", { style: { ...T.lead, margin: '0 0 10px' }, children: "A real run over a versioned ground-truth corpus, scored against thresholds committed before the benchmark existed. Reported per workload rather than as one average: an engine can be entirely fit for reading a settings panel and entirely unfit for reading grey-on-grey, and a single number hides both." }), _jsx("div", { style: T.meta, children: OCR_BY_WORKLOAD.map(row => (_jsxs(Row, { label: row.workload, children: [_jsx(StatusChip, { tone: row.passes ? 'active' : 'error', children: row.passes ? 'Qualified' : 'Not qualified' }), ` CER ${String(row.cer)} · word accuracy ${String(row.wordAccuracy)} `, `· invented words ${String(row.hallucination)} · ${String(row.samples)} sample(s)`] }, row.workload))) }), _jsx("p", { style: T.note, children: "Accuracy is not a GPU question and is measured here. GPU throughput is not, because there is no GPU on this machine \u2014 that remains externally unvalidated." })] })] }))
144
+ : null, _jsx("p", { style: T.note, children: "An install is never automatic. Where a download is required, its size, hardware requirement and licence are shown before anything is fetched \u2014 multi-gigabyte weights are not something a product should acquire because a page was opened." })] }));
145
+ }
146
+ /* ── 3. Sources & Devices ───────────────────────────────────────────────── */
147
+ const SOURCES = [
148
+ { id: 'files', name: 'Files', purpose: 'Documents and recordings you open.', local: true, permission: 'Granted per file, when you pick one' },
149
+ { id: 'video', name: 'Video', purpose: 'Recorded footage, indexed so a citation can point at a moment.', local: true, permission: 'Granted per file' },
150
+ { id: 'browser', name: 'Browser', purpose: 'A supervised browser that acts and reports a receipt.', local: true, permission: 'No OS permission needed' },
151
+ { id: 'screen', name: 'Screen', purpose: 'The whole display.', local: true, permission: 'Requested at first use' },
152
+ { id: 'window', name: 'Window', purpose: 'One application window.', local: true, permission: 'Requested at first use' },
153
+ { id: 'camera', name: 'Camera', purpose: 'Live visual input.', local: true, permission: 'Requested at first use' },
154
+ { id: 'microphone', name: 'Microphone', purpose: 'Live audio input.', local: true, permission: 'Requested at first use' },
155
+ { id: 'live', name: 'Live session', purpose: 'A continuous session over one or more of the above.', local: true, permission: 'Inherits its sources' },
156
+ ];
157
+ /**
158
+ * Sources & Devices.
159
+ *
160
+ * Nothing here asks the operating system for anything. A permission prompt on
161
+ * page load trains people to click Allow without reading, so a permission is
162
+ * requested when a capability is first used and not before — which is also why
163
+ * every row says when it would ask.
164
+ */
165
+ export function SourcesSection() {
166
+ return (_jsxs("div", { style: T.page, children: [_jsx("p", { style: T.lead, children: "Opening this page requests nothing. A permission is asked for when a capability is first used, because a prompt on load teaches people to allow without reading. Every source below is local: what it captures stays on this machine unless a separate media-upload consent is given." }), SOURCES.map(source => (_jsxs("div", { style: T.card, children: [_jsxs("div", { style: T.cardHead, children: [_jsx("h3", { style: T.title, children: source.name }), _jsxs("span", { style: { display: 'flex', gap: '6px' }, children: [_jsx(Where, { local: source.local }), _jsx(StatusChip, { tone: "neutral", children: "Not requested" })] })] }), _jsx("p", { style: { ...T.lead, margin: '6px 0 0' }, children: source.purpose }), _jsx("div", { style: T.meta, children: _jsx(Row, { label: "Permission", children: source.permission }) })] }, source.id)))] }));
167
+ }
168
+ /* ── 4. Memory & Retrieval ──────────────────────────────────────────────── */
169
+ const MEMORY_MODES = [
170
+ { id: 'off', name: 'Off', detail: 'Nothing is written and nothing is recalled.' },
171
+ { id: 'session_only', name: 'Session only', detail: 'Kept for this session and never reaches a later one.' },
172
+ { id: 'local_personal', name: 'Local Personal', detail: 'A ledger on this machine, for this profile.' },
173
+ { id: 'workspace_shared', name: 'Workspace Shared', detail: 'Knowledge and decisions are shared; personal taste is not.' },
174
+ ];
175
+ /**
176
+ * Memory & Retrieval.
177
+ *
178
+ * The encryption row is the one that matters. The ledger is a plain file with
179
+ * the profile's permissions, and saying so is the difference between a product
180
+ * a person can calibrate their trust against and one that misleads them about
181
+ * where their data sits.
182
+ */
183
+ export function MemorySection() {
184
+ return (_jsxs("div", { style: T.page, children: [_jsx("p", { style: T.lead, children: "Memory is a product capability, not a plugin. The ledger is the authority and every projection \u2014 taste, index, log \u2014 is rebuilt from it, which is what makes Forget remove a record rather than hide it." }), _jsx("h2", { style: T.h2, children: "Mode" }), MEMORY_MODES.map(mode => (_jsxs("div", { style: T.card, children: [_jsxs("div", { style: T.cardHead, children: [_jsx("h3", { style: T.title, children: mode.name }), mode.id === 'local_personal'
185
+ ? _jsx(StatusChip, { tone: "active", children: "Selected in this profile" })
186
+ : _jsx(StatusChip, { tone: "neutral", children: "Available" })] }), _jsx("p", { style: { ...T.lead, margin: '6px 0 0' }, children: mode.detail })] }, mode.id))), _jsx("h2", { style: T.h2, children: "Storage" }), _jsxs("div", { style: T.card, children: [_jsxs("div", { style: T.meta, children: [_jsx(Row, { label: "Ledger", children: "An append-only event log on this machine" }), _jsx(Row, { label: "Projections", children: "Rebuilt from the ledger, never edited in place" }), _jsx(Row, { label: "Embeddings", children: "Unbound \u2014 see Role Bindings. Retrieval falls back to lexical matching." }), _jsx(Row, { label: "Retention", children: "Kept until forgotten. Forget writes a tombstone." }), _jsx(Row, { label: "Encryption at rest", children: _jsx(StatusChip, { tone: "caution", children: "Not encrypted" }) })] }), _jsx("p", { style: T.note, children: "The ledger is a plain file. It is created owner-only \u2014 no group, no others \u2014 on systems that enforce file permissions; Windows has no equivalent and the file inherits the folder it sits in. On Desktop it is intended to move behind the OS keychain; until that is implemented and tested, this page will keep saying it is not encrypted. Claiming otherwise would be the one thing a privacy setting must never do." })] })] }));
187
+ }
188
+ /* ── 5. Verification ────────────────────────────────────────────────────── */
189
+ /**
190
+ * Verification.
191
+ *
192
+ * The screen leads with the distinction the whole product rests on, because
193
+ * this is where somebody configures how much proof they want and needs to know
194
+ * what a verdict does and does not mean.
195
+ */
196
+ export function VerificationSection() {
197
+ return (_jsxs("div", { style: T.page, children: [_jsxs("div", { style: { ...T.card, borderColor: 'var(--watch-accent)' }, children: [_jsx("h3", { style: T.title, children: "Agent completed \u2260 Verified" }), _jsx("p", { style: { ...T.lead, margin: '8px 0 0' }, children: "A tool returning without an error means the call finished. It does not mean the thing happened. Only a verification against the world produces a verdict, and only Watch Core produces one \u2014 no plugin, no client and no model can mint a verdict, by construction rather than by convention." })] }), _jsx("h2", { style: T.h2, children: "Verifier" }), _jsxs("div", { style: T.card, children: [_jsxs("div", { style: T.cardHead, children: [_jsx("h3", { style: T.title, children: "Deterministic checks" }), _jsxs("span", { style: { display: 'flex', gap: '6px' }, children: [_jsx(Where, { local: true }), _jsx(StatusChip, { tone: "active", children: "Available" })] })] }), _jsx("p", { style: { ...T.lead, margin: '6px 0 0' }, children: "Reads a row, an HTTP status, a file, an exit code. Needs no model, so it works offline and its result does not depend on a provider." })] }), _jsxs("div", { style: T.card, children: [_jsxs("div", { style: T.cardHead, children: [_jsx("h3", { style: T.title, children: "Model-assisted checks" }), _jsx(StatusChip, { tone: "neutral", children: "Verifier role not bound" })] }), _jsx("p", { style: { ...T.lead, margin: '6px 0 0' }, children: "For expectations a deterministic check cannot express. Bind the Verifier role to enable them; until then, expectations that need one return INCONCLUSIVE rather than a guess." })] }), _jsx("h2", { style: T.h2, children: "Verdicts" }), _jsxs("div", { style: T.card, children: [_jsxs("div", { style: T.meta, children: [_jsx(Row, { label: "VERIFIED", children: "Checked against the world, and it held." }), _jsx(Row, { label: "FAILED", children: "Checked, and it did not hold." }), _jsx(Row, { label: "UNVERIFIED", children: "Not checked. Not a failure \u2014 an absence." }), _jsx(Row, { label: "INCONCLUSIVE", children: "Checked, and the evidence did not settle it." })] }), _jsx("p", { style: T.note, children: "Green is reserved for VERIFIED and nothing else reaches it \u2014 not a high confidence, not a completed turn, not five checks out of six." })] })] }));
198
+ }
199
+ /* ── 6. Diagnostics ─────────────────────────────────────────────────────── */
200
+ /**
201
+ * What the engine is doing, in one chip.
202
+ *
203
+ * `connected` is the only state that gets the active tone. Everything else —
204
+ * including the mock backend, which is *working* and is still not the product
205
+ * — reads as a problem, because on this screen it is one.
206
+ */
207
+ function CoreStateChip({ health, reading }) {
208
+ if (health === null || health === undefined) {
209
+ return (_jsx(StatusChip, { tone: "neutral", children: reading ? 'Reading…' : 'Could not be read' }));
210
+ }
211
+ if (health.blocker === 'connected')
212
+ return _jsx(StatusChip, { tone: "active", children: "Connected" });
213
+ if (health.isTestOnlyMock)
214
+ return _jsx(StatusChip, { tone: "caution", children: "Test-only mock" });
215
+ return _jsx(StatusChip, { tone: "caution", children: health.phase });
216
+ }
217
+ /**
218
+ * Diagnostics. What is actually running, and what is not.
219
+ *
220
+ * The capability readiness list lives here rather than in the first-run notice.
221
+ * It needs the settings panel's width; the onboarding seat is 256 pixels wide,
222
+ * and putting this there once already spilled two thousand pixels out of a
223
+ * clipped sidebar column.
224
+ */
225
+ export function DiagnosticsSection({ openSection, roles, health, reading, onRefresh } = {}) {
226
+ return (_jsxs("div", { style: T.page, children: [_jsx("p", { style: T.lead, children: "What this installation actually consists of. Where a value cannot be read from the running system it says so, rather than showing a plausible default." }), _jsx("h2", { style: T.h2, children: "Capability readiness" }), _jsx(ReadinessList, { openSection: openSection, roles: roles, health: health, reading: reading }), _jsx("h2", { style: T.h2, children: "Versions" }), _jsx("div", { style: T.card, children: _jsxs("div", { style: T.meta, children: [_jsx(Row, { label: "DeepWatch", children: "0.1.0" }), _jsx(Row, { label: "DeepSeek Harness", children: "0.1.1-rc.2" }), _jsx(Row, { label: "Watch Core", children: health === null || health === undefined || health.coreVersion === null
227
+ ? _jsx(StatusChip, { tone: "neutral", children: "Not reported" })
228
+ : health.coreVersion }), _jsx(Row, { label: "Bridge protocol", children: health === null || health === undefined || health.protocolVersion === null
229
+ ? _jsx(StatusChip, { tone: "neutral", children: "Not reported" })
230
+ : `${String(health.protocolVersion)} (Core supports ${health.protocolMin === null ? '?' : String(health.protocolMin)}-${health.protocolVersion === null ? '?' : String(health.protocolVersion)})` }), _jsx(Row, { label: "Memory store schema", children: "1" })] }) }), _jsx("h2", { style: T.h2, children: "Health" }), _jsxs("div", { style: T.card, children: [_jsxs("div", { style: T.meta, children: [_jsx(Row, { label: "Watch Core", children: _jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: '8px' }, children: [_jsx(CoreStateChip, { health: health, reading: reading === true }), onRefresh === undefined
231
+ ? null
232
+ : (_jsx("button", { type: "button", onClick: onRefresh, disabled: reading === true, style: {
233
+ background: 'none', border: 'none', padding: 0,
234
+ font: 'inherit', fontSize: '12px', cursor: 'pointer',
235
+ color: tokenFor('info'), textDecoration: 'underline',
236
+ }, children: "Re-read" }))] }) }), _jsx(Row, { label: "Bridge transport", children: health === null || health === undefined
237
+ ? _jsx(StatusChip, { tone: "neutral", children: "Not reported" })
238
+ : health.isTestOnlyMock
239
+ ? _jsx(StatusChip, { tone: "caution", children: "Test-only mock backend" })
240
+ : health.transport === null
241
+ ? _jsx(StatusChip, { tone: "neutral", children: "Not reported" })
242
+ : health.transport }), _jsx(Row, { label: "Contract", children: health === null || health === undefined
243
+ ? _jsx(StatusChip, { tone: "neutral", children: "Not reported" })
244
+ : health.contractsMatch
245
+ ? _jsx(StatusChip, { tone: "active", children: "Matches this build" })
246
+ : _jsx(StatusChip, { tone: "caution", children: health.contractDrift.length === 0
247
+ ? 'Unverified'
248
+ : `Drifted: ${health.contractDrift.join(', ')}` }) }), _jsx(Row, { label: "Capabilities", children: health === null || health === undefined
249
+ ? _jsx(StatusChip, { tone: "neutral", children: "Not reported" })
250
+ : `${String(health.capabilities.ready)} ready · `
251
+ + `${String(health.capabilities.degraded)} degraded · `
252
+ + `${String(health.capabilities.unavailable)} unavailable · `
253
+ + `${String(health.capabilities.unknown)} unknown` }), _jsx(Row, { label: "Last handshake", children: health === null || health === undefined || health.lastHandshakeAt === null
254
+ ? _jsx(StatusChip, { tone: "neutral", children: "Never" })
255
+ : health.lastHandshakeAt }), _jsx(Row, { label: "Engine starts", children: health === null || health === undefined
256
+ ? _jsx(StatusChip, { tone: "neutral", children: "Not reported" })
257
+ : String(health.restartCount) }), health !== null && health !== undefined && health.blocker !== 'connected'
258
+ ? (_jsx(Row, { label: "Blocker", children: _jsxs("span", { children: [_jsx(StatusChip, { tone: "caution", children: health.blocker }), health.fix === '' ? null : _jsxs("span", { style: T.note, children: [" ", health.fix] })] }) }))
259
+ : null, _jsx(Row, { label: "Offline", children: _jsx(StatusChip, { tone: "active", children: "Offline only" }) }), _jsx(Row, { label: "Media upload consent", children: _jsx(StatusChip, { tone: "neutral", children: "Not given" }) })] }), _jsx("p", { style: T.note, children: "Offline-only and media-upload consent are two separate settings on purpose. Holding a provider credential is not permission to upload a frame, a transcript or a screen capture, and no agent can change either from inside a session." })] })] }));
260
+ }
261
+ /* ── 7. About ───────────────────────────────────────────────────────────── */
262
+ /**
263
+ * About.
264
+ *
265
+ * This is where the foundation becomes explicit. Watch is the product and
266
+ * DeepSeek Harness is what it is built on; both statements belong on the same
267
+ * screen, and the independence disclosure belongs beside them so the
268
+ * attribution cannot be read as an endorsement.
269
+ */
270
+ export function AboutSection() {
271
+ return (_jsxs("div", { style: T.page, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '14px', marginBottom: '4px' }, children: [_jsx("img", { src: WATCH_MARK_PNG, width: 44, height: 44, alt: "", "aria-hidden": "true", style: { width: '44px', height: '44px', objectFit: 'contain', flexShrink: 0 } }), _jsxs("div", { children: [_jsx("h3", { style: { ...T.title, fontSize: '19px' }, children: PRODUCT_NAME }), _jsx("p", { style: { ...T.lead, margin: '2px 0 0' }, children: "An agent that sees, remembers, and can prove what actually happened." })] })] }), _jsx("div", { style: T.card, children: _jsxs("div", { style: T.meta, children: [_jsx(Row, { label: "DeepWatch", children: "0.1.0" }), _jsx(Row, { label: "Watch Core", children: "Reported by the Bridge, not read from here" }), _jsx(Row, { label: "Built on", children: "DeepSeek Harness 0.1.1-rc.2" }), _jsx(Row, { label: "DSH commit", children: "b150a551b8d465e31e418e1b2eaf5e79bbb7d28e" })] }) }), _jsxs("div", { style: { ...T.card, borderColor: 'var(--watch-accent)' }, children: [_jsx("p", { style: { margin: 0, fontSize: '13px', lineHeight: 1.6 }, children: ATTRIBUTION }), _jsx("p", { style: { margin: '8px 0 0', fontSize: '13px', lineHeight: 1.6, color: 'var(--dsw-alias-label-secondary)' }, children: INDEPENDENCE })] }), _jsx("h2", { style: T.h2, children: "Licences" }), _jsx("div", { style: T.card, children: _jsxs("div", { style: T.meta, children: [_jsx(Row, { label: "DeepWatch", children: "MIT" }), _jsx(Row, { label: "DeepSeek Harness", children: "MIT, and its notice is carried unmodified" }), _jsx(Row, { label: "Third-party notices", children: "THIRD_PARTY_NOTICES.md, shipped with this distribution" }), _jsx(Row, { label: "Model weights", children: "Distributed with none. A code licence is not a weights licence." })] }) })] }));
272
+ }
273
+ //# sourceMappingURL=components.js.map