@agent-native/core 0.43.0 → 0.44.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 (87) hide show
  1. package/dist/chat-threads/store.d.ts.map +1 -1
  2. package/dist/chat-threads/store.js +71 -10
  3. package/dist/chat-threads/store.js.map +1 -1
  4. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  5. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  6. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  7. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  8. package/dist/cli/recap.d.ts.map +1 -1
  9. package/dist/cli/recap.js +2 -13
  10. package/dist/cli/recap.js.map +1 -1
  11. package/dist/cli/skills.d.ts +2 -2
  12. package/dist/cli/skills.d.ts.map +1 -1
  13. package/dist/cli/skills.js +13 -13
  14. package/dist/cli/skills.js.map +1 -1
  15. package/dist/client/AssistantChat.d.ts.map +1 -1
  16. package/dist/client/AssistantChat.js +76 -18
  17. package/dist/client/AssistantChat.js.map +1 -1
  18. package/dist/client/blocks/index.d.ts +0 -2
  19. package/dist/client/blocks/index.d.ts.map +1 -1
  20. package/dist/client/blocks/index.js +0 -2
  21. package/dist/client/blocks/index.js.map +1 -1
  22. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  23. package/dist/client/blocks/library/AnnotatedCodeBlock.js +1 -1
  24. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  25. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  26. package/dist/client/blocks/library/ApiEndpointBlock.js +1 -1
  27. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  28. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  29. package/dist/client/blocks/library/DiffBlock.js +68 -24
  30. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  31. package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
  32. package/dist/client/blocks/library/FileTreeBlock.js +4 -0
  33. package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
  34. package/dist/client/blocks/library/annotation-rail.d.ts +0 -19
  35. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
  36. package/dist/client/blocks/library/annotation-rail.js +0 -19
  37. package/dist/client/blocks/library/annotation-rail.js.map +1 -1
  38. package/dist/client/blocks/library/code-tabs.js +1 -1
  39. package/dist/client/blocks/library/code-tabs.js.map +1 -1
  40. package/dist/client/blocks/library/server-specs.d.ts.map +1 -1
  41. package/dist/client/blocks/library/server-specs.js +0 -10
  42. package/dist/client/blocks/library/server-specs.js.map +1 -1
  43. package/dist/client/blocks/library/specs.d.ts.map +1 -1
  44. package/dist/client/blocks/library/specs.js +0 -2
  45. package/dist/client/blocks/library/specs.js.map +1 -1
  46. package/dist/client/blocks/library/wireframe.config.d.ts.map +1 -1
  47. package/dist/client/blocks/library/wireframe.config.js +19 -2
  48. package/dist/client/blocks/library/wireframe.config.js.map +1 -1
  49. package/dist/client/blocks/mdx.d.ts.map +1 -1
  50. package/dist/client/blocks/mdx.js +11 -0
  51. package/dist/client/blocks/mdx.js.map +1 -1
  52. package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
  53. package/dist/client/rich-markdown-editor/DragHandle.js +35 -72
  54. package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
  55. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
  56. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +1 -1
  57. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  58. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +9 -1
  59. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  60. package/dist/client/rich-markdown-editor/SharedRichEditor.js +3 -1
  61. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  62. package/dist/client/rich-markdown-editor/extensions.d.ts +13 -1
  63. package/dist/client/rich-markdown-editor/extensions.d.ts.map +1 -1
  64. package/dist/client/rich-markdown-editor/extensions.js +4 -2
  65. package/dist/client/rich-markdown-editor/extensions.js.map +1 -1
  66. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  67. package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -1
  68. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  69. package/dist/server/poll.d.ts.map +1 -1
  70. package/dist/server/poll.js +30 -14
  71. package/dist/server/poll.js.map +1 -1
  72. package/dist/styles/blocks.css +10 -2
  73. package/dist/templates/default/.agents/skills/storing-data/SKILL.md +2 -0
  74. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +141 -0
  75. package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +2 -0
  76. package/package.json +1 -1
  77. package/src/templates/default/.agents/skills/storing-data/SKILL.md +2 -0
  78. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +141 -0
  79. package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +2 -0
  80. package/dist/client/blocks/library/decision.config.d.ts +0 -37
  81. package/dist/client/blocks/library/decision.config.d.ts.map +0 -1
  82. package/dist/client/blocks/library/decision.config.js +0 -32
  83. package/dist/client/blocks/library/decision.config.js.map +0 -1
  84. package/dist/client/blocks/library/decision.d.ts +0 -19
  85. package/dist/client/blocks/library/decision.d.ts.map +0 -1
  86. package/dist/client/blocks/library/decision.js +0 -119
  87. package/dist/client/blocks/library/decision.js.map +0 -1
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: performance
3
+ description: >-
4
+ Keep apps and templates loading fast. Read when adding a data model, a
5
+ list/read action, a page or sidebar that loads data, or when something loads
6
+ slowly. Covers column projection, indexing hot-path queries, avoiding N+1 and
7
+ round-trip waterfalls, cheap polling, and not recomputing on every read.
8
+ metadata:
9
+ internal: true
10
+ ---
11
+
12
+ # Performance — Keep Loads Fast
13
+
14
+ ## Rule
15
+
16
+ Treat every list, every read, and every page load as a latency budget. Two
17
+ things dominate it: **how much data crosses the wire**, and **how many
18
+ round-trips and table scans it takes**. On a hosted/serverless SQL backend each
19
+ query is a network round-trip, and an unindexed filter scans the whole — often
20
+ shared and growing — table. So default to **projected columns**, **indexed
21
+ hot-path queries**, and **parallel/batched** fetches. These rules are
22
+ provider-agnostic: they hold on SQLite, Postgres, or any managed SQL backend.
23
+
24
+ This skill is about the data and load path. See the `storing-data` skill for the schema
25
+ and migration mechanics it references, and the `real-time-sync` skill for how updates
26
+ already reach the UI without polling.
27
+
28
+ ## 1. Project columns — never `SELECT *` on a list
29
+
30
+ A list/index query should select only the columns the list actually renders.
31
+
32
+ - **Never return heavy columns in a list**: large JSON/text blobs such as
33
+ document bodies, rendered HTML, `config`/`layout`/`spec`/`data`/`tracks`,
34
+ tool results, or base64 attachments. Pulling them for every row is the single
35
+ most common cause of a slow list.
36
+ - Heavy/full columns belong on the **single-item GET/detail** path only.
37
+ - Need a preview from a big column? Select a **truncated substring at the DB**,
38
+ not the whole column — and it stays portable:
39
+
40
+ ```ts
41
+ // Drizzle — project, and truncate the heavy column for the preview
42
+ const rows = await db
43
+ .select({
44
+ id: docs.id,
45
+ title: docs.title,
46
+ updatedAt: docs.updatedAt,
47
+ // substr/length work on both SQLite and Postgres
48
+ preview: sql<string>`substr(${docs.content}, 1, 400)`,
49
+ })
50
+ .from(docs)
51
+ .where(accessFilter(docs, docShares))
52
+ .orderBy(desc(docs.updatedAt));
53
+ ```
54
+
55
+ - After narrowing the projection, update the row mapper and its return type so a
56
+ dropped column is provably unused on the list path. If the list genuinely
57
+ renders a heavy column (a thumbnail, an inline preview the UI shows), keep it —
58
+ don't break behavior to chase a payload win.
59
+
60
+ ## 2. Index the hot paths
61
+
62
+ Indexes are added through the **versioned migration array** in
63
+ `server/plugins/db.ts` as `CREATE INDEX IF NOT EXISTS …` — not through a
64
+ schema-level `index()` helper (the framework applies indexes via migrations; see
65
+ the `storing-data` skill). Add an index for any column a hot query **filters or sorts**
66
+ on. The recurring ones:
67
+
68
+ - **Ownable tables** → `(owner_email, org_id, <the list's ORDER BY column>)`.
69
+ Access scoping filters by owner/org and lists sort by `updated_at`/`created_at`.
70
+ - **Shares tables** (`{resource}_shares`) → `(resource_id, principal_type, principal_id)`.
71
+ Access checks run correlated `EXISTS` subqueries against these on every list.
72
+ - **Child / foreign-key columns** used to load children (e.g. `responses.form_id`,
73
+ `comments.parent_id`, an events log's `*_id`) → index the FK, plus its sort
74
+ column when the children are ordered. An unindexed FK means a full scan of the
75
+ child table on every parent open. **A foreign-key reference does not create an
76
+ index automatically** — add it explicitly.
77
+ - **Status-filtered lists** → match the real `WHERE`, e.g. `(owner_email, status)`
78
+ or `(status, <sort>)`.
79
+
80
+ Keep index DDL **dialect-agnostic and idempotent**:
81
+
82
+ ```sql
83
+ CREATE INDEX IF NOT EXISTS forms_owner_org_updated_idx ON forms (owner_email, org_id, updated_at)
84
+ ```
85
+
86
+ No `DESC`, no partial `WHERE`, no provider-specific syntax — it then runs on
87
+ SQLite and Postgres alike, is safe to re-run, and applies on next startup.
88
+ Indexes mostly bite **as data grows** and on **unbounded child tables** (a
89
+ seq-scan of 10 rows is instant; of a shared, ever-growing log it is not), so
90
+ index the growing tables first.
91
+
92
+ ## 3. Don't fan out queries — batch and parallelize
93
+
94
+ - **No N+1.** Never loop issuing one query per item. Load children for many
95
+ parents in one `inArray(child.parentId, ids)` query, then group in memory.
96
+ - **Count in SQL** (`count()`), never "select all rows then `.length`".
97
+ - **Parallelize independent queries** with `Promise.all` rather than sequential
98
+ `await`s — each `await` is another round-trip.
99
+ - Prefer **one composed endpoint** over several dependent calls.
100
+
101
+ ## 4. Avoid client-side waterfalls
102
+
103
+ - Don't gate query B on query A's result unless B truly needs it. Fire
104
+ independent `useActionQuery` / `useQuery` hooks **in parallel**; never make the
105
+ loading skeleton wait on a serial chain.
106
+ - Load the visible page from one read where possible, and **lazy-load**
107
+ secondary / below-the-fold data after first paint.
108
+
109
+ ## 5. Poll cheaply; compute once
110
+
111
+ - Updates already reach the UI through the `real-time-sync` skill (`useDbSync` / SSE).
112
+ Don't add an aggressive `refetchInterval` that re-runs a heavy list/read every
113
+ couple of seconds. If you must poll, use a **wide interval** and a **cheap**
114
+ endpoint.
115
+ - **Never do expensive per-request work on a read that runs on every load/poll**:
116
+ re-rendering HTML/markdown, pretty-printing, re-parsing / migrating /
117
+ normalizing / sanitizing stored JSON. Do that work at **write time** (store the
118
+ result) or compute it **lazily only for the caller that needs it**. Reads on
119
+ the hot path must be cheap.
120
+ - Data the UI doesn't display (export formats, alternate renderings) belongs in a
121
+ separate on-demand action, not baked into the hot read.
122
+
123
+ ## 6. Big payloads and long lists
124
+
125
+ - **Paginate or window** unbounded lists (messages, responses, events, activity).
126
+ Don't load the entire history on open; load a recent window and fetch older on
127
+ demand.
128
+ - Don't store **unbounded blobs inline** in a row that a list/load pulls.
129
+ Reference large content separately so opening the parent stays cheap.
130
+ - **Virtualize** very long rendered lists on the client so off-screen rows aren't
131
+ parsed/rendered every update.
132
+
133
+ ## Checklist — run before shipping a list/read or a new table
134
+
135
+ - [ ] List selects only displayed columns; heavy blobs excluded or `substr`-truncated.
136
+ - [ ] Every hot-path `WHERE` / `ORDER BY` column is indexed (owner/org/sort,
137
+ shares `resource_id`, child FKs, status filters) via a `db.ts` migration.
138
+ - [ ] No N+1; independent queries parallelized; counts via SQL `count()`.
139
+ - [ ] Client fires independent queries in parallel, not a waterfall.
140
+ - [ ] No heavy recompute on every read; no aggressive polling of heavy endpoints.
141
+ - [ ] Unbounded lists are paginated/windowed; large blobs aren't inlined on the hot path.
@@ -14,6 +14,8 @@ metadata:
14
14
 
15
15
  All application data lives in **SQL** (SQLite locally, persistent database in production). The agent and UI share the same database. Do not store durable app data in the filesystem.
16
16
 
17
+ When you add a data model, a list, or a read path, also follow the `performance` skill: project only the columns a list renders, index the columns hot queries filter/sort on, and avoid query waterfalls — so apps stay fast as data grows.
18
+
17
19
  ## How It Works
18
20
 
19
21
  Agent-native apps use Drizzle ORM over the configured SQL backend. Local development works out of the box with a SQLite file at `data/app.db`; production and shared preview deploys need a persistent `DATABASE_URL` because container/serverless filesystems can reset. The code should behave the same across backends, but the local SQLite file is not durable once deployed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -14,6 +14,8 @@ metadata:
14
14
 
15
15
  All application data lives in **SQL** (SQLite locally, persistent database in production). The agent and UI share the same database. Do not store durable app data in the filesystem.
16
16
 
17
+ When you add a data model, a list, or a read path, also follow the `performance` skill: project only the columns a list renders, index the columns hot queries filter/sort on, and avoid query waterfalls — so apps stay fast as data grows.
18
+
17
19
  ## How It Works
18
20
 
19
21
  Agent-native apps use Drizzle ORM over the configured SQL backend. Local development works out of the box with a SQLite file at `data/app.db`; production and shared preview deploys need a persistent `DATABASE_URL` because container/serverless filesystems can reset. The code should behave the same across backends, but the local SQLite file is not durable once deployed.
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: performance
3
+ description: >-
4
+ Keep apps and templates loading fast. Read when adding a data model, a
5
+ list/read action, a page or sidebar that loads data, or when something loads
6
+ slowly. Covers column projection, indexing hot-path queries, avoiding N+1 and
7
+ round-trip waterfalls, cheap polling, and not recomputing on every read.
8
+ metadata:
9
+ internal: true
10
+ ---
11
+
12
+ # Performance — Keep Loads Fast
13
+
14
+ ## Rule
15
+
16
+ Treat every list, every read, and every page load as a latency budget. Two
17
+ things dominate it: **how much data crosses the wire**, and **how many
18
+ round-trips and table scans it takes**. On a hosted/serverless SQL backend each
19
+ query is a network round-trip, and an unindexed filter scans the whole — often
20
+ shared and growing — table. So default to **projected columns**, **indexed
21
+ hot-path queries**, and **parallel/batched** fetches. These rules are
22
+ provider-agnostic: they hold on SQLite, Postgres, or any managed SQL backend.
23
+
24
+ This skill is about the data and load path. See the `storing-data` skill for the schema
25
+ and migration mechanics it references, and the `real-time-sync` skill for how updates
26
+ already reach the UI without polling.
27
+
28
+ ## 1. Project columns — never `SELECT *` on a list
29
+
30
+ A list/index query should select only the columns the list actually renders.
31
+
32
+ - **Never return heavy columns in a list**: large JSON/text blobs such as
33
+ document bodies, rendered HTML, `config`/`layout`/`spec`/`data`/`tracks`,
34
+ tool results, or base64 attachments. Pulling them for every row is the single
35
+ most common cause of a slow list.
36
+ - Heavy/full columns belong on the **single-item GET/detail** path only.
37
+ - Need a preview from a big column? Select a **truncated substring at the DB**,
38
+ not the whole column — and it stays portable:
39
+
40
+ ```ts
41
+ // Drizzle — project, and truncate the heavy column for the preview
42
+ const rows = await db
43
+ .select({
44
+ id: docs.id,
45
+ title: docs.title,
46
+ updatedAt: docs.updatedAt,
47
+ // substr/length work on both SQLite and Postgres
48
+ preview: sql<string>`substr(${docs.content}, 1, 400)`,
49
+ })
50
+ .from(docs)
51
+ .where(accessFilter(docs, docShares))
52
+ .orderBy(desc(docs.updatedAt));
53
+ ```
54
+
55
+ - After narrowing the projection, update the row mapper and its return type so a
56
+ dropped column is provably unused on the list path. If the list genuinely
57
+ renders a heavy column (a thumbnail, an inline preview the UI shows), keep it —
58
+ don't break behavior to chase a payload win.
59
+
60
+ ## 2. Index the hot paths
61
+
62
+ Indexes are added through the **versioned migration array** in
63
+ `server/plugins/db.ts` as `CREATE INDEX IF NOT EXISTS …` — not through a
64
+ schema-level `index()` helper (the framework applies indexes via migrations; see
65
+ the `storing-data` skill). Add an index for any column a hot query **filters or sorts**
66
+ on. The recurring ones:
67
+
68
+ - **Ownable tables** → `(owner_email, org_id, <the list's ORDER BY column>)`.
69
+ Access scoping filters by owner/org and lists sort by `updated_at`/`created_at`.
70
+ - **Shares tables** (`{resource}_shares`) → `(resource_id, principal_type, principal_id)`.
71
+ Access checks run correlated `EXISTS` subqueries against these on every list.
72
+ - **Child / foreign-key columns** used to load children (e.g. `responses.form_id`,
73
+ `comments.parent_id`, an events log's `*_id`) → index the FK, plus its sort
74
+ column when the children are ordered. An unindexed FK means a full scan of the
75
+ child table on every parent open. **A foreign-key reference does not create an
76
+ index automatically** — add it explicitly.
77
+ - **Status-filtered lists** → match the real `WHERE`, e.g. `(owner_email, status)`
78
+ or `(status, <sort>)`.
79
+
80
+ Keep index DDL **dialect-agnostic and idempotent**:
81
+
82
+ ```sql
83
+ CREATE INDEX IF NOT EXISTS forms_owner_org_updated_idx ON forms (owner_email, org_id, updated_at)
84
+ ```
85
+
86
+ No `DESC`, no partial `WHERE`, no provider-specific syntax — it then runs on
87
+ SQLite and Postgres alike, is safe to re-run, and applies on next startup.
88
+ Indexes mostly bite **as data grows** and on **unbounded child tables** (a
89
+ seq-scan of 10 rows is instant; of a shared, ever-growing log it is not), so
90
+ index the growing tables first.
91
+
92
+ ## 3. Don't fan out queries — batch and parallelize
93
+
94
+ - **No N+1.** Never loop issuing one query per item. Load children for many
95
+ parents in one `inArray(child.parentId, ids)` query, then group in memory.
96
+ - **Count in SQL** (`count()`), never "select all rows then `.length`".
97
+ - **Parallelize independent queries** with `Promise.all` rather than sequential
98
+ `await`s — each `await` is another round-trip.
99
+ - Prefer **one composed endpoint** over several dependent calls.
100
+
101
+ ## 4. Avoid client-side waterfalls
102
+
103
+ - Don't gate query B on query A's result unless B truly needs it. Fire
104
+ independent `useActionQuery` / `useQuery` hooks **in parallel**; never make the
105
+ loading skeleton wait on a serial chain.
106
+ - Load the visible page from one read where possible, and **lazy-load**
107
+ secondary / below-the-fold data after first paint.
108
+
109
+ ## 5. Poll cheaply; compute once
110
+
111
+ - Updates already reach the UI through the `real-time-sync` skill (`useDbSync` / SSE).
112
+ Don't add an aggressive `refetchInterval` that re-runs a heavy list/read every
113
+ couple of seconds. If you must poll, use a **wide interval** and a **cheap**
114
+ endpoint.
115
+ - **Never do expensive per-request work on a read that runs on every load/poll**:
116
+ re-rendering HTML/markdown, pretty-printing, re-parsing / migrating /
117
+ normalizing / sanitizing stored JSON. Do that work at **write time** (store the
118
+ result) or compute it **lazily only for the caller that needs it**. Reads on
119
+ the hot path must be cheap.
120
+ - Data the UI doesn't display (export formats, alternate renderings) belongs in a
121
+ separate on-demand action, not baked into the hot read.
122
+
123
+ ## 6. Big payloads and long lists
124
+
125
+ - **Paginate or window** unbounded lists (messages, responses, events, activity).
126
+ Don't load the entire history on open; load a recent window and fetch older on
127
+ demand.
128
+ - Don't store **unbounded blobs inline** in a row that a list/load pulls.
129
+ Reference large content separately so opening the parent stays cheap.
130
+ - **Virtualize** very long rendered lists on the client so off-screen rows aren't
131
+ parsed/rendered every update.
132
+
133
+ ## Checklist — run before shipping a list/read or a new table
134
+
135
+ - [ ] List selects only displayed columns; heavy blobs excluded or `substr`-truncated.
136
+ - [ ] Every hot-path `WHERE` / `ORDER BY` column is indexed (owner/org/sort,
137
+ shares `resource_id`, child FKs, status filters) via a `db.ts` migration.
138
+ - [ ] No N+1; independent queries parallelized; counts via SQL `count()`.
139
+ - [ ] Client fires independent queries in parallel, not a waterfall.
140
+ - [ ] No heavy recompute on every read; no aggressive polling of heavy endpoints.
141
+ - [ ] Unbounded lists are paginated/windowed; large blobs aren't inlined on the hot path.
@@ -14,6 +14,8 @@ metadata:
14
14
 
15
15
  All application data lives in **SQL** (SQLite locally, persistent database in production). The agent and UI share the same database. Do not store durable app data in the filesystem.
16
16
 
17
+ When you add a data model, a list, or a read path, also follow the `performance` skill: project only the columns a list renders, index the columns hot queries filter/sort on, and avoid query waterfalls — so apps stay fast as data grows.
18
+
17
19
  ## How It Works
18
20
 
19
21
  Agent-native apps use Drizzle ORM over the configured SQL backend. Local development works out of the box with a SQLite file at `data/app.db`; production and shared preview deploys need a persistent `DATABASE_URL` because container/serverless filesystems can reset. The code should behave the same across backends, but the local SQLite file is not durable once deployed.
@@ -1,37 +0,0 @@
1
- import { z } from "zod";
2
- import type { BlockMdxConfig } from "../types.js";
3
- /**
4
- * Pure (React-free) part of the shared `decision` block: its data schema and MDX
5
- * round-trip config. Lives in core so BOTH apps' server/shared registries and
6
- * the client spec (`decision.tsx`) consume one definition. Keeping it React-free
7
- * means importing it into a server module never pulls React into the Nitro/SSR
8
- * bundle.
9
- *
10
- * The MDX `tag` + `question`/`options` attribute shape MUST match the legacy
11
- * `<Decision question options />` encoding so stored `.mdx` round-trips
12
- * byte-compatibly (the block originated in the plan template before moving here).
13
- */
14
- export interface DecisionOption {
15
- id: string;
16
- label: string;
17
- detail?: string;
18
- /**
19
- * Authored recommendation only. A reviewer's actual selection does NOT live
20
- * here — responses belong in comments / events, never in the canonical
21
- * document body.
22
- */
23
- recommended?: boolean;
24
- }
25
- export interface DecisionData {
26
- question: string;
27
- options: DecisionOption[];
28
- }
29
- export declare const decisionSchema: z.ZodType<DecisionData>;
30
- /**
31
- * MDX config: `question` and `options` are both attributes — exactly the legacy
32
- * `<Decision question options />` form. `toAttrs` writes them in their historical
33
- * order; `fromAttrs` tolerates missing attributes with the same `?? "Decision"` /
34
- * `?? []` defaults the plan template used.
35
- */
36
- export declare const decisionMdx: BlockMdxConfig<DecisionData>;
37
- //# sourceMappingURL=decision.config.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decision.config.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/decision.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAID,eAAO,MAAM,cAAc,EAaV,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;AAEzC;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,cAAc,CAAC,YAAY,CAUpD,CAAC"}
@@ -1,32 +0,0 @@
1
- import { z } from "zod";
2
- const decisionIdSchema = z.string().trim().min(1).max(120);
3
- export const decisionSchema = z.object({
4
- question: z.string().trim().min(1).max(500),
5
- options: z
6
- .array(z.object({
7
- id: decisionIdSchema,
8
- label: z.string().trim().min(1).max(200),
9
- detail: z.string().trim().max(800).optional(),
10
- recommended: z.boolean().optional(),
11
- }))
12
- .min(1)
13
- .max(20),
14
- });
15
- /**
16
- * MDX config: `question` and `options` are both attributes — exactly the legacy
17
- * `<Decision question options />` form. `toAttrs` writes them in their historical
18
- * order; `fromAttrs` tolerates missing attributes with the same `?? "Decision"` /
19
- * `?? []` defaults the plan template used.
20
- */
21
- export const decisionMdx = {
22
- tag: "Decision",
23
- toAttrs: (data) => ({
24
- question: data.question,
25
- options: data.options,
26
- }),
27
- fromAttrs: (attrs) => ({
28
- question: attrs.string("question") ?? "Decision",
29
- options: attrs.array("options") ?? [],
30
- }),
31
- };
32
- //# sourceMappingURL=decision.config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decision.config.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/decision.config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgCxB,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAE3D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAC3C,OAAO,EAAE,CAAC;SACP,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;QACP,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;QACxC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC7C,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KACpC,CAAC,CACH;SACA,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;CACX,CAAuC,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAiC;IACvD,GAAG,EAAE,UAAU;IACf,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAClB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAC;IACF,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACrB,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,UAAU;QAChD,OAAO,EAAE,KAAK,CAAC,KAAK,CAAiB,SAAS,CAAC,IAAI,EAAE;KACtD,CAAC;CACH,CAAC","sourcesContent":["import { z } from \"zod\";\nimport type { BlockMdxConfig } from \"../types.js\";\n\n/**\n * Pure (React-free) part of the shared `decision` block: its data schema and MDX\n * round-trip config. Lives in core so BOTH apps' server/shared registries and\n * the client spec (`decision.tsx`) consume one definition. Keeping it React-free\n * means importing it into a server module never pulls React into the Nitro/SSR\n * bundle.\n *\n * The MDX `tag` + `question`/`options` attribute shape MUST match the legacy\n * `<Decision question options />` encoding so stored `.mdx` round-trips\n * byte-compatibly (the block originated in the plan template before moving here).\n */\n\nexport interface DecisionOption {\n id: string;\n label: string;\n detail?: string;\n /**\n * Authored recommendation only. A reviewer's actual selection does NOT live\n * here — responses belong in comments / events, never in the canonical\n * document body.\n */\n recommended?: boolean;\n}\n\nexport interface DecisionData {\n question: string;\n options: DecisionOption[];\n}\n\nconst decisionIdSchema = z.string().trim().min(1).max(120);\n\nexport const decisionSchema = z.object({\n question: z.string().trim().min(1).max(500),\n options: z\n .array(\n z.object({\n id: decisionIdSchema,\n label: z.string().trim().min(1).max(200),\n detail: z.string().trim().max(800).optional(),\n recommended: z.boolean().optional(),\n }),\n )\n .min(1)\n .max(20),\n}) as unknown as z.ZodType<DecisionData>;\n\n/**\n * MDX config: `question` and `options` are both attributes — exactly the legacy\n * `<Decision question options />` form. `toAttrs` writes them in their historical\n * order; `fromAttrs` tolerates missing attributes with the same `?? \"Decision\"` /\n * `?? []` defaults the plan template used.\n */\nexport const decisionMdx: BlockMdxConfig<DecisionData> = {\n tag: \"Decision\",\n toAttrs: (data) => ({\n question: data.question,\n options: data.options,\n }),\n fromAttrs: (attrs) => ({\n question: attrs.string(\"question\") ?? \"Decision\",\n options: attrs.array<DecisionOption>(\"options\") ?? [],\n }),\n};\n"]}
@@ -1,19 +0,0 @@
1
- import type { BlockReadProps, BlockEditProps } from "../types.js";
2
- import { type DecisionData } from "./decision.config.js";
3
- /**
4
- * Standard `decision` block — a decision prompt with inline-editable option
5
- * cards and one authored "recommended" choice. Lives in core so any app can
6
- * register it (it originated in the plan template).
7
- *
8
- * The root `<section>` keeps the app-neutral `an-block` class (document-flow
9
- * spacing hook) alongside the legacy `plan-block` class (styled by the plan
10
- * template's own stylesheet), so plan renders as before and any other app gets
11
- * theme-token styling. All inner color comes from shadcn theme tokens
12
- * (`text-muted-foreground`, `text-foreground`, `bg-muted`, `bg-background`,
13
- * `border-border`, `ring`), so it reads correctly in any template palette.
14
- */
15
- export declare function DecisionBlock({ data, blockId, title, }: BlockReadProps<DecisionData>): import("react/jsx-runtime").JSX.Element;
16
- export declare function DecisionBlockEdit({ data, onChange, editable, blockId, title, summary, ctx, }: BlockEditProps<DecisionData>): import("react/jsx-runtime").JSX.Element;
17
- /** Full client spec for the shared `decision` block (schema + MDX + Read/Edit). */
18
- export declare const decisionBlock: import("../types.js").BlockSpec<DecisionData>;
19
- //# sourceMappingURL=decision.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/decision.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,OAAO,EACP,KAAK,GACN,EAAE,cAAc,CAAC,YAAY,CAAC,2CAsC9B;AAaD,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,GACJ,EAAE,cAAc,CAAC,YAAY,CAAC,2CAiI9B;AA+GD,mFAAmF;AACnF,eAAO,MAAM,aAAa,+CAgCxB,CAAC"}
@@ -1,119 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { IconCheck, IconPencil, IconPlus, IconTrash, } from "@tabler/icons-react";
4
- import { cn } from "../../utils.js";
5
- import { defineBlock } from "../types.js";
6
- import { decisionMdx, decisionSchema, } from "./decision.config.js";
7
- /**
8
- * Standard `decision` block — a decision prompt with inline-editable option
9
- * cards and one authored "recommended" choice. Lives in core so any app can
10
- * register it (it originated in the plan template).
11
- *
12
- * The root `<section>` keeps the app-neutral `an-block` class (document-flow
13
- * spacing hook) alongside the legacy `plan-block` class (styled by the plan
14
- * template's own stylesheet), so plan renders as before and any other app gets
15
- * theme-token styling. All inner color comes from shadcn theme tokens
16
- * (`text-muted-foreground`, `text-foreground`, `bg-muted`, `bg-background`,
17
- * `border-border`, `ring`), so it reads correctly in any template palette.
18
- */
19
- export function DecisionBlock({ data, blockId, title, }) {
20
- return (_jsxs("section", { className: "an-block plan-block", "data-block-id": blockId, children: [title && _jsx("div", { className: "an-block-label plan-block-label", children: title }), _jsx("p", { className: "mt-3 max-w-3xl text-lg leading-8 text-muted-foreground", children: data.question }), _jsx("div", { className: "mt-6 grid gap-3 md:grid-cols-2", children: data.options.map((option) => (_jsxs("article", { className: cn("rounded-xl border border-border bg-muted p-4", option.recommended
21
- ? "shadow-[inset_3px_0_0_hsl(var(--ring))]"
22
- : "opacity-85"), children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsx("h3", { className: "text-lg font-semibold tracking-tight text-foreground", children: option.label }), option.recommended && (_jsx("span", { className: "rounded-full border border-border px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground", children: "Recommended" }))] }), option.detail && (_jsx("p", { className: "mt-3 text-sm leading-6 text-muted-foreground", children: option.detail }))] }, option.id))) })] }));
23
- }
24
- const inlineInputClass = "w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground shadow-sm outline-none transition-colors placeholder:text-muted-foreground focus:border-ring";
25
- const inlineTextareaClass = "w-full resize-y rounded-md border border-border bg-background px-3 py-2 text-sm leading-6 text-foreground shadow-sm outline-none transition-colors placeholder:text-muted-foreground focus:border-ring";
26
- const inlineLabelClass = "text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground";
27
- function newLocalId(prefix) {
28
- return `${prefix}-${Math.random().toString(36).slice(2, 10)}`;
29
- }
30
- export function DecisionBlockEdit({ data, onChange, editable, blockId, title, summary, ctx, }) {
31
- const updateOption = (optionId, patch) => onChange({
32
- ...data,
33
- options: data.options.map((option) => option.id === optionId ? { ...option, ...patch } : option),
34
- });
35
- const removeOption = (optionId) => {
36
- if (data.options.length <= 1)
37
- return;
38
- onChange({
39
- ...data,
40
- options: data.options.filter((option) => option.id !== optionId),
41
- });
42
- };
43
- const addOption = () => {
44
- if (data.options.length >= 20)
45
- return;
46
- onChange({
47
- ...data,
48
- options: [
49
- ...data.options,
50
- { id: newLocalId("option"), label: "New option" },
51
- ],
52
- });
53
- };
54
- const settings = editable
55
- ? (ctx.renderEditSurface?.({
56
- title: "Decision",
57
- blockId,
58
- blockType: "decision",
59
- blockTitle: title,
60
- blockSummary: summary,
61
- blockData: data,
62
- trigger: (_jsx("button", { type: "button", "data-plan-interactive": true, "aria-label": "Edit decision options", className: "flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-muted text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground", children: _jsx(IconPencil, { className: "size-4" }) })),
63
- children: (_jsx(DecisionSettings, { options: data.options, onToggleRecommended: (option) => updateOption(option.id, { recommended: !option.recommended }), onRemove: removeOption, onAdd: addOption })),
64
- }) ?? (_jsx(DecisionInlineSettings, { options: data.options, onToggleRecommended: (option) => updateOption(option.id, { recommended: !option.recommended }), onRemove: removeOption, onAdd: addOption })))
65
- : null;
66
- return (_jsxs("div", { className: "grid gap-5", "data-plan-interactive": true, children: [_jsxs("div", { className: "flex items-start gap-3", children: [_jsxs("label", { className: "grid min-w-0 flex-1 gap-1.5", children: [_jsx("span", { className: inlineLabelClass, children: "Question" }), _jsx("textarea", { className: inlineTextareaClass, rows: 2, value: data.question, disabled: !editable, onChange: (event) => onChange({ ...data, question: event.target.value }) })] }), settings] }), _jsx("div", { className: "grid gap-3", children: data.options.map((option) => (_jsxs("article", { className: cn("rounded-lg border border-border bg-muted p-4", option.recommended &&
67
- "border-ring/60 shadow-[inset_3px_0_0_hsl(var(--ring))]"), children: [_jsx("div", { className: "grid gap-3", children: _jsxs("label", { className: "grid gap-1.5", children: [_jsx("span", { className: inlineLabelClass, children: "Option" }), _jsx("input", { className: inlineInputClass, value: option.label, disabled: !editable, onChange: (event) => updateOption(option.id, { label: event.target.value }) })] }) }), _jsxs("label", { className: "mt-3 grid gap-1.5", children: [_jsx("span", { className: inlineLabelClass, children: "Detail" }), _jsx("textarea", { className: inlineTextareaClass, rows: 2, value: option.detail ?? "", disabled: !editable, onChange: (event) => updateOption(option.id, {
68
- detail: event.target.value || undefined,
69
- }) })] })] }, option.id))) })] }));
70
- }
71
- /** Option-management controls rendered inside the host's edit surface popover. */
72
- function DecisionSettings({ options, onToggleRecommended, onRemove, onAdd, }) {
73
- return (_jsxs("div", { className: "grid gap-3", children: [_jsx("div", { className: "text-sm font-semibold text-foreground", children: "Decision settings" }), _jsx("div", { className: "grid gap-2", children: options.map((option, index) => (_jsxs("div", { className: "grid gap-2 rounded-md border border-border bg-muted/20 p-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "min-w-0 truncate text-xs font-medium text-foreground", children: option.label.trim() || `Option ${index + 1}` }), option.recommended && (_jsx("span", { className: "shrink-0 rounded-full border border-border px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground", children: "Recommended" }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { type: "button", "data-plan-interactive": true, onClick: () => onToggleRecommended(option), className: cn("inline-flex h-8 flex-1 items-center justify-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-medium transition-colors", option.recommended
74
- ? "bg-background text-foreground shadow-sm"
75
- : "text-muted-foreground hover:bg-background/70 hover:text-foreground"), children: [option.recommended && _jsx(IconCheck, { className: "size-3.5" }), option.recommended ? "Recommended" : "Mark recommended"] }), _jsx("button", { type: "button", "data-plan-interactive": true, "aria-label": `Delete ${option.label || `option ${index + 1}`}`, disabled: options.length <= 1, onClick: () => onRemove(option.id), className: "inline-flex size-8 shrink-0 items-center justify-center rounded-md border border-border text-destructive transition-colors hover:bg-destructive/10 disabled:cursor-not-allowed disabled:opacity-50", children: _jsx(IconTrash, { className: "size-3.5" }) })] })] }, option.id))) }), _jsxs("button", { type: "button", "data-plan-interactive": true, disabled: options.length >= 20, onClick: onAdd, className: "inline-flex h-8 items-center justify-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50", children: [_jsx(IconPlus, { className: "size-3.5" }), "Add option"] })] }));
76
- }
77
- /**
78
- * Fallback for hosts that do not provide `ctx.renderEditSurface`: a plain
79
- * disclosure button that reveals the same settings inline (no overlay primitive,
80
- * so core stays shadcn-free).
81
- */
82
- function DecisionInlineSettings(props) {
83
- const [open, setOpen] = useState(false);
84
- return (_jsxs("div", { className: "grid gap-2", children: [_jsx("button", { type: "button", "data-plan-interactive": true, "aria-label": "Edit decision options", "aria-expanded": open, onClick: () => setOpen((value) => !value), className: "flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-muted text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground", children: _jsx(IconPencil, { className: "size-4" }) }), open && (_jsx("div", { className: "w-80 rounded-md border border-border bg-background p-3 shadow-sm", children: _jsx(DecisionSettings, { ...props }) }))] }));
85
- }
86
- /** Full client spec for the shared `decision` block (schema + MDX + Read/Edit). */
87
- export const decisionBlock = defineBlock({
88
- type: "decision",
89
- schema: decisionSchema,
90
- mdx: decisionMdx,
91
- Read: DecisionBlock,
92
- Edit: DecisionBlockEdit,
93
- placement: ["block"],
94
- // `panel`: the document shows the clean read view (question + option cards with
95
- // the recommended pick highlighted), and the corner pencil opens the editor in
96
- // a popover. NOT `inline` — an inline schema-editing form (question + per-option
97
- // textareas) rendered straight into the doc reads as a confusing data-entry wall
98
- // rather than a decision. Mirrors how `question-form` / `visual-questions` edit.
99
- editSurface: "panel",
100
- label: "Decision",
101
- description: "A decision prompt with option cards and an authored recommended choice. Shows a clean read view in the document; edit the question and options from the corner pencil.",
102
- empty: () => ({
103
- question: "Which implementation direction should we take?",
104
- options: [
105
- {
106
- id: "recommended",
107
- label: "Recommended path",
108
- detail: "Smallest useful slice with clear rollback.",
109
- recommended: true,
110
- },
111
- {
112
- id: "alternative",
113
- label: "Alternative",
114
- detail: "Broader pass that touches more surfaces.",
115
- },
116
- ],
117
- }),
118
- });
119
- //# sourceMappingURL=decision.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"decision.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/decision.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from \"react\";\nimport {\n IconCheck,\n IconPencil,\n IconPlus,\n IconTrash,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../../utils.js\";\nimport { defineBlock } from \"../types.js\";\nimport type { BlockReadProps, BlockEditProps } from \"../types.js\";\nimport {\n decisionMdx,\n decisionSchema,\n type DecisionData,\n type DecisionOption,\n} from \"./decision.config.js\";\n\n/**\n * Standard `decision` block — a decision prompt with inline-editable option\n * cards and one authored \"recommended\" choice. Lives in core so any app can\n * register it (it originated in the plan template).\n *\n * The root `<section>` keeps the app-neutral `an-block` class (document-flow\n * spacing hook) alongside the legacy `plan-block` class (styled by the plan\n * template's own stylesheet), so plan renders as before and any other app gets\n * theme-token styling. All inner color comes from shadcn theme tokens\n * (`text-muted-foreground`, `text-foreground`, `bg-muted`, `bg-background`,\n * `border-border`, `ring`), so it reads correctly in any template palette.\n */\nexport function DecisionBlock({\n data,\n blockId,\n title,\n}: BlockReadProps<DecisionData>) {\n return (\n <section className=\"an-block plan-block\" data-block-id={blockId}>\n {title && <div className=\"an-block-label plan-block-label\">{title}</div>}\n <p className=\"mt-3 max-w-3xl text-lg leading-8 text-muted-foreground\">\n {data.question}\n </p>\n <div className=\"mt-6 grid gap-3 md:grid-cols-2\">\n {data.options.map((option) => (\n <article\n key={option.id}\n className={cn(\n \"rounded-xl border border-border bg-muted p-4\",\n option.recommended\n ? \"shadow-[inset_3px_0_0_hsl(var(--ring))]\"\n : \"opacity-85\",\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <h3 className=\"text-lg font-semibold tracking-tight text-foreground\">\n {option.label}\n </h3>\n {option.recommended && (\n <span className=\"rounded-full border border-border px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground\">\n Recommended\n </span>\n )}\n </div>\n {option.detail && (\n <p className=\"mt-3 text-sm leading-6 text-muted-foreground\">\n {option.detail}\n </p>\n )}\n </article>\n ))}\n </div>\n </section>\n );\n}\n\nconst inlineInputClass =\n \"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground shadow-sm outline-none transition-colors placeholder:text-muted-foreground focus:border-ring\";\nconst inlineTextareaClass =\n \"w-full resize-y rounded-md border border-border bg-background px-3 py-2 text-sm leading-6 text-foreground shadow-sm outline-none transition-colors placeholder:text-muted-foreground focus:border-ring\";\nconst inlineLabelClass =\n \"text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground\";\n\nfunction newLocalId(prefix: string): string {\n return `${prefix}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport function DecisionBlockEdit({\n data,\n onChange,\n editable,\n blockId,\n title,\n summary,\n ctx,\n}: BlockEditProps<DecisionData>) {\n const updateOption = (optionId: string, patch: Partial<DecisionOption>) =>\n onChange({\n ...data,\n options: data.options.map((option) =>\n option.id === optionId ? { ...option, ...patch } : option,\n ),\n });\n\n const removeOption = (optionId: string) => {\n if (data.options.length <= 1) return;\n onChange({\n ...data,\n options: data.options.filter((option) => option.id !== optionId),\n });\n };\n\n const addOption = () => {\n if (data.options.length >= 20) return;\n onChange({\n ...data,\n options: [\n ...data.options,\n { id: newLocalId(\"option\"), label: \"New option\" },\n ],\n });\n };\n\n const settings = editable\n ? (ctx.renderEditSurface?.({\n title: \"Decision\",\n blockId,\n blockType: \"decision\",\n blockTitle: title,\n blockSummary: summary,\n blockData: data,\n trigger: (\n <button\n type=\"button\"\n data-plan-interactive\n aria-label=\"Edit decision options\"\n className=\"flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-muted text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground\"\n >\n <IconPencil className=\"size-4\" />\n </button>\n ),\n children: (\n <DecisionSettings\n options={data.options}\n onToggleRecommended={(option) =>\n updateOption(option.id, { recommended: !option.recommended })\n }\n onRemove={removeOption}\n onAdd={addOption}\n />\n ),\n }) ?? (\n // No panel surface provided by the host: fall back to a plain inline\n // settings card so option management still works.\n <DecisionInlineSettings\n options={data.options}\n onToggleRecommended={(option) =>\n updateOption(option.id, { recommended: !option.recommended })\n }\n onRemove={removeOption}\n onAdd={addOption}\n />\n ))\n : null;\n\n return (\n <div className=\"grid gap-5\" data-plan-interactive>\n <div className=\"flex items-start gap-3\">\n <label className=\"grid min-w-0 flex-1 gap-1.5\">\n <span className={inlineLabelClass}>Question</span>\n <textarea\n className={inlineTextareaClass}\n rows={2}\n value={data.question}\n disabled={!editable}\n onChange={(event) =>\n onChange({ ...data, question: event.target.value })\n }\n />\n </label>\n {settings}\n </div>\n <div className=\"grid gap-3\">\n {data.options.map((option) => (\n <article\n key={option.id}\n className={cn(\n \"rounded-lg border border-border bg-muted p-4\",\n option.recommended &&\n \"border-ring/60 shadow-[inset_3px_0_0_hsl(var(--ring))]\",\n )}\n >\n <div className=\"grid gap-3\">\n <label className=\"grid gap-1.5\">\n <span className={inlineLabelClass}>Option</span>\n <input\n className={inlineInputClass}\n value={option.label}\n disabled={!editable}\n onChange={(event) =>\n updateOption(option.id, { label: event.target.value })\n }\n />\n </label>\n </div>\n <label className=\"mt-3 grid gap-1.5\">\n <span className={inlineLabelClass}>Detail</span>\n <textarea\n className={inlineTextareaClass}\n rows={2}\n value={option.detail ?? \"\"}\n disabled={!editable}\n onChange={(event) =>\n updateOption(option.id, {\n detail: event.target.value || undefined,\n })\n }\n />\n </label>\n </article>\n ))}\n </div>\n </div>\n );\n}\n\n/** Option-management controls rendered inside the host's edit surface popover. */\nfunction DecisionSettings({\n options,\n onToggleRecommended,\n onRemove,\n onAdd,\n}: {\n options: DecisionOption[];\n onToggleRecommended: (option: DecisionOption) => void;\n onRemove: (optionId: string) => void;\n onAdd: () => void;\n}) {\n return (\n <div className=\"grid gap-3\">\n <div className=\"text-sm font-semibold text-foreground\">\n Decision settings\n </div>\n <div className=\"grid gap-2\">\n {options.map((option, index) => (\n <div\n key={option.id}\n className=\"grid gap-2 rounded-md border border-border bg-muted/20 p-2\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"min-w-0 truncate text-xs font-medium text-foreground\">\n {option.label.trim() || `Option ${index + 1}`}\n </span>\n {option.recommended && (\n <span className=\"shrink-0 rounded-full border border-border px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-muted-foreground\">\n Recommended\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n data-plan-interactive\n onClick={() => onToggleRecommended(option)}\n className={cn(\n \"inline-flex h-8 flex-1 items-center justify-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-medium transition-colors\",\n option.recommended\n ? \"bg-background text-foreground shadow-sm\"\n : \"text-muted-foreground hover:bg-background/70 hover:text-foreground\",\n )}\n >\n {option.recommended && <IconCheck className=\"size-3.5\" />}\n {option.recommended ? \"Recommended\" : \"Mark recommended\"}\n </button>\n <button\n type=\"button\"\n data-plan-interactive\n aria-label={`Delete ${option.label || `option ${index + 1}`}`}\n disabled={options.length <= 1}\n onClick={() => onRemove(option.id)}\n className=\"inline-flex size-8 shrink-0 items-center justify-center rounded-md border border-border text-destructive transition-colors hover:bg-destructive/10 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <IconTrash className=\"size-3.5\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n <button\n type=\"button\"\n data-plan-interactive\n disabled={options.length >= 20}\n onClick={onAdd}\n className=\"inline-flex h-8 items-center justify-center gap-1.5 rounded-md border border-border px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <IconPlus className=\"size-3.5\" />\n Add option\n </button>\n </div>\n );\n}\n\n/**\n * Fallback for hosts that do not provide `ctx.renderEditSurface`: a plain\n * disclosure button that reveals the same settings inline (no overlay primitive,\n * so core stays shadcn-free).\n */\nfunction DecisionInlineSettings(props: {\n options: DecisionOption[];\n onToggleRecommended: (option: DecisionOption) => void;\n onRemove: (optionId: string) => void;\n onAdd: () => void;\n}) {\n const [open, setOpen] = useState(false);\n return (\n <div className=\"grid gap-2\">\n <button\n type=\"button\"\n data-plan-interactive\n aria-label=\"Edit decision options\"\n aria-expanded={open}\n onClick={() => setOpen((value) => !value)}\n className=\"flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-muted text-muted-foreground transition-colors hover:bg-accent/60 hover:text-foreground\"\n >\n <IconPencil className=\"size-4\" />\n </button>\n {open && (\n <div className=\"w-80 rounded-md border border-border bg-background p-3 shadow-sm\">\n <DecisionSettings {...props} />\n </div>\n )}\n </div>\n );\n}\n\n/** Full client spec for the shared `decision` block (schema + MDX + Read/Edit). */\nexport const decisionBlock = defineBlock<DecisionData>({\n type: \"decision\",\n schema: decisionSchema,\n mdx: decisionMdx,\n Read: DecisionBlock,\n Edit: DecisionBlockEdit,\n placement: [\"block\"],\n // `panel`: the document shows the clean read view (question + option cards with\n // the recommended pick highlighted), and the corner pencil opens the editor in\n // a popover. NOT `inline` — an inline schema-editing form (question + per-option\n // textareas) rendered straight into the doc reads as a confusing data-entry wall\n // rather than a decision. Mirrors how `question-form` / `visual-questions` edit.\n editSurface: \"panel\",\n label: \"Decision\",\n description:\n \"A decision prompt with option cards and an authored recommended choice. Shows a clean read view in the document; edit the question and options from the corner pencil.\",\n empty: () => ({\n question: \"Which implementation direction should we take?\",\n options: [\n {\n id: \"recommended\",\n label: \"Recommended path\",\n detail: \"Smallest useful slice with clear rollback.\",\n recommended: true,\n },\n {\n id: \"alternative\",\n label: \"Alternative\",\n detail: \"Broader pass that touches more surfaces.\",\n },\n ],\n }),\n});\n"]}