@tangle-network/agent-app 0.29.0 → 0.31.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 (44) hide show
  1. package/dist/{DesignCanvasEditor-IB2FMBBD.js → DesignCanvasEditor-QQNAZRJQ.js} +3 -3
  2. package/dist/IntakeInterview-WHVKMNQW.js +8 -0
  3. package/dist/IntakeInterview-WHVKMNQW.js.map +1 -0
  4. package/dist/chunk-4K7BZYO7.js +124 -0
  5. package/dist/chunk-4K7BZYO7.js.map +1 -0
  6. package/dist/chunk-5I72FRZY.js +212 -0
  7. package/dist/chunk-5I72FRZY.js.map +1 -0
  8. package/dist/{chunk-S5CAJX6O.js → chunk-FJUOBNIY.js} +2 -2
  9. package/dist/{chunk-Y2SZVW35.js → chunk-LVD37K5V.js} +41 -41
  10. package/dist/chunk-LVD37K5V.js.map +1 -0
  11. package/dist/chunk-ND3XEGBE.js +119 -0
  12. package/dist/chunk-ND3XEGBE.js.map +1 -0
  13. package/dist/chunk-USYXHDKE.js +30 -0
  14. package/dist/chunk-USYXHDKE.js.map +1 -0
  15. package/dist/{chunk-N4ZFKQ5C.js → chunk-YYYXGLLQ.js} +2 -2
  16. package/dist/design-canvas-react/index.js +3 -3
  17. package/dist/design-canvas-react/lazy.js +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/intakes/api.d.ts +59 -0
  20. package/dist/intakes/api.js +63 -0
  21. package/dist/intakes/api.js.map +1 -0
  22. package/dist/intakes/drizzle.d.ts +385 -0
  23. package/dist/intakes/drizzle.js +61 -0
  24. package/dist/intakes/drizzle.js.map +1 -0
  25. package/dist/intakes/index.d.ts +42 -0
  26. package/dist/intakes/index.js +31 -0
  27. package/dist/intakes/index.js.map +1 -0
  28. package/dist/intakes-react/index.d.ts +48 -0
  29. package/dist/intakes-react/index.js +8 -0
  30. package/dist/intakes-react/index.js.map +1 -0
  31. package/dist/intakes-react/lazy.d.ts +8 -0
  32. package/dist/intakes-react/lazy.js +9 -0
  33. package/dist/intakes-react/lazy.js.map +1 -0
  34. package/dist/model-Cuj7d-xT.d.ts +116 -0
  35. package/dist/theme/index.js +1 -1
  36. package/dist/theme/tokens.css +50 -48
  37. package/dist/web-react/index.d.ts +6 -1
  38. package/dist/web-react/index.js +8 -14
  39. package/dist/web-react/index.js.map +1 -1
  40. package/package.json +26 -1
  41. package/dist/chunk-Y2SZVW35.js.map +0 -1
  42. /package/dist/{DesignCanvasEditor-IB2FMBBD.js.map → DesignCanvasEditor-QQNAZRJQ.js.map} +0 -0
  43. /package/dist/{chunk-S5CAJX6O.js.map → chunk-FJUOBNIY.js.map} +0 -0
  44. /package/dist/{chunk-N4ZFKQ5C.js.map → chunk-YYYXGLLQ.js.map} +0 -0
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  DesignCanvasEditor
3
- } from "./chunk-N4ZFKQ5C.js";
3
+ } from "./chunk-YYYXGLLQ.js";
4
4
  import "./chunk-YEWTVDDT.js";
5
5
  import "./chunk-V5XZKHCL.js";
6
6
  import "./chunk-XWJXZ6WE.js";
7
- import "./chunk-Y2SZVW35.js";
7
+ import "./chunk-LVD37K5V.js";
8
8
  import "./chunk-M2EBUDZ7.js";
9
9
  import "./chunk-HFC4BTWJ.js";
10
10
  export {
11
11
  DesignCanvasEditor
12
12
  };
13
- //# sourceMappingURL=DesignCanvasEditor-IB2FMBBD.js.map
13
+ //# sourceMappingURL=DesignCanvasEditor-QQNAZRJQ.js.map
@@ -0,0 +1,8 @@
1
+ import {
2
+ IntakeInterview
3
+ } from "./chunk-5I72FRZY.js";
4
+ import "./chunk-ND3XEGBE.js";
5
+ export {
6
+ IntakeInterview
7
+ };
8
+ //# sourceMappingURL=IntakeInterview-WHVKMNQW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,124 @@
1
+ import {
2
+ emptyPayload,
3
+ markComplete,
4
+ payloadComplete,
5
+ withAnswer
6
+ } from "./chunk-USYXHDKE.js";
7
+ import {
8
+ getQuestion,
9
+ validateAnswer
10
+ } from "./chunk-ND3XEGBE.js";
11
+
12
+ // src/intakes/drizzle/store.ts
13
+ import { eq } from "drizzle-orm";
14
+ var IntakeError = class extends Error {
15
+ code;
16
+ constructor(code, message) {
17
+ super(message);
18
+ this.name = "IntakeError";
19
+ this.code = code;
20
+ }
21
+ };
22
+ function createScopedStore(opts) {
23
+ const { db, graph, table, scopeColumn, scopeValue, insertScope } = opts;
24
+ async function loadRow() {
25
+ const [row] = await db.select().from(table).where(eq(scopeColumn, scopeValue)).limit(1);
26
+ return row ?? null;
27
+ }
28
+ function toState(row) {
29
+ if (!row) {
30
+ return { payload: emptyPayload(graph), completed: false, completedAt: null };
31
+ }
32
+ const payload = normalizePayload(row.payload, row.graphId);
33
+ const completedAt = toDate(row.completedAt);
34
+ return {
35
+ payload,
36
+ completed: completedAt != null && payloadComplete(graph, payload),
37
+ completedAt
38
+ };
39
+ }
40
+ async function get() {
41
+ return toState(await loadRow());
42
+ }
43
+ async function upsertPayload(payload, completedAt) {
44
+ const existing = await loadRow();
45
+ if (existing) {
46
+ await db.update(table).set({ graphId: payload.graphId, payload, completedAt, updatedAt: /* @__PURE__ */ new Date() }).where(eq(scopeColumn, scopeValue));
47
+ return;
48
+ }
49
+ await db.insert(table).values({
50
+ ...insertScope,
51
+ graphId: payload.graphId,
52
+ payload,
53
+ completedAt
54
+ });
55
+ }
56
+ async function save(questionId, value) {
57
+ const question = getQuestion(graph, questionId);
58
+ if (!question) throw new IntakeError("unknown-question", `No question '${questionId}' in intake '${graph.id}'`);
59
+ const validity = validateAnswer(question, value);
60
+ if (!validity.ok) {
61
+ throw new IntakeError("invalid-answer", `Answer to '${questionId}' rejected: ${validity.reason}`);
62
+ }
63
+ const current = await get();
64
+ const next = withAnswer(current.payload, questionId, value);
65
+ await upsertPayload(next, current.completedAt);
66
+ return toState({ graphId: next.graphId, payload: next, completedAt: current.completedAt });
67
+ }
68
+ async function complete() {
69
+ const current = await get();
70
+ if (current.payload.graphId !== graph.id) {
71
+ throw new IntakeError("stale-graph", `Intake payload was collected against '${current.payload.graphId}', not '${graph.id}'`);
72
+ }
73
+ if (!payloadComplete(graph, current.payload)) {
74
+ throw new IntakeError("incomplete", `Intake '${graph.id}' has unanswered required questions`);
75
+ }
76
+ const completedPayload = markComplete(current.payload);
77
+ const completedAt = new Date(completedPayload.completedAt);
78
+ await upsertPayload(completedPayload, completedAt);
79
+ return { payload: completedPayload, completed: true, completedAt };
80
+ }
81
+ return { get, save, complete };
82
+ }
83
+ function createUserIntakeStore(opts) {
84
+ return createScopedStore({
85
+ db: opts.db,
86
+ graph: opts.graph,
87
+ table: opts.table,
88
+ scopeColumn: opts.table.userId,
89
+ scopeValue: opts.userId,
90
+ insertScope: { userId: opts.userId }
91
+ });
92
+ }
93
+ function createProjectIntakeStore(opts) {
94
+ return createScopedStore({
95
+ db: opts.db,
96
+ graph: opts.graph,
97
+ table: opts.table,
98
+ scopeColumn: opts.table.workspaceId,
99
+ scopeValue: opts.workspaceId,
100
+ insertScope: { workspaceId: opts.workspaceId }
101
+ });
102
+ }
103
+ function normalizePayload(raw, graphId) {
104
+ if (raw && typeof raw === "object" && "answers" in raw) {
105
+ const candidate = raw;
106
+ return {
107
+ graphId: candidate.graphId ?? graphId,
108
+ answers: candidate.answers ?? {},
109
+ ...candidate.completedAt ? { completedAt: candidate.completedAt } : {}
110
+ };
111
+ }
112
+ return { graphId, answers: {} };
113
+ }
114
+ function toDate(value) {
115
+ if (value == null) return null;
116
+ return value instanceof Date ? value : new Date(Number(value) * (Number(value) < 1e12 ? 1e3 : 1));
117
+ }
118
+
119
+ export {
120
+ IntakeError,
121
+ createUserIntakeStore,
122
+ createProjectIntakeStore
123
+ };
124
+ //# sourceMappingURL=chunk-4K7BZYO7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/intakes/drizzle/store.ts"],"sourcesContent":["/**\n * Drizzle-backed store over the tables from `createIntakeTables`. One store\n * builder per scope: `createUserIntakeStore` (keyed on userId) and\n * `createProjectIntakeStore` (keyed on workspaceId). Each returns the same\n * three-method surface — `get` / `save` / `complete` — so the api handlers and\n * the UI talk to one shape regardless of scope.\n *\n * Works against any SQLite drizzle driver (better-sqlite3, D1, libsql): the\n * builders are awaited, never `.run()`/`.all()`, so sync and async drivers\n * behave identically.\n *\n * Validation is fail-loud and pure (from the `./intakes` leaf). `save` runs\n * `validateAnswer` before writing, so an invalid answer can never enter the\n * payload; `complete` runs `payloadComplete` and refuses to stamp an\n * incomplete (or stale-graph) intake — it throws a typed `IntakeError` rather\n * than silently writing a half-finished onboarding state.\n *\n * `getProjectIntakeStore` pins `workspaceId` in every WHERE clause; RBAC runs\n * in the route before the store is built, but the scope key is enforced here\n * too, so a leaked id can never read or write across the boundary.\n *\n * Imports `drizzle-orm`, so this is a subpath, never re-exported from root.\n */\n\nimport { eq } from 'drizzle-orm'\nimport type { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core'\nimport {\n type IntakeAnswerValue,\n type IntakeGraph,\n getQuestion,\n validateAnswer,\n} from '../model'\nimport {\n type IntakePayload,\n emptyPayload,\n markComplete,\n payloadComplete,\n withAnswer,\n} from '../completion'\nimport type { ProjectIntakeTable, UserIntakeTable } from './schema'\n\n/** Any SQLite drizzle database — `any` erases driver-specific generics so\n * better-sqlite3, D1, and libsql handles all fit. */\nexport type IntakeDatabase = BaseSQLiteDatabase<'sync' | 'async', any, any>\n\n/** A loaded intake: the payload plus whether it is complete against the graph. */\nexport interface IntakeState {\n payload: IntakePayload\n completed: boolean\n completedAt: Date | null\n}\n\nexport type IntakeErrorCode = 'invalid-answer' | 'unknown-question' | 'incomplete' | 'stale-graph'\n\n/** Thrown by store mutations on a refused write — callers map it to a 4xx. */\nexport class IntakeError extends Error {\n readonly code: IntakeErrorCode\n constructor(code: IntakeErrorCode, message: string) {\n super(message)\n this.name = 'IntakeError'\n this.code = code\n }\n}\n\n/** The three-method store surface, identical for both scopes. */\nexport interface IntakeStore {\n /** Load the current intake, or seed an empty payload when none exists yet. */\n get(): Promise<IntakeState>\n /** Validate and persist one answer; returns the updated state. */\n save(questionId: string, value: IntakeAnswerValue): Promise<IntakeState>\n /** Stamp the intake complete; throws IntakeError when not yet completable. */\n complete(): Promise<IntakeState>\n}\n\ninterface IntakeTableShape {\n id: any\n graphId: any\n payload: any\n completedAt: any\n updatedAt: any\n}\n\ninterface ScopedStoreOptions {\n db: IntakeDatabase\n graph: IntakeGraph\n table: IntakeTableShape\n /** The scope column (userIntake.userId or projectIntake.workspaceId). */\n scopeColumn: any\n scopeValue: string\n /** Extra columns to set on insert (the scope key). */\n insertScope: Record<string, unknown>\n}\n\nfunction createScopedStore(opts: ScopedStoreOptions): IntakeStore {\n const { db, graph, table, scopeColumn, scopeValue, insertScope } = opts\n\n async function loadRow() {\n const [row] = await db\n .select()\n .from(table as any)\n .where(eq(scopeColumn, scopeValue))\n .limit(1)\n return (row ?? null) as\n | { id: string; graphId: string; payload: unknown; completedAt: Date | number | null }\n | null\n }\n\n function toState(\n row: { graphId: string; payload: unknown; completedAt: Date | number | null } | null,\n ): IntakeState {\n if (!row) {\n return { payload: emptyPayload(graph), completed: false, completedAt: null }\n }\n const payload = normalizePayload(row.payload, row.graphId)\n const completedAt = toDate(row.completedAt)\n return {\n payload,\n completed: completedAt != null && payloadComplete(graph, payload),\n completedAt,\n }\n }\n\n async function get(): Promise<IntakeState> {\n return toState(await loadRow())\n }\n\n async function upsertPayload(payload: IntakePayload, completedAt: Date | null) {\n const existing = await loadRow()\n if (existing) {\n await db\n .update(table as any)\n .set({ graphId: payload.graphId, payload, completedAt, updatedAt: new Date() })\n .where(eq(scopeColumn, scopeValue))\n return\n }\n await db.insert(table as any).values({\n ...insertScope,\n graphId: payload.graphId,\n payload,\n completedAt,\n })\n }\n\n async function save(questionId: string, value: IntakeAnswerValue): Promise<IntakeState> {\n const question = getQuestion(graph, questionId)\n if (!question) throw new IntakeError('unknown-question', `No question '${questionId}' in intake '${graph.id}'`)\n const validity = validateAnswer(question, value)\n if (!validity.ok) {\n throw new IntakeError('invalid-answer', `Answer to '${questionId}' rejected: ${validity.reason}`)\n }\n\n const current = await get()\n const next = withAnswer(current.payload, questionId, value)\n await upsertPayload(next, current.completedAt)\n return toState({ graphId: next.graphId, payload: next, completedAt: current.completedAt })\n }\n\n async function complete(): Promise<IntakeState> {\n const current = await get()\n if (current.payload.graphId !== graph.id) {\n throw new IntakeError('stale-graph', `Intake payload was collected against '${current.payload.graphId}', not '${graph.id}'`)\n }\n if (!payloadComplete(graph, current.payload)) {\n throw new IntakeError('incomplete', `Intake '${graph.id}' has unanswered required questions`)\n }\n const completedPayload = markComplete(current.payload)\n const completedAt = new Date(completedPayload.completedAt!)\n await upsertPayload(completedPayload, completedAt)\n return { payload: completedPayload, completed: true, completedAt }\n }\n\n return { get, save, complete }\n}\n\nexport interface CreateUserIntakeStoreOptions {\n db: IntakeDatabase\n /** The user-intake table from createIntakeTables. */\n table: UserIntakeTable\n /** The intake definition this store collects answers against. */\n graph: IntakeGraph\n /** The user whose onboarding this is. */\n userId: string\n}\n\n/** Build the per-user onboarding store, scoped to one userId. */\nexport function createUserIntakeStore(opts: CreateUserIntakeStoreOptions): IntakeStore {\n return createScopedStore({\n db: opts.db,\n graph: opts.graph,\n table: opts.table as unknown as IntakeTableShape,\n scopeColumn: opts.table.userId,\n scopeValue: opts.userId,\n insertScope: { userId: opts.userId },\n })\n}\n\nexport interface CreateProjectIntakeStoreOptions {\n db: IntakeDatabase\n /** The project-intake table from createIntakeTables (workspace scope). */\n table: ProjectIntakeTable\n graph: IntakeGraph\n /** The workspace this intake is attached to. */\n workspaceId: string\n}\n\n/** Build the per-project store, scoped to one workspaceId. */\nexport function createProjectIntakeStore(opts: CreateProjectIntakeStoreOptions): IntakeStore {\n return createScopedStore({\n db: opts.db,\n graph: opts.graph,\n table: opts.table as unknown as IntakeTableShape,\n scopeColumn: opts.table.workspaceId,\n scopeValue: opts.workspaceId,\n insertScope: { workspaceId: opts.workspaceId },\n })\n}\n\nfunction normalizePayload(raw: unknown, graphId: string): IntakePayload {\n if (raw && typeof raw === 'object' && 'answers' in (raw as Record<string, unknown>)) {\n const candidate = raw as Partial<IntakePayload>\n return {\n graphId: candidate.graphId ?? graphId,\n answers: candidate.answers ?? {},\n ...(candidate.completedAt ? { completedAt: candidate.completedAt } : {}),\n }\n }\n return { graphId, answers: {} }\n}\n\nfunction toDate(value: Date | number | null | undefined): Date | null {\n if (value == null) return null\n return value instanceof Date ? value : new Date(Number(value) * (Number(value) < 1e12 ? 1000 : 1))\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,212 @@
1
+ import {
2
+ validateAnswer
3
+ } from "./chunk-ND3XEGBE.js";
4
+
5
+ // src/intakes-react/components/IntakeInterview.tsx
6
+ import { useEffect, useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ function IntakeInterview({
9
+ view: initialView,
10
+ onAnswer,
11
+ onComplete,
12
+ onDone,
13
+ onNotice
14
+ }) {
15
+ const [view, setView] = useState(initialView);
16
+ const [draft, setDraft] = useState(currentAnswer(initialView));
17
+ const [busy, setBusy] = useState(false);
18
+ const [doneFired, setDoneFired] = useState(false);
19
+ useEffect(() => {
20
+ setView(initialView);
21
+ setDraft(currentAnswer(initialView));
22
+ }, [initialView]);
23
+ useEffect(() => {
24
+ if (view.completed && !doneFired) {
25
+ setDoneFired(true);
26
+ onDone?.();
27
+ }
28
+ }, [view.completed, doneFired, onDone]);
29
+ function notify(kind, message) {
30
+ onNotice?.({ kind, message });
31
+ }
32
+ const question = view.nextQuestion;
33
+ async function submit() {
34
+ if (!question || busy) return;
35
+ const validity = validateAnswer(question, draft);
36
+ if (!validity.ok) {
37
+ notify("error", `Please answer: ${validity.reason}`);
38
+ return;
39
+ }
40
+ setBusy(true);
41
+ try {
42
+ const next = await onAnswer({ questionId: question.id, value: normalize(question, draft) });
43
+ setView(next);
44
+ setDraft(currentAnswer(next));
45
+ } catch (err) {
46
+ notify("error", err instanceof Error ? err.message : "Failed to save answer");
47
+ } finally {
48
+ setBusy(false);
49
+ }
50
+ }
51
+ async function finish() {
52
+ if (busy) return;
53
+ setBusy(true);
54
+ try {
55
+ const next = await onComplete();
56
+ setView(next);
57
+ notify("success", "All set.");
58
+ } catch (err) {
59
+ notify("error", err instanceof Error ? err.message : "Failed to finish");
60
+ } finally {
61
+ setBusy(false);
62
+ }
63
+ }
64
+ return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-5", children: [
65
+ /* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-1", children: [
66
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-[var(--text-primary)]", children: view.title }),
67
+ view.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--text-muted)]", children: view.description }),
68
+ /* @__PURE__ */ jsx(ProgressBar, { answered: view.progress.answered, total: view.progress.total })
69
+ ] }),
70
+ view.completed ? /* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--text-secondary)]", children: "Thanks \u2014 your intake is complete." }) : question ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
71
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
72
+ /* @__PURE__ */ jsx("p", { className: "text-base font-medium text-[var(--text-primary)]", children: question.prompt }),
73
+ question.help && /* @__PURE__ */ jsx("p", { className: "text-xs text-[var(--text-muted)]", children: question.help })
74
+ ] }),
75
+ /* @__PURE__ */ jsx(AnswerField, { question, value: draft, onChange: setDraft, onSubmit: () => void submit() }),
76
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
77
+ "button",
78
+ {
79
+ type: "button",
80
+ onClick: () => void submit(),
81
+ disabled: busy,
82
+ className: "rounded bg-[var(--brand-primary)] px-4 py-1.5 text-sm text-white disabled:opacity-50",
83
+ children: busy ? "Saving\u2026" : "Continue"
84
+ }
85
+ ) })
86
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
87
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--text-secondary)]", children: "That's everything. Ready to finish?" }),
88
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
89
+ "button",
90
+ {
91
+ type: "button",
92
+ onClick: () => void finish(),
93
+ disabled: busy,
94
+ className: "rounded bg-[var(--brand-primary)] px-4 py-1.5 text-sm text-white disabled:opacity-50",
95
+ children: busy ? "Finishing\u2026" : "Finish"
96
+ }
97
+ ) })
98
+ ] })
99
+ ] });
100
+ }
101
+ function AnswerField({ question, value, onChange, onSubmit }) {
102
+ const inputClass = "rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-2 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-muted)]";
103
+ switch (question.type) {
104
+ case "long-text":
105
+ return /* @__PURE__ */ jsx(
106
+ "textarea",
107
+ {
108
+ rows: 4,
109
+ "aria-label": question.prompt,
110
+ value: typeof value === "string" ? value : "",
111
+ onChange: (event) => onChange(event.target.value),
112
+ className: inputClass
113
+ }
114
+ );
115
+ case "boolean":
116
+ return /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: [{ v: true, l: "Yes" }, { v: false, l: "No" }].map((opt) => /* @__PURE__ */ jsx(
117
+ "button",
118
+ {
119
+ type: "button",
120
+ onClick: () => onChange(opt.v),
121
+ className: `rounded border px-4 py-1.5 text-sm ${value === opt.v ? "border-[var(--brand-primary)] bg-[var(--brand-primary)] text-white" : "border-[var(--border-default)] bg-[var(--bg-input)] text-[var(--text-secondary)]"}`,
122
+ children: opt.l
123
+ },
124
+ opt.l
125
+ )) });
126
+ case "number":
127
+ return /* @__PURE__ */ jsx(
128
+ "input",
129
+ {
130
+ type: "number",
131
+ "aria-label": question.prompt,
132
+ value: typeof value === "number" ? value : "",
133
+ onChange: (event) => onChange(event.target.value === "" ? null : Number(event.target.value)),
134
+ onKeyDown: (event) => {
135
+ if (event.key === "Enter") onSubmit();
136
+ },
137
+ className: inputClass
138
+ }
139
+ );
140
+ case "single-select":
141
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: (question.options ?? []).map((option) => /* @__PURE__ */ jsx(
142
+ "button",
143
+ {
144
+ type: "button",
145
+ onClick: () => onChange(option.value),
146
+ className: `rounded border px-3 py-2 text-left text-sm ${value === option.value ? "border-[var(--brand-primary)] bg-[var(--bg-input)] text-[var(--text-primary)]" : "border-[var(--border-default)] bg-[var(--bg-input)] text-[var(--text-secondary)]"}`,
147
+ children: option.label
148
+ },
149
+ option.value
150
+ )) });
151
+ case "multi-select": {
152
+ const selected = Array.isArray(value) ? value : [];
153
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1.5", children: (question.options ?? []).map((option) => {
154
+ const on = selected.includes(option.value);
155
+ return /* @__PURE__ */ jsx(
156
+ "button",
157
+ {
158
+ type: "button",
159
+ onClick: () => onChange(on ? selected.filter((v) => v !== option.value) : [...selected, option.value]),
160
+ className: `rounded border px-3 py-2 text-left text-sm ${on ? "border-[var(--brand-primary)] bg-[var(--bg-input)] text-[var(--text-primary)]" : "border-[var(--border-default)] bg-[var(--bg-input)] text-[var(--text-secondary)]"}`,
161
+ children: option.label
162
+ },
163
+ option.value
164
+ );
165
+ }) });
166
+ }
167
+ default:
168
+ return /* @__PURE__ */ jsx(
169
+ "input",
170
+ {
171
+ type: question.type === "email" ? "email" : question.type === "url" ? "url" : "text",
172
+ "aria-label": question.prompt,
173
+ value: typeof value === "string" ? value : "",
174
+ onChange: (event) => onChange(event.target.value),
175
+ onKeyDown: (event) => {
176
+ if (event.key === "Enter") onSubmit();
177
+ },
178
+ className: inputClass
179
+ }
180
+ );
181
+ }
182
+ }
183
+ function ProgressBar({ answered, total }) {
184
+ const pct = total > 0 ? Math.round(answered / total * 100) : 0;
185
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
186
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 flex-1 overflow-hidden rounded-full bg-[var(--bg-input)]", children: /* @__PURE__ */ jsx("div", { className: "h-full rounded-full bg-[var(--brand-primary)] transition-all", style: { width: `${pct}%` } }) }),
187
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-[var(--text-muted)]", children: [
188
+ answered,
189
+ "/",
190
+ total
191
+ ] })
192
+ ] });
193
+ }
194
+ function currentAnswer(view) {
195
+ const id = view.nextQuestion?.id;
196
+ if (!id) return null;
197
+ return view.answers[id] ?? defaultDraft(view.nextQuestion);
198
+ }
199
+ function defaultDraft(question) {
200
+ return question.type === "multi-select" ? [] : null;
201
+ }
202
+ function normalize(question, value) {
203
+ if (typeof value === "string" && (question.type === "text" || question.type === "long-text" || question.type === "url" || question.type === "email")) {
204
+ return value.trim();
205
+ }
206
+ return value;
207
+ }
208
+
209
+ export {
210
+ IntakeInterview
211
+ };
212
+ //# sourceMappingURL=chunk-5I72FRZY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/intakes-react/components/IntakeInterview.tsx"],"sourcesContent":["/**\n * Agent-led intake interview: asks one question at a time, advancing as each\n * answer is saved. Fully callback-driven — the host supplies the loaded `view`\n * and the async `onAnswer` / `onComplete` callbacks (backed by `./intakes/api`),\n * so this imports no app router, fetch client, or toast. Styled with the\n * shipped Tangle Quiet tokens (`var(--*)`).\n *\n * The server owns traversal: each `onAnswer` resolves with the next view (next\n * question + progress), so the component never re-derives the graph — it renders\n * `view.nextQuestion`, and when that is null and the view is completable it\n * shows the finish action. Local validation only gates the submit button; the\n * authoritative validation runs in the store.\n */\n\nimport { useEffect, useState } from 'react'\nimport type { IntakeAnswerValue, IntakeQuestion } from '../../intakes/model'\nimport { validateAnswer } from '../../intakes/model'\nimport type { IntakeInterviewProps, IntakeView } from '../contracts'\n\nexport function IntakeInterview({\n view: initialView,\n onAnswer,\n onComplete,\n onDone,\n onNotice,\n}: IntakeInterviewProps) {\n const [view, setView] = useState<IntakeView>(initialView)\n const [draft, setDraft] = useState<IntakeAnswerValue>(currentAnswer(initialView))\n const [busy, setBusy] = useState(false)\n const [doneFired, setDoneFired] = useState(false)\n\n useEffect(() => {\n setView(initialView)\n setDraft(currentAnswer(initialView))\n }, [initialView])\n\n useEffect(() => {\n if (view.completed && !doneFired) {\n setDoneFired(true)\n onDone?.()\n }\n }, [view.completed, doneFired, onDone])\n\n function notify(kind: 'success' | 'error', message: string) {\n onNotice?.({ kind, message })\n }\n\n const question = view.nextQuestion\n\n async function submit() {\n if (!question || busy) return\n const validity = validateAnswer(question, draft)\n if (!validity.ok) {\n notify('error', `Please answer: ${validity.reason}`)\n return\n }\n setBusy(true)\n try {\n const next = await onAnswer({ questionId: question.id, value: normalize(question, draft) })\n setView(next)\n setDraft(currentAnswer(next))\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to save answer')\n } finally {\n setBusy(false)\n }\n }\n\n async function finish() {\n if (busy) return\n setBusy(true)\n try {\n const next = await onComplete()\n setView(next)\n notify('success', 'All set.')\n } catch (err) {\n notify('error', err instanceof Error ? err.message : 'Failed to finish')\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <section className=\"flex flex-col gap-5\">\n <header className=\"flex flex-col gap-1\">\n <h2 className=\"text-lg font-semibold text-[var(--text-primary)]\">{view.title}</h2>\n {view.description && <p className=\"text-sm text-[var(--text-muted)]\">{view.description}</p>}\n <ProgressBar answered={view.progress.answered} total={view.progress.total} />\n </header>\n\n {view.completed ? (\n <p className=\"text-sm text-[var(--text-secondary)]\">Thanks — your intake is complete.</p>\n ) : question ? (\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex flex-col gap-1\">\n <p className=\"text-base font-medium text-[var(--text-primary)]\">{question.prompt}</p>\n {question.help && <p className=\"text-xs text-[var(--text-muted)]\">{question.help}</p>}\n </div>\n\n <AnswerField question={question} value={draft} onChange={setDraft} onSubmit={() => void submit()} />\n\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={() => void submit()}\n disabled={busy}\n className=\"rounded bg-[var(--brand-primary)] px-4 py-1.5 text-sm text-white disabled:opacity-50\"\n >\n {busy ? 'Saving…' : 'Continue'}\n </button>\n </div>\n </div>\n ) : (\n <div className=\"flex flex-col gap-3\">\n <p className=\"text-sm text-[var(--text-secondary)]\">That's everything. Ready to finish?</p>\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={() => void finish()}\n disabled={busy}\n className=\"rounded bg-[var(--brand-primary)] px-4 py-1.5 text-sm text-white disabled:opacity-50\"\n >\n {busy ? 'Finishing…' : 'Finish'}\n </button>\n </div>\n </div>\n )}\n </section>\n )\n}\n\ninterface AnswerFieldProps {\n question: IntakeQuestion\n value: IntakeAnswerValue\n onChange(value: IntakeAnswerValue): void\n onSubmit(): void\n}\n\nfunction AnswerField({ question, value, onChange, onSubmit }: AnswerFieldProps) {\n const inputClass =\n 'rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-2 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-muted)]'\n\n switch (question.type) {\n case 'long-text':\n return (\n <textarea\n rows={4}\n aria-label={question.prompt}\n value={typeof value === 'string' ? value : ''}\n onChange={(event) => onChange(event.target.value)}\n className={inputClass}\n />\n )\n case 'boolean':\n return (\n <div className=\"flex gap-2\">\n {[{ v: true, l: 'Yes' }, { v: false, l: 'No' }].map((opt) => (\n <button\n key={opt.l}\n type=\"button\"\n onClick={() => onChange(opt.v)}\n className={`rounded border px-4 py-1.5 text-sm ${\n value === opt.v\n ? 'border-[var(--brand-primary)] bg-[var(--brand-primary)] text-white'\n : 'border-[var(--border-default)] bg-[var(--bg-input)] text-[var(--text-secondary)]'\n }`}\n >\n {opt.l}\n </button>\n ))}\n </div>\n )\n case 'number':\n return (\n <input\n type=\"number\"\n aria-label={question.prompt}\n value={typeof value === 'number' ? value : ''}\n onChange={(event) => onChange(event.target.value === '' ? null : Number(event.target.value))}\n onKeyDown={(event) => { if (event.key === 'Enter') onSubmit() }}\n className={inputClass}\n />\n )\n case 'single-select':\n return (\n <div className=\"flex flex-col gap-1.5\">\n {(question.options ?? []).map((option) => (\n <button\n key={option.value}\n type=\"button\"\n onClick={() => onChange(option.value)}\n className={`rounded border px-3 py-2 text-left text-sm ${\n value === option.value\n ? 'border-[var(--brand-primary)] bg-[var(--bg-input)] text-[var(--text-primary)]'\n : 'border-[var(--border-default)] bg-[var(--bg-input)] text-[var(--text-secondary)]'\n }`}\n >\n {option.label}\n </button>\n ))}\n </div>\n )\n case 'multi-select': {\n const selected = Array.isArray(value) ? value : []\n return (\n <div className=\"flex flex-col gap-1.5\">\n {(question.options ?? []).map((option) => {\n const on = selected.includes(option.value)\n return (\n <button\n key={option.value}\n type=\"button\"\n onClick={() =>\n onChange(on ? selected.filter((v) => v !== option.value) : [...selected, option.value])\n }\n className={`rounded border px-3 py-2 text-left text-sm ${\n on\n ? 'border-[var(--brand-primary)] bg-[var(--bg-input)] text-[var(--text-primary)]'\n : 'border-[var(--border-default)] bg-[var(--bg-input)] text-[var(--text-secondary)]'\n }`}\n >\n {option.label}\n </button>\n )\n })}\n </div>\n )\n }\n default:\n return (\n <input\n type={question.type === 'email' ? 'email' : question.type === 'url' ? 'url' : 'text'}\n aria-label={question.prompt}\n value={typeof value === 'string' ? value : ''}\n onChange={(event) => onChange(event.target.value)}\n onKeyDown={(event) => { if (event.key === 'Enter') onSubmit() }}\n className={inputClass}\n />\n )\n }\n}\n\nfunction ProgressBar({ answered, total }: { answered: number; total: number }) {\n const pct = total > 0 ? Math.round((answered / total) * 100) : 0\n return (\n <div className=\"flex items-center gap-2\">\n <div className=\"h-1.5 flex-1 overflow-hidden rounded-full bg-[var(--bg-input)]\">\n <div className=\"h-full rounded-full bg-[var(--brand-primary)] transition-all\" style={{ width: `${pct}%` }} />\n </div>\n <span className=\"text-xs text-[var(--text-muted)]\">{answered}/{total}</span>\n </div>\n )\n}\n\nfunction currentAnswer(view: IntakeView): IntakeAnswerValue {\n const id = view.nextQuestion?.id\n if (!id) return null\n return view.answers[id] ?? defaultDraft(view.nextQuestion!)\n}\n\nfunction defaultDraft(question: IntakeQuestion): IntakeAnswerValue {\n return question.type === 'multi-select' ? [] : null\n}\n\n/** Trim text answers before submit; pass others through unchanged. */\nfunction normalize(question: IntakeQuestion, value: IntakeAnswerValue): IntakeAnswerValue {\n if (typeof value === 'string' && (question.type === 'text' || question.type === 'long-text' || question.type === 'url' || question.type === 'email')) {\n return value.trim()\n }\n return value\n}\n"],"mappings":";;;;;AAcA,SAAS,WAAW,gBAAgB;AAsE9B,SACE,KADF;AAjEC,SAAS,gBAAgB;AAAA,EAC9B,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,CAAC,MAAM,OAAO,IAAI,SAAqB,WAAW;AACxD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA4B,cAAc,WAAW,CAAC;AAChF,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,YAAU,MAAM;AACd,YAAQ,WAAW;AACnB,aAAS,cAAc,WAAW,CAAC;AAAA,EACrC,GAAG,CAAC,WAAW,CAAC;AAEhB,YAAU,MAAM;AACd,QAAI,KAAK,aAAa,CAAC,WAAW;AAChC,mBAAa,IAAI;AACjB,eAAS;AAAA,IACX;AAAA,EACF,GAAG,CAAC,KAAK,WAAW,WAAW,MAAM,CAAC;AAEtC,WAAS,OAAO,MAA2B,SAAiB;AAC1D,eAAW,EAAE,MAAM,QAAQ,CAAC;AAAA,EAC9B;AAEA,QAAM,WAAW,KAAK;AAEtB,iBAAe,SAAS;AACtB,QAAI,CAAC,YAAY,KAAM;AACvB,UAAM,WAAW,eAAe,UAAU,KAAK;AAC/C,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO,SAAS,kBAAkB,SAAS,MAAM,EAAE;AACnD;AAAA,IACF;AACA,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,OAAO,MAAM,SAAS,EAAE,YAAY,SAAS,IAAI,OAAO,UAAU,UAAU,KAAK,EAAE,CAAC;AAC1F,cAAQ,IAAI;AACZ,eAAS,cAAc,IAAI,CAAC;AAAA,IAC9B,SAAS,KAAK;AACZ,aAAO,SAAS,eAAe,QAAQ,IAAI,UAAU,uBAAuB;AAAA,IAC9E,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAEA,iBAAe,SAAS;AACtB,QAAI,KAAM;AACV,YAAQ,IAAI;AACZ,QAAI;AACF,YAAM,OAAO,MAAM,WAAW;AAC9B,cAAQ,IAAI;AACZ,aAAO,WAAW,UAAU;AAAA,IAC9B,SAAS,KAAK;AACZ,aAAO,SAAS,eAAe,QAAQ,IAAI,UAAU,kBAAkB;AAAA,IACzE,UAAE;AACA,cAAQ,KAAK;AAAA,IACf;AAAA,EACF;AAEA,SACE,qBAAC,aAAQ,WAAU,uBACjB;AAAA,yBAAC,YAAO,WAAU,uBAChB;AAAA,0BAAC,QAAG,WAAU,oDAAoD,eAAK,OAAM;AAAA,MAC5E,KAAK,eAAe,oBAAC,OAAE,WAAU,oCAAoC,eAAK,aAAY;AAAA,MACvF,oBAAC,eAAY,UAAU,KAAK,SAAS,UAAU,OAAO,KAAK,SAAS,OAAO;AAAA,OAC7E;AAAA,IAEC,KAAK,YACJ,oBAAC,OAAE,WAAU,wCAAuC,oDAAiC,IACnF,WACF,qBAAC,SAAI,WAAU,uBACb;AAAA,2BAAC,SAAI,WAAU,uBACb;AAAA,4BAAC,OAAE,WAAU,oDAAoD,mBAAS,QAAO;AAAA,QAChF,SAAS,QAAQ,oBAAC,OAAE,WAAU,oCAAoC,mBAAS,MAAK;AAAA,SACnF;AAAA,MAEA,oBAAC,eAAY,UAAoB,OAAO,OAAO,UAAU,UAAU,UAAU,MAAM,KAAK,OAAO,GAAG;AAAA,MAElG,oBAAC,SAAI,WAAU,oBACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,KAAK,OAAO;AAAA,UAC3B,UAAU;AAAA,UACV,WAAU;AAAA,UAET,iBAAO,iBAAY;AAAA;AAAA,MACtB,GACF;AAAA,OACF,IAEA,qBAAC,SAAI,WAAU,uBACb;AAAA,0BAAC,OAAE,WAAU,wCAAuC,iDAAmC;AAAA,MACvF,oBAAC,SAAI,WAAU,oBACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,KAAK,OAAO;AAAA,UAC3B,UAAU;AAAA,UACV,WAAU;AAAA,UAET,iBAAO,oBAAe;AAAA;AAAA,MACzB,GACF;AAAA,OACF;AAAA,KAEJ;AAEJ;AASA,SAAS,YAAY,EAAE,UAAU,OAAO,UAAU,SAAS,GAAqB;AAC9E,QAAM,aACJ;AAEF,UAAQ,SAAS,MAAM;AAAA,IACrB,KAAK;AACH,aACE;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN,cAAY,SAAS;AAAA,UACrB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,UAC3C,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,UAChD,WAAW;AAAA;AAAA,MACb;AAAA,IAEJ,KAAK;AACH,aACE,oBAAC,SAAI,WAAU,cACZ,WAAC,EAAE,GAAG,MAAM,GAAG,MAAM,GAAG,EAAE,GAAG,OAAO,GAAG,KAAK,CAAC,EAAE,IAAI,CAAC,QACnD;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM,SAAS,IAAI,CAAC;AAAA,UAC7B,WAAW,sCACT,UAAU,IAAI,IACV,uEACA,kFACN;AAAA,UAEC,cAAI;AAAA;AAAA,QATA,IAAI;AAAA,MAUX,CACD,GACH;AAAA,IAEJ,KAAK;AACH,aACE;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,cAAY,SAAS;AAAA,UACrB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,UAC3C,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,UAAU,KAAK,OAAO,OAAO,MAAM,OAAO,KAAK,CAAC;AAAA,UAC3F,WAAW,CAAC,UAAU;AAAE,gBAAI,MAAM,QAAQ,QAAS,UAAS;AAAA,UAAE;AAAA,UAC9D,WAAW;AAAA;AAAA,MACb;AAAA,IAEJ,KAAK;AACH,aACE,oBAAC,SAAI,WAAU,yBACX,oBAAS,WAAW,CAAC,GAAG,IAAI,CAAC,WAC7B;AAAA,QAAC;AAAA;AAAA,UAEC,MAAK;AAAA,UACL,SAAS,MAAM,SAAS,OAAO,KAAK;AAAA,UACpC,WAAW,8CACT,UAAU,OAAO,QACb,kFACA,kFACN;AAAA,UAEC,iBAAO;AAAA;AAAA,QATH,OAAO;AAAA,MAUd,CACD,GACH;AAAA,IAEJ,KAAK,gBAAgB;AACnB,YAAM,WAAW,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC;AACjD,aACE,oBAAC,SAAI,WAAU,yBACX,oBAAS,WAAW,CAAC,GAAG,IAAI,CAAC,WAAW;AACxC,cAAM,KAAK,SAAS,SAAS,OAAO,KAAK;AACzC,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,SAAS,MACP,SAAS,KAAK,SAAS,OAAO,CAAC,MAAM,MAAM,OAAO,KAAK,IAAI,CAAC,GAAG,UAAU,OAAO,KAAK,CAAC;AAAA,YAExF,WAAW,8CACT,KACI,kFACA,kFACN;AAAA,YAEC,iBAAO;AAAA;AAAA,UAXH,OAAO;AAAA,QAYd;AAAA,MAEJ,CAAC,GACH;AAAA,IAEJ;AAAA,IACA;AACE,aACE;AAAA,QAAC;AAAA;AAAA,UACC,MAAM,SAAS,SAAS,UAAU,UAAU,SAAS,SAAS,QAAQ,QAAQ;AAAA,UAC9E,cAAY,SAAS;AAAA,UACrB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,UAC3C,UAAU,CAAC,UAAU,SAAS,MAAM,OAAO,KAAK;AAAA,UAChD,WAAW,CAAC,UAAU;AAAE,gBAAI,MAAM,QAAQ,QAAS,UAAS;AAAA,UAAE;AAAA,UAC9D,WAAW;AAAA;AAAA,MACb;AAAA,EAEN;AACF;AAEA,SAAS,YAAY,EAAE,UAAU,MAAM,GAAwC;AAC7E,QAAM,MAAM,QAAQ,IAAI,KAAK,MAAO,WAAW,QAAS,GAAG,IAAI;AAC/D,SACE,qBAAC,SAAI,WAAU,2BACb;AAAA,wBAAC,SAAI,WAAU,kEACb,8BAAC,SAAI,WAAU,gEAA+D,OAAO,EAAE,OAAO,GAAG,GAAG,IAAI,GAAG,GAC7G;AAAA,IACA,qBAAC,UAAK,WAAU,oCAAoC;AAAA;AAAA,MAAS;AAAA,MAAE;AAAA,OAAM;AAAA,KACvE;AAEJ;AAEA,SAAS,cAAc,MAAqC;AAC1D,QAAM,KAAK,KAAK,cAAc;AAC9B,MAAI,CAAC,GAAI,QAAO;AAChB,SAAO,KAAK,QAAQ,EAAE,KAAK,aAAa,KAAK,YAAa;AAC5D;AAEA,SAAS,aAAa,UAA6C;AACjE,SAAO,SAAS,SAAS,iBAAiB,CAAC,IAAI;AACjD;AAGA,SAAS,UAAU,UAA0B,OAA6C;AACxF,MAAI,OAAO,UAAU,aAAa,SAAS,SAAS,UAAU,SAAS,SAAS,eAAe,SAAS,SAAS,SAAS,SAAS,SAAS,UAAU;AACpJ,WAAO,MAAM,KAAK;AAAA,EACpB;AACA,SAAO;AACT;","names":[]}
@@ -1,7 +1,7 @@
1
1
  // src/design-canvas-react/lazy.tsx
2
2
  import { lazy } from "react";
3
3
  var DesignCanvasLazy = lazy(
4
- () => import("./DesignCanvasEditor-IB2FMBBD.js").then((m) => ({ default: m.DesignCanvasEditor }))
4
+ () => import("./DesignCanvasEditor-QQNAZRJQ.js").then((m) => ({ default: m.DesignCanvasEditor }))
5
5
  );
6
6
  var DesignCanvasChromeLazy = lazy(
7
7
  () => import("./DesignCanvas-AOIVN3SJ.js").then((m) => ({ default: m.DesignCanvas }))
@@ -11,4 +11,4 @@ export {
11
11
  DesignCanvasLazy,
12
12
  DesignCanvasChromeLazy
13
13
  };
14
- //# sourceMappingURL=chunk-S5CAJX6O.js.map
14
+ //# sourceMappingURL=chunk-FJUOBNIY.js.map
@@ -1,29 +1,29 @@
1
1
  // src/theme/theme.ts
2
2
  var lightTheme = {
3
3
  background: "0 0% 100%",
4
- foreground: "222 47% 11%",
5
- card: "0 0% 100%",
6
- cardForeground: "222 47% 11%",
4
+ foreground: "0 0% 5%",
5
+ card: "240 7% 97%",
6
+ cardForeground: "0 0% 5%",
7
7
  popover: "0 0% 100%",
8
- popoverForeground: "222 47% 11%",
9
- primary: "221 83% 47%",
8
+ popoverForeground: "0 0% 5%",
9
+ primary: "245 62% 57%",
10
10
  primaryForeground: "0 0% 100%",
11
- secondary: "210 40% 96%",
12
- secondaryForeground: "222 47% 30%",
13
- muted: "210 40% 96%",
14
- mutedForeground: "215 16% 44%",
15
- accent: "210 40% 96%",
16
- accentForeground: "222 47% 11%",
17
- destructive: "0 72% 45%",
11
+ secondary: "240 6% 93%",
12
+ secondaryForeground: "0 0% 10%",
13
+ muted: "240 6% 93%",
14
+ mutedForeground: "240 5% 38%",
15
+ accent: "240 6% 93%",
16
+ accentForeground: "0 0% 10%",
17
+ destructive: "0 72% 41%",
18
18
  destructiveForeground: "0 0% 100%",
19
- border: "214 32% 91%",
20
- input: "214 32% 91%",
21
- ring: "221 83% 53%",
22
- success: "142 72% 29%",
19
+ border: "240 6% 89%",
20
+ input: "240 6% 89%",
21
+ ring: "245 62% 57%",
22
+ success: "160 84% 26%",
23
23
  successForeground: "0 0% 100%",
24
- warning: "38 92% 32%",
24
+ warning: "41 96% 38%",
25
25
  warningForeground: "38 92% 12%",
26
- canvasBackdrop: "hsl(220 13% 91%)",
26
+ canvasBackdrop: "hsl(240 7% 90%)",
27
27
  canvasRender: {
28
28
  grid: "#c0c0c0",
29
29
  snapGrid: "#a0a0a0",
@@ -39,30 +39,30 @@ var lightTheme = {
39
39
  }
40
40
  };
41
41
  var darkTheme = {
42
- background: "222 47% 7%",
43
- foreground: "210 40% 98%",
44
- card: "222 47% 11%",
45
- cardForeground: "210 40% 98%",
46
- popover: "222 47% 11%",
47
- popoverForeground: "210 40% 98%",
48
- primary: "217 91% 72%",
49
- primaryForeground: "222 47% 11%",
50
- secondary: "217 33% 17%",
51
- secondaryForeground: "210 40% 90%",
52
- muted: "217 33% 17%",
53
- mutedForeground: "215 20% 65%",
54
- accent: "217 33% 17%",
55
- accentForeground: "210 40% 98%",
56
- destructive: "0 78% 67%",
42
+ background: "240 8% 5%",
43
+ foreground: "240 6% 93%",
44
+ card: "240 5% 8%",
45
+ cardForeground: "240 6% 93%",
46
+ popover: "240 4% 13%",
47
+ popoverForeground: "240 6% 93%",
48
+ primary: "239 84% 74%",
49
+ primaryForeground: "0 0% 100%",
50
+ secondary: "240 5% 11%",
51
+ secondaryForeground: "240 6% 93%",
52
+ muted: "240 5% 11%",
53
+ mutedForeground: "240 4% 62%",
54
+ accent: "240 5% 11%",
55
+ accentForeground: "240 6% 93%",
56
+ destructive: "348 90% 68%",
57
57
  destructiveForeground: "0 0% 12%",
58
- border: "217 33% 20%",
59
- input: "217 33% 20%",
60
- ring: "217 91% 60%",
61
- success: "142 60% 50%",
62
- successForeground: "142 80% 12%",
63
- warning: "38 95% 58%",
58
+ border: "240 3% 13%",
59
+ input: "240 5% 11%",
60
+ ring: "239 84% 74%",
61
+ success: "160 70% 52%",
62
+ successForeground: "160 84% 10%",
63
+ warning: "40 94% 56%",
64
64
  warningForeground: "38 92% 12%",
65
- canvasBackdrop: "hsl(0 0% 10%)",
65
+ canvasBackdrop: "hsl(240 8% 5%)",
66
66
  canvasRender: {
67
67
  grid: "#3a3a3a",
68
68
  snapGrid: "#5a5a5a",
@@ -122,4 +122,4 @@ export {
122
122
  themeColor,
123
123
  themeToCssVars
124
124
  };
125
- //# sourceMappingURL=chunk-Y2SZVW35.js.map
125
+ //# sourceMappingURL=chunk-LVD37K5V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/theme/theme.ts"],"sourcesContent":["/**\n * Typed mirror of tokens.css for runtime/JS theming. The canonical source is\n * tokens.css (`import '@tangle-network/agent-app/styles'`); this module is for\n * apps that compute theme variables in JS, or read color values where CSS\n * custom properties cannot reach — notably Konva canvas render code, which\n * paints to a bitmap and cannot resolve `var(--…)`.\n *\n * Values are shadcn-style HSL channel triples (\"H S% L%\"); wrap with `color()`.\n */\n\nexport interface AgentAppTheme {\n background: string\n foreground: string\n card: string\n cardForeground: string\n popover: string\n popoverForeground: string\n primary: string\n primaryForeground: string\n secondary: string\n secondaryForeground: string\n muted: string\n mutedForeground: string\n accent: string\n accentForeground: string\n destructive: string\n destructiveForeground: string\n border: string\n input: string\n ring: string\n success: string\n successForeground: string\n warning: string\n warningForeground: string\n /** Full CSS color (not a triple) — the canvas/scene backdrop. */\n canvasBackdrop: string\n /** Konva render palette — full hex colors the bitmap canvas paints with\n * (it cannot resolve `var(--…)`). NOT emitted by themeToCssVars. */\n canvasRender: CanvasRenderPalette\n}\n\n/**\n * Colors the Konva design-canvas paints directly. Konva renders to a bitmap\n * and cannot read CSS custom properties, so these are full hex strings sourced\n * from the active theme and threaded through the canvas components.\n */\nexport interface CanvasRenderPalette {\n /** Grid line color (GridLayer). */\n grid: string\n /** Grid-snap guide line (SnapGuidesOverlay, kind 'grid'). */\n snapGrid: string\n /** Saved ruler-guide snap line (kind 'guide'). */\n snapGuide: string\n /** Page edge/center snap line (kinds 'page-edge'/'page-center'). */\n snapPage: string\n /** Element edge/center snap line (kinds 'element-edge'/'element-center'). */\n snapElement: string\n /** Transformer border + anchor stroke (SelectionLayer). */\n selectionStroke: string\n /** Transformer anchor fill (SelectionLayer). */\n selectionAnchorFill: string\n /** Video placeholder fill (ElementNode VideoNode). */\n placeholderFill: string\n /** Video placeholder stroke (ElementNode VideoNode). */\n placeholderStroke: string\n /** Broken/loading image placeholder fill (ElementNode ImageNode). */\n brokenFill: string\n /** Broken/loading image placeholder stroke (ElementNode ImageNode). */\n brokenStroke: string\n}\n\nexport const lightTheme: AgentAppTheme = {\n background: '0 0% 100%',\n foreground: '0 0% 5%',\n card: '240 7% 97%',\n cardForeground: '0 0% 5%',\n popover: '0 0% 100%',\n popoverForeground: '0 0% 5%',\n primary: '245 62% 57%',\n primaryForeground: '0 0% 100%',\n secondary: '240 6% 93%',\n secondaryForeground: '0 0% 10%',\n muted: '240 6% 93%',\n mutedForeground: '240 5% 38%',\n accent: '240 6% 93%',\n accentForeground: '0 0% 10%',\n destructive: '0 72% 41%',\n destructiveForeground: '0 0% 100%',\n border: '240 6% 89%',\n input: '240 6% 89%',\n ring: '245 62% 57%',\n success: '160 84% 26%',\n successForeground: '0 0% 100%',\n warning: '41 96% 38%',\n warningForeground: '38 92% 12%',\n canvasBackdrop: 'hsl(240 7% 90%)',\n canvasRender: {\n grid: '#c0c0c0',\n snapGrid: '#a0a0a0',\n snapGuide: '#3b82f6',\n snapPage: '#f59e0b',\n snapElement: '#f43f5e',\n selectionStroke: '#00a1ff',\n selectionAnchorFill: '#ffffff',\n placeholderFill: '#1f2937',\n placeholderStroke: '#374151',\n brokenFill: '#e5e7eb',\n brokenStroke: '#9ca3af',\n },\n}\n\nexport const darkTheme: AgentAppTheme = {\n background: '240 8% 5%',\n foreground: '240 6% 93%',\n card: '240 5% 8%',\n cardForeground: '240 6% 93%',\n popover: '240 4% 13%',\n popoverForeground: '240 6% 93%',\n primary: '239 84% 74%',\n primaryForeground: '0 0% 100%',\n secondary: '240 5% 11%',\n secondaryForeground: '240 6% 93%',\n muted: '240 5% 11%',\n mutedForeground: '240 4% 62%',\n accent: '240 5% 11%',\n accentForeground: '240 6% 93%',\n destructive: '348 90% 68%',\n destructiveForeground: '0 0% 12%',\n border: '240 3% 13%',\n input: '240 5% 11%',\n ring: '239 84% 74%',\n success: '160 70% 52%',\n successForeground: '160 84% 10%',\n warning: '40 94% 56%',\n warningForeground: '38 92% 12%',\n canvasBackdrop: 'hsl(240 8% 5%)',\n canvasRender: {\n grid: '#3a3a3a',\n snapGrid: '#5a5a5a',\n snapGuide: '#3b82f6',\n snapPage: '#f59e0b',\n snapElement: '#f43f5e',\n selectionStroke: '#00a1ff',\n selectionAnchorFill: '#e5e7eb',\n placeholderFill: '#2a2f3a',\n placeholderStroke: '#3f4654',\n brokenFill: '#262b33',\n brokenStroke: '#4b5563',\n },\n}\n\n/** Wrap a channel triple in `hsl()`; pass through values already in a color form. */\nexport function themeColor(value: string): string {\n return value.startsWith('hsl') || value.startsWith('#') || value.startsWith('rgb')\n ? value\n : `hsl(${value})`\n}\n\n/**\n * Map a theme to the full CSS-variable set (shadcn triples + canvas/sequences\n * aliases + canvas surface). Apply at runtime to scope a theme without loading\n * tokens.css: `Object.assign(el.style, themeToCssVars(darkTheme))`.\n */\nexport function themeToCssVars(theme: AgentAppTheme): Record<string, string> {\n return {\n '--background': theme.background,\n '--foreground': theme.foreground,\n '--card': theme.card,\n '--card-foreground': theme.cardForeground,\n '--popover': theme.popover,\n '--popover-foreground': theme.popoverForeground,\n '--primary': theme.primary,\n '--primary-foreground': theme.primaryForeground,\n '--secondary': theme.secondary,\n '--secondary-foreground': theme.secondaryForeground,\n '--muted': theme.muted,\n '--muted-foreground': theme.mutedForeground,\n '--accent': theme.accent,\n '--accent-foreground': theme.accentForeground,\n '--destructive': theme.destructive,\n '--destructive-foreground': theme.destructiveForeground,\n '--border': theme.border,\n '--input': theme.input,\n '--ring': theme.ring,\n '--success': theme.success,\n '--success-foreground': theme.successForeground,\n '--warning': theme.warning,\n '--warning-foreground': theme.warningForeground,\n '--bg-input': `hsl(${theme.card})`,\n '--text-primary': `hsl(${theme.foreground})`,\n '--text-secondary': `hsl(${theme.secondaryForeground})`,\n '--text-muted': `hsl(${theme.mutedForeground})`,\n '--text-danger': `hsl(${theme.destructive})`,\n '--border-default': `hsl(${theme.border})`,\n '--brand-primary': `hsl(${theme.primary})`,\n '--canvas-backdrop': theme.canvasBackdrop,\n }\n}\n"],"mappings":"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","names":[]}