@widgetic/creator 0.3.72 → 0.3.74
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/CreatorApp.svelte +821 -73
- package/dist/components/BrandKitPanel.svelte +556 -0
- package/dist/components/BrandKitPanel.svelte.d.ts +32 -0
- package/dist/components/DesignQuickEdit.svelte +218 -0
- package/dist/components/DesignQuickEdit.svelte.d.ts +33 -0
- package/dist/components/FileUrlSourceControl.svelte +69 -0
- package/dist/components/FileUrlSourceControl.svelte.d.ts +15 -0
- package/dist/components/VersionDropdownRow.svelte +86 -1
- package/dist/components/VersionDropdownRow.svelte.d.ts +10 -0
- package/dist/components/WidgetDetails.svelte +19 -2
- package/dist/components/WidgetShapeLivePreviews.svelte +8 -3
- package/dist/retiredSurface.test.d.ts +1 -0
- package/dist/retiredSurface.test.js +113 -0
- package/dist/stores/websocketStore.d.ts +3 -0
- package/dist/stores/websocketStore.js +4 -0
- package/dist/utils/canvasShapeSnapClient.d.ts +27 -0
- package/dist/utils/canvasShapeSnapClient.js +67 -0
- package/dist/utils/designIntentJevClient.d.ts +32 -0
- package/dist/utils/designIntentJevClient.js +51 -0
- package/dist/utils/designIntentResolver.d.ts +107 -0
- package/dist/utils/designIntentResolver.js +509 -0
- package/dist/utils/designIntentResolver.test.d.ts +1 -0
- package/dist/utils/designIntentResolver.test.js +246 -0
- package/dist/utils/designIntentTelemetry.d.ts +176 -0
- package/dist/utils/designIntentTelemetry.js +474 -0
- package/dist/utils/designIntentTelemetry.test.d.ts +1 -0
- package/dist/utils/designIntentTelemetry.test.js +385 -0
- package/dist/utils/designQuickEditValues.d.ts +51 -0
- package/dist/utils/designQuickEditValues.js +77 -0
- package/dist/utils/designQuickEditValues.test.d.ts +1 -0
- package/dist/utils/designQuickEditValues.test.js +100 -0
- package/dist/utils/pinScrollToBottom.js +9 -2
- package/package.json +2 -2
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { collectDesignProperties, resolveDesignIntent, toDesignControlRequest } from './designIntentResolver';
|
|
3
|
+
/**
|
|
4
|
+
* A widget-shaped design schema (the post-codegen shape: one componentGroup,
|
|
5
|
+
* components carrying `properties`, options under `inputController.config`).
|
|
6
|
+
*/
|
|
7
|
+
const SCHEMA = {
|
|
8
|
+
componentGroups: [
|
|
9
|
+
{
|
|
10
|
+
name: 'Design',
|
|
11
|
+
components: [
|
|
12
|
+
{
|
|
13
|
+
id: 'appearance',
|
|
14
|
+
type: 'layoutGroup',
|
|
15
|
+
propertyGroups: [
|
|
16
|
+
{
|
|
17
|
+
name: 'Colors',
|
|
18
|
+
properties: [
|
|
19
|
+
{
|
|
20
|
+
id: 'backgroundColor',
|
|
21
|
+
type: 'string',
|
|
22
|
+
defaultValue: '#ffffff',
|
|
23
|
+
inputController: { type: 'colorPicker', description: 'Background Color' }
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'titleColor',
|
|
27
|
+
type: 'string',
|
|
28
|
+
defaultValue: '#111827',
|
|
29
|
+
inputController: { type: 'colorPicker', description: 'Title Color' }
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'widgetBorderColor',
|
|
33
|
+
type: 'string',
|
|
34
|
+
defaultValue: '#e5e7eb',
|
|
35
|
+
inputController: { type: 'colorPicker', description: 'Border Color' }
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'Spacing',
|
|
41
|
+
properties: [
|
|
42
|
+
{
|
|
43
|
+
id: 'padding',
|
|
44
|
+
type: 'number',
|
|
45
|
+
defaultValue: 16,
|
|
46
|
+
inputController: { type: 'slider', description: 'Padding', config: { min: 0, max: 64, step: 1 } }
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'borderRadius',
|
|
50
|
+
type: 'number',
|
|
51
|
+
defaultValue: 8,
|
|
52
|
+
inputController: { type: 'number', description: 'Border Radius', config: { min: 0, max: 40, step: 1 } }
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'Layout',
|
|
58
|
+
properties: [
|
|
59
|
+
{
|
|
60
|
+
id: 'layout',
|
|
61
|
+
type: 'string',
|
|
62
|
+
defaultValue: 'grid',
|
|
63
|
+
inputController: {
|
|
64
|
+
type: 'dropdown',
|
|
65
|
+
description: 'Layout Style',
|
|
66
|
+
config: {
|
|
67
|
+
options: [
|
|
68
|
+
{ label: 'Grid', value: 'grid' },
|
|
69
|
+
{ label: 'List', value: 'list' },
|
|
70
|
+
{ label: 'Carousel', value: 'carousel' }
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'showShadow',
|
|
77
|
+
type: 'boolean',
|
|
78
|
+
defaultValue: true,
|
|
79
|
+
inputController: { type: 'switch', description: 'Drop Shadow' }
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'widgetTitle',
|
|
83
|
+
type: 'string',
|
|
84
|
+
defaultValue: 'My Widget',
|
|
85
|
+
inputController: { type: 'textInput', description: 'Widget Title' }
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
};
|
|
95
|
+
const LIVE = { backgroundColor: '#ffffff', padding: 16, borderRadius: 8, showShadow: true };
|
|
96
|
+
describe('collectDesignProperties', () => {
|
|
97
|
+
it('flattens nested property groups into descriptors', () => {
|
|
98
|
+
const props = collectDesignProperties(SCHEMA);
|
|
99
|
+
expect(props.map((p) => p.id)).toEqual([
|
|
100
|
+
'backgroundColor',
|
|
101
|
+
'titleColor',
|
|
102
|
+
'widgetBorderColor',
|
|
103
|
+
'padding',
|
|
104
|
+
'borderRadius',
|
|
105
|
+
'layout',
|
|
106
|
+
'showShadow',
|
|
107
|
+
'widgetTitle'
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
it('reads dropdown options from inputController.config.options', () => {
|
|
111
|
+
const dropdown = collectDesignProperties(SCHEMA).find((p) => p.id === 'layout');
|
|
112
|
+
expect(dropdown?.options).toHaveLength(3);
|
|
113
|
+
expect(dropdown?.options[1]).toEqual({ label: 'List', value: 'list' });
|
|
114
|
+
});
|
|
115
|
+
it('reads numeric constraints', () => {
|
|
116
|
+
const padding = collectDesignProperties(SCHEMA).find((p) => p.id === 'padding');
|
|
117
|
+
expect(padding).toMatchObject({ min: 0, max: 64, step: 1 });
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe('resolveDesignIntent — colors', () => {
|
|
121
|
+
it('resolves an explicit hex on the named color property', () => {
|
|
122
|
+
const r = resolveDesignIntent('make the background #1a1a2e', SCHEMA, LIVE);
|
|
123
|
+
expect(r?.changes).toHaveLength(1);
|
|
124
|
+
expect(r?.changes[0]).toMatchObject({
|
|
125
|
+
propertyId: 'backgroundColor',
|
|
126
|
+
value: '#1a1a2e',
|
|
127
|
+
reason: 'explicit-color'
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
it('expands 3-digit hex', () => {
|
|
131
|
+
const r = resolveDesignIntent('set the background to #abc', SCHEMA, LIVE);
|
|
132
|
+
expect(r?.changes[0].value).toBe('#aabbcc');
|
|
133
|
+
});
|
|
134
|
+
it('resolves a named color', () => {
|
|
135
|
+
const r = resolveDesignIntent('change the background to red', SCHEMA, LIVE);
|
|
136
|
+
expect(r?.changes[0]).toMatchObject({ propertyId: 'backgroundColor', value: '#ff0000', reason: 'named-color' });
|
|
137
|
+
});
|
|
138
|
+
it('resolves rgb()', () => {
|
|
139
|
+
const r = resolveDesignIntent('background rgb(26, 26, 46)', SCHEMA, LIVE);
|
|
140
|
+
expect(r?.changes[0].value).toBe('#1a1a2e');
|
|
141
|
+
});
|
|
142
|
+
it('does not touch a number property when only a color is given', () => {
|
|
143
|
+
const r = resolveDesignIntent('make the padding red', SCHEMA, LIVE);
|
|
144
|
+
expect(r).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('resolveDesignIntent — numbers (arithmetic stays in code)', () => {
|
|
148
|
+
it('sets an explicit number', () => {
|
|
149
|
+
const r = resolveDesignIntent('set the border radius to 12', SCHEMA, LIVE);
|
|
150
|
+
expect(r?.changes[0]).toMatchObject({ propertyId: 'borderRadius', value: 12, reason: 'explicit-number' });
|
|
151
|
+
});
|
|
152
|
+
it('increases by an absolute delta using the current value', () => {
|
|
153
|
+
const r = resolveDesignIntent('increase the padding by 8', SCHEMA, LIVE);
|
|
154
|
+
expect(r?.changes[0]).toMatchObject({ propertyId: 'padding', value: 24, reason: 'delta-number' });
|
|
155
|
+
});
|
|
156
|
+
it('applies a percentage delta', () => {
|
|
157
|
+
const r = resolveDesignIntent('reduce the padding by 25%', SCHEMA, LIVE);
|
|
158
|
+
expect(r?.changes[0].value).toBe(12);
|
|
159
|
+
});
|
|
160
|
+
it('doubles the current value', () => {
|
|
161
|
+
const r = resolveDesignIntent('double the border radius', SCHEMA, LIVE);
|
|
162
|
+
expect(r?.changes[0].value).toBe(16);
|
|
163
|
+
});
|
|
164
|
+
it('halves the current value', () => {
|
|
165
|
+
const r = resolveDesignIntent('half the padding', SCHEMA, LIVE);
|
|
166
|
+
expect(r?.changes[0].value).toBe(8);
|
|
167
|
+
});
|
|
168
|
+
it('clamps to the schema max', () => {
|
|
169
|
+
const r = resolveDesignIntent('increase the padding by 500', SCHEMA, LIVE);
|
|
170
|
+
expect(r?.changes[0].value).toBe(64);
|
|
171
|
+
});
|
|
172
|
+
it('uses the schema default when no live value is supplied', () => {
|
|
173
|
+
const r = resolveDesignIntent('increase the padding by 4', SCHEMA);
|
|
174
|
+
expect(r?.changes[0]).toMatchObject({ previousValue: 16, value: 20 });
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe('resolveDesignIntent — booleans and options', () => {
|
|
178
|
+
it('turns a switch on', () => {
|
|
179
|
+
const r = resolveDesignIntent('turn the drop shadow off', SCHEMA, LIVE);
|
|
180
|
+
expect(r?.changes[0]).toMatchObject({ propertyId: 'showShadow', value: false, reason: 'boolean' });
|
|
181
|
+
});
|
|
182
|
+
it('enables', () => {
|
|
183
|
+
const r = resolveDesignIntent('enable the shadow', SCHEMA, LIVE);
|
|
184
|
+
expect(r?.changes[0]).toMatchObject({ propertyId: 'showShadow', value: true });
|
|
185
|
+
});
|
|
186
|
+
it('selects a dropdown option by label', () => {
|
|
187
|
+
const r = resolveDesignIntent('switch the layout to carousel', SCHEMA, LIVE);
|
|
188
|
+
expect(r?.changes[0]).toMatchObject({ propertyId: 'layout', value: 'carousel', reason: 'option' });
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
describe('resolveDesignIntent — text', () => {
|
|
192
|
+
it('resolves only explicitly quoted text', () => {
|
|
193
|
+
const r = resolveDesignIntent('set the widget title to "Grocery list"', SCHEMA, LIVE);
|
|
194
|
+
expect(r?.changes[0]).toMatchObject({ propertyId: 'widgetTitle', value: 'Grocery list' });
|
|
195
|
+
});
|
|
196
|
+
it('refuses unquoted text (falls through to codegen)', () => {
|
|
197
|
+
expect(resolveDesignIntent('change the title to grocery list', SCHEMA, LIVE)).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
describe('resolveDesignIntent — bails out (falls through to codegen/JEV)', () => {
|
|
201
|
+
it('returns null for vague intent', () => {
|
|
202
|
+
expect(resolveDesignIntent('make it pop', SCHEMA, LIVE)).toBeNull();
|
|
203
|
+
expect(resolveDesignIntent('give it more breathing room', SCHEMA, LIVE)).toBeNull();
|
|
204
|
+
expect(resolveDesignIntent('make it feel more premium', SCHEMA, LIVE)).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
it('returns null for a color with no named property', () => {
|
|
207
|
+
expect(resolveDesignIntent('make it red', SCHEMA, LIVE)).toBeNull();
|
|
208
|
+
});
|
|
209
|
+
it('returns null for a non-design ask', () => {
|
|
210
|
+
expect(resolveDesignIntent('add a confetti animation when the timer ends', SCHEMA, LIVE)).toBeNull();
|
|
211
|
+
});
|
|
212
|
+
it('returns null when two properties tie on name match', () => {
|
|
213
|
+
// "color" alone names all three color properties equally → ambiguous.
|
|
214
|
+
expect(resolveDesignIntent('change the color to red', SCHEMA, LIVE)).toBeNull();
|
|
215
|
+
});
|
|
216
|
+
it('returns null for an empty schema', () => {
|
|
217
|
+
expect(resolveDesignIntent('make the background red', {}, LIVE)).toBeNull();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
describe('toDesignControlRequest', () => {
|
|
221
|
+
it('carries the resolved value + constraints for the inline controller', () => {
|
|
222
|
+
const r = resolveDesignIntent('increase the padding by 8', SCHEMA, LIVE);
|
|
223
|
+
const card = toDesignControlRequest(r, SCHEMA, LIVE);
|
|
224
|
+
expect(card.summary).toContain('Padding');
|
|
225
|
+
expect(card.properties[0]).toMatchObject({
|
|
226
|
+
id: 'padding',
|
|
227
|
+
controllerType: 'slider',
|
|
228
|
+
value: 24,
|
|
229
|
+
min: 0,
|
|
230
|
+
max: 64
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
it('records the composition the change landed in, so a reload can re-attach it', () => {
|
|
234
|
+
const r = resolveDesignIntent('increase the padding by 8', SCHEMA, LIVE);
|
|
235
|
+
expect(toDesignControlRequest(r, SCHEMA, LIVE, 'composition-1').compositionId).toBe('composition-1');
|
|
236
|
+
});
|
|
237
|
+
it('omits the composition rather than recording null when there is none', () => {
|
|
238
|
+
// A card persisted before the field existed, and one authored against a
|
|
239
|
+
// composition that was never saved, both arrive without it — and the host
|
|
240
|
+
// treats exactly that shape as "attached".
|
|
241
|
+
const r = resolveDesignIntent('increase the padding by 8', SCHEMA, LIVE);
|
|
242
|
+
expect(toDesignControlRequest(r, SCHEMA, LIVE)).not.toHaveProperty('compositionId');
|
|
243
|
+
expect(toDesignControlRequest(r, SCHEMA, LIVE, null)).not.toHaveProperty('compositionId');
|
|
244
|
+
expect(toDesignControlRequest(r, SCHEMA, LIVE, '')).not.toHaveProperty('compositionId');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* designIntentTelemetry — shadow-mode measurement for the canvas chat.
|
|
3
|
+
*
|
|
4
|
+
* Every message a user sends in the canvas chat is bucketed by *how it could be
|
|
5
|
+
* resolved*, independently of what the app actually does:
|
|
6
|
+
*
|
|
7
|
+
* literal_arithmetic — explicit value / arithmetic in code (the deterministic
|
|
8
|
+
* resolver already handles it; no model needed)
|
|
9
|
+
* bounded_set_semantic — a design ask whose property/value needs judgment
|
|
10
|
+
* (the JEV-shaped residual: Choice over the closed set)
|
|
11
|
+
* free_text — needs a text value (an LLM)
|
|
12
|
+
* new_property — asks for a design property that does not exist yet
|
|
13
|
+
* (generation; an LLM)
|
|
14
|
+
* not_design — not a design tweak at all → codegen as today
|
|
15
|
+
*
|
|
16
|
+
* This module is measurement only: it never applies a change and never alters a
|
|
17
|
+
* send. It records one event per message, persists rolling counts so fractions
|
|
18
|
+
* survive reloads, and reports them. Enabling it is safe in production.
|
|
19
|
+
*
|
|
20
|
+
* The Jev fallback is measured twice over: how big its candidate pool is (the
|
|
21
|
+
* bounded-set-semantic bucket) and how much of that pool it resolves. Every dial
|
|
22
|
+
* is attributed to the *ask shape* that produced it, so a fall-through count can
|
|
23
|
+
* be read against the question it implicates — a weak value bucket and a weak
|
|
24
|
+
* property roster produce entirely different reasons — instead of as one global
|
|
25
|
+
* total.
|
|
26
|
+
*/
|
|
27
|
+
export type DesignIntentBucket = 'literal_arithmetic' | 'bounded_set_semantic' | 'free_text' | 'new_property' | 'not_design';
|
|
28
|
+
export declare const DESIGN_INTENT_BUCKETS: DesignIntentBucket[];
|
|
29
|
+
/**
|
|
30
|
+
* The two shapes a bounded-set-semantic ask arrives in. Which one it is decides
|
|
31
|
+
* what a fall-through implicates, because a different question carries the
|
|
32
|
+
* weight in each:
|
|
33
|
+
*
|
|
34
|
+
* property_name — the message named a property, so the **value** question is
|
|
35
|
+
* under test: `low_confidence_value` / `no_value_stated` point
|
|
36
|
+
* at the value option strings.
|
|
37
|
+
* value_only — only a value was stated and which property it targets needs
|
|
38
|
+
* judgment, so the **property** question is: `low_confidence`
|
|
39
|
+
* / `none_of_these` point at the property option strings or
|
|
40
|
+
* the confidence floor.
|
|
41
|
+
*/
|
|
42
|
+
export type DesignIntentAskShape = 'property_named' | 'value_only';
|
|
43
|
+
/** Every ask shape, plus dials that outlived their classification (a refresh
|
|
44
|
+
* mid-conversation, or counts persisted before shapes were recorded). */
|
|
45
|
+
export type DesignIntentJevShape = DesignIntentAskShape | 'unknown';
|
|
46
|
+
export declare const JE_ASK_SHAPES: DesignIntentJevShape[];
|
|
47
|
+
export interface DesignIntentClassification {
|
|
48
|
+
bucket: DesignIntentBucket;
|
|
49
|
+
/** Design properties the message named (empty when nothing matched). */
|
|
50
|
+
propertyIds: string[];
|
|
51
|
+
detail: string;
|
|
52
|
+
/** Set on the bounded-set-semantic bucket only — the shape is what Jev's
|
|
53
|
+
* reason counts are read against. */
|
|
54
|
+
shape?: DesignIntentAskShape;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Bucket a single chat message. Pure and deterministic — the same heuristics the
|
|
58
|
+
* resolver uses for property naming, plus a few value/verb signals.
|
|
59
|
+
*/
|
|
60
|
+
export declare function classifyDesignIntent(message: string, schema: unknown): DesignIntentClassification;
|
|
61
|
+
export interface DesignIntentTelemetryEvent extends DesignIntentClassification {
|
|
62
|
+
messageLength: number;
|
|
63
|
+
at: number;
|
|
64
|
+
}
|
|
65
|
+
/** One ask shape's slice of the Jev dials. */
|
|
66
|
+
export interface DesignIntentJevShapeBreakdown {
|
|
67
|
+
attempts: number;
|
|
68
|
+
acted: number;
|
|
69
|
+
fellThrough: number;
|
|
70
|
+
/** `acted / attempts`, 0 when this shape was never dialed. */
|
|
71
|
+
resolvedShare: number;
|
|
72
|
+
byReason: Record<string, number>;
|
|
73
|
+
}
|
|
74
|
+
export interface DesignIntentJevSnapshot {
|
|
75
|
+
/** Jev dials attempted on the bounded-set-semantic bucket. */
|
|
76
|
+
attempts: number;
|
|
77
|
+
/** Dials whose verdict was applied (enforce mode + confident + bounded value). */
|
|
78
|
+
acted: number;
|
|
79
|
+
/** Dials that fell through to codegen, for any reason. */
|
|
80
|
+
fellThrough: number;
|
|
81
|
+
/** `acted / attempts`, 0 when nothing was attempted. */
|
|
82
|
+
resolvedShare: number;
|
|
83
|
+
/** Fall-through (or acted) reasons, for tuning the questions and the gates. */
|
|
84
|
+
byReason: Record<string, number>;
|
|
85
|
+
/** The same counters split by ask shape, so a reason count can be read against
|
|
86
|
+
* the question that produced it rather than as a global total. */
|
|
87
|
+
byShape: Record<DesignIntentJevShape, DesignIntentJevShapeBreakdown>;
|
|
88
|
+
/** Dials attributed to a bucket other than the one the design says is ever
|
|
89
|
+
* dialed. Should stay 0: a non-zero value means a dial is happening where the
|
|
90
|
+
* module claims it cannot, so it is counted rather than folded in. */
|
|
91
|
+
offBucketDials: number;
|
|
92
|
+
}
|
|
93
|
+
export interface DesignIntentTelemetrySnapshot {
|
|
94
|
+
/** Every recorded send, including ones with no design schema loaded. */
|
|
95
|
+
total: number;
|
|
96
|
+
/** Sends that could be classified (a design schema was loaded). */
|
|
97
|
+
counted: number;
|
|
98
|
+
/** Sends recorded while no design schema was available (unclassifiable). */
|
|
99
|
+
noSchema: number;
|
|
100
|
+
buckets: Record<DesignIntentBucket, number>;
|
|
101
|
+
/** Share of `counted` per bucket, 0–1. */
|
|
102
|
+
fractions: Record<DesignIntentBucket, number>;
|
|
103
|
+
/** Share of `counted` the deterministic path could have applied. */
|
|
104
|
+
deterministicShare: number;
|
|
105
|
+
/** Share of `counted` that is a design ask needing a model (JEV or LLM). */
|
|
106
|
+
modelShare: number;
|
|
107
|
+
/** How the Jev fallback fared on its one candidate bucket. */
|
|
108
|
+
jev: DesignIntentJevSnapshot;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The one bucket Jev is asked about. It is the residual where a design property
|
|
112
|
+
* or value needs judgment but the value space is closed — `free_text` and
|
|
113
|
+
* `new_property` can only fall through (Jev cannot generate), and `not_design`
|
|
114
|
+
* is not a design ask at all, so neither is ever dialed.
|
|
115
|
+
*/
|
|
116
|
+
export declare const JEV_CANDIDATE_BUCKET: DesignIntentBucket;
|
|
117
|
+
export declare function isJevCandidateBucket(bucket: DesignIntentBucket | null | undefined): boolean;
|
|
118
|
+
export type DesignIntentJevSource = 'jev' | 'degraded' | 'disabled' | 'no_properties' | 'client_unavailable';
|
|
119
|
+
/** The on-disk shape of one ask shape's counters — exported because loading it
|
|
120
|
+
* is a migration, and a migration is worth asserting on directly. */
|
|
121
|
+
export interface PersistedShapeBreakdown {
|
|
122
|
+
attempts: number;
|
|
123
|
+
acted: number;
|
|
124
|
+
fellThrough: number;
|
|
125
|
+
byReason: Record<string, number>;
|
|
126
|
+
}
|
|
127
|
+
/** The persisted Jev counters. `byShape` and `offBucketDials` were added after
|
|
128
|
+
* the first version of this payload shipped, so a stored payload from before
|
|
129
|
+
* them must still load — see `parsePersistedTelemetry`. */
|
|
130
|
+
export interface PersistedJev {
|
|
131
|
+
attempts: number;
|
|
132
|
+
acted: number;
|
|
133
|
+
fellThrough: number;
|
|
134
|
+
byReason: Record<string, number>;
|
|
135
|
+
byShape: Record<DesignIntentJevShape, PersistedShapeBreakdown>;
|
|
136
|
+
offBucketDials: number;
|
|
137
|
+
}
|
|
138
|
+
/** The whole persisted payload, as stored under `STORAGE_KEY`. */
|
|
139
|
+
export interface PersistedDesignIntentTelemetry {
|
|
140
|
+
total: number;
|
|
141
|
+
noSchema: number;
|
|
142
|
+
buckets: Record<DesignIntentBucket, number>;
|
|
143
|
+
jev: PersistedJev;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Parse one persisted payload. Pure, so the upgrade path — every shape recorded
|
|
147
|
+
* before `byShape` existed — is testable without a browser: counts that are
|
|
148
|
+
* absent, negative or not numbers become zero, and the flat reason map it does
|
|
149
|
+
* have is kept.
|
|
150
|
+
*/
|
|
151
|
+
export declare function parsePersistedTelemetry(raw: string | null): PersistedDesignIntentTelemetry;
|
|
152
|
+
/**
|
|
153
|
+
* Record one canvas-chat send and bucket it. Shadow mode: this does not change
|
|
154
|
+
* any behavior — the caller generates exactly as it did before. Returns the
|
|
155
|
+
* classification so a caller could log or assert on it.
|
|
156
|
+
*/
|
|
157
|
+
export declare function recordDesignIntentTelemetry(message: string, schema: unknown): DesignIntentClassification;
|
|
158
|
+
/**
|
|
159
|
+
* Record the outcome of one Jev fallback dial. This closes the measurement
|
|
160
|
+
* loop: the bucketing tells us how big the candidate pool is, and this tells us
|
|
161
|
+
* how much of it Jev actually resolves — the number that earns the flip from
|
|
162
|
+
* `shadow` to `enforce`.
|
|
163
|
+
*/
|
|
164
|
+
export declare function recordDesignIntentJevOutcome(outcome: {
|
|
165
|
+
source: DesignIntentJevSource;
|
|
166
|
+
acted: boolean;
|
|
167
|
+
reason: string;
|
|
168
|
+
/** The classification of the send that triggered this dial. Without it the
|
|
169
|
+
* dial is counted, but under `unknown` rather than guessed into a shape. */
|
|
170
|
+
classification?: Pick<DesignIntentClassification, 'bucket' | 'shape'> | null;
|
|
171
|
+
}): void;
|
|
172
|
+
export declare function getDesignIntentTelemetrySnapshot(): DesignIntentTelemetrySnapshot;
|
|
173
|
+
export declare function formatDesignIntentTelemetryReport(snapshot: DesignIntentTelemetrySnapshot): string;
|
|
174
|
+
/** Recent events (last 100, newest last) — for a quick look in the console. */
|
|
175
|
+
export declare function getRecentDesignIntentEvents(): DesignIntentTelemetryEvent[];
|
|
176
|
+
export declare function resetDesignIntentTelemetry(): void;
|