@burdenoff/microfe-bigconsole 2026.727.1 → 2026.727.3

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.
@@ -0,0 +1,106 @@
1
+ var e = [
2
+ "chart",
3
+ "funnel_chart",
4
+ "heatmap",
5
+ "table",
6
+ "metric_card",
7
+ "retention"
8
+ ];
9
+ function t(t) {
10
+ return typeof t == "string" && e.includes(t);
11
+ }
12
+ var n = new Set([
13
+ "html",
14
+ "iframeUrl",
15
+ "script",
16
+ "scriptUrl",
17
+ "render",
18
+ "srcDoc",
19
+ "onClick"
20
+ ]);
21
+ function r(e) {
22
+ let t = e.trim().toLowerCase().replace(/\s+/g, "");
23
+ return t.startsWith("javascript:") || t.startsWith("data:text/html") || t.includes("<script") || /\son[a-z]+=/.test(e.toLowerCase());
24
+ }
25
+ function i(e, t, a = "config") {
26
+ for (let [o, s] of Object.entries(e)) {
27
+ let e = `${a}.${o}`;
28
+ if (n.has(o)) {
29
+ t.push({
30
+ code: "UNSAFE_CONTENT",
31
+ severity: "error",
32
+ message: `"${o}" is not allowed — generated widgets must use the declarative rendering model.`,
33
+ path: e
34
+ });
35
+ continue;
36
+ }
37
+ if (typeof s == "string" && r(s)) {
38
+ t.push({
39
+ code: "UNSAFE_CONTENT",
40
+ severity: "error",
41
+ message: `"${o}" contains executable or remote content, which is not permitted.`,
42
+ path: e
43
+ });
44
+ continue;
45
+ }
46
+ s && typeof s == "object" && !Array.isArray(s) && i(s, t, e);
47
+ }
48
+ }
49
+ function a(e, n) {
50
+ let r = [];
51
+ if (e.version !== "1.0" && r.push({
52
+ code: "VERSION_UNSUPPORTED",
53
+ severity: "error",
54
+ message: `Proposal version "${e.version}" is not supported by this builder.`,
55
+ path: "version"
56
+ }), t(e.widgetType) || r.push({
57
+ code: "UNSUPPORTED_WIDGET_TYPE",
58
+ severity: "error",
59
+ message: `"${e.widgetType}" is not an approved widget type.`,
60
+ path: "widgetType"
61
+ }), e.presentation.title?.trim() || r.push({
62
+ code: "MISSING_TITLE",
63
+ severity: "error",
64
+ message: "The widget needs a title before it can be saved.",
65
+ path: "presentation.title"
66
+ }), e.binding.dataSinkId ? n.permittedDataSinkIds.includes(e.binding.dataSinkId) || r.push({
67
+ code: "DATASINK_NOT_PERMITTED",
68
+ severity: "error",
69
+ message: "You do not have access to the proposed DataSink in this workspace.",
70
+ path: "binding.dataSinkId"
71
+ }) : r.push({
72
+ code: "MISSING_DATASINK",
73
+ severity: "error",
74
+ message: "Pick a DataSink — data can only reach a widget through a governed sink.",
75
+ path: "binding.dataSinkId"
76
+ }), e.binding.parserId ? n.permittedParserIds.includes(e.binding.parserId) || r.push({
77
+ code: "PARSER_NOT_PERMITTED",
78
+ severity: "error",
79
+ message: "You do not have access to the proposed parser in this workspace.",
80
+ path: "binding.parserId"
81
+ }) : r.push({
82
+ code: "MISSING_PARSER",
83
+ severity: "warning",
84
+ message: "No parser selected — the widget will receive the sink payload unshaped.",
85
+ path: "binding.parserId"
86
+ }), n.availableFields) {
87
+ let t = new Set(n.availableFields);
88
+ for (let [n, i] of Object.entries(e.fieldMapping)) i && !t.has(i) && r.push({
89
+ code: "FIELD_MISMATCH",
90
+ severity: "error",
91
+ message: `"${i}" (${n}) is not present in the parser output.`,
92
+ path: `fieldMapping.${n}`
93
+ });
94
+ }
95
+ return i(e.presentation.config, r), {
96
+ issues: r,
97
+ canApprove: !r.some((e) => e.severity === "error")
98
+ };
99
+ }
100
+ function o(e, t) {
101
+ return t.loading ? "loading" : t.accessError || e.issues.some((e) => e.code === "DATASINK_NOT_PERMITTED" || e.code === "PARSER_NOT_PERMITTED") ? "access-error" : e.issues.some((e) => e.code === "FIELD_MISMATCH") ? "field-mismatch" : t.rowCount ? "valid" : "no-data";
102
+ }
103
+ //#endregion
104
+ export { e as PROPOSABLE_WIDGET_TYPES, o as derivePreviewState, t as isProposableWidgetType, a as validateProposal };
105
+
106
+ //# sourceMappingURL=proposalContract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposalContract.js","names":[],"sources":["../../../src/bigconsole/customWidget/proposalContract.ts"],"sourcesContent":["/**\n * Custom-widget proposal contract (BOFF-5532 / BOFF-3021).\n *\n * The AI custom-widget builder turns a natural-language prompt into a *typed,\n * reviewable proposal* rather than writing a widget straight to the dashboard.\n * This module is the single source of truth for that proposal's shape.\n *\n * The backend operation that will eventually emit these proposals is owned by\n * BOFF-3021's backend half and does not exist yet, so the contract is versioned\n * and lives here. `proposalSource.ts` is the seam that swaps a local generator\n * for the real GraphQL operation without any UI change.\n *\n * Two rules the contract deliberately encodes, from BOFF-3021's scope:\n * 1. A proposal only ever *references* governed DataSinks and Parsers by id —\n * it can never carry inline data or credentials.\n * 2. Rendering is declarative and allowlisted. A proposal names a built-in\n * widget type and a config; it can never carry executable code, remote\n * script URLs, or raw HTML.\n */\n\nimport type { WidgetType } from '../types';\n\n/** Bump when the proposal shape changes incompatibly. */\nexport const CUSTOM_WIDGET_PROPOSAL_VERSION = '1.0' as const;\n\n/**\n * Widget types a proposal is allowed to request.\n *\n * Deliberately narrower than the full `WidgetType` union: these are the\n * federation-accessible types with production-quality renderers, so an approved\n * proposal is guaranteed to render. Anything else is rejected at validation.\n */\nexport const PROPOSABLE_WIDGET_TYPES = [\n 'chart',\n 'funnel_chart',\n 'heatmap',\n 'table',\n 'metric_card',\n 'retention',\n] as const;\n\nexport type ProposableWidgetType = (typeof PROPOSABLE_WIDGET_TYPES)[number];\n\nexport function isProposableWidgetType(value: unknown): value is ProposableWidgetType {\n return typeof value === 'string' && (PROPOSABLE_WIDGET_TYPES as readonly string[]).includes(value);\n}\n\n/**\n * How a proposal binds to data. References only — never inline rows, never\n * credentials, always a workspace-scoped id the gateway will RBAC-check.\n */\nexport interface ProposalBinding {\n /** DataSink id. Required: data may only enter through a governed sink. */\n dataSinkId: string;\n /** Human-readable sink key, carried for display and for export/import resolution. */\n dataSinkKey?: string;\n /** Parser id that shapes the sink payload into widget-ready output. */\n parserId?: string;\n /** Parser display name, for the review UI. */\n parserName?: string;\n /** Dot-notation path applied to the parser output. */\n dataPath?: string;\n}\n\n/**\n * Which fields of the parsed rows feed which visual role. Keys are role names\n * (`xAxisField`, `valueField`, …); values are field names in the parser output.\n */\nexport type ProposalFieldMapping = Record<string, string>;\n\n/** Presentation settings the reviewer can edit before approving. */\nexport interface ProposalPresentation {\n title: string;\n description?: string;\n /** Declarative widget config — merged into the created widget's `config`. */\n config: Record<string, unknown>;\n}\n\n/** A single typed custom-widget proposal awaiting review. */\nexport interface CustomWidgetProposal {\n version: typeof CUSTOM_WIDGET_PROPOSAL_VERSION;\n /** Client-side id for this proposal; not a widget id. */\n id: string;\n /** The prompt this proposal came from, kept as provenance. */\n sourcePrompt: string;\n /** What produced it — `local-heuristic-v1` today, a model id once wired. */\n generatedBy: string;\n widgetType: ProposableWidgetType;\n binding: ProposalBinding;\n fieldMapping: ProposalFieldMapping;\n presentation: ProposalPresentation;\n /** Optional short plain-language rationale shown to the reviewer. */\n rationale?: string;\n}\n\n// ============================================================================\n// Validation\n// ============================================================================\n\nexport type ProposalIssueCode =\n | 'UNSUPPORTED_WIDGET_TYPE'\n | 'MISSING_DATASINK'\n | 'DATASINK_NOT_PERMITTED'\n | 'MISSING_PARSER'\n | 'PARSER_NOT_PERMITTED'\n | 'FIELD_MISMATCH'\n | 'MISSING_TITLE'\n | 'UNSAFE_CONTENT'\n | 'VERSION_UNSUPPORTED';\n\nexport interface ProposalIssue {\n code: ProposalIssueCode;\n /** `error` blocks approval; `warning` does not. */\n severity: 'error' | 'warning';\n message: string;\n /** Which part of the proposal to highlight, e.g. `binding.parserId`. */\n path?: string;\n}\n\n/**\n * The preview's observable state. These are exactly the states BOFF-5532\n * requires the preview to cover.\n */\nexport type ProposalPreviewState = 'loading' | 'valid' | 'no-data' | 'field-mismatch' | 'access-error';\n\nexport interface ProposalValidation {\n issues: ProposalIssue[];\n /** True when no `error`-severity issue is present. */\n canApprove: boolean;\n}\n\n/**\n * Config keys that would smuggle executable or remote content through a\n * \"declarative\" proposal. BOFF-3021 puts these explicitly out of scope.\n */\nconst UNSAFE_CONFIG_KEYS = new Set(['html', 'iframeUrl', 'script', 'scriptUrl', 'render', 'srcDoc', 'onClick']);\n\n/** Rejects `javascript:`-style URLs and inline event handlers hidden in strings. */\nfunction containsUnsafeString(value: string): boolean {\n const normalized = value.trim().toLowerCase().replace(/\\s+/g, '');\n return (\n normalized.startsWith('javascript:') ||\n normalized.startsWith('data:text/html') ||\n normalized.includes('<script') ||\n /\\son[a-z]+=/.test(value.toLowerCase())\n );\n}\n\nfunction scanForUnsafeContent(config: Record<string, unknown>, issues: ProposalIssue[], pathPrefix = 'config'): void {\n for (const [key, value] of Object.entries(config)) {\n const path = `${pathPrefix}.${key}`;\n\n if (UNSAFE_CONFIG_KEYS.has(key)) {\n issues.push({\n code: 'UNSAFE_CONTENT',\n severity: 'error',\n message: `\"${key}\" is not allowed — generated widgets must use the declarative rendering model.`,\n path,\n });\n continue;\n }\n\n if (typeof value === 'string' && containsUnsafeString(value)) {\n issues.push({\n code: 'UNSAFE_CONTENT',\n severity: 'error',\n message: `\"${key}\" contains executable or remote content, which is not permitted.`,\n path,\n });\n continue;\n }\n\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n scanForUnsafeContent(value as Record<string, unknown>, issues, path);\n }\n }\n}\n\nexport interface ValidateProposalContext {\n /** DataSink ids the current user may bind to (already RBAC-filtered by the API). */\n permittedDataSinkIds: readonly string[];\n /** Parser ids the current user may bind to. */\n permittedParserIds: readonly string[];\n /**\n * Field names present in the parser's actual output, when known. When\n * undefined the field mapping is not checked (preview has not resolved yet).\n */\n availableFields?: readonly string[];\n}\n\n/**\n * Validates a proposal against what the current user is actually permitted to\n * bind, and against the declarative-rendering rules.\n *\n * Permission checks here are a *UX* affordance so the reviewer sees a clear\n * message instead of a failed write — the gateway's `@rbac` directives remain\n * the enforcement boundary.\n */\nexport function validateProposal(proposal: CustomWidgetProposal, context: ValidateProposalContext): ProposalValidation {\n const issues: ProposalIssue[] = [];\n\n if (proposal.version !== CUSTOM_WIDGET_PROPOSAL_VERSION) {\n issues.push({\n code: 'VERSION_UNSUPPORTED',\n severity: 'error',\n message: `Proposal version \"${proposal.version}\" is not supported by this builder.`,\n path: 'version',\n });\n }\n\n if (!isProposableWidgetType(proposal.widgetType)) {\n issues.push({\n code: 'UNSUPPORTED_WIDGET_TYPE',\n severity: 'error',\n message: `\"${proposal.widgetType}\" is not an approved widget type.`,\n path: 'widgetType',\n });\n }\n\n if (!proposal.presentation.title?.trim()) {\n issues.push({\n code: 'MISSING_TITLE',\n severity: 'error',\n message: 'The widget needs a title before it can be saved.',\n path: 'presentation.title',\n });\n }\n\n // --- binding ---\n if (!proposal.binding.dataSinkId) {\n issues.push({\n code: 'MISSING_DATASINK',\n severity: 'error',\n message: 'Pick a DataSink — data can only reach a widget through a governed sink.',\n path: 'binding.dataSinkId',\n });\n } else if (!context.permittedDataSinkIds.includes(proposal.binding.dataSinkId)) {\n issues.push({\n code: 'DATASINK_NOT_PERMITTED',\n severity: 'error',\n message: 'You do not have access to the proposed DataSink in this workspace.',\n path: 'binding.dataSinkId',\n });\n }\n\n if (!proposal.binding.parserId) {\n issues.push({\n code: 'MISSING_PARSER',\n severity: 'warning',\n message: 'No parser selected — the widget will receive the sink payload unshaped.',\n path: 'binding.parserId',\n });\n } else if (!context.permittedParserIds.includes(proposal.binding.parserId)) {\n issues.push({\n code: 'PARSER_NOT_PERMITTED',\n severity: 'error',\n message: 'You do not have access to the proposed parser in this workspace.',\n path: 'binding.parserId',\n });\n }\n\n // --- field mapping, only once the real fields are known ---\n if (context.availableFields) {\n const available = new Set(context.availableFields);\n for (const [role, field] of Object.entries(proposal.fieldMapping)) {\n if (field && !available.has(field)) {\n issues.push({\n code: 'FIELD_MISMATCH',\n severity: 'error',\n message: `\"${field}\" (${role}) is not present in the parser output.`,\n path: `fieldMapping.${role}`,\n });\n }\n }\n }\n\n scanForUnsafeContent(proposal.presentation.config, issues);\n\n return { issues, canApprove: !issues.some((issue) => issue.severity === 'error') };\n}\n\n/** Derives the preview state from validation plus what the preview fetch returned. */\nexport function derivePreviewState(\n validation: ProposalValidation,\n options: { loading: boolean; accessError?: boolean; rowCount?: number }\n): ProposalPreviewState {\n if (options.loading) return 'loading';\n if (options.accessError) return 'access-error';\n\n const hasAccessIssue = validation.issues.some(\n (issue) => issue.code === 'DATASINK_NOT_PERMITTED' || issue.code === 'PARSER_NOT_PERMITTED'\n );\n if (hasAccessIssue) return 'access-error';\n\n if (validation.issues.some((issue) => issue.code === 'FIELD_MISMATCH')) return 'field-mismatch';\n if (!options.rowCount) return 'no-data';\n return 'valid';\n}\n"],"mappings":"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"}
@@ -0,0 +1,204 @@
1
+ //#region src/bigconsole/customWidget/proposalSource.ts
2
+ var e = [
3
+ {
4
+ type: "funnel_chart",
5
+ words: [
6
+ "funnel",
7
+ "conversion",
8
+ "drop-off",
9
+ "dropoff",
10
+ "stage"
11
+ ]
12
+ },
13
+ {
14
+ type: "heatmap",
15
+ words: [
16
+ "heatmap",
17
+ "heat map",
18
+ "matrix",
19
+ "intensity",
20
+ "by hour",
21
+ "density"
22
+ ]
23
+ },
24
+ {
25
+ type: "retention",
26
+ words: ["retention", "cohort"]
27
+ },
28
+ {
29
+ type: "table",
30
+ words: [
31
+ "table",
32
+ "list of",
33
+ "rows",
34
+ "breakdown table",
35
+ "grid"
36
+ ]
37
+ },
38
+ {
39
+ type: "metric_card",
40
+ words: [
41
+ "kpi",
42
+ "single number",
43
+ "metric card",
44
+ "scorecard",
45
+ "total"
46
+ ]
47
+ },
48
+ {
49
+ type: "chart",
50
+ words: [
51
+ "chart",
52
+ "trend",
53
+ "over time",
54
+ "line",
55
+ "bar",
56
+ "area",
57
+ "pie",
58
+ "graph"
59
+ ]
60
+ }
61
+ ], t = [
62
+ {
63
+ chartType: "bar",
64
+ words: ["bar"]
65
+ },
66
+ {
67
+ chartType: "area",
68
+ words: ["area", "cumulative"]
69
+ },
70
+ {
71
+ chartType: "pie",
72
+ words: [
73
+ "pie",
74
+ "composition",
75
+ "share",
76
+ "mix"
77
+ ]
78
+ },
79
+ {
80
+ chartType: "line",
81
+ words: [
82
+ "line",
83
+ "trend",
84
+ "over time"
85
+ ]
86
+ }
87
+ ];
88
+ function n(t) {
89
+ let n = t.toLowerCase();
90
+ for (let { type: t, words: r } of e) if (r.some((e) => n.includes(e))) return t;
91
+ return "chart";
92
+ }
93
+ function r(e) {
94
+ let n = e.toLowerCase();
95
+ for (let { chartType: e, words: r } of t) if (r.some((e) => n.includes(e))) return e;
96
+ return "line";
97
+ }
98
+ function i(e, t) {
99
+ if (t.length === 0) return;
100
+ let n = e.toLowerCase().split(/[^a-z0-9]+/).filter((e) => e.length > 2), r;
101
+ for (let e of t) {
102
+ let t = `${e.key} ${e.name ?? ""}`.toLowerCase(), i = n.reduce((e, n) => t.includes(n) ? e + 1 : e, 0);
103
+ (!r || i > r.score) && (r = {
104
+ sink: e,
105
+ score: i
106
+ });
107
+ }
108
+ return r?.sink ?? t[0];
109
+ }
110
+ function a(e) {
111
+ switch (e) {
112
+ case "heatmap": return {
113
+ xAxisField: "column",
114
+ yAxisField: "row",
115
+ valueField: "value"
116
+ };
117
+ case "funnel_chart": return {
118
+ labelField: "name",
119
+ valueField: "value"
120
+ };
121
+ case "metric_card": return { valueField: "value" };
122
+ case "table": return {};
123
+ case "retention": return {
124
+ cohortField: "cohort",
125
+ valueField: "value"
126
+ };
127
+ default: return {
128
+ xAxisField: "name",
129
+ labelField: "name",
130
+ valueField: "value"
131
+ };
132
+ }
133
+ }
134
+ function o(e, t) {
135
+ let n = {
136
+ showLegend: !0,
137
+ showTooltip: !0
138
+ };
139
+ switch (e) {
140
+ case "chart": return {
141
+ ...n,
142
+ chartType: r(t),
143
+ showGrid: !0
144
+ };
145
+ case "funnel_chart": return {
146
+ ...n,
147
+ chartType: "funnel",
148
+ showLabels: !0,
149
+ showPercentages: !0,
150
+ orientation: "vertical"
151
+ };
152
+ case "heatmap": return {
153
+ ...n,
154
+ colorScheme: "blue",
155
+ showValues: !0
156
+ };
157
+ case "table": return {
158
+ ...n,
159
+ pageSize: 10,
160
+ sortable: !0
161
+ };
162
+ case "metric_card": return {
163
+ ...n,
164
+ showTrend: !0
165
+ };
166
+ default: return n;
167
+ }
168
+ }
169
+ function s(e) {
170
+ let t = e.trim().replace(/^(please\s+)?(create|build|make|add|show me|show|give me)\s+(an?\s+|the\s+)?/i, "").split(/[.;\n]/)[0].trim();
171
+ if (!t) return "Custom widget";
172
+ let n = t.charAt(0).toUpperCase() + t.slice(1);
173
+ return n.length > 60 ? `${n.slice(0, 57)}…` : n;
174
+ }
175
+ var c = 0, l = {
176
+ id: "local-heuristic-v1",
177
+ async propose({ prompt: e, sinks: t, parsers: r }) {
178
+ let u = n(e), d = i(e, t), f = d ? r.find((e) => e.inputSinkKey === d.key) : void 0;
179
+ return c += 1, {
180
+ version: "1.0",
181
+ id: `proposal-${c}`,
182
+ sourcePrompt: e,
183
+ generatedBy: l.id,
184
+ widgetType: u,
185
+ binding: {
186
+ dataSinkId: d?.id ?? "",
187
+ dataSinkKey: d?.key,
188
+ parserId: f?.id,
189
+ parserName: f?.name
190
+ },
191
+ fieldMapping: a(u),
192
+ presentation: {
193
+ title: s(e),
194
+ description: `Generated from: "${e}"`,
195
+ config: o(u, e)
196
+ },
197
+ rationale: d ? `Matched DataSink "${d.key}"${f ? ` and parser "${f.name}"` : " (no parser found for this sink)"}.` : "No DataSink is available in this workspace — create one before saving this widget."
198
+ };
199
+ }
200
+ };
201
+ //#endregion
202
+ export { l as localProposalSource };
203
+
204
+ //# sourceMappingURL=proposalSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposalSource.js","names":[],"sources":["../../../src/bigconsole/customWidget/proposalSource.ts"],"sourcesContent":["/**\n * Proposal source — the seam between the review UI and whatever produces\n * proposals (BOFF-5532 / BOFF-3021).\n *\n * The UI only ever talks to `CustomWidgetProposalSource`. Today the default\n * implementation derives a proposal locally from the prompt and the workspace's\n * real sinks and parsers, because the backend operation that will emit typed\n * proposals is owned by BOFF-3021's backend half and does not exist yet.\n *\n * When it lands, add a `graphqlProposalSource` here that calls it and swap the\n * value passed to the panel. Nothing in the review, edit, approve or persist\n * path changes: those already run against real sinks, real parsers and the real\n * `createBigConsoleWidget` mutation.\n */\n\nimport {\n CUSTOM_WIDGET_PROPOSAL_VERSION,\n type CustomWidgetProposal,\n type ProposableWidgetType,\n type ProposalFieldMapping,\n} from './proposalContract';\n\n/** A sink the current user may bind to, as the panel knows it. */\nexport interface ProposalCandidateSink {\n id: string;\n key: string;\n name?: string | null;\n}\n\n/** A parser the current user may bind to. */\nexport interface ProposalCandidateParser {\n id: string;\n name: string;\n inputSinkKey: string;\n}\n\nexport interface ProposalRequest {\n prompt: string;\n sinks: readonly ProposalCandidateSink[];\n parsers: readonly ProposalCandidateParser[];\n}\n\nexport interface CustomWidgetProposalSource {\n /** Stable id, surfaced in the UI so reviewers know what produced a proposal. */\n readonly id: string;\n propose(request: ProposalRequest): Promise<CustomWidgetProposal>;\n}\n\n// ============================================================================\n// Local heuristic source\n// ============================================================================\n\n/**\n * Keyword → widget type. Ordered most-specific first so \"conversion funnel\"\n * matches funnel before a generic \"chart\" would win.\n */\nconst TYPE_KEYWORDS: ReadonlyArray<{ type: ProposableWidgetType; words: readonly string[] }> = [\n { type: 'funnel_chart', words: ['funnel', 'conversion', 'drop-off', 'dropoff', 'stage'] },\n { type: 'heatmap', words: ['heatmap', 'heat map', 'matrix', 'intensity', 'by hour', 'density'] },\n { type: 'retention', words: ['retention', 'cohort'] },\n { type: 'table', words: ['table', 'list of', 'rows', 'breakdown table', 'grid'] },\n { type: 'metric_card', words: ['kpi', 'single number', 'metric card', 'scorecard', 'total'] },\n { type: 'chart', words: ['chart', 'trend', 'over time', 'line', 'bar', 'area', 'pie', 'graph'] },\n];\n\nconst CHART_SUBTYPE_KEYWORDS: ReadonlyArray<{ chartType: string; words: readonly string[] }> = [\n { chartType: 'bar', words: ['bar'] },\n { chartType: 'area', words: ['area', 'cumulative'] },\n { chartType: 'pie', words: ['pie', 'composition', 'share', 'mix'] },\n { chartType: 'line', words: ['line', 'trend', 'over time'] },\n];\n\nfunction detectWidgetType(prompt: string): ProposableWidgetType {\n const lower = prompt.toLowerCase();\n for (const { type, words } of TYPE_KEYWORDS) {\n if (words.some((word) => lower.includes(word))) return type;\n }\n return 'chart';\n}\n\nfunction detectChartSubtype(prompt: string): string {\n const lower = prompt.toLowerCase();\n for (const { chartType, words } of CHART_SUBTYPE_KEYWORDS) {\n if (words.some((word) => lower.includes(word))) return chartType;\n }\n return 'line';\n}\n\n/**\n * Picks the sink whose key/name shares the most word-stems with the prompt.\n * Falls back to the first permitted sink so a proposal is always reviewable\n * rather than failing outright.\n */\nfunction pickSink(prompt: string, sinks: readonly ProposalCandidateSink[]): ProposalCandidateSink | undefined {\n if (sinks.length === 0) return undefined;\n const tokens = prompt\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((token) => token.length > 2);\n\n let best: { sink: ProposalCandidateSink; score: number } | undefined;\n for (const sink of sinks) {\n const haystack = `${sink.key} ${sink.name ?? ''}`.toLowerCase();\n const score = tokens.reduce((acc, token) => (haystack.includes(token) ? acc + 1 : acc), 0);\n if (!best || score > best.score) best = { sink, score };\n }\n return best?.sink ?? sinks[0];\n}\n\n/** Default field mapping per widget type, using the renderers' conventions. */\nfunction defaultFieldMapping(type: ProposableWidgetType): ProposalFieldMapping {\n switch (type) {\n case 'heatmap':\n return { xAxisField: 'column', yAxisField: 'row', valueField: 'value' };\n case 'funnel_chart':\n return { labelField: 'name', valueField: 'value' };\n case 'metric_card':\n return { valueField: 'value' };\n case 'table':\n return {};\n case 'retention':\n return { cohortField: 'cohort', valueField: 'value' };\n case 'chart':\n default:\n return { xAxisField: 'name', labelField: 'name', valueField: 'value' };\n }\n}\n\nfunction defaultConfig(type: ProposableWidgetType, prompt: string): Record<string, unknown> {\n const base: Record<string, unknown> = { showLegend: true, showTooltip: true };\n switch (type) {\n case 'chart':\n return { ...base, chartType: detectChartSubtype(prompt), showGrid: true };\n case 'funnel_chart':\n return { ...base, chartType: 'funnel', showLabels: true, showPercentages: true, orientation: 'vertical' };\n case 'heatmap':\n return { ...base, colorScheme: 'blue', showValues: true };\n case 'table':\n return { ...base, pageSize: 10, sortable: true };\n case 'metric_card':\n return { ...base, showTrend: true };\n default:\n return base;\n }\n}\n\n/** Turns the prompt's first clause into a widget title. */\nfunction deriveTitle(prompt: string): string {\n const cleaned = prompt\n .trim()\n .replace(/^(please\\s+)?(create|build|make|add|show me|show|give me)\\s+(an?\\s+|the\\s+)?/i, '')\n .split(/[.;\\n]/)[0]\n .trim();\n if (!cleaned) return 'Custom widget';\n const titled = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);\n return titled.length > 60 ? `${titled.slice(0, 57)}…` : titled;\n}\n\nlet proposalCounter = 0;\n\n/**\n * Derives a proposal from the prompt and the workspace's real, permitted sinks\n * and parsers. Deterministic, so the review UI and its tests behave predictably.\n */\nexport const localProposalSource: CustomWidgetProposalSource = {\n id: 'local-heuristic-v1',\n\n async propose({ prompt, sinks, parsers }: ProposalRequest): Promise<CustomWidgetProposal> {\n const widgetType = detectWidgetType(prompt);\n const sink = pickSink(prompt, sinks);\n const parser = sink ? parsers.find((candidate) => candidate.inputSinkKey === sink.key) : undefined;\n\n proposalCounter += 1;\n\n return {\n version: CUSTOM_WIDGET_PROPOSAL_VERSION,\n id: `proposal-${proposalCounter}`,\n sourcePrompt: prompt,\n generatedBy: localProposalSource.id,\n widgetType,\n binding: {\n dataSinkId: sink?.id ?? '',\n dataSinkKey: sink?.key,\n parserId: parser?.id,\n parserName: parser?.name,\n },\n fieldMapping: defaultFieldMapping(widgetType),\n presentation: {\n title: deriveTitle(prompt),\n description: `Generated from: \"${prompt}\"`,\n config: defaultConfig(widgetType, prompt),\n },\n rationale: sink\n ? `Matched DataSink \"${sink.key}\"${parser ? ` and parser \"${parser.name}\"` : ' (no parser found for this sink)'}.`\n : 'No DataSink is available in this workspace — create one before saving this widget.',\n };\n },\n};\n"],"mappings":"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"}