sgiant-ai-widget 1.0.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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/dist/ai-invalidation.d.ts +29 -0
  4. package/dist/ai-invalidation.d.ts.map +1 -0
  5. package/dist/ai-invalidation.js +121 -0
  6. package/dist/ai-invalidation.js.map +1 -0
  7. package/dist/contrast.d.ts +87 -0
  8. package/dist/contrast.d.ts.map +1 -0
  9. package/dist/contrast.js +151 -0
  10. package/dist/contrast.js.map +1 -0
  11. package/dist/directive.d.ts +37 -0
  12. package/dist/directive.d.ts.map +1 -0
  13. package/dist/directive.js +87 -0
  14. package/dist/directive.js.map +1 -0
  15. package/dist/dom.d.ts +83 -0
  16. package/dist/dom.d.ts.map +1 -0
  17. package/dist/dom.js +146 -0
  18. package/dist/dom.js.map +1 -0
  19. package/dist/host-actions.d.ts +192 -0
  20. package/dist/host-actions.d.ts.map +1 -0
  21. package/dist/host-actions.js +443 -0
  22. package/dist/host-actions.js.map +1 -0
  23. package/dist/icons.d.ts +21 -0
  24. package/dist/icons.d.ts.map +1 -0
  25. package/dist/icons.js +84 -0
  26. package/dist/icons.js.map +1 -0
  27. package/dist/index.d.ts +600 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +4833 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/labels.d.ts +187 -0
  32. package/dist/labels.d.ts.map +1 -0
  33. package/dist/labels.js +232 -0
  34. package/dist/labels.js.map +1 -0
  35. package/dist/limits.d.ts +43 -0
  36. package/dist/limits.d.ts.map +1 -0
  37. package/dist/limits.js +48 -0
  38. package/dist/limits.js.map +1 -0
  39. package/dist/markdown.d.ts +23 -0
  40. package/dist/markdown.d.ts.map +1 -0
  41. package/dist/markdown.js +0 -0
  42. package/dist/markdown.js.map +1 -0
  43. package/dist/message-chrome.d.ts +45 -0
  44. package/dist/message-chrome.d.ts.map +1 -0
  45. package/dist/message-chrome.js +101 -0
  46. package/dist/message-chrome.js.map +1 -0
  47. package/dist/pane-follow.d.ts +67 -0
  48. package/dist/pane-follow.d.ts.map +1 -0
  49. package/dist/pane-follow.js +61 -0
  50. package/dist/pane-follow.js.map +1 -0
  51. package/dist/prefix.d.ts +9 -0
  52. package/dist/prefix.d.ts.map +1 -0
  53. package/dist/prefix.js +9 -0
  54. package/dist/prefix.js.map +1 -0
  55. package/dist/proposal-summary.d.ts +19 -0
  56. package/dist/proposal-summary.d.ts.map +1 -0
  57. package/dist/proposal-summary.js +73 -0
  58. package/dist/proposal-summary.js.map +1 -0
  59. package/dist/replay.d.ts +125 -0
  60. package/dist/replay.d.ts.map +1 -0
  61. package/dist/replay.js +172 -0
  62. package/dist/replay.js.map +1 -0
  63. package/dist/safe-url.d.ts +47 -0
  64. package/dist/safe-url.d.ts.map +1 -0
  65. package/dist/safe-url.js +80 -0
  66. package/dist/safe-url.js.map +1 -0
  67. package/dist/sgiant-ai-widget.global.js +778 -0
  68. package/dist/sgiant-ai-widget.global.js.map +7 -0
  69. package/dist/specs.d.ts +173 -0
  70. package/dist/specs.d.ts.map +1 -0
  71. package/dist/specs.js +231 -0
  72. package/dist/specs.js.map +1 -0
  73. package/dist/storage.d.ts +77 -0
  74. package/dist/storage.d.ts.map +1 -0
  75. package/dist/storage.js +124 -0
  76. package/dist/storage.js.map +1 -0
  77. package/dist/styles.d.ts +29 -0
  78. package/dist/styles.d.ts.map +1 -0
  79. package/dist/styles.js +876 -0
  80. package/dist/styles.js.map +1 -0
  81. package/dist/ui-control.d.ts +8 -0
  82. package/dist/ui-control.d.ts.map +1 -0
  83. package/dist/ui-control.js +8 -0
  84. package/dist/ui-control.js.map +1 -0
  85. package/dist/ui-render.d.ts +75 -0
  86. package/dist/ui-render.d.ts.map +1 -0
  87. package/dist/ui-render.js +617 -0
  88. package/dist/ui-render.js.map +1 -0
  89. package/dist/ui-spec.d.ts +143 -0
  90. package/dist/ui-spec.d.ts.map +1 -0
  91. package/dist/ui-spec.js +288 -0
  92. package/dist/ui-spec.js.map +1 -0
  93. package/package.json +61 -0
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The directive vocabulary: what the model may emit inside `[[tag:{json}]]`,
3
+ * and the parsing that turns it into something a renderer can draw.
4
+ *
5
+ * Extracted from index.ts (#320) at zero leakage. This is the half of the
6
+ * widget's extension seam that #306 calls "a real extension point" — the model
7
+ * emits a directive, the host registers a renderer, the widget hands over the
8
+ * parsed JSON. A third party writing their own renderer needs these shapes,
9
+ * and needs to find them without reading 7000 lines.
10
+ *
11
+ * `stripDirectivesForReplay` lives here rather than beside the replay code
12
+ * because it is the same grammar read in the other direction: it removes
13
+ * exactly what this file defines.
14
+ */
15
+ /** A data widget the assistant can render inline via `[[widget:{json}]]`. */
16
+ export interface WidgetSpec {
17
+ /** "stat" | "kpis" | "list" | "table". Unknown kinds fall back to a list. */
18
+ kind?: string;
19
+ title?: string;
20
+ /** stat: the big value + caption + optional delta. */
21
+ value?: string | number;
22
+ caption?: string;
23
+ delta?: string;
24
+ /** kpis: tiles of {label,value}. */
25
+ items?: Array<{
26
+ label?: string;
27
+ value?: string | number;
28
+ delta?: string;
29
+ }>;
30
+ /** list: plain bullet lines. */
31
+ lines?: string[];
32
+ /** table: header columns + row cells. */
33
+ columns?: string[];
34
+ rows?: Array<Array<string | number>>;
35
+ }
36
+ /** A navigation suggestion the assistant emits via `[[navigate:{json}]]`. */
37
+ export interface NavigateSpec {
38
+ path: string;
39
+ label?: string;
40
+ }
41
+ /** A dynamic HTML preview the assistant draws via `[[preview:{json}]]` — rendered
42
+ * in a fully sandboxed iframe (no scripts, no same-origin) so arbitrary HTML+CSS
43
+ * paints the real look but nothing can execute or reach out. */
44
+ export interface PreviewSpec {
45
+ html: string;
46
+ title?: string;
47
+ }
48
+ /** An in-app action the assistant proposes via `[[action:{json}]]`. The host
49
+ * maps `name` to a real operation; `confirm` (if set) requires user approval. */
50
+ export interface ActionSpec {
51
+ name: string;
52
+ label?: string;
53
+ /** Confirmation prompt — when set, the user must approve before it runs. */
54
+ confirm?: string;
55
+ /** Opaque data passed to the host's onWidgetAction(name, data). */
56
+ data?: Record<string, string>;
57
+ }
58
+ /** Quick-reply chips the assistant offers via `[[chips:{json}]]` — tappable
59
+ * answer options so the user picks instead of typing. Single-select sends on
60
+ * tap; multi-select toggles + a Send button; `other` adds a "type your own"
61
+ * chip. The chosen text is sent as a NORMAL message (history stays in order). */
62
+ export interface ChipsSpec {
63
+ options: string[];
64
+ multi?: boolean;
65
+ other?: boolean;
66
+ }
67
+ /** Sentinel the assistant emits to ask the widget to render an email form. */
68
+ export declare const LEAD_TOKEN = "[[collect-email]]";
69
+ /** One field in an AI-rendered form directive. */
70
+ export interface FormField {
71
+ name: string;
72
+ label?: string;
73
+ /** Kept in step with what `buildField` can actually draw — a spec allowed to
74
+ * ask for a control the builder cannot render is a promise to the model that
75
+ * the UI then breaks. */
76
+ type?: "text" | "email" | "number" | "textarea" | "select" | "checkbox" | "radio";
77
+ placeholder?: string;
78
+ required?: boolean;
79
+ options?: string[];
80
+ }
81
+ export interface FormSpec {
82
+ action: string;
83
+ title?: string;
84
+ fields: FormField[];
85
+ submit?: string;
86
+ }
87
+ /**
88
+ * A field the ASSISTANT asked the user to fill in on a proposal card.
89
+ *
90
+ * Declared by the proposal, never by the widget. A table of "which args are
91
+ * editable for which tool" hardcoded in the UI would mean the chat can only
92
+ * ever ask the questions the frontend was built to ask — a new tool, or a
93
+ * decision the model wants confirmed, would need a UI release. The model knows
94
+ * what it is unsure about; it says so, and the card renders it.
95
+ *
96
+ * Same shape as a `[[form:…]]` field, and built by the same `buildField`, so an
97
+ * input looks identical wherever the chat draws one.
98
+ */
99
+ export interface ProposalField {
100
+ /** The proposal ARG this field overwrites on apply. */
101
+ arg: string;
102
+ label?: string;
103
+ type?: string;
104
+ placeholder?: string;
105
+ options?: string[];
106
+ required?: boolean;
107
+ }
108
+ /**
109
+ * A control `buildField` produced, plus how to read its answer.
110
+ *
111
+ * The reader is part of the return value because "what the user chose" is not
112
+ * `.value` for every control type — a checkbox is `.checked`, a radio group is
113
+ * whichever of its inputs is checked — and every caller wants the same thing: a
114
+ * string to put in the payload.
115
+ */
116
+ export interface BuiltField {
117
+ /** Append THIS — the control, or the wrapper a labelled/grouped one needs. */
118
+ node: HTMLElement;
119
+ read: () => string;
120
+ /** The control draws its own caption; a caller adding one would double it. */
121
+ selfLabelled?: boolean;
122
+ }
123
+ /** Truthiness for a prefilled checkbox — model output, so accept the obvious
124
+ * spellings rather than demanding exactly `"true"`. */
125
+ export declare function isTruthyValue(v: string | undefined): boolean;
126
+ /** A proposal arg a field can be PREFILLED from: anything JSON-primitive, which
127
+ * is everything with one obvious rendering. Objects/arrays have none, and
128
+ * null/undefined mean the assistant proposed nothing for that arg. */
129
+ export declare function isPrimitiveArg(v: unknown): v is string | number | boolean;
130
+ /** Read the fields off a proposal frame, defensively — this is model output. */
131
+ export declare function proposalFields(raw: unknown): ProposalField[];
132
+ /** Pull a `[[form:{json}]]` directive out of assistant text, if present. Uses
133
+ * the shared brace-matching extractor, then validates the form shape. */
134
+ export declare function parseFormDirective(text: string): {
135
+ spec: FormSpec;
136
+ stripped: string;
137
+ } | null;
138
+ /**
139
+ * REPLAY view of a stored message: strip every interactive directive (which the
140
+ * live turn already rendered as buttons/forms) so a reopened conversation shows
141
+ * clean prose instead of raw `[[navigate:…]]` / `[[form:…]]` code, with a short
142
+ * INERT note per directive (no re-execution). Used by the history/thread
143
+ * restore path; live turns still render the real interactive widgets.
144
+ */
145
+ export declare function stripDirectivesForReplay(text: string): {
146
+ clean: string;
147
+ notes: string[];
148
+ navs: NavigateSpec[];
149
+ uis: unknown[];
150
+ };
151
+ /**
152
+ * Build one editable control for a proposal form, plus how to READ it back.
153
+ *
154
+ * Moved here from index.ts (#320) with ZERO closure dependencies — the whole
155
+ * function reaches only `el`, `PREFIX`, and this file's own `BuiltField` and
156
+ * `isTruthyValue`. It sat in the 6000-line closure purely because that is
157
+ * where it was typed; nothing about it was ever widget state.
158
+ *
159
+ * `read` is the point of the return shape: every control answers as a STRING
160
+ * whatever it was drawn as, because the submit path posts a flat
161
+ * `Record<string, string>`. A checkbox that returned a boolean would be the
162
+ * one field in the form with a different contract.
163
+ */
164
+ export declare function buildField(field: {
165
+ name: string;
166
+ type?: string;
167
+ label?: string;
168
+ placeholder?: string;
169
+ options?: string[];
170
+ required?: boolean;
171
+ value?: string;
172
+ }): BuiltField;
173
+ //# sourceMappingURL=specs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specs.d.ts","sourceRoot":"","sources":["../src/specs.ts"],"names":[],"mappings":"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"}
package/dist/specs.js ADDED
@@ -0,0 +1,231 @@
1
+ /**
2
+ * The directive vocabulary: what the model may emit inside `[[tag:{json}]]`,
3
+ * and the parsing that turns it into something a renderer can draw.
4
+ *
5
+ * Extracted from index.ts (#320) at zero leakage. This is the half of the
6
+ * widget's extension seam that #306 calls "a real extension point" — the model
7
+ * emits a directive, the host registers a renderer, the widget hands over the
8
+ * parsed JSON. A third party writing their own renderer needs these shapes,
9
+ * and needs to find them without reading 7000 lines.
10
+ *
11
+ * `stripDirectivesForReplay` lives here rather than beside the replay code
12
+ * because it is the same grammar read in the other direction: it removes
13
+ * exactly what this file defines.
14
+ */
15
+ import { parseJsonDirective } from "./directive.js";
16
+ import { el } from "./dom.js";
17
+ import { PREFIX } from "./prefix.js";
18
+ /** Sentinel the assistant emits to ask the widget to render an email form. */
19
+ export const LEAD_TOKEN = "[[collect-email]]";
20
+ /** Truthiness for a prefilled checkbox — model output, so accept the obvious
21
+ * spellings rather than demanding exactly `"true"`. */
22
+ export function isTruthyValue(v) {
23
+ return ["true", "1", "yes", "on"].includes((v ?? "").trim().toLowerCase());
24
+ }
25
+ /** A proposal arg a field can be PREFILLED from: anything JSON-primitive, which
26
+ * is everything with one obvious rendering. Objects/arrays have none, and
27
+ * null/undefined mean the assistant proposed nothing for that arg. */
28
+ export function isPrimitiveArg(v) {
29
+ return (typeof v === "string" || typeof v === "number" || typeof v === "boolean");
30
+ }
31
+ /** Read the fields off a proposal frame, defensively — this is model output. */
32
+ export function proposalFields(raw) {
33
+ if (!Array.isArray(raw))
34
+ return [];
35
+ return raw
36
+ .filter((f) => Boolean(f) &&
37
+ typeof f === "object" &&
38
+ typeof f.arg === "string" &&
39
+ Boolean(f.arg))
40
+ .slice(0, 6);
41
+ }
42
+ /** Pull a `[[form:{json}]]` directive out of assistant text, if present. Uses
43
+ * the shared brace-matching extractor, then validates the form shape. */
44
+ export function parseFormDirective(text) {
45
+ const r = parseJsonDirective(text, "form");
46
+ if (!r)
47
+ return null;
48
+ const { spec } = r;
49
+ if (!spec || typeof spec.action !== "string" || !Array.isArray(spec.fields))
50
+ return null;
51
+ return r;
52
+ }
53
+ /**
54
+ * REPLAY view of a stored message: strip every interactive directive (which the
55
+ * live turn already rendered as buttons/forms) so a reopened conversation shows
56
+ * clean prose instead of raw `[[navigate:…]]` / `[[form:…]]` code, with a short
57
+ * INERT note per directive (no re-execution). Used by the history/thread
58
+ * restore path; live turns still render the real interactive widgets.
59
+ */
60
+ export function stripDirectivesForReplay(text) {
61
+ let t = text;
62
+ const notes = [];
63
+ const uis = [];
64
+ // Navigation is idempotent and side-effect-free, so on replay we hand it back
65
+ // to be re-rendered as a REAL clickable chip rather than flattened to an inert
66
+ // note — otherwise every "Open <page>" the assistant offered goes dead the
67
+ // moment the thread reloads (which send() does after each turn for canonical
68
+ // ids). This is the true cause of #111: the model DID emit [[navigate]], the
69
+ // restore path is what killed it.
70
+ const navs = [];
71
+ for (let i = 0; i < 8; i++) {
72
+ const w = parseJsonDirective(t, "navigate");
73
+ if (!w)
74
+ break;
75
+ t = w.stripped;
76
+ if (w.spec.path)
77
+ navs.push(w.spec);
78
+ else
79
+ notes.push(`↗ ${w.spec.label || "Open page"}`);
80
+ }
81
+ for (let i = 0; i < 8; i++) {
82
+ const w = parseJsonDirective(t, "action");
83
+ if (!w)
84
+ break;
85
+ t = w.stripped;
86
+ notes.push(`• ${w.spec.label || w.spec.name}`);
87
+ }
88
+ for (let i = 0; i < 8; i++) {
89
+ const w = parseJsonDirective(t, "widget");
90
+ if (!w)
91
+ break;
92
+ t = w.stripped;
93
+ notes.push(`▦ ${w.spec.title || "widget"}`);
94
+ }
95
+ // Composed UI cards are handed BACK to be re-drawn, not flattened to a note.
96
+ // A card is the substance of the turn, not a decoration on it: a client who
97
+ // reopens the conversation tomorrow to look at the scenes they were approving
98
+ // would otherwise find "▦ card" where the scenes had been. Re-drawing is safe
99
+ // because every button on a card requires a click — nothing here can run by
100
+ // itself on restore.
101
+ for (let i = 0; i < 4; i++) {
102
+ const u = parseJsonDirective(t, "ui");
103
+ if (!u)
104
+ break;
105
+ t = u.stripped;
106
+ uis.push(u.spec);
107
+ }
108
+ for (let i = 0; i < 3; i++) {
109
+ const p = parseJsonDirective(t, "preview");
110
+ if (!p)
111
+ break;
112
+ t = p.stripped;
113
+ notes.push(`[preview] ${p.spec.title || "Preview"}`);
114
+ }
115
+ for (let i = 0; i < 4; i++) {
116
+ const c = parseJsonDirective(t, "chips");
117
+ if (!c)
118
+ break;
119
+ t = c.stripped;
120
+ notes.push("💬 options offered");
121
+ }
122
+ const f = parseFormDirective(t);
123
+ if (f) {
124
+ t = f.stripped;
125
+ notes.push(`📝 ${f.spec.title || "Form"} — submitted`);
126
+ }
127
+ if (t.includes(LEAD_TOKEN)) {
128
+ t = t.replace(LEAD_TOKEN, "").trim();
129
+ notes.push("📝 Form — submitted");
130
+ }
131
+ return { clean: t, notes, navs, uis };
132
+ }
133
+ /**
134
+ * Build one editable control for a proposal form, plus how to READ it back.
135
+ *
136
+ * Moved here from index.ts (#320) with ZERO closure dependencies — the whole
137
+ * function reaches only `el`, `PREFIX`, and this file's own `BuiltField` and
138
+ * `isTruthyValue`. It sat in the 6000-line closure purely because that is
139
+ * where it was typed; nothing about it was ever widget state.
140
+ *
141
+ * `read` is the point of the return shape: every control answers as a STRING
142
+ * whatever it was drawn as, because the submit path posts a flat
143
+ * `Record<string, string>`. A checkbox that returned a boolean would be the
144
+ * one field in the form with a different contract.
145
+ */
146
+ export function buildField(field) {
147
+ const type = field.type ?? "text";
148
+ // Boolean/choice controls carry their own caption — a bare 16px box with
149
+ // the label somewhere above it reads as decoration, not as a question.
150
+ if (type === "checkbox") {
151
+ const row = el("label", `${PREFIX}-field-check`);
152
+ const box = el("input", `${PREFIX}-check`);
153
+ box.type = "checkbox";
154
+ box.checked = isTruthyValue(field.value);
155
+ if (field.required)
156
+ box.required = true;
157
+ row.appendChild(box);
158
+ const cap = el("span", `${PREFIX}-field-check-label`);
159
+ cap.textContent = field.label ?? field.placeholder ?? field.name;
160
+ row.appendChild(cap);
161
+ // Stringified so a field's answer is always a string, whatever it was
162
+ // drawn as — the submit path posts a flat Record<string,string>.
163
+ return {
164
+ node: row,
165
+ selfLabelled: true,
166
+ read: () => (box.checked ? "true" : "false"),
167
+ };
168
+ }
169
+ if (type === "radio" && field.options?.length) {
170
+ const group = el("div", `${PREFIX}-field-group`);
171
+ // The `name` attribute is what makes radios mutually exclusive, so it has
172
+ // to be unique per rendered group — two proposal cards asking the same
173
+ // question would otherwise fight over one selection.
174
+ const groupName = `${PREFIX}-${field.name}-${Math.random().toString(36).slice(2, 9)}`;
175
+ for (const o of field.options) {
176
+ const row = el("label", `${PREFIX}-field-check`);
177
+ const radio = el("input", `${PREFIX}-check`);
178
+ radio.type = "radio";
179
+ radio.name = groupName;
180
+ radio.value = o;
181
+ // Required propagates to the inputs (one required radio makes the whole
182
+ // group required). It was dropped here, so a required choice reported
183
+ // itself as answered while nothing was selected.
184
+ if (field.required)
185
+ radio.required = true;
186
+ if (o === field.value)
187
+ radio.checked = true;
188
+ row.appendChild(radio);
189
+ const cap = el("span", `${PREFIX}-field-check-label`);
190
+ cap.textContent = o;
191
+ row.appendChild(cap);
192
+ group.appendChild(row);
193
+ }
194
+ return {
195
+ node: group,
196
+ read: () => group.querySelector("input:checked")?.value ?? "",
197
+ };
198
+ }
199
+ let input;
200
+ if (type === "textarea") {
201
+ input = el("textarea", `${PREFIX}-field`);
202
+ }
203
+ else if (type === "select") {
204
+ const sel = el("select", `${PREFIX}-field`);
205
+ for (const o of field.options ?? []) {
206
+ const opt = document.createElement("option");
207
+ opt.value = o;
208
+ opt.textContent = o;
209
+ sel.appendChild(opt);
210
+ }
211
+ input = sel;
212
+ }
213
+ else {
214
+ const i = el("input", `${PREFIX}-field`);
215
+ i.type = type === "number" ? "number" : type;
216
+ input = i;
217
+ }
218
+ if ("placeholder" in input && field.placeholder)
219
+ input.placeholder = field.label
220
+ ? `${field.label} — ${field.placeholder}`
221
+ : field.placeholder;
222
+ else if ("placeholder" in input && field.label)
223
+ input.placeholder = field.label;
224
+ if (field.required)
225
+ input.required = true;
226
+ // Prefilled: the card hands back a value the user can accept or rewrite.
227
+ if (field.value !== undefined)
228
+ input.value = field.value;
229
+ return { node: input, read: () => input.value.trim() };
230
+ }
231
+ //# sourceMappingURL=specs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specs.js","sourceRoot":"","sources":["../src/specs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Every persisted value the widget owns, and the two rules that govern all of
3
+ * them.
4
+ *
5
+ * Extracted from index.ts (#320). Storage was not one region of that file — it
6
+ * was 33 `localStorage` touches spread across 8 key-scoped concerns, from the
7
+ * draft textarea to the launcher position to the advanced-pane width. Each site
8
+ * re-implemented the same two rules, which is how they drifted:
9
+ *
10
+ * 1. AN EMPTY KEY MEANS PERSISTENCE IS OFF. Most keys are derived from
11
+ * `opts.persistKey`, which is optional: no persistKey, no writing to a
12
+ * visitor's browser. That was eight separate `if (!key) return` guards.
13
+ * 2. BLOCKED STORAGE IS NON-FATAL. Private windows, cleared site data and
14
+ * "block third-party cookies" all throw on access rather than returning
15
+ * null, so every read and every write needs a try/catch. That was ten
16
+ * of them, each with its own comment saying the same thing.
17
+ *
18
+ * THE ONE PLACE THOSE RULES ARE NOT UNIFORM is the point of collecting them.
19
+ * `hasOpenedBefore` returns TRUE when storage is unreadable, while every other
20
+ * flag defaults to false. That is deliberate — a returning reader should get
21
+ * the quiet pebble, not the first-visit pill on every single page load — but in
22
+ * index.ts it was one `return true` inside one of ten identical-looking catch
23
+ * blocks, indistinguishable from a typo. Here it is an argument with a name.
24
+ *
25
+ * These helpers take the key rather than closing over it, so they are callable
26
+ * from anywhere and testable without constructing a widget.
27
+ */
28
+ /**
29
+ * A key that names a real slot.
30
+ *
31
+ * "Persistence is off" is spelled TWO ways in the caller — `null` for the five
32
+ * keys derived from `opts.persistKey`, `""` for the two derived from the
33
+ * advanced pane. Neither spelling is wrong and unifying them in index.ts would
34
+ * be a behaviour-free diff across a 5800-line file, so this is the one place
35
+ * that has to know both, and the only place that should.
36
+ */
37
+ type StorageKey = string | null | undefined;
38
+ /**
39
+ * Read a string. Returns `fallback` when the key is off, absent, or storage is
40
+ * unreadable — the three cases callers have never needed to tell apart.
41
+ */
42
+ export declare function readItem(key: StorageKey, fallback?: string): string;
43
+ /** Write a string, or REMOVE the key when given an empty value or null. */
44
+ export declare function writeItem(key: StorageKey, value: string | null): void;
45
+ /**
46
+ * Read a `"1"`/`"0"` flag.
47
+ *
48
+ * ABSENT AND UNREADABLE ARE DIFFERENT STATES, and collapsing them is a bug this
49
+ * function was written with and had to have removed. A key that is simply not
50
+ * set means "false" — the flag has never been turned on. `whenUnavailable` is
51
+ * only for the case where storage THREW, i.e. the answer is unknowable rather
52
+ * than no.
53
+ *
54
+ * The distinction is invisible in four of the five callers, whose answer is
55
+ * false either way. In the fifth it inverts the first-visit experience: a
56
+ * first-ever visitor has no `sg_ayca_opened` key, and answering "unavailable"
57
+ * there would greet them as a returning reader and suppress the pill they are
58
+ * supposed to see.
59
+ */
60
+ export declare function readFlag(key: StorageKey, whenUnavailable?: boolean): boolean;
61
+ /** Write a `"1"`/`"0"` flag. */
62
+ export declare function writeFlag(key: StorageKey, on: boolean): void;
63
+ /**
64
+ * Read JSON and hand it to a validator before returning it.
65
+ *
66
+ * The validator is required rather than optional on purpose: everything in
67
+ * localStorage was written by a PREVIOUS BUILD of this widget and may not match
68
+ * the current shape. Two of the callers here already learned that — the tracked
69
+ * jobs list deliberately accepts entries with no `kind` because a build that
70
+ * predates the field wrote them, and dropping those would lose the card for
71
+ * work still running across a deploy.
72
+ */
73
+ export declare function readJson<T>(key: StorageKey, isValid: (value: unknown) => value is T): T | null;
74
+ /** Write JSON, or REMOVE the key when given null. */
75
+ export declare function writeJson(key: StorageKey, value: unknown | null): void;
76
+ export {};
77
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;;;;;;;GAQG;AACH,KAAK,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAG5C;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,SAAK,GAAG,MAAM,CAO/D;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAQrE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,eAAe,UAAQ,GAAG,OAAO,CAO1E;AAED,gCAAgC;AAChC,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAE5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,GACtC,CAAC,GAAG,IAAI,CAUV;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAQtE"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Every persisted value the widget owns, and the two rules that govern all of
3
+ * them.
4
+ *
5
+ * Extracted from index.ts (#320). Storage was not one region of that file — it
6
+ * was 33 `localStorage` touches spread across 8 key-scoped concerns, from the
7
+ * draft textarea to the launcher position to the advanced-pane width. Each site
8
+ * re-implemented the same two rules, which is how they drifted:
9
+ *
10
+ * 1. AN EMPTY KEY MEANS PERSISTENCE IS OFF. Most keys are derived from
11
+ * `opts.persistKey`, which is optional: no persistKey, no writing to a
12
+ * visitor's browser. That was eight separate `if (!key) return` guards.
13
+ * 2. BLOCKED STORAGE IS NON-FATAL. Private windows, cleared site data and
14
+ * "block third-party cookies" all throw on access rather than returning
15
+ * null, so every read and every write needs a try/catch. That was ten
16
+ * of them, each with its own comment saying the same thing.
17
+ *
18
+ * THE ONE PLACE THOSE RULES ARE NOT UNIFORM is the point of collecting them.
19
+ * `hasOpenedBefore` returns TRUE when storage is unreadable, while every other
20
+ * flag defaults to false. That is deliberate — a returning reader should get
21
+ * the quiet pebble, not the first-visit pill on every single page load — but in
22
+ * index.ts it was one `return true` inside one of ten identical-looking catch
23
+ * blocks, indistinguishable from a typo. Here it is an argument with a name.
24
+ *
25
+ * These helpers take the key rather than closing over it, so they are callable
26
+ * from anywhere and testable without constructing a widget.
27
+ */
28
+ const live = (key) => Boolean(key);
29
+ /**
30
+ * Read a string. Returns `fallback` when the key is off, absent, or storage is
31
+ * unreadable — the three cases callers have never needed to tell apart.
32
+ */
33
+ export function readItem(key, fallback = "") {
34
+ if (!live(key))
35
+ return fallback;
36
+ try {
37
+ return localStorage.getItem(key) ?? fallback;
38
+ }
39
+ catch {
40
+ return fallback;
41
+ }
42
+ }
43
+ /** Write a string, or REMOVE the key when given an empty value or null. */
44
+ export function writeItem(key, value) {
45
+ if (!live(key))
46
+ return;
47
+ try {
48
+ if (value)
49
+ localStorage.setItem(key, value);
50
+ else
51
+ localStorage.removeItem(key);
52
+ }
53
+ catch {
54
+ /* storage full or blocked — the value just does not persist */
55
+ }
56
+ }
57
+ /**
58
+ * Read a `"1"`/`"0"` flag.
59
+ *
60
+ * ABSENT AND UNREADABLE ARE DIFFERENT STATES, and collapsing them is a bug this
61
+ * function was written with and had to have removed. A key that is simply not
62
+ * set means "false" — the flag has never been turned on. `whenUnavailable` is
63
+ * only for the case where storage THREW, i.e. the answer is unknowable rather
64
+ * than no.
65
+ *
66
+ * The distinction is invisible in four of the five callers, whose answer is
67
+ * false either way. In the fifth it inverts the first-visit experience: a
68
+ * first-ever visitor has no `sg_ayca_opened` key, and answering "unavailable"
69
+ * there would greet them as a returning reader and suppress the pill they are
70
+ * supposed to see.
71
+ */
72
+ export function readFlag(key, whenUnavailable = false) {
73
+ if (!live(key))
74
+ return false;
75
+ try {
76
+ return localStorage.getItem(key) === "1";
77
+ }
78
+ catch {
79
+ return whenUnavailable;
80
+ }
81
+ }
82
+ /** Write a `"1"`/`"0"` flag. */
83
+ export function writeFlag(key, on) {
84
+ writeItem(key, on ? "1" : "0");
85
+ }
86
+ /**
87
+ * Read JSON and hand it to a validator before returning it.
88
+ *
89
+ * The validator is required rather than optional on purpose: everything in
90
+ * localStorage was written by a PREVIOUS BUILD of this widget and may not match
91
+ * the current shape. Two of the callers here already learned that — the tracked
92
+ * jobs list deliberately accepts entries with no `kind` because a build that
93
+ * predates the field wrote them, and dropping those would lose the card for
94
+ * work still running across a deploy.
95
+ */
96
+ export function readJson(key, isValid) {
97
+ if (!live(key))
98
+ return null;
99
+ try {
100
+ const raw = localStorage.getItem(key);
101
+ if (!raw)
102
+ return null;
103
+ const parsed = JSON.parse(raw);
104
+ return isValid(parsed) ? parsed : null;
105
+ }
106
+ catch {
107
+ return null; // corrupt, or unreadable — either way, start fresh
108
+ }
109
+ }
110
+ /** Write JSON, or REMOVE the key when given null. */
111
+ export function writeJson(key, value) {
112
+ if (!live(key))
113
+ return;
114
+ try {
115
+ if (value === null)
116
+ localStorage.removeItem(key);
117
+ else
118
+ localStorage.setItem(key, JSON.stringify(value));
119
+ }
120
+ catch {
121
+ /* storage full or blocked — non-fatal */
122
+ }
123
+ }
124
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAYH,MAAM,IAAI,GAAG,CAAC,GAAe,EAAiB,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAE9D;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAe,EAAE,QAAQ,GAAG,EAAE;IACrD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IAChC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,SAAS,CAAC,GAAe,EAAE,KAAoB;IAC7D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO;IACvB,IAAI,CAAC;QACH,IAAI,KAAK;YAAE,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;;YACvC,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,+DAA+D;IACjE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAe,EAAE,eAAe,GAAG,KAAK;IAC/D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,eAAe,CAAC;IACzB,CAAC;AACH,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,SAAS,CAAC,GAAe,EAAE,EAAW;IACpD,SAAS,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CACtB,GAAe,EACf,OAAuC;IAEvC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,mDAAmD;IAClE,CAAC;AACH,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,GAAe,EAAE,KAAqB;IAC9D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO;IACvB,IAAI,CAAC;QACH,IAAI,KAAK,KAAK,IAAI;YAAE,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;;YAC5C,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,yCAAyC;IAC3C,CAAC;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Does the page around us define the PLATFORM's control variables?
3
+ *
4
+ * When the widget is mounted inside one of our own apps, `--input` / `--ring` /
5
+ * `--card` / … are already on `<html>` (set by the host's own stylesheet), and
6
+ * a text field in chat should be the same object as a text field on the page
7
+ * behind it — same border, same focus ring, same radius, flipping together when
8
+ * the app toggles `.dark`. When the widget is embedded on a customer's site,
9
+ * none of that exists and it must fall back to its own `--aiw-*` palette.
10
+ *
11
+ * The check is deliberately shape-based, not merely presence-based: `--input`
12
+ * is a plausible name for anyone to invent, but our tokens are HSL COMPONENTS
13
+ * ("220 9% 85%") so they can be used with an alpha. A site whose `--input` is
14
+ * `#fff` fails the test and we leave its page alone — the failure mode of
15
+ * guessing wrong here is an unreadable chat form on someone else's website.
16
+ */
17
+ /**
18
+ * The near-black the user bubble mixes the accent into.
19
+ *
20
+ * Exported because `index.ts` derives that bubble's foreground against the
21
+ * RESULT of the mix, not against the accent (#307 one level down: over the teal
22
+ * all three hosts pass, the bubble is `#4a9d9e` and the old literal `#fff` was
23
+ * 3.18:1). Two copies of this number would put the derivation and the paint on
24
+ * different backgrounds, which is the same bug again.
25
+ */
26
+ export declare const USER_BUBBLE_INK = "#04191b";
27
+ export declare function hostDefinesPlatformTokens(): boolean;
28
+ export declare function injectStyles(side: "left" | "right"): void;
29
+ //# sourceMappingURL=styles.d.ts.map