@guuey/mcp-apps-host 0.23.1 → 0.24.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.
- package/dist/react.d.ts +13 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +14 -6
- package/dist/view-strings.d.ts +50 -0
- package/dist/view-strings.d.ts.map +1 -0
- package/dist/view-strings.js +38 -0
- package/package.json +4 -4
- package/src/react.tsx +25 -6
- package/src/view-strings.ts +54 -0
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
|
|
107
|
-
*
|
|
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
|
package/dist/react.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.24.0",
|
|
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",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@ggui-ai/protocol": "0.
|
|
36
|
+
"@ggui-ai/protocol": "0.17.0",
|
|
37
37
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
38
|
-
"@silverprotocol/core": "0.6.
|
|
38
|
+
"@silverprotocol/core": "0.6.2"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": ">=18"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@types/react": "^19.0.0",
|
|
51
51
|
"react": "^19.0.0",
|
|
52
52
|
"typescript": "^5.0.0",
|
|
53
|
-
"vitest": "^
|
|
53
|
+
"vitest": "^4.1.11"
|
|
54
54
|
},
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public"
|
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
|
|
135
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|