@guuey/mcp-apps-host 0.23.1 → 0.23.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.d.ts CHANGED
@@ -36,11 +36,14 @@ import { type CSSProperties, type ReactNode } from "react";
36
36
  import { type AttachViewHostConfig } from "./view-host.js";
37
37
  import type { ViewCspDiagnosis, ViewHostPhase } from "./view-host-protocol.js";
38
38
  import type { ResolvedViewMount } from "./card-mount.js";
39
+ import { type ViewHostStrings } from "./view-strings.js";
39
40
  export { attachViewHost, viewDocumentHtml } from "./view-host.js";
40
41
  export type { AttachViewHostConfig, ViewCspEvents, ViewFrameLike, ViewHostEvents } from "./view-host.js";
41
42
  export { attachSandboxPageDelivery, isSandboxProxyReady, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, type SandboxPageDeliveryConfig, } from "./sandbox-page.js";
42
43
  export type { ViewCspDiagnosis, ViewCspOrigins, ViewHostPhase } from "./view-host-protocol.js";
43
44
  export type { ResolvedViewMount, ViewMount, ViewMountChannel } from "./card-mount.js";
45
+ export { defaultViewHostStrings } from "./view-strings.js";
46
+ export type { ViewHostStrings } from "./view-strings.js";
44
47
  export interface GuueyViewProps extends Pick<AttachViewHostConfig, "hostCapabilities" | "hostInfo" | "hostContext" | "onCallTool" | "onUpdateModelContext" | "onUserMessage" | "onOpenLink" | "onReadResource" | "onSizeChanged" | "negotiationTimeoutMs" | "cspOrigins" | "onCspDiagnosis"> {
45
48
  /** The resolved card to mount (see `toolResultViewMount`/`resolveViewMount`). */
46
49
  mount: ResolvedViewMount;
@@ -103,15 +106,23 @@ export interface GuueyViewProps extends Pick<AttachViewHostConfig, "hostCapabili
103
106
  onPhaseChange?: (phase: ViewHostPhase) => void;
104
107
  /**
105
108
  * Replace the default status/failure line for a phase. Return `null` for
106
- * "render nothing". The default: a quiet "Negotiating with view…" line
107
- * while `"negotiating"`; a labeled failure for `"no-handshake"` on the
109
+ * "render nothing". The default: a quiet loading line while
110
+ * `"negotiating"`; a labeled failure for `"no-handshake"` on the
108
111
  * `"ggui"` channel; nothing once `"connected"` (the view owns its
109
112
  * pixels) and nothing for a silent `"inline"` card. The second argument
110
113
  * is the CSP diagnosis when the tripwire caught one (see
111
114
  * {@link AttachViewHostConfig.cspOrigins}) — the default label folds it
112
115
  * in; a custom renderer decides how to show it.
116
+ *
117
+ * To change only the WORDS, prefer {@link GuueyViewProps.strings}: this
118
+ * hook makes a host reimplement the CSP/channel branching to do it.
113
119
  */
114
120
  renderStatus?: (phase: ViewHostPhase, diagnosis?: ViewCspDiagnosis) => ReactNode;
121
+ /**
122
+ * Override the default status line's copy (guuey#1325). Merged over
123
+ * {@link defaultViewHostStrings}, so a partial override is enough.
124
+ */
125
+ strings?: Partial<ViewHostStrings>;
115
126
  }
116
127
  /**
117
128
  * Mount a resolved view and play the MCP Apps Host for it. See the module
@@ -1 +1 @@
1
- {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAwC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjG,OAAO,EAAoC,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACzG,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/F,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAKtF,MAAM,WAAW,cACf,SAAQ,IAAI,CACV,oBAAoB,EAClB,kBAAkB,GAClB,UAAU,GACV,aAAa,GACb,YAAY,GACZ,sBAAsB,GACtB,eAAe,GACf,YAAY,GACZ,gBAAgB,GAChB,eAAe,GACf,sBAAsB,GACtB,YAAY,GACZ,gBAAgB,CACnB;IACD,iFAAiF;IACjF,KAAK,EAAE,iBAAiB,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IACtC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,gBAAgB,KAAK,SAAS,CAAC;CAClF;AA8CD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CAkL1D"}
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAwC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACjG,OAAO,EAAoC,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEjF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACzG,OAAO,EACL,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,6BAA6B,EAC7B,KAAK,yBAAyB,GAC/B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC/F,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACtF,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKzD,MAAM,WAAW,cACf,SAAQ,IAAI,CACV,oBAAoB,EAClB,kBAAkB,GAClB,UAAU,GACV,aAAa,GACb,YAAY,GACZ,sBAAsB,GACtB,eAAe,GACf,YAAY,GACZ,gBAAgB,GAChB,eAAe,GACf,sBAAsB,GACtB,YAAY,GACZ,gBAAgB,CACnB;IACD,iFAAiF;IACjF,KAAK,EAAE,iBAAiB,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IACtC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,gBAAgB,KAAK,SAAS,CAAC;IACjF;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;CACpC;AAkDD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CAsL1D"}
package/dist/react.js CHANGED
@@ -37,8 +37,10 @@ import { useEffect, useMemo, useRef, useState } from "react";
37
37
  import { attachViewHost, viewDocumentHtml } from "./view-host.js";
38
38
  import { attachSandboxPageDelivery } from "./sandbox-page.js";
39
39
  import { decideAutoResize, initialAutoResizeState } from "./auto-resize.js";
40
+ import { defaultViewHostStrings } from "./view-strings.js";
40
41
  export { attachViewHost, viewDocumentHtml } from "./view-host.js";
41
42
  export { attachSandboxPageDelivery, isSandboxProxyReady, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, } from "./sandbox-page.js";
43
+ export { defaultViewHostStrings } from "./view-strings.js";
42
44
  /** Accessible name for a mounted view when the caller has nothing better. */
43
45
  const DEFAULT_TITLE = "Generated view";
44
46
  const statusLineStyle = {
@@ -52,9 +54,11 @@ const statusLineStyle = {
52
54
  opacity: 0.65,
53
55
  pointerEvents: "none",
54
56
  };
55
- function defaultStatus(phase, channel, diagnosis) {
57
+ function defaultStatus(phase, channel, diagnosis, strings) {
56
58
  if (phase === "negotiating") {
57
- return _jsx("p", { style: statusLineStyle, children: "Negotiating with view\u2026" });
59
+ // guuey#1279/#1325: the copy says what the PERSON is waiting for. The
60
+ // phase name is machinery and never reaches the frame.
61
+ return _jsx("p", { style: statusLineStyle, children: strings.viewLoading });
58
62
  }
59
63
  // A CSP diagnosis (guuey#235) is the WHY behind a silent frame — on any
60
64
  // channel: a blocked runtime bundle never gets to negotiate, so the
@@ -66,8 +70,9 @@ function defaultStatus(phase, channel, diagnosis) {
66
70
  if (phase === "no-handshake" && channel === "ggui") {
67
71
  // A ggui shell negotiates unconditionally before painting, so silence
68
72
  // here is a boot failure with no other author — label it (role=alert
69
- // so it is announced, not just drawn).
70
- return (_jsx("p", { role: "alert", style: { ...statusLineStyle, opacity: 1, pointerEvents: "auto" }, children: "This view did not start \u2014 it never negotiated with the host." }));
73
+ // so it is announced, not just drawn). The label states the OUTCOME the
74
+ // person sees; the handshake that did not happen is machinery.
75
+ return (_jsx("p", { role: "alert", style: { ...statusLineStyle, opacity: 1, pointerEvents: "auto" }, children: strings.viewBootFailure }));
71
76
  }
72
77
  return null;
73
78
  }
@@ -76,7 +81,10 @@ function defaultStatus(phase, channel, diagnosis) {
76
81
  * docblock for the sandbox and state contracts.
77
82
  */
78
83
  export function GuueyView(props) {
79
- const { mount, sandboxPageUrl, autoResize, maxHeight, dangerouslyAddSandboxFlags, allow, title, className, style, onPhaseChange, renderStatus, ...hostConfig } = props;
84
+ const { mount, sandboxPageUrl, autoResize, maxHeight, dangerouslyAddSandboxFlags, allow, title, className, style, onPhaseChange, renderStatus, strings: stringOverrides, ...hostConfig } = props;
85
+ // Read only during render (no effect depends on it), so a plain spread is
86
+ // correct — no identity churn to guard against.
87
+ const viewStrings = { ...defaultViewHostStrings, ...stringOverrides };
80
88
  const frameRef = useRef(null);
81
89
  const [phase, setPhase] = useState("negotiating");
82
90
  // The CSP tripwire's verdict for THIS document, if any (guuey#235).
@@ -211,5 +219,5 @@ export function GuueyView(props) {
211
219
  border: 0,
212
220
  } }, `${page?.href ?? "srcdoc"}::${mount.resource.uri}`), renderStatus !== undefined
213
221
  ? renderStatus(phase, diagnosis)
214
- : defaultStatus(phase, mount.channel, diagnosis)] }));
222
+ : defaultStatus(phase, mount.channel, diagnosis, viewStrings)] }));
215
223
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The view host's own user-facing copy — the i18n seam for the default
3
+ * status line (guuey#1279 Track A, extended on guuey#1325).
4
+ *
5
+ * ## Why this file exists
6
+ *
7
+ * `GuueyView`'s default status used to hardcode its English copy inline, and
8
+ * that copy narrated the PROTOCOL: "Negotiating with view…" is the
9
+ * {@link ViewHostPhase} member `"negotiating"` shown to a person, and the
10
+ * boot-failure line said the view "never negotiated with the host". guuey#1279
11
+ * rules that lifecycle vocabulary is machinery and must not reach chrome; the
12
+ * rule was enforced in `@guuey/chat` but this package renders its own chrome,
13
+ * so the same defect survived here and shipped — a large blank frame captioned
14
+ * with a protocol noun on a public page (guuey#1325).
15
+ *
16
+ * Two things were wrong, and they are separable:
17
+ *
18
+ * 1. **The words.** Fixed by using product copy, deliberately IDENTICAL to
19
+ * `@guuey/chat`'s `viewNegotiating` / `viewBootFailure` so the two packages
20
+ * speak with one voice on the same states. They cannot share a constant:
21
+ * `@guuey/chat` depends on this package, so an import the other way is a
22
+ * cycle. The duplication is intentional and pinned by a test.
23
+ *
24
+ * 2. **The seam.** Hardcoded literals are unreachable by translation. A host
25
+ * that localised every string it could still got English here, and
26
+ * `renderStatus` — the only existing escape — forces a host to reimplement
27
+ * the CSP/channel branching just to change a word. These strings are
28
+ * overridable on their own.
29
+ */
30
+ /** The default status line's copy. Every user-facing word this package renders. */
31
+ export interface ViewHostStrings {
32
+ /**
33
+ * While the view is loading — the `"negotiating"` phase. Says what the
34
+ * PERSON is waiting for (a view), never what the protocol is doing.
35
+ */
36
+ viewLoading: string;
37
+ /**
38
+ * A ggui shell that never started — the `"no-handshake"` phase on the
39
+ * `"ggui"` channel, where silence is a boot failure with no other author.
40
+ * A CSP diagnosis outranks this (it names the actual blocked URI).
41
+ */
42
+ viewBootFailure: string;
43
+ }
44
+ /**
45
+ * en defaults. Kept byte-identical to `@guuey/chat`'s `viewNegotiating` and
46
+ * `viewBootFailure` — `view-chrome-vocabulary.test.ts` pins both the wording
47
+ * and the absence of protocol vocabulary.
48
+ */
49
+ export declare const defaultViewHostStrings: ViewHostStrings;
50
+ //# sourceMappingURL=view-strings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-strings.d.ts","sourceRoot":"","sources":["../src/view-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,eAGpC,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The view host's own user-facing copy — the i18n seam for the default
3
+ * status line (guuey#1279 Track A, extended on guuey#1325).
4
+ *
5
+ * ## Why this file exists
6
+ *
7
+ * `GuueyView`'s default status used to hardcode its English copy inline, and
8
+ * that copy narrated the PROTOCOL: "Negotiating with view…" is the
9
+ * {@link ViewHostPhase} member `"negotiating"` shown to a person, and the
10
+ * boot-failure line said the view "never negotiated with the host". guuey#1279
11
+ * rules that lifecycle vocabulary is machinery and must not reach chrome; the
12
+ * rule was enforced in `@guuey/chat` but this package renders its own chrome,
13
+ * so the same defect survived here and shipped — a large blank frame captioned
14
+ * with a protocol noun on a public page (guuey#1325).
15
+ *
16
+ * Two things were wrong, and they are separable:
17
+ *
18
+ * 1. **The words.** Fixed by using product copy, deliberately IDENTICAL to
19
+ * `@guuey/chat`'s `viewNegotiating` / `viewBootFailure` so the two packages
20
+ * speak with one voice on the same states. They cannot share a constant:
21
+ * `@guuey/chat` depends on this package, so an import the other way is a
22
+ * cycle. The duplication is intentional and pinned by a test.
23
+ *
24
+ * 2. **The seam.** Hardcoded literals are unreachable by translation. A host
25
+ * that localised every string it could still got English here, and
26
+ * `renderStatus` — the only existing escape — forces a host to reimplement
27
+ * the CSP/channel branching just to change a word. These strings are
28
+ * overridable on their own.
29
+ */
30
+ /**
31
+ * en defaults. Kept byte-identical to `@guuey/chat`'s `viewNegotiating` and
32
+ * `viewBootFailure` — `view-chrome-vocabulary.test.ts` pins both the wording
33
+ * and the absence of protocol vocabulary.
34
+ */
35
+ export const defaultViewHostStrings = {
36
+ viewLoading: "Loading view…",
37
+ viewBootFailure: "This view couldn't start",
38
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guuey/mcp-apps-host",
3
- "version": "0.23.1",
3
+ "version": "0.23.2",
4
4
  "description": "The MCP Apps (SEP-1865) Host role for guuey's chat surfaces — view-mount narrowing across UI channels, ui:// locator rehydration by resources/read, and the sandbox-trust channel contract. Vendor-neutral: any spec-following MCP App mounts through it.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/react.tsx CHANGED
@@ -38,6 +38,7 @@ import { attachSandboxPageDelivery } from "./sandbox-page.js";
38
38
  import { decideAutoResize, initialAutoResizeState, type AutoResizeState } from "./auto-resize.js";
39
39
  import type { ViewCspDiagnosis, ViewHostPhase } from "./view-host-protocol.js";
40
40
  import type { ResolvedViewMount } from "./card-mount.js";
41
+ import { defaultViewHostStrings, type ViewHostStrings } from "./view-strings.js";
41
42
 
42
43
  export { attachViewHost, viewDocumentHtml } from "./view-host.js";
43
44
  export type { AttachViewHostConfig, ViewCspEvents, ViewFrameLike, ViewHostEvents } from "./view-host.js";
@@ -50,6 +51,8 @@ export {
50
51
  } from "./sandbox-page.js";
51
52
  export type { ViewCspDiagnosis, ViewCspOrigins, ViewHostPhase } from "./view-host-protocol.js";
52
53
  export type { ResolvedViewMount, ViewMount, ViewMountChannel } from "./card-mount.js";
54
+ export { defaultViewHostStrings } from "./view-strings.js";
55
+ export type { ViewHostStrings } from "./view-strings.js";
53
56
 
54
57
  /** Accessible name for a mounted view when the caller has nothing better. */
55
58
  const DEFAULT_TITLE = "Generated view";
@@ -131,15 +134,23 @@ export interface GuueyViewProps
131
134
  onPhaseChange?: (phase: ViewHostPhase) => void;
132
135
  /**
133
136
  * Replace the default status/failure line for a phase. Return `null` for
134
- * "render nothing". The default: a quiet "Negotiating with view…" line
135
- * while `"negotiating"`; a labeled failure for `"no-handshake"` on the
137
+ * "render nothing". The default: a quiet loading line while
138
+ * `"negotiating"`; a labeled failure for `"no-handshake"` on the
136
139
  * `"ggui"` channel; nothing once `"connected"` (the view owns its
137
140
  * pixels) and nothing for a silent `"inline"` card. The second argument
138
141
  * is the CSP diagnosis when the tripwire caught one (see
139
142
  * {@link AttachViewHostConfig.cspOrigins}) — the default label folds it
140
143
  * in; a custom renderer decides how to show it.
144
+ *
145
+ * To change only the WORDS, prefer {@link GuueyViewProps.strings}: this
146
+ * hook makes a host reimplement the CSP/channel branching to do it.
141
147
  */
142
148
  renderStatus?: (phase: ViewHostPhase, diagnosis?: ViewCspDiagnosis) => ReactNode;
149
+ /**
150
+ * Override the default status line's copy (guuey#1325). Merged over
151
+ * {@link defaultViewHostStrings}, so a partial override is enough.
152
+ */
153
+ strings?: Partial<ViewHostStrings>;
143
154
  }
144
155
 
145
156
  const statusLineStyle: CSSProperties = {
@@ -158,9 +169,12 @@ function defaultStatus(
158
169
  phase: ViewHostPhase,
159
170
  channel: ResolvedViewMount["channel"],
160
171
  diagnosis: ViewCspDiagnosis | undefined,
172
+ strings: ViewHostStrings,
161
173
  ): ReactNode {
162
174
  if (phase === "negotiating") {
163
- return <p style={statusLineStyle}>Negotiating with view…</p>;
175
+ // guuey#1279/#1325: the copy says what the PERSON is waiting for. The
176
+ // phase name is machinery and never reaches the frame.
177
+ return <p style={statusLineStyle}>{strings.viewLoading}</p>;
164
178
  }
165
179
  // A CSP diagnosis (guuey#235) is the WHY behind a silent frame — on any
166
180
  // channel: a blocked runtime bundle never gets to negotiate, so the
@@ -176,10 +190,11 @@ function defaultStatus(
176
190
  if (phase === "no-handshake" && channel === "ggui") {
177
191
  // A ggui shell negotiates unconditionally before painting, so silence
178
192
  // here is a boot failure with no other author — label it (role=alert
179
- // so it is announced, not just drawn).
193
+ // so it is announced, not just drawn). The label states the OUTCOME the
194
+ // person sees; the handshake that did not happen is machinery.
180
195
  return (
181
196
  <p role="alert" style={{ ...statusLineStyle, opacity: 1, pointerEvents: "auto" }}>
182
- This view did not start — it never negotiated with the host.
197
+ {strings.viewBootFailure}
183
198
  </p>
184
199
  );
185
200
  }
@@ -203,8 +218,12 @@ export function GuueyView(props: GuueyViewProps): ReactNode {
203
218
  style,
204
219
  onPhaseChange,
205
220
  renderStatus,
221
+ strings: stringOverrides,
206
222
  ...hostConfig
207
223
  } = props;
224
+ // Read only during render (no effect depends on it), so a plain spread is
225
+ // correct — no identity churn to guard against.
226
+ const viewStrings: ViewHostStrings = { ...defaultViewHostStrings, ...stringOverrides };
208
227
  const frameRef = useRef<HTMLIFrameElement>(null);
209
228
  const [phase, setPhase] = useState<ViewHostPhase>("negotiating");
210
229
  // The CSP tripwire's verdict for THIS document, if any (guuey#235).
@@ -365,7 +384,7 @@ export function GuueyView(props: GuueyViewProps): ReactNode {
365
384
  />
366
385
  {renderStatus !== undefined
367
386
  ? renderStatus(phase, diagnosis)
368
- : defaultStatus(phase, mount.channel, diagnosis)}
387
+ : defaultStatus(phase, mount.channel, diagnosis, viewStrings)}
369
388
  </div>
370
389
  );
371
390
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The view host's own user-facing copy — the i18n seam for the default
3
+ * status line (guuey#1279 Track A, extended on guuey#1325).
4
+ *
5
+ * ## Why this file exists
6
+ *
7
+ * `GuueyView`'s default status used to hardcode its English copy inline, and
8
+ * that copy narrated the PROTOCOL: "Negotiating with view…" is the
9
+ * {@link ViewHostPhase} member `"negotiating"` shown to a person, and the
10
+ * boot-failure line said the view "never negotiated with the host". guuey#1279
11
+ * rules that lifecycle vocabulary is machinery and must not reach chrome; the
12
+ * rule was enforced in `@guuey/chat` but this package renders its own chrome,
13
+ * so the same defect survived here and shipped — a large blank frame captioned
14
+ * with a protocol noun on a public page (guuey#1325).
15
+ *
16
+ * Two things were wrong, and they are separable:
17
+ *
18
+ * 1. **The words.** Fixed by using product copy, deliberately IDENTICAL to
19
+ * `@guuey/chat`'s `viewNegotiating` / `viewBootFailure` so the two packages
20
+ * speak with one voice on the same states. They cannot share a constant:
21
+ * `@guuey/chat` depends on this package, so an import the other way is a
22
+ * cycle. The duplication is intentional and pinned by a test.
23
+ *
24
+ * 2. **The seam.** Hardcoded literals are unreachable by translation. A host
25
+ * that localised every string it could still got English here, and
26
+ * `renderStatus` — the only existing escape — forces a host to reimplement
27
+ * the CSP/channel branching just to change a word. These strings are
28
+ * overridable on their own.
29
+ */
30
+
31
+ /** The default status line's copy. Every user-facing word this package renders. */
32
+ export interface ViewHostStrings {
33
+ /**
34
+ * While the view is loading — the `"negotiating"` phase. Says what the
35
+ * PERSON is waiting for (a view), never what the protocol is doing.
36
+ */
37
+ viewLoading: string;
38
+ /**
39
+ * A ggui shell that never started — the `"no-handshake"` phase on the
40
+ * `"ggui"` channel, where silence is a boot failure with no other author.
41
+ * A CSP diagnosis outranks this (it names the actual blocked URI).
42
+ */
43
+ viewBootFailure: string;
44
+ }
45
+
46
+ /**
47
+ * en defaults. Kept byte-identical to `@guuey/chat`'s `viewNegotiating` and
48
+ * `viewBootFailure` — `view-chrome-vocabulary.test.ts` pins both the wording
49
+ * and the absence of protocol vocabulary.
50
+ */
51
+ export const defaultViewHostStrings: ViewHostStrings = {
52
+ viewLoading: "Loading view…",
53
+ viewBootFailure: "This view couldn't start",
54
+ };