@xemahq/ui-kernel 0.9.1 → 0.10.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/README.md +2 -2
- package/dist/lib/biome-host/biome-builders.d.ts.map +1 -1
- package/dist/lib/biome-host/biome-builders.js +3 -1
- package/dist/lib/biome-host/biome-builders.js.map +1 -1
- package/dist/lib/biome-host/biome-registry.d.ts.map +1 -1
- package/dist/lib/biome-host/biome-registry.js.map +1 -1
- package/dist/lib/biome-host/biome-scope-hooks.d.ts +11 -0
- package/dist/lib/biome-host/biome-scope-hooks.d.ts.map +1 -0
- package/dist/lib/biome-host/biome-scope-hooks.js +25 -0
- package/dist/lib/biome-host/biome-scope-hooks.js.map +1 -0
- package/dist/lib/biome-host/biome-scope.d.ts +1 -10
- package/dist/lib/biome-host/biome-scope.d.ts.map +1 -1
- package/dist/lib/biome-host/biome-scope.js +1 -24
- package/dist/lib/biome-host/biome-scope.js.map +1 -1
- package/dist/lib/biome-host/biome-scoped-query.d.ts +1 -1
- package/dist/lib/biome-host/biome-scoped-query.d.ts.map +1 -1
- package/dist/lib/biome-host/biome-scoped-query.js +1 -1
- package/dist/lib/biome-host/biome-scoped-query.js.map +1 -1
- package/dist/lib/biome-host/create-biome-orval-config.js +2 -2
- package/dist/lib/biome-host/create-biome-orval-config.js.map +1 -1
- package/dist/lib/biome-host/errors.js.map +1 -1
- package/dist/lib/biome-host/index.d.ts +1 -2
- package/dist/lib/biome-host/index.d.ts.map +1 -1
- package/dist/lib/biome-host/index.js +1 -2
- package/dist/lib/biome-host/index.js.map +1 -1
- package/dist/lib/biome-host/session-contributions.d.ts +9 -4
- package/dist/lib/biome-host/session-contributions.d.ts.map +1 -1
- package/dist/lib/capabilities/capability-context.d.ts +9 -0
- package/dist/lib/capabilities/capability-context.d.ts.map +1 -0
- package/dist/lib/capabilities/capability-context.js +13 -0
- package/dist/lib/capabilities/capability-context.js.map +1 -0
- package/dist/lib/capabilities/capability-provider.d.ts +2 -8
- package/dist/lib/capabilities/capability-provider.d.ts.map +1 -1
- package/dist/lib/capabilities/capability-provider.js +1 -12
- package/dist/lib/capabilities/capability-provider.js.map +1 -1
- package/dist/lib/capabilities/index.d.ts +1 -0
- package/dist/lib/capabilities/index.d.ts.map +1 -1
- package/dist/lib/capabilities/index.js +1 -0
- package/dist/lib/capabilities/index.js.map +1 -1
- package/dist/lib/capabilities/use-capability.d.ts.map +1 -1
- package/dist/lib/capabilities/use-capability.js +1 -1
- package/dist/lib/capabilities/use-capability.js.map +1 -1
- package/dist/lib/system-bus/deeplink.js.map +1 -1
- package/dist/lib/system-bus/host-ports.d.ts +2 -1
- package/dist/lib/system-bus/host-ports.d.ts.map +1 -1
- package/dist/lib/system-bus/intent-registry.d.ts.map +1 -1
- package/dist/lib/system-bus/intent-registry.js.map +1 -1
- package/dist/offline/index.d.ts +2 -1
- package/dist/offline/index.d.ts.map +1 -1
- package/dist/offline/index.js +2 -1
- package/dist/offline/index.js.map +1 -1
- package/dist/offline/offline-status-badge.js +1 -1
- package/dist/offline/offline-status-badge.js.map +1 -1
- package/dist/offline/outbox.js.map +1 -1
- package/dist/offline/react-hooks.d.ts +7 -0
- package/dist/offline/react-hooks.d.ts.map +1 -0
- package/dist/offline/react-hooks.js +25 -0
- package/dist/offline/react-hooks.js.map +1 -0
- package/dist/offline/react.d.ts +0 -4
- package/dist/offline/react.d.ts.map +1 -1
- package/dist/offline/react.js +2 -24
- package/dist/offline/react.js.map +1 -1
- package/dist/offline/storage.d.ts.map +1 -1
- package/dist/offline/storage.js +8 -5
- package/dist/offline/storage.js.map +1 -1
- package/dist/registry/index.d.ts +1 -0
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -0
- package/dist/registry/index.js.map +1 -1
- package/dist/registry/lib/primitives/SessionEventCard.d.ts.map +1 -1
- package/dist/registry/lib/primitives/SessionEventCard.js +1 -1
- package/dist/registry/lib/primitives/SessionEventCard.js.map +1 -1
- package/dist/registry/lib/session-context-builder.d.ts +10 -2
- package/dist/registry/lib/session-context-builder.d.ts.map +1 -1
- package/dist/registry/lib/session-context-builder.js +14 -7
- package/dist/registry/lib/session-context-builder.js.map +1 -1
- package/dist/registry/lib/session-context-provider.d.ts +1 -3
- package/dist/registry/lib/session-context-provider.d.ts.map +1 -1
- package/dist/registry/lib/session-context-provider.js +2 -15
- package/dist/registry/lib/session-context-provider.js.map +1 -1
- package/dist/registry/lib/session-context.d.ts +5 -0
- package/dist/registry/lib/session-context.d.ts.map +1 -0
- package/dist/registry/lib/session-context.js +15 -0
- package/dist/registry/lib/session-context.js.map +1 -0
- package/dist/registry/lib/session-selectors.d.ts.map +1 -1
- package/dist/registry/lib/session-selectors.js +3 -1
- package/dist/registry/lib/session-selectors.js.map +1 -1
- package/dist/ui/branding/app-branding-context.d.ts +9 -0
- package/dist/ui/branding/app-branding-context.d.ts.map +1 -0
- package/dist/ui/branding/app-branding-context.js +10 -0
- package/dist/ui/branding/app-branding-context.js.map +1 -0
- package/dist/ui/branding/app-branding-provider.d.ts +0 -6
- package/dist/ui/branding/app-branding-provider.d.ts.map +1 -1
- package/dist/ui/branding/app-branding-provider.js +1 -8
- package/dist/ui/branding/app-branding-provider.js.map +1 -1
- package/dist/ui/capture/schema-field-input.js +1 -1
- package/dist/ui/capture/schema-field-input.js.map +1 -1
- package/dist/ui/capture/use-prefill.d.ts.map +1 -1
- package/dist/ui/capture/use-prefill.js +3 -1
- package/dist/ui/capture/use-prefill.js.map +1 -1
- package/dist/ui/capture/use-wizard.d.ts +1 -1
- package/dist/ui/capture/use-wizard.d.ts.map +1 -1
- package/dist/ui/capture/use-wizard.js +1 -1
- package/dist/ui/capture/use-wizard.js.map +1 -1
- package/dist/ui/capture/wizard.d.ts.map +1 -1
- package/dist/ui/capture/wizard.js.map +1 -1
- package/dist/ui/index.d.ts +5 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +5 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/primitives/alert-dialog.js +1 -1
- package/dist/ui/primitives/alert-dialog.js.map +1 -1
- package/dist/ui/primitives/badge-variants.d.ts +4 -0
- package/dist/ui/primitives/badge-variants.d.ts.map +1 -0
- package/dist/ui/primitives/badge-variants.js +15 -0
- package/dist/ui/primitives/badge-variants.js.map +1 -0
- package/dist/ui/primitives/badge.d.ts +3 -5
- package/dist/ui/primitives/badge.d.ts.map +1 -1
- package/dist/ui/primitives/badge.js +2 -15
- package/dist/ui/primitives/badge.js.map +1 -1
- package/dist/ui/primitives/button-variants.d.ts +5 -0
- package/dist/ui/primitives/button-variants.d.ts.map +1 -0
- package/dist/ui/primitives/button-variants.js +24 -0
- package/dist/ui/primitives/button-variants.js.map +1 -0
- package/dist/ui/primitives/button.d.ts +3 -6
- package/dist/ui/primitives/button.d.ts.map +1 -1
- package/dist/ui/primitives/button.js +2 -24
- package/dist/ui/primitives/button.js.map +1 -1
- package/dist/ui/primitives/form-stepper.d.ts +1 -13
- package/dist/ui/primitives/form-stepper.d.ts.map +1 -1
- package/dist/ui/primitives/form-stepper.js +1 -6
- package/dist/ui/primitives/form-stepper.js.map +1 -1
- package/dist/ui/primitives/form-stepper.types.d.ts +14 -0
- package/dist/ui/primitives/form-stepper.types.d.ts.map +1 -0
- package/dist/ui/primitives/form-stepper.types.js +7 -0
- package/dist/ui/primitives/form-stepper.types.js.map +1 -0
- package/dist/ui/primitives/preview-device-frame.js +1 -1
- package/dist/ui/primitives/preview-device-frame.js.map +1 -1
- package/dist/ui/primitives/tag-multi-select.d.ts.map +1 -1
- package/dist/ui/primitives/tag-multi-select.js +1 -1
- package/dist/ui/primitives/tag-multi-select.js.map +1 -1
- package/dist/vista/index.d.ts +4 -0
- package/dist/vista/index.d.ts.map +1 -1
- package/dist/vista/index.js +4 -0
- package/dist/vista/index.js.map +1 -1
- package/dist/vista/surfaces/FilesSurface.d.ts +1 -12
- package/dist/vista/surfaces/FilesSurface.d.ts.map +1 -1
- package/dist/vista/surfaces/FilesSurface.js +2 -46
- package/dist/vista/surfaces/FilesSurface.js.map +1 -1
- package/dist/vista/surfaces/FormSurface.d.ts.map +1 -1
- package/dist/vista/surfaces/FormSurface.js +39 -12
- package/dist/vista/surfaces/FormSurface.js.map +1 -1
- package/dist/vista/surfaces/elicitation-transport.d.ts +12 -3
- package/dist/vista/surfaces/elicitation-transport.d.ts.map +1 -1
- package/dist/vista/surfaces/elicitation-transport.js +96 -3
- package/dist/vista/surfaces/elicitation-transport.js.map +1 -1
- package/dist/vista/surfaces/file-surface-utils.d.ts +13 -0
- package/dist/vista/surfaces/file-surface-utils.d.ts.map +1 -0
- package/dist/vista/surfaces/file-surface-utils.js +46 -0
- package/dist/vista/surfaces/file-surface-utils.js.map +1 -0
- package/dist/vista/surfaces/index.d.ts +4 -3
- package/dist/vista/surfaces/index.d.ts.map +1 -1
- package/dist/vista/surfaces/index.js +4 -81
- package/dist/vista/surfaces/index.js.map +1 -1
- package/dist/vista/surfaces/register-surfaces.d.ts +2 -0
- package/dist/vista/surfaces/register-surfaces.d.ts.map +1 -0
- package/dist/vista/surfaces/register-surfaces.js +80 -0
- package/dist/vista/surfaces/register-surfaces.js.map +1 -0
- package/dist/vista/surfaces/text-surfaces.js +1 -1
- package/dist/vista/surfaces/text-surfaces.js.map +1 -1
- package/dist/vista/vista-code-renderer-context.d.ts +9 -0
- package/dist/vista/vista-code-renderer-context.d.ts.map +1 -0
- package/dist/vista/vista-code-renderer-context.js +6 -0
- package/dist/vista/vista-code-renderer-context.js.map +1 -0
- package/dist/vista/vista-code-renderer.d.ts +1 -6
- package/dist/vista/vista-code-renderer.d.ts.map +1 -1
- package/dist/vista/vista-code-renderer.js +1 -5
- package/dist/vista/vista-code-renderer.js.map +1 -1
- package/dist/vista/vista-file-context.d.ts +8 -0
- package/dist/vista/vista-file-context.d.ts.map +1 -0
- package/dist/vista/vista-file-context.js +6 -0
- package/dist/vista/vista-file-context.js.map +1 -0
- package/dist/vista/vista-file-source.d.ts +1 -6
- package/dist/vista/vista-file-source.d.ts.map +1 -1
- package/dist/vista/vista-file-source.js +2 -5
- package/dist/vista/vista-file-source.js.map +1 -1
- package/dist/vista/vista-form-context.d.ts +13 -0
- package/dist/vista/vista-form-context.d.ts.map +1 -0
- package/dist/vista/vista-form-context.js +6 -0
- package/dist/vista/vista-form-context.js.map +1 -0
- package/dist/vista/vista-form-host.d.ts +1 -10
- package/dist/vista/vista-form-host.d.ts.map +1 -1
- package/dist/vista/vista-form-host.js +1 -5
- package/dist/vista/vista-form-host.js.map +1 -1
- package/dist/vista/vista-open-context.d.ts +1 -3
- package/dist/vista/vista-open-context.d.ts.map +1 -1
- package/dist/vista/vista-open-context.js +1 -5
- package/dist/vista/vista-open-context.js.map +1 -1
- package/dist/vista/vista-open-state.d.ts +5 -0
- package/dist/vista/vista-open-state.d.ts.map +1 -0
- package/dist/vista/vista-open-state.js +6 -0
- package/dist/vista/vista-open-state.js.map +1 -0
- package/dist/vista/vista-payloads.d.ts +2 -3
- package/dist/vista/vista-payloads.d.ts.map +1 -1
- package/dist/vista/vista-payloads.js +18 -21
- package/dist/vista/vista-payloads.js.map +1 -1
- package/dist/vista/vista-tools.d.ts.map +1 -1
- package/dist/vista/vista-tools.js.map +1 -1
- package/package.json +3 -3
- package/src/lib/biome-host/biome-builders.ts +4 -1
- package/src/lib/biome-host/biome-registry.ts +0 -1
- package/src/lib/biome-host/biome-scope-hooks.ts +39 -0
- package/src/lib/biome-host/biome-scope.tsx +1 -68
- package/src/lib/biome-host/biome-scoped-query.ts +4 -1
- package/src/lib/biome-host/create-biome-orval-config.ts +2 -2
- package/src/lib/biome-host/errors.ts +1 -1
- package/src/lib/biome-host/index.ts +1 -2
- package/src/lib/biome-host/session-contributions.ts +21 -18
- package/src/lib/capabilities/capability-context.ts +25 -0
- package/src/lib/capabilities/capability-provider.tsx +5 -34
- package/src/lib/capabilities/index.ts +1 -0
- package/src/lib/capabilities/use-capability.ts +4 -1
- package/src/lib/system-bus/deeplink.ts +1 -1
- package/src/lib/system-bus/host-ports.ts +2 -1
- package/src/lib/system-bus/intent-registry.ts +1 -2
- package/src/offline/index.ts +4 -2
- package/src/offline/offline-status-badge.tsx +1 -1
- package/src/offline/outbox.test.ts +23 -16
- package/src/offline/outbox.ts +1 -1
- package/src/offline/react-hooks.ts +37 -0
- package/src/offline/react.tsx +1 -40
- package/src/offline/storage.ts +11 -6
- package/src/registry/index.ts +1 -0
- package/src/registry/lib/primitives/SessionEventCard.tsx +4 -2
- package/src/registry/lib/session-context-builder.ts +33 -17
- package/src/registry/lib/session-context-provider.tsx +8 -25
- package/src/registry/lib/session-context.ts +21 -0
- package/src/registry/lib/session-selectors.ts +3 -1
- package/src/ui/branding/app-branding-context.ts +20 -0
- package/src/ui/branding/app-branding-provider.tsx +5 -27
- package/src/ui/capture/schema-field-input.tsx +7 -1
- package/src/ui/capture/use-prefill.ts +4 -1
- package/src/ui/capture/use-wizard.ts +2 -2
- package/src/ui/capture/wizard.tsx +2 -1
- package/src/ui/index.ts +7 -2
- package/src/ui/primitives/alert-dialog.tsx +1 -1
- package/src/ui/primitives/badge-variants.ts +21 -0
- package/src/ui/primitives/badge.tsx +3 -19
- package/src/ui/primitives/button-variants.ts +31 -0
- package/src/ui/primitives/button.tsx +3 -28
- package/src/ui/primitives/form-stepper.tsx +1 -16
- package/src/ui/primitives/form-stepper.types.ts +15 -0
- package/src/ui/primitives/preview-device-frame.tsx +1 -1
- package/src/ui/primitives/tag-multi-select.tsx +3 -4
- package/src/vista/form-payload.spec.ts +71 -25
- package/src/vista/index.ts +4 -0
- package/src/vista/surfaces/FilesSurface.tsx +2 -68
- package/src/vista/surfaces/FormSurface.tsx +125 -26
- package/src/vista/surfaces/elicitation-transport.spec.ts +205 -23
- package/src/vista/surfaces/elicitation-transport.ts +192 -14
- package/src/vista/surfaces/file-surface-utils.ts +62 -0
- package/src/vista/surfaces/index.ts +24 -0
- package/src/vista/surfaces/{index.tsx → register-surfaces.tsx} +1 -21
- package/src/vista/surfaces/text-surfaces.tsx +1 -1
- package/src/vista/vista-code-renderer-context.ts +16 -0
- package/src/vista/vista-code-renderer.tsx +4 -16
- package/src/vista/vista-file-context.ts +14 -0
- package/src/vista/vista-file-source.tsx +5 -28
- package/src/vista/vista-form-context.ts +20 -0
- package/src/vista/vista-form-host.tsx +11 -36
- package/src/vista/vista-open-context.tsx +4 -14
- package/src/vista/vista-open-state.ts +11 -0
- package/src/vista/vista-payloads.ts +44 -46
- package/src/vista/vista-tools.ts +2 -2
- package/dist/lib/biome-host/response-envelope.d.ts +0 -3
- package/dist/lib/biome-host/response-envelope.d.ts.map +0 -1
- package/dist/lib/biome-host/response-envelope.js +0 -21
- package/dist/lib/biome-host/response-envelope.js.map +0 -1
- package/dist/lib/biome-host/session-profiles.d.ts +0 -18
- package/dist/lib/biome-host/session-profiles.d.ts.map +0 -1
- package/dist/lib/biome-host/session-profiles.js +0 -35
- package/dist/lib/biome-host/session-profiles.js.map +0 -1
- package/src/lib/biome-host/response-envelope.ts +0 -69
- package/src/lib/biome-host/session-profiles.ts +0 -99
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
// This module is the ONLY code that ever reads a captured field value out of
|
|
5
5
|
// the form surface, and it does exactly one thing with it: put it in the body
|
|
6
|
-
// of a `fetch` to
|
|
7
|
-
//
|
|
6
|
+
// of a `fetch` to an endpoint derived from the HOST-CONFIGURED trusted
|
|
7
|
+
// elicitation-service base URL, bearing the SUBJECT's own JWT.
|
|
8
8
|
//
|
|
9
9
|
// Three properties make "the agent cannot receive the value" structural:
|
|
10
10
|
//
|
|
@@ -15,15 +15,17 @@
|
|
|
15
15
|
// • the response is PROJECTED, not passed through: `toOutcome` constructs a
|
|
16
16
|
// fresh object with exactly three keys, so a service that echoed a secret
|
|
17
17
|
// back could still not leak one through this boundary;
|
|
18
|
-
// • the
|
|
19
|
-
//
|
|
20
|
-
//
|
|
18
|
+
// • the payload carries only an opaque ref. It has NO network authority. This
|
|
19
|
+
// module validates the host-configured base URL and derives the exact
|
|
20
|
+
// status/submit paths before a bearer token or field value reaches fetch.
|
|
21
21
|
//
|
|
22
22
|
// Errors carry the SERVICE's message verbatim (that is how the backend's hard
|
|
23
23
|
// 400 on an empty credential form becomes readable UI copy) and never the
|
|
24
24
|
// request body.
|
|
25
25
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
26
|
-
import
|
|
26
|
+
import { isValidVistaFormRef } from '../vista-payloads';
|
|
27
|
+
|
|
28
|
+
import type { VistaFormOutcome } from '../vista-form-context';
|
|
27
29
|
|
|
28
30
|
/** A non-2xx answer from the owning service, carrying its message verbatim. */
|
|
29
31
|
export class VistaFormSubmitError extends Error {
|
|
@@ -36,6 +38,19 @@ export class VistaFormSubmitError extends Error {
|
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
/** Invalid trusted-host transport configuration or unsafe path input. */
|
|
42
|
+
export class VistaFormTransportConfigError extends Error {
|
|
43
|
+
constructor(message: string) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'VistaFormTransportConfigError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface TrustedElicitationEndpoints {
|
|
50
|
+
readonly statusUrl: string;
|
|
51
|
+
readonly submitUrl: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
type UnknownRecord = Readonly<Record<string, unknown>>;
|
|
40
55
|
|
|
41
56
|
function asRecord(value: unknown): UnknownRecord | null {
|
|
@@ -69,7 +84,9 @@ export function extractServiceMessage(body: unknown, status: number): string {
|
|
|
69
84
|
: (record.message ?? asRecord(record.error)?.message ?? record.error);
|
|
70
85
|
if (typeof candidate === 'string' && candidate.length > 0) return candidate;
|
|
71
86
|
if (Array.isArray(candidate)) {
|
|
72
|
-
const joined = candidate
|
|
87
|
+
const joined = candidate
|
|
88
|
+
.filter((m): m is string => typeof m === 'string')
|
|
89
|
+
.join(' ');
|
|
73
90
|
if (joined.length > 0) return joined;
|
|
74
91
|
}
|
|
75
92
|
if (typeof body === 'string' && body.length > 0) return body;
|
|
@@ -108,7 +125,10 @@ function toOutcome(body: unknown, ref: string): VistaFormOutcome | null {
|
|
|
108
125
|
}
|
|
109
126
|
|
|
110
127
|
/** The `Authorization`/`Content-Type` pair every request here carries. */
|
|
111
|
-
function authHeaders(
|
|
128
|
+
function authHeaders(
|
|
129
|
+
accessToken: string,
|
|
130
|
+
json: boolean,
|
|
131
|
+
): Record<string, string> {
|
|
112
132
|
const headers: Record<string, string> = {
|
|
113
133
|
Authorization: `Bearer ${accessToken}`,
|
|
114
134
|
Accept: 'application/json',
|
|
@@ -117,6 +137,146 @@ function authHeaders(accessToken: string, json: boolean): Record<string, string>
|
|
|
117
137
|
return headers;
|
|
118
138
|
}
|
|
119
139
|
|
|
140
|
+
function browserOrigin(): string | undefined {
|
|
141
|
+
return typeof globalThis.location === 'undefined'
|
|
142
|
+
? undefined
|
|
143
|
+
: globalThis.location.origin;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isLoopbackHostname(hostname: string): boolean {
|
|
147
|
+
return (
|
|
148
|
+
hostname === 'localhost' ||
|
|
149
|
+
hostname.endsWith('.localhost') ||
|
|
150
|
+
hostname === '127.0.0.1' ||
|
|
151
|
+
hostname === '[::1]'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Parse the host's deploy-time trust anchor. It must be a canonical absolute
|
|
157
|
+
* HTTP(S) URL without credentials/query/fragment. HTTPS is mandatory except
|
|
158
|
+
* for loopback or same-host HTTP when the page itself is also served over HTTP.
|
|
159
|
+
*/
|
|
160
|
+
function trustedApiBaseUrl(
|
|
161
|
+
raw: string,
|
|
162
|
+
activeBrowserOrigin: string | undefined,
|
|
163
|
+
): URL {
|
|
164
|
+
if (raw.length === 0 || raw.trim() !== raw) {
|
|
165
|
+
throw new VistaFormTransportConfigError(
|
|
166
|
+
'The trusted elicitation API base URL is empty or contains whitespace.',
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let url: URL;
|
|
171
|
+
try {
|
|
172
|
+
url = new URL(raw);
|
|
173
|
+
} catch {
|
|
174
|
+
throw new VistaFormTransportConfigError(
|
|
175
|
+
'The trusted elicitation API base URL must be absolute.',
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
(url.protocol !== 'https:' && url.protocol !== 'http:') ||
|
|
181
|
+
url.username.length > 0 ||
|
|
182
|
+
url.password.length > 0 ||
|
|
183
|
+
url.search.length > 0 ||
|
|
184
|
+
url.hash.length > 0
|
|
185
|
+
) {
|
|
186
|
+
throw new VistaFormTransportConfigError(
|
|
187
|
+
'The trusted elicitation API base URL must use HTTP(S) and contain no credentials, query, or fragment.',
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const canonicalPath =
|
|
192
|
+
url.pathname === '/' ? '' : url.pathname.replace(/\/+$/, '');
|
|
193
|
+
const canonical = `${url.origin}${canonicalPath}`;
|
|
194
|
+
const suppliedWithoutOneTrailingSlash = raw.endsWith('/')
|
|
195
|
+
? raw.slice(0, -1)
|
|
196
|
+
: raw;
|
|
197
|
+
if (suppliedWithoutOneTrailingSlash !== canonical) {
|
|
198
|
+
throw new VistaFormTransportConfigError(
|
|
199
|
+
'The trusted elicitation API base URL must be canonical and contain no traversal or ambiguous path.',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (url.protocol === 'http:' && !isLoopbackHostname(url.hostname)) {
|
|
204
|
+
let page: URL | null = null;
|
|
205
|
+
if (activeBrowserOrigin !== undefined) {
|
|
206
|
+
try {
|
|
207
|
+
page = new URL(activeBrowserOrigin);
|
|
208
|
+
} catch {
|
|
209
|
+
page = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (
|
|
213
|
+
page === null ||
|
|
214
|
+
page.protocol !== 'http:' ||
|
|
215
|
+
page.hostname !== url.hostname
|
|
216
|
+
) {
|
|
217
|
+
throw new VistaFormTransportConfigError(
|
|
218
|
+
'Insecure elicitation HTTP is allowed only on loopback or the same host as an HTTP development page.',
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return new URL(canonical);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Derive the only two authenticated endpoints from trusted host configuration
|
|
228
|
+
* plus one opaque path segment. Calling this directly remains safe even if a
|
|
229
|
+
* caller bypasses the surface-payload validator.
|
|
230
|
+
*/
|
|
231
|
+
export function resolveTrustedElicitationEndpoints(
|
|
232
|
+
apiBaseUrl: string,
|
|
233
|
+
ref: string,
|
|
234
|
+
activeBrowserOrigin: string | undefined = browserOrigin(),
|
|
235
|
+
): TrustedElicitationEndpoints {
|
|
236
|
+
if (!isValidVistaFormRef(ref)) {
|
|
237
|
+
throw new VistaFormTransportConfigError(
|
|
238
|
+
'The elicitation reference is not a valid opaque base64url path segment.',
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const base = trustedApiBaseUrl(apiBaseUrl, activeBrowserOrigin)
|
|
242
|
+
.toString()
|
|
243
|
+
.replace(/\/$/, '');
|
|
244
|
+
const statusUrl = `${base}/${ref}`;
|
|
245
|
+
return { statusUrl, submitUrl: `${statusUrl}/submit` };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Resolve an unauthenticated launch path under the SAME trusted service origin.
|
|
250
|
+
* Absolute/network-path URLs and dot-segment traversal fail closed.
|
|
251
|
+
*/
|
|
252
|
+
export function resolveTrustedElicitationLaunchUrl(
|
|
253
|
+
apiBaseUrl: string,
|
|
254
|
+
path: string,
|
|
255
|
+
activeBrowserOrigin: string | undefined = browserOrigin(),
|
|
256
|
+
): string {
|
|
257
|
+
const base = trustedApiBaseUrl(apiBaseUrl, activeBrowserOrigin);
|
|
258
|
+
if (
|
|
259
|
+
!path.startsWith('/') ||
|
|
260
|
+
path.startsWith('//') ||
|
|
261
|
+
path.includes('\\') ||
|
|
262
|
+
path.includes('#')
|
|
263
|
+
) {
|
|
264
|
+
throw new VistaFormTransportConfigError(
|
|
265
|
+
'The elicitation launch target must be a root-relative service path.',
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
const resolved = new URL(path, `${base.origin}/`);
|
|
269
|
+
if (
|
|
270
|
+
resolved.origin !== base.origin ||
|
|
271
|
+
`${resolved.pathname}${resolved.search}` !== path
|
|
272
|
+
) {
|
|
273
|
+
throw new VistaFormTransportConfigError(
|
|
274
|
+
'The elicitation launch path contains traversal or an ambiguous encoding.',
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return resolved.toString();
|
|
278
|
+
}
|
|
279
|
+
|
|
120
280
|
/**
|
|
121
281
|
* POST the captured fields to the owning service.
|
|
122
282
|
*
|
|
@@ -125,20 +285,30 @@ function authHeaders(accessToken: string, json: boolean): Record<string, string>
|
|
|
125
285
|
* user reads.
|
|
126
286
|
*/
|
|
127
287
|
export async function submitElicitationFields(args: {
|
|
128
|
-
readonly
|
|
288
|
+
readonly apiBaseUrl: string;
|
|
129
289
|
readonly ref: string;
|
|
130
290
|
readonly accessToken: string;
|
|
131
291
|
readonly fields: Readonly<Record<string, string>>;
|
|
132
292
|
}): Promise<VistaFormOutcome> {
|
|
133
|
-
const
|
|
293
|
+
const { submitUrl } = resolveTrustedElicitationEndpoints(
|
|
294
|
+
args.apiBaseUrl,
|
|
295
|
+
args.ref,
|
|
296
|
+
);
|
|
297
|
+
const response = await fetch(submitUrl, {
|
|
134
298
|
method: 'POST',
|
|
135
299
|
headers: authHeaders(args.accessToken, true),
|
|
136
300
|
body: JSON.stringify({ fields: args.fields }),
|
|
137
301
|
credentials: 'omit',
|
|
302
|
+
// A 307/308 could otherwise replay the credential body to a different
|
|
303
|
+
// origin. Authenticated elicitation endpoints must never redirect.
|
|
304
|
+
redirect: 'error',
|
|
138
305
|
});
|
|
139
306
|
const body = await readBody(response);
|
|
140
307
|
if (!response.ok) {
|
|
141
|
-
throw new VistaFormSubmitError(
|
|
308
|
+
throw new VistaFormSubmitError(
|
|
309
|
+
response.status,
|
|
310
|
+
extractServiceMessage(body, response.status),
|
|
311
|
+
);
|
|
142
312
|
}
|
|
143
313
|
const outcome = toOutcome(body, args.ref);
|
|
144
314
|
if (outcome === null) {
|
|
@@ -158,18 +328,26 @@ export async function submitElicitationFields(args: {
|
|
|
158
328
|
* Returns `null` while still pending — an explicit "not yet", never a guess.
|
|
159
329
|
*/
|
|
160
330
|
export async function readElicitationStatus(args: {
|
|
161
|
-
readonly
|
|
331
|
+
readonly apiBaseUrl: string;
|
|
162
332
|
readonly ref: string;
|
|
163
333
|
readonly accessToken: string;
|
|
164
334
|
}): Promise<VistaFormOutcome | null> {
|
|
165
|
-
const
|
|
335
|
+
const { statusUrl } = resolveTrustedElicitationEndpoints(
|
|
336
|
+
args.apiBaseUrl,
|
|
337
|
+
args.ref,
|
|
338
|
+
);
|
|
339
|
+
const response = await fetch(statusUrl, {
|
|
166
340
|
method: 'GET',
|
|
167
341
|
headers: authHeaders(args.accessToken, false),
|
|
168
342
|
credentials: 'omit',
|
|
343
|
+
redirect: 'error',
|
|
169
344
|
});
|
|
170
345
|
const body = await readBody(response);
|
|
171
346
|
if (!response.ok) {
|
|
172
|
-
throw new VistaFormSubmitError(
|
|
347
|
+
throw new VistaFormSubmitError(
|
|
348
|
+
response.status,
|
|
349
|
+
extractServiceMessage(body, response.status),
|
|
350
|
+
);
|
|
173
351
|
}
|
|
174
352
|
return toOutcome(body, args.ref);
|
|
175
353
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { VistaFileEntry } from '../vista-payloads';
|
|
2
|
+
|
|
3
|
+
interface FileExtMeta {
|
|
4
|
+
readonly color: string;
|
|
5
|
+
readonly tag: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const EXT_META: Readonly<Record<string, FileExtMeta>> = {
|
|
9
|
+
ts: { color: 'hsl(var(--info))', tag: 'TS' },
|
|
10
|
+
tsx: { color: 'hsl(var(--info))', tag: 'TSX' },
|
|
11
|
+
js: { color: 'hsl(var(--warning))', tag: 'JS' },
|
|
12
|
+
jsx: { color: 'hsl(var(--warning))', tag: 'JSX' },
|
|
13
|
+
json: { color: 'hsl(var(--ink-3))', tag: 'JSON' },
|
|
14
|
+
md: { color: 'hsl(var(--p-people))', tag: 'MD' },
|
|
15
|
+
css: { color: 'hsl(var(--p-sales))', tag: 'CSS' },
|
|
16
|
+
html: { color: 'hsl(var(--p-sales))', tag: 'HTML' },
|
|
17
|
+
yaml: { color: 'hsl(var(--ink-3))', tag: 'YAML' },
|
|
18
|
+
yml: { color: 'hsl(var(--ink-3))', tag: 'YAML' },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function fileExtMeta(name: string): FileExtMeta {
|
|
22
|
+
if (/\.test\.[tj]sx?$/.test(name) || /\.spec\.[tj]sx?$/.test(name)) {
|
|
23
|
+
return { color: 'hsl(var(--success))', tag: 'TEST' };
|
|
24
|
+
}
|
|
25
|
+
const ext = name.split('.').pop() ?? '';
|
|
26
|
+
return (
|
|
27
|
+
EXT_META[ext] ?? {
|
|
28
|
+
color: 'hsl(var(--ink-4))',
|
|
29
|
+
tag: (ext || 'FILE').toUpperCase().slice(0, 4),
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface VistaFileGroup {
|
|
35
|
+
readonly dir: string;
|
|
36
|
+
readonly files: readonly VistaFileEntry[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function compareDirs(a: string, b: string): number {
|
|
40
|
+
if (a === '') return -1;
|
|
41
|
+
if (b === '') return 1;
|
|
42
|
+
return a.localeCompare(b);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function groupFilesByFolder(
|
|
46
|
+
files: readonly VistaFileEntry[],
|
|
47
|
+
): VistaFileGroup[] {
|
|
48
|
+
const groups = new Map<string, VistaFileEntry[]>();
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
const slash = file.path.lastIndexOf('/');
|
|
51
|
+
const dir = slash < 0 ? '' : file.path.slice(0, slash);
|
|
52
|
+
const bucket = groups.get(dir);
|
|
53
|
+
if (bucket) bucket.push(file);
|
|
54
|
+
else groups.set(dir, [file]);
|
|
55
|
+
}
|
|
56
|
+
return [...groups.keys()].sort(compareDirs).map((dir) => ({
|
|
57
|
+
dir,
|
|
58
|
+
files: [...(groups.get(dir) ?? [])].sort((a, b) =>
|
|
59
|
+
a.path.localeCompare(b.path),
|
|
60
|
+
),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import './register-surfaces';
|
|
2
|
+
|
|
3
|
+
export { ChartSurface } from './ChartSurface';
|
|
4
|
+
export { FilesSurface } from './FilesSurface';
|
|
5
|
+
export {
|
|
6
|
+
fileExtMeta,
|
|
7
|
+
groupFilesByFolder,
|
|
8
|
+
type VistaFileGroup,
|
|
9
|
+
} from './file-surface-utils';
|
|
10
|
+
export { FormSurface } from './FormSurface';
|
|
11
|
+
export {
|
|
12
|
+
extractServiceMessage,
|
|
13
|
+
readElicitationStatus,
|
|
14
|
+
resolveTrustedElicitationEndpoints,
|
|
15
|
+
resolveTrustedElicitationLaunchUrl,
|
|
16
|
+
submitElicitationFields,
|
|
17
|
+
VistaFormSubmitError,
|
|
18
|
+
VistaFormTransportConfigError,
|
|
19
|
+
} from './elicitation-transport';
|
|
20
|
+
export { HtmlSurface } from './HtmlSurface';
|
|
21
|
+
export { TableSurface } from './TableSurface';
|
|
22
|
+
export { UrlSurface } from './UrlSurface';
|
|
23
|
+
export { CodeSurface, DiffSurface, JsonSurface } from './text-surfaces';
|
|
24
|
+
export { ImageSurface, MediaSurface, PdfSurface } from './blob-surfaces';
|
|
@@ -29,11 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { SurfaceKind } from '@xemahq/kernel-contracts/surface';
|
|
31
31
|
|
|
32
|
-
import {
|
|
33
|
-
ImageSurface,
|
|
34
|
-
MediaSurface,
|
|
35
|
-
PdfSurface,
|
|
36
|
-
} from './blob-surfaces';
|
|
32
|
+
import { ImageSurface, MediaSurface, PdfSurface } from './blob-surfaces';
|
|
37
33
|
import { ChartSurface } from './ChartSurface';
|
|
38
34
|
import { FilesSurface } from './FilesSurface';
|
|
39
35
|
import { FormSurface } from './FormSurface';
|
|
@@ -139,19 +135,3 @@ vistaSurfaceRegistry.registerFirstParty(SurfaceKind.Form, {
|
|
|
139
135
|
validate: validateFormPayload,
|
|
140
136
|
render: (payload) => <FormSurface {...payload} />,
|
|
141
137
|
});
|
|
142
|
-
|
|
143
|
-
export { ChartSurface } from './ChartSurface';
|
|
144
|
-
export { FilesSurface, fileExtMeta, groupFilesByFolder } from './FilesSurface';
|
|
145
|
-
export { FormSurface } from './FormSurface';
|
|
146
|
-
export {
|
|
147
|
-
submitElicitationFields,
|
|
148
|
-
readElicitationStatus,
|
|
149
|
-
extractServiceMessage,
|
|
150
|
-
VistaFormSubmitError,
|
|
151
|
-
} from './elicitation-transport';
|
|
152
|
-
export { HtmlSurface } from './HtmlSurface';
|
|
153
|
-
export { TableSurface } from './TableSurface';
|
|
154
|
-
export { UrlSurface } from './UrlSurface';
|
|
155
|
-
export { CodeSurface, DiffSurface, JsonSurface } from './text-surfaces';
|
|
156
|
-
export { ImageSurface, MediaSurface, PdfSurface } from './blob-surfaces';
|
|
157
|
-
export type { VistaFileGroup } from './FilesSurface';
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `plain` tab kind was collapsed into it; one kind, one renderer, no alias).
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { useVistaCodeRenderer } from '../vista-code-renderer';
|
|
12
|
+
import { useVistaCodeRenderer } from '../vista-code-renderer-context';
|
|
13
13
|
import { VistaFileText } from '../vista-file-source';
|
|
14
14
|
|
|
15
15
|
import type { VistaTextPayload } from '../vista-payloads';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
export type VistaCodeRenderer = (args: {
|
|
6
|
+
readonly content: string;
|
|
7
|
+
readonly path?: string;
|
|
8
|
+
readonly language?: string;
|
|
9
|
+
}) => ReactNode;
|
|
10
|
+
|
|
11
|
+
export const VistaCodeRendererContext =
|
|
12
|
+
createContext<VistaCodeRenderer | null>(null);
|
|
13
|
+
|
|
14
|
+
export function useVistaCodeRenderer(): VistaCodeRenderer | null {
|
|
15
|
+
return useContext(VistaCodeRendererContext);
|
|
16
|
+
}
|
|
@@ -14,20 +14,13 @@
|
|
|
14
14
|
* the intended rendering. Neither is an error path.
|
|
15
15
|
*/
|
|
16
16
|
import {
|
|
17
|
-
createContext,
|
|
18
|
-
useContext,
|
|
19
17
|
type ReactElement,
|
|
20
18
|
type ReactNode,
|
|
21
19
|
} from 'react';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
readonly path?: string;
|
|
27
|
-
readonly language?: string;
|
|
28
|
-
}) => ReactNode;
|
|
29
|
-
|
|
30
|
-
const VistaCodeRendererContext = createContext<VistaCodeRenderer | null>(null);
|
|
20
|
+
import {
|
|
21
|
+
VistaCodeRendererContext,
|
|
22
|
+
type VistaCodeRenderer,
|
|
23
|
+
} from './vista-code-renderer-context';
|
|
31
24
|
|
|
32
25
|
export function VistaCodeRendererProvider({
|
|
33
26
|
renderer,
|
|
@@ -39,8 +32,3 @@ export function VistaCodeRendererProvider({
|
|
|
39
32
|
</VistaCodeRendererContext.Provider>
|
|
40
33
|
);
|
|
41
34
|
}
|
|
42
|
-
|
|
43
|
-
/** The host's code renderer, or `null` for the built-in mono pane. */
|
|
44
|
-
export function useVistaCodeRenderer(): VistaCodeRenderer | null {
|
|
45
|
-
return useContext(VistaCodeRendererContext);
|
|
46
|
-
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface VistaFileSource {
|
|
4
|
+
readText(path: string): Promise<string>;
|
|
5
|
+
resolveUrl(path: string): Promise<string>;
|
|
6
|
+
readonly revision?: string | number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const VistaFileSourceContext =
|
|
10
|
+
createContext<VistaFileSource | null>(null);
|
|
11
|
+
|
|
12
|
+
export function useVistaFileSource(): VistaFileSource | null {
|
|
13
|
+
return useContext(VistaFileSourceContext);
|
|
14
|
+
}
|
|
@@ -13,34 +13,16 @@
|
|
|
13
13
|
* "content unavailable".
|
|
14
14
|
*/
|
|
15
15
|
import {
|
|
16
|
-
createContext,
|
|
17
|
-
useContext,
|
|
18
16
|
useEffect,
|
|
19
17
|
useState,
|
|
20
18
|
type ReactElement,
|
|
21
19
|
type ReactNode,
|
|
22
20
|
} from 'react';
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Resolve a workspace-relative path to a fetchable URL (`blob:`, `data:`, or
|
|
30
|
-
* a durable `https:` URL) for binary surfaces. Rejects on failure.
|
|
31
|
-
*
|
|
32
|
-
* The host owns the lifetime of anything it creates (e.g. revoking an object
|
|
33
|
-
* URL); the surface only reads.
|
|
34
|
-
*/
|
|
35
|
-
resolveUrl(path: string): Promise<string>;
|
|
36
|
-
/**
|
|
37
|
-
* Opaque revision token. Change it to make every mounted path-backed surface
|
|
38
|
-
* re-read (the host bumps it on a workspace file-change event).
|
|
39
|
-
*/
|
|
40
|
-
readonly revision?: string | number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const VistaFileSourceContext = createContext<VistaFileSource | null>(null);
|
|
21
|
+
import {
|
|
22
|
+
VistaFileSourceContext,
|
|
23
|
+
useVistaFileSource,
|
|
24
|
+
type VistaFileSource,
|
|
25
|
+
} from './vista-file-context';
|
|
44
26
|
|
|
45
27
|
export function VistaFileSourceProvider({
|
|
46
28
|
source,
|
|
@@ -53,11 +35,6 @@ export function VistaFileSourceProvider({
|
|
|
53
35
|
);
|
|
54
36
|
}
|
|
55
37
|
|
|
56
|
-
/** The active file source, or `null` when no provider is mounted. */
|
|
57
|
-
export function useVistaFileSource(): VistaFileSource | null {
|
|
58
|
-
return useContext(VistaFileSourceContext);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
38
|
/** Async load state for a path-backed surface. */
|
|
62
39
|
type LoadState =
|
|
63
40
|
| { readonly status: 'loading' }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface VistaFormOutcome {
|
|
4
|
+
readonly submitted: true;
|
|
5
|
+
readonly ref: string;
|
|
6
|
+
readonly connectionId: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface VistaFormHost {
|
|
10
|
+
readonly elicitationApiBaseUrl: string;
|
|
11
|
+
getAccessToken(): Promise<string>;
|
|
12
|
+
onOutcome(outcome: VistaFormOutcome): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const VistaFormHostContext =
|
|
16
|
+
createContext<VistaFormHost | null>(null);
|
|
17
|
+
|
|
18
|
+
export function useVistaFormHost(): VistaFormHost | null {
|
|
19
|
+
return useContext(VistaFormHostContext);
|
|
20
|
+
}
|
|
@@ -18,16 +18,22 @@
|
|
|
18
18
|
* 2. `getAccessToken` returns the SUBJECT's token. The host binds it to the
|
|
19
19
|
* logged-in user's session; it is never a service token and never the
|
|
20
20
|
* agent's token, so the service authorizes the human, not the model.
|
|
21
|
-
* 3.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* app's own BFF/chat transport.
|
|
21
|
+
* 3. `elicitationApiBaseUrl` is supplied by trusted host configuration, never
|
|
22
|
+
* by the agent-controlled payload. The kernel validates its scheme and
|
|
23
|
+
* canonical authority/path before deriving the status/submit endpoints.
|
|
25
24
|
*
|
|
26
25
|
* There is deliberately NO default host: a `form` surface mounted without a
|
|
27
26
|
* provider renders a visible refusal and cannot submit. Fail-closed — a form
|
|
28
27
|
* that silently posts nowhere would be worse than one that says it cannot.
|
|
29
28
|
*/
|
|
30
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
type ReactElement,
|
|
31
|
+
type ReactNode,
|
|
32
|
+
} from 'react';
|
|
33
|
+
import {
|
|
34
|
+
VistaFormHostContext,
|
|
35
|
+
type VistaFormHost,
|
|
36
|
+
} from './vista-form-context';
|
|
31
37
|
|
|
32
38
|
/**
|
|
33
39
|
* Everything a settled elicitation reports. Closed, scalar-only, and
|
|
@@ -35,32 +41,6 @@ import { createContext, useContext, type ReactElement, type ReactNode } from 're
|
|
|
35
41
|
* persistence layer behind it has no column able to hold a field value, and
|
|
36
42
|
* neither does this type.
|
|
37
43
|
*/
|
|
38
|
-
export interface VistaFormOutcome {
|
|
39
|
-
/** Always `true`: an unsettled elicitation produces no outcome at all. */
|
|
40
|
-
readonly submitted: true;
|
|
41
|
-
/** The elicitation reference the agent polls. Opaque, non-secret. */
|
|
42
|
-
readonly ref: string;
|
|
43
|
-
/** The connection the submission provisioned, when the service reported one. */
|
|
44
|
-
readonly connectionId: string | null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Host-supplied capabilities for the `form` surface. */
|
|
48
|
-
export interface VistaFormHost {
|
|
49
|
-
/**
|
|
50
|
-
* The SUBJECT's own access token. Called once per request so a refreshed
|
|
51
|
-
* token is picked up without remounting the surface.
|
|
52
|
-
*/
|
|
53
|
-
getAccessToken(): Promise<string>;
|
|
54
|
-
/**
|
|
55
|
-
* Called exactly once when the elicitation settles. The host relays this to
|
|
56
|
-
* whatever needs to know (cache invalidation, the agent session). By type it
|
|
57
|
-
* can carry nothing but the outcome.
|
|
58
|
-
*/
|
|
59
|
-
onOutcome(outcome: VistaFormOutcome): void;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const VistaFormHostContext = createContext<VistaFormHost | null>(null);
|
|
63
|
-
|
|
64
44
|
export function VistaFormHostProvider({
|
|
65
45
|
host,
|
|
66
46
|
children,
|
|
@@ -71,8 +51,3 @@ export function VistaFormHostProvider({
|
|
|
71
51
|
</VistaFormHostContext.Provider>
|
|
72
52
|
);
|
|
73
53
|
}
|
|
74
|
-
|
|
75
|
-
/** The active form host, or `null` when no provider is mounted. */
|
|
76
|
-
export function useVistaFormHost(): VistaFormHost | null {
|
|
77
|
-
return useContext(VistaFormHostContext);
|
|
78
|
-
}
|
|
@@ -12,18 +12,13 @@
|
|
|
12
12
|
* silently does nothing.
|
|
13
13
|
*/
|
|
14
14
|
import {
|
|
15
|
-
createContext,
|
|
16
|
-
useContext,
|
|
17
15
|
type ReactElement,
|
|
18
16
|
type ReactNode,
|
|
19
17
|
} from 'react';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export type VistaOpenFn = (surface: VistaTab) => string;
|
|
25
|
-
|
|
26
|
-
const VistaOpenContext = createContext<VistaOpenFn | null>(null);
|
|
18
|
+
import {
|
|
19
|
+
VistaOpenContext,
|
|
20
|
+
type VistaOpenFn,
|
|
21
|
+
} from './vista-open-state';
|
|
27
22
|
|
|
28
23
|
export function VistaOpenProvider({
|
|
29
24
|
open,
|
|
@@ -35,8 +30,3 @@ export function VistaOpenProvider({
|
|
|
35
30
|
</VistaOpenContext.Provider>
|
|
36
31
|
);
|
|
37
32
|
}
|
|
38
|
-
|
|
39
|
-
/** The panel's `open`, or `null` when no panel is mounted above. */
|
|
40
|
-
export function useVistaOpen(): VistaOpenFn | null {
|
|
41
|
-
return useContext(VistaOpenContext);
|
|
42
|
-
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { VistaTab } from './vista-model';
|
|
4
|
+
|
|
5
|
+
export type VistaOpenFn = (surface: VistaTab) => string;
|
|
6
|
+
|
|
7
|
+
export const VistaOpenContext = createContext<VistaOpenFn | null>(null);
|
|
8
|
+
|
|
9
|
+
export function useVistaOpen(): VistaOpenFn | null {
|
|
10
|
+
return useContext(VistaOpenContext);
|
|
11
|
+
}
|