pi-design-deck 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -13
- package/deck-schema.ts +33 -3
- package/deck-server.ts +64 -10
- package/export-html.ts +329 -0
- package/form/css/controls.css +152 -16
- package/form/css/layout.css +7 -0
- package/form/deck.html +16 -0
- package/form/js/deck-core.js +118 -0
- package/form/js/deck-interact.js +30 -12
- package/form/js/deck-render.js +2 -0
- package/form/js/deck-session.js +31 -12
- package/generate-prompts.ts +8 -10
- package/index.ts +318 -42
- package/package.json +2 -1
- package/prompts/deck-discover.md +3 -1
- package/prompts/deck-plan.md +3 -1
- package/prompts/deck.md +3 -1
- package/skills/design-deck/SKILL.md +45 -9
- package/skills/design-deck/references/component-gallery/INDEX.md +88 -0
- package/skills/design-deck/references/component-gallery/LOOKUP.md +592 -0
- package/skills/design-deck/references/component-gallery/components/INDEX.md +106 -0
- package/skills/design-deck/references/component-gallery/components/actions.md +354 -0
- package/skills/design-deck/references/component-gallery/components/data-display.md +812 -0
- package/skills/design-deck/references/component-gallery/components/feedback.md +513 -0
- package/skills/design-deck/references/component-gallery/components/inputs.md +921 -0
- package/skills/design-deck/references/component-gallery/components/layout.md +167 -0
- package/skills/design-deck/references/component-gallery/components/navigation.md +350 -0
- package/skills/design-deck/references/component-gallery/components/overlays.md +208 -0
- package/skills/design-deck/references/component-gallery/components/utilities.md +29 -0
- package/skills/design-deck/references/component-gallery/components.md +1383 -0
package/README.md
CHANGED
|
@@ -96,10 +96,12 @@ The browser opens, the user picks "JWT + Refresh Tokens", and the agent receives
|
|
|
96
96
|
- **Generate-more loop**: Users click "Generate another option" and the agent pushes a new option into the live deck via SSE. No page reload.
|
|
97
97
|
- **Model selector**: Dropdown to pick which model generates new options. Save as default, or override per-request.
|
|
98
98
|
- **Thinking level**: Adjust reasoning effort for option generation when the selected model supports it.
|
|
99
|
-
- **Slide columns**: `columns` property (1, 2, or
|
|
99
|
+
- **Slide columns**: `columns` property (1, 2, 3, or 4) per slide. Auto-detected from option count if omitted.
|
|
100
100
|
- **Smart rebalancing**: Grid layout recalculates after generate-more adds options to minimize orphans.
|
|
101
101
|
- **Option aside**: Explanatory text rendered below the preview. Supports `\n` for line breaks.
|
|
102
|
-
- **Save/load snapshots**: `Cmd+S` saves the deck to disk.
|
|
102
|
+
- **Save/load snapshots**: `Cmd+S` saves the deck to disk. Use `action: "list"` to enumerate saved decks, `action: "open"` to reopen one by deck ID, or pass a file path to `slides`.
|
|
103
|
+
- **Notes persistence**: Saved decks include selected-option notes and summary-slide final instructions, and reopening restores both from disk.
|
|
104
|
+
- **Standalone HTML export**: `action: "export"` writes a read-only `export.html` next to the saved deck snapshot.
|
|
103
105
|
- **Light/dark/auto theme**: Full theme toggle with `Cmd+Shift+L` (configurable). Persists in localStorage.
|
|
104
106
|
- **Heartbeat watchdog**: Server detects lost browser connections (60s grace) and cleans up.
|
|
105
107
|
- **Idle timeout**: 5-minute inactivity timer after generate-more. Closes the deck if the agent doesn't respond.
|
|
@@ -110,7 +112,7 @@ The browser opens, the user picks "JWT + Refresh Tokens", and the agent receives
|
|
|
110
112
|
|
|
111
113
|
1. Agent calls `design_deck()` with slides JSON — local HTTP server starts, browser opens
|
|
112
114
|
2. User navigates slides, picks one option per slide
|
|
113
|
-
3. Optionally clicks "Generate
|
|
115
|
+
3. Optionally clicks "Generate N options" — agent generates and pushes via `add-options`, deck stays open
|
|
114
116
|
4. User submits — selections returned to agent as `{ slideId: "selected label" }`
|
|
115
117
|
|
|
116
118
|
The server persists across tool re-invocations. When generate-more fires, the tool resolves with instructions for the agent to create a new option. The browser shows a skeleton placeholder with shimmer animation until the new option arrives via SSE.
|
|
@@ -154,7 +156,7 @@ Image blocks reference absolute file paths. The server copies each file into a t
|
|
|
154
156
|
|
|
155
157
|
### Columns
|
|
156
158
|
|
|
157
|
-
Each slide supports `columns: 1 | 2 | 3` to control the grid layout. Omit it and the deck auto-detects based on option count. Use `columns: 1` for wide architecture diagrams, `columns: 2` for side-by-side comparisons.
|
|
159
|
+
Each slide supports `columns: 1 | 2 | 3 | 4` to control the grid layout. Omit it and the deck auto-detects based on option count. Use `columns: 1` for wide architecture diagrams, `columns: 2` for side-by-side comparisons, `columns: 4` for many small items.
|
|
158
160
|
|
|
159
161
|
### Aside
|
|
160
162
|
|
|
@@ -166,17 +168,17 @@ The slide ID `"summary"` is reserved for the built-in summary slide that appears
|
|
|
166
168
|
|
|
167
169
|
## Generate-More Loop
|
|
168
170
|
|
|
169
|
-
When the user clicks "Generate
|
|
171
|
+
When the user clicks "Generate N options," the tool resolves with a structured prompt telling the agent which slide needs options, how many, what options already exist, and what format to use. The agent generates the requested options and pushes them all at once:
|
|
170
172
|
|
|
171
173
|
```typescript
|
|
172
174
|
design_deck({
|
|
173
|
-
action: "add-
|
|
175
|
+
action: "add-options",
|
|
174
176
|
slideId: "arch",
|
|
175
|
-
|
|
177
|
+
options: '[{"label": "Serverless", "previewBlocks": [...]}, {"label": "Edge", "previewBlocks": [...]}]'
|
|
176
178
|
})
|
|
177
179
|
```
|
|
178
180
|
|
|
179
|
-
The browser shows the new
|
|
181
|
+
The browser shows the new options with entry animations. The `add-options` call blocks until the next user action (submit, cancel, or another generate-more).
|
|
180
182
|
|
|
181
183
|
### Model Override
|
|
182
184
|
|
|
@@ -213,6 +215,21 @@ design_deck({ slides: "~/.pi/deck-snapshots/api-design-myapp-main-2026-02-22-143
|
|
|
213
215
|
|
|
214
216
|
The deck opens with selections pre-populated and image paths resolved relative to the snapshot directory.
|
|
215
217
|
|
|
218
|
+
**Listing saved decks:**
|
|
219
|
+
```typescript
|
|
220
|
+
design_deck({ action: "list" })
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Opening by deck ID:**
|
|
224
|
+
```typescript
|
|
225
|
+
design_deck({ action: "open", deckId: "api-design-myapp-main-2026-02-22-143000-submitted" })
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Exporting standalone HTML:**
|
|
229
|
+
```typescript
|
|
230
|
+
design_deck({ action: "export", deckId: "api-design-myapp-main-2026-02-22-143000-submitted", format: "html" })
|
|
231
|
+
```
|
|
232
|
+
|
|
216
233
|
**Snapshot structure:**
|
|
217
234
|
```
|
|
218
235
|
~/.pi/deck-snapshots/
|
|
@@ -272,15 +289,20 @@ The agent handles these when you use the slash commands or ask in natural langua
|
|
|
272
289
|
| Parameter | Type | Description |
|
|
273
290
|
|-----------|------|-------------|
|
|
274
291
|
| `slides` | string | JSON string of deck config, or file path to a saved deck |
|
|
275
|
-
| `action` | `"add-option"` \| `"replace-options"` | Push
|
|
292
|
+
| `action` | `"add-options"` \| `"add-option"` \| `"replace-options"` \| `"list"` \| `"open"` \| `"export"` | Push/replace options, list saved decks, reopen a saved deck, or export one |
|
|
276
293
|
| `slideId` | string | Target slide ID (required with actions) |
|
|
277
294
|
| `option` | string | JSON string of one option (required with `add-option`) |
|
|
278
|
-
| `options` | string | JSON string of option array (required with `replace-options`) |
|
|
295
|
+
| `options` | string | JSON string of option array (required with `add-options` or `replace-options`) |
|
|
296
|
+
| `deckId` | string | Saved deck ID from `action: "list"` (required with `open` / `export`) |
|
|
297
|
+
| `format` | string | Export format for `action: "export"` (`"html"` currently supported) |
|
|
279
298
|
|
|
280
|
-
|
|
299
|
+
Six modes of invocation:
|
|
281
300
|
1. **Start a new deck:** `design_deck({ slides: "<JSON>" })`
|
|
282
|
-
2. **Add
|
|
283
|
-
3. **
|
|
301
|
+
2. **Add options to running deck:** `design_deck({ action: "add-options", slideId: "...", options: "<JSON array>" })` — blocks until next user action
|
|
302
|
+
3. **Add single option (non-blocking):** `design_deck({ action: "add-option", slideId: "...", option: "<JSON>" })`
|
|
303
|
+
4. **Replace all options on a slide:** `design_deck({ action: "replace-options", slideId: "...", options: "<JSON array>" })`
|
|
304
|
+
5. **List saved decks:** `design_deck({ action: "list" })`
|
|
305
|
+
6. **Open or export a saved deck:** `design_deck({ action: "open" | "export", deckId: "..." })`
|
|
284
306
|
|
|
285
307
|
## File Structure
|
|
286
308
|
|
|
@@ -313,6 +335,18 @@ The extension includes a `design-deck` skill at `skills/design-deck/SKILL.md` th
|
|
|
313
335
|
|
|
314
336
|
The skill is declared in `package.json` under `pi.skills` and is automatically discovered when the extension is installed. No manual copying needed.
|
|
315
337
|
|
|
338
|
+
### Component Gallery Reference
|
|
339
|
+
|
|
340
|
+
The skill includes a reference library for 60 UI components with best practices, common layouts, and aliases. Each component links to [component.gallery](https://component.gallery) where the agent can browse real screenshots when needed.
|
|
341
|
+
|
|
342
|
+
**Before:** "Show me collapse options" → agent might not connect that to accordion, or know what components are available for the use case.
|
|
343
|
+
|
|
344
|
+
**After:** Agent has 60 components to suggest from. Knows *collapse = accordion = disclosure = expander*. Knows *Blueprint = dense, dark-native; Ant = clean, blue primary.* Can browse [100+ real implementations](https://component.gallery/components/accordion/) when it needs concrete references.
|
|
345
|
+
|
|
346
|
+
The reference enables discovery (find/suggest components), cross-referencing (connect related terms), and design vocabulary (know what systems look like) — plus guidance on *when* to show distinct design systems vs variations of one style.
|
|
347
|
+
|
|
348
|
+
A separate vocabulary lookup (`LOOKUP.md`) resolves ambiguous user terms to canonical components. When a user says "dropdown" (Select? Combobox? Dropdown menu?) or "popup" (Modal? Popover? Tooltip?) or describes intent ("I need something that expands"), the agent can consult the lookup to understand what they mean and ask the right clarifying questions when needed.
|
|
349
|
+
|
|
316
350
|
## Limitations
|
|
317
351
|
|
|
318
352
|
- Only one deck can be active at a time. Complete or cancel before starting another.
|
|
@@ -320,3 +354,7 @@ The skill is declared in `package.json` under `pi.skills` and is automatically d
|
|
|
320
354
|
- The `summary` slide ID is reserved and cannot be used for custom slides.
|
|
321
355
|
- Mermaid diagrams load from CDN — requires internet on first load.
|
|
322
356
|
- macOS tested primarily; Linux and Windows support is best-effort.
|
|
357
|
+
|
|
358
|
+
## Credits
|
|
359
|
+
|
|
360
|
+
UI component reference data sourced from [component.gallery](https://component.gallery) by Iain Bean.
|
package/deck-schema.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface DeckSlide {
|
|
|
17
17
|
id: string;
|
|
18
18
|
title: string;
|
|
19
19
|
context?: string;
|
|
20
|
-
columns?: 1 | 2 | 3;
|
|
20
|
+
columns?: 1 | 2 | 3 | 4;
|
|
21
21
|
options: DeckOption[];
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -155,8 +155,8 @@ function validateDeckSlide(slide: unknown, index: number): DeckSlide {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
if (obj.columns !== undefined) {
|
|
158
|
-
if (obj.columns !== 1 && obj.columns !== 2 && obj.columns !== 3) {
|
|
159
|
-
throw new Error(`Slide "${obj.id}": columns must be 1, 2, or
|
|
158
|
+
if (obj.columns !== 1 && obj.columns !== 2 && obj.columns !== 3 && obj.columns !== 4) {
|
|
159
|
+
throw new Error(`Slide "${obj.id}": columns must be 1, 2, 3, or 4`);
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -229,6 +229,11 @@ export interface SavedDeckData {
|
|
|
229
229
|
config: DeckConfig;
|
|
230
230
|
selections: Record<string, string>;
|
|
231
231
|
savedAt: string;
|
|
232
|
+
id?: string;
|
|
233
|
+
status?: "submitted" | "in-progress" | "cancelled";
|
|
234
|
+
modifiedAt?: string;
|
|
235
|
+
notes?: Record<string, string>;
|
|
236
|
+
finalNotes?: string;
|
|
232
237
|
savedFrom?: {
|
|
233
238
|
cwd: string;
|
|
234
239
|
branch: string | null;
|
|
@@ -236,6 +241,14 @@ export interface SavedDeckData {
|
|
|
236
241
|
};
|
|
237
242
|
}
|
|
238
243
|
|
|
244
|
+
export type SavedDeckStatus = NonNullable<SavedDeckData["status"]>;
|
|
245
|
+
|
|
246
|
+
export function deriveDeckStatusFromFolderName(folderName: string): SavedDeckStatus {
|
|
247
|
+
if (folderName.endsWith("-submitted")) return "submitted";
|
|
248
|
+
if (folderName.endsWith("-cancelled")) return "cancelled";
|
|
249
|
+
return "in-progress";
|
|
250
|
+
}
|
|
251
|
+
|
|
239
252
|
export function validateSavedDeck(data: unknown): SavedDeckData {
|
|
240
253
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
241
254
|
throw new Error("Invalid saved deck: must be an object");
|
|
@@ -251,10 +264,27 @@ export function validateSavedDeck(data: unknown): SavedDeckData {
|
|
|
251
264
|
}
|
|
252
265
|
}
|
|
253
266
|
|
|
267
|
+
const notes: Record<string, string> = {};
|
|
268
|
+
if (obj.notes && typeof obj.notes === "object" && !Array.isArray(obj.notes)) {
|
|
269
|
+
for (const [key, val] of Object.entries(obj.notes as Record<string, unknown>)) {
|
|
270
|
+
if (typeof val === "string") notes[key] = val;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const status =
|
|
275
|
+
obj.status === "submitted" || obj.status === "in-progress" || obj.status === "cancelled"
|
|
276
|
+
? obj.status
|
|
277
|
+
: undefined;
|
|
278
|
+
|
|
254
279
|
return {
|
|
255
280
|
config,
|
|
256
281
|
selections,
|
|
257
282
|
savedAt: typeof obj.savedAt === "string" ? obj.savedAt : new Date().toISOString(),
|
|
283
|
+
id: typeof obj.id === "string" && obj.id.trim() !== "" ? obj.id : undefined,
|
|
284
|
+
status,
|
|
285
|
+
modifiedAt: typeof obj.modifiedAt === "string" ? obj.modifiedAt : undefined,
|
|
286
|
+
notes: Object.keys(notes).length > 0 ? notes : undefined,
|
|
287
|
+
finalNotes: typeof obj.finalNotes === "string" ? obj.finalNotes : undefined,
|
|
258
288
|
savedFrom: obj.savedFrom && typeof obj.savedFrom === "object"
|
|
259
289
|
? obj.savedFrom as SavedDeckData["savedFrom"]
|
|
260
290
|
: undefined,
|
package/deck-server.ts
CHANGED
|
@@ -66,6 +66,10 @@ const ABANDONED_GRACE_MS = 60000;
|
|
|
66
66
|
const WATCHDOG_INTERVAL_MS = 5000;
|
|
67
67
|
const GENERATE_TIMEOUT_MS = 90_000;
|
|
68
68
|
|
|
69
|
+
export function getDefaultSnapshotDir(): string {
|
|
70
|
+
return join(homedir(), ".pi", "deck-snapshots");
|
|
71
|
+
}
|
|
72
|
+
|
|
69
73
|
function toStringMap(value: unknown): Record<string, string> | null {
|
|
70
74
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
71
75
|
return null;
|
|
@@ -100,7 +104,7 @@ function processOptionAssets(option: DeckOption, assetsDir: string): DeckOption
|
|
|
100
104
|
return { ...option, previewBlocks: processImageBlocks(option.previewBlocks, assetsDir) };
|
|
101
105
|
}
|
|
102
106
|
|
|
103
|
-
const DECK_SNAPSHOTS_DIR =
|
|
107
|
+
const DECK_SNAPSHOTS_DIR = getDefaultSnapshotDir();
|
|
104
108
|
|
|
105
109
|
function sanitizeForFilename(value: string): string {
|
|
106
110
|
return value.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40).replace(/_+$/, "") || "unknown";
|
|
@@ -114,17 +118,30 @@ function saveDeckSnapshot(
|
|
|
114
118
|
gitBranch: string | null,
|
|
115
119
|
sessionId: string,
|
|
116
120
|
baseDir: string,
|
|
117
|
-
|
|
121
|
+
options?: {
|
|
122
|
+
status?: "submitted" | "in-progress" | "cancelled";
|
|
123
|
+
notes?: Record<string, string>;
|
|
124
|
+
finalNotes?: string;
|
|
125
|
+
}
|
|
118
126
|
): { path: string; relativePath: string } {
|
|
119
127
|
const now = new Date();
|
|
128
|
+
const nowIso = now.toISOString();
|
|
120
129
|
const date = now.toISOString().slice(0, 10);
|
|
121
130
|
const time = now.toTimeString().slice(0, 8).replace(/:/g, "");
|
|
122
131
|
const titleSlug = sanitizeForFilename(config.title || "deck");
|
|
123
132
|
const project = sanitizeForFilename(basename(normalizedCwd) || "unknown");
|
|
124
133
|
const branch = sanitizeForFilename(gitBranch || "nogit");
|
|
134
|
+
const suffix = options?.status === "submitted" || options?.status === "cancelled" ? options.status : undefined;
|
|
125
135
|
const safeSuffix = suffix ? `-${suffix}` : "";
|
|
126
|
-
const
|
|
127
|
-
|
|
136
|
+
const baseFolderName = `${titleSlug}-${project}-${branch}-${date}-${time}${safeSuffix}`;
|
|
137
|
+
let folderName = baseFolderName;
|
|
138
|
+
let snapshotPath = join(baseDir, folderName);
|
|
139
|
+
let collisionIndex = 2;
|
|
140
|
+
while (existsSync(snapshotPath)) {
|
|
141
|
+
folderName = `${baseFolderName}-${collisionIndex}`;
|
|
142
|
+
snapshotPath = join(baseDir, folderName);
|
|
143
|
+
collisionIndex += 1;
|
|
144
|
+
}
|
|
128
145
|
const imagesPath = join(snapshotPath, "images");
|
|
129
146
|
|
|
130
147
|
mkdirSync(snapshotPath, { recursive: true });
|
|
@@ -149,7 +166,12 @@ function saveDeckSnapshot(
|
|
|
149
166
|
const data = {
|
|
150
167
|
config: saved,
|
|
151
168
|
selections,
|
|
152
|
-
savedAt:
|
|
169
|
+
savedAt: nowIso,
|
|
170
|
+
id: folderName,
|
|
171
|
+
status: options?.status,
|
|
172
|
+
modifiedAt: nowIso,
|
|
173
|
+
notes: options?.notes && Object.keys(options.notes).length > 0 ? options.notes : undefined,
|
|
174
|
+
finalNotes: options?.finalNotes ? options.finalNotes : undefined,
|
|
153
175
|
savedFrom: { cwd: normalizedCwd, branch: gitBranch, sessionId },
|
|
154
176
|
};
|
|
155
177
|
writeFileSync(join(snapshotPath, "deck.json"), JSON.stringify(data, null, 2));
|
|
@@ -167,6 +189,8 @@ export interface DeckServerOptions {
|
|
|
167
189
|
port?: number;
|
|
168
190
|
theme?: { mode?: string; toggleHotkey?: string };
|
|
169
191
|
savedSelections?: Record<string, string>;
|
|
192
|
+
savedNotes?: Record<string, { label: string; notes: string }>;
|
|
193
|
+
savedFinalNotes?: string;
|
|
170
194
|
snapshotDir?: string;
|
|
171
195
|
autoSaveOnSubmit?: boolean;
|
|
172
196
|
models?: ModelsPayload;
|
|
@@ -192,7 +216,19 @@ export async function startDeckServer(
|
|
|
192
216
|
options: DeckServerOptions,
|
|
193
217
|
callbacks: DeckServerCallbacks
|
|
194
218
|
): Promise<DeckServerHandle> {
|
|
195
|
-
const {
|
|
219
|
+
const {
|
|
220
|
+
config,
|
|
221
|
+
sessionToken,
|
|
222
|
+
sessionId,
|
|
223
|
+
cwd,
|
|
224
|
+
port,
|
|
225
|
+
theme,
|
|
226
|
+
savedSelections,
|
|
227
|
+
savedNotes,
|
|
228
|
+
savedFinalNotes,
|
|
229
|
+
snapshotDir,
|
|
230
|
+
autoSaveOnSubmit,
|
|
231
|
+
} = options;
|
|
196
232
|
const normalizedCwd = normalizePath(cwd);
|
|
197
233
|
const gitBranch = getGitBranch(cwd);
|
|
198
234
|
|
|
@@ -293,6 +329,8 @@ export async function startDeckServer(
|
|
|
293
329
|
gitBranch,
|
|
294
330
|
theme,
|
|
295
331
|
savedSelections,
|
|
332
|
+
savedNotes,
|
|
333
|
+
savedFinalNotes,
|
|
296
334
|
});
|
|
297
335
|
const title = config.title ? `${config.title} — Design Deck` : "Design Deck";
|
|
298
336
|
const html = DECK_TEMPLATE
|
|
@@ -426,7 +464,11 @@ export async function startDeckServer(
|
|
|
426
464
|
touchHeartbeat();
|
|
427
465
|
if (autoSaveOnSubmit !== false) {
|
|
428
466
|
try {
|
|
429
|
-
saveDeckSnapshot(config, selections, assetsDir, normalizedCwd, gitBranch, sessionId, snapshotDir || DECK_SNAPSHOTS_DIR,
|
|
467
|
+
saveDeckSnapshot(config, selections, assetsDir, normalizedCwd, gitBranch, sessionId, snapshotDir || DECK_SNAPSHOTS_DIR, {
|
|
468
|
+
status: "submitted",
|
|
469
|
+
notes,
|
|
470
|
+
finalNotes: finalNotes || undefined,
|
|
471
|
+
});
|
|
430
472
|
} catch {}
|
|
431
473
|
}
|
|
432
474
|
markCompleted();
|
|
@@ -441,12 +483,22 @@ export async function startDeckServer(
|
|
|
441
483
|
const body = await safeParseBody(req, res);
|
|
442
484
|
if (!body) return;
|
|
443
485
|
if (!validateTokenBody(body, sessionToken, res)) return;
|
|
486
|
+
if (completed) {
|
|
487
|
+
sendJson(res, 409, { ok: false, error: "Session closed" });
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
444
490
|
|
|
445
|
-
const payload = body as { selections?: unknown };
|
|
491
|
+
const payload = body as { selections?: unknown; notes?: unknown; finalNotes?: unknown };
|
|
446
492
|
const selections = toStringMap(payload.selections) ?? {};
|
|
493
|
+
const notes = toStringMap(payload.notes) ?? undefined;
|
|
494
|
+
const finalNotes = typeof payload.finalNotes === "string" ? payload.finalNotes.trim() : undefined;
|
|
447
495
|
|
|
448
496
|
try {
|
|
449
|
-
const result = saveDeckSnapshot(config, selections, assetsDir, normalizedCwd, gitBranch, sessionId, snapshotDir || DECK_SNAPSHOTS_DIR
|
|
497
|
+
const result = saveDeckSnapshot(config, selections, assetsDir, normalizedCwd, gitBranch, sessionId, snapshotDir || DECK_SNAPSHOTS_DIR, {
|
|
498
|
+
status: "in-progress",
|
|
499
|
+
notes,
|
|
500
|
+
finalNotes: finalNotes || undefined,
|
|
501
|
+
});
|
|
450
502
|
sendJson(res, 200, { ok: true, path: result.path, relativePath: result.relativePath });
|
|
451
503
|
} catch (err) {
|
|
452
504
|
const message = err instanceof Error ? err.message : "Save failed";
|
|
@@ -473,7 +525,9 @@ export async function startDeckServer(
|
|
|
473
525
|
const cancelSelections = toStringMap(payload.selections);
|
|
474
526
|
if (cancelSelections && Object.keys(cancelSelections).length > 0) {
|
|
475
527
|
try {
|
|
476
|
-
saveDeckSnapshot(config, cancelSelections, assetsDir, normalizedCwd, gitBranch, sessionId, snapshotDir || DECK_SNAPSHOTS_DIR,
|
|
528
|
+
saveDeckSnapshot(config, cancelSelections, assetsDir, normalizedCwd, gitBranch, sessionId, snapshotDir || DECK_SNAPSHOTS_DIR, {
|
|
529
|
+
status: "cancelled",
|
|
530
|
+
});
|
|
477
531
|
} catch {}
|
|
478
532
|
}
|
|
479
533
|
|