pi-design-deck 0.3.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 +2 -2
- package/deck-schema.ts +3 -3
- package/export-html.ts +1 -1
- package/form/css/controls.css +101 -16
- package/form/css/layout.css +7 -0
- package/form/deck.html +14 -0
- package/form/js/deck-core.js +72 -0
- package/form/js/deck-session.js +6 -3
- package/index.ts +1 -1
- package/package.json +1 -1
- package/skills/design-deck/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ 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
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`.
|
|
@@ -156,7 +156,7 @@ Image blocks reference absolute file paths. The server copies each file into a t
|
|
|
156
156
|
|
|
157
157
|
### Columns
|
|
158
158
|
|
|
159
|
-
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.
|
|
160
160
|
|
|
161
161
|
### Aside
|
|
162
162
|
|
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
|
|
package/export-html.ts
CHANGED
|
@@ -157,7 +157,7 @@ function escapeHtml(value: string): string {
|
|
|
157
157
|
.replace(/"/g, """);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
function optionCountClass(count: number, columns?: 1 | 2 | 3): string {
|
|
160
|
+
function optionCountClass(count: number, columns?: 1 | 2 | 3 | 4): string {
|
|
161
161
|
if (columns === 1) return "cols-1";
|
|
162
162
|
if (columns && count >= columns && count % columns !== 1) {
|
|
163
163
|
return `cols-${columns}`;
|
package/form/css/controls.css
CHANGED
|
@@ -154,10 +154,17 @@
|
|
|
154
154
|
z-index: 10;
|
|
155
155
|
animation: regen-fade-in 0.3s ease-out;
|
|
156
156
|
}
|
|
157
|
+
.regen-overlay.cols-4 { grid-template-columns: repeat(4, 1fr); }
|
|
157
158
|
.regen-overlay.cols-3 { grid-template-columns: repeat(3, 1fr); }
|
|
158
159
|
.regen-overlay.cols-2 { grid-template-columns: repeat(2, 1fr); }
|
|
159
160
|
.regen-overlay.cols-1 { grid-template-columns: 1fr; }
|
|
160
161
|
|
|
162
|
+
/* Regen overlay respects layout override */
|
|
163
|
+
.deck[data-layout="1"] .regen-overlay { grid-template-columns: 1fr; }
|
|
164
|
+
.deck[data-layout="2"] .regen-overlay { grid-template-columns: repeat(2, 1fr); }
|
|
165
|
+
.deck[data-layout="3"] .regen-overlay { grid-template-columns: repeat(3, 1fr); }
|
|
166
|
+
.deck[data-layout="4"] .regen-overlay { grid-template-columns: repeat(4, 1fr); }
|
|
167
|
+
|
|
161
168
|
@keyframes regen-fade-in {
|
|
162
169
|
from { opacity: 0; }
|
|
163
170
|
to { opacity: 1; }
|
|
@@ -197,26 +204,60 @@
|
|
|
197
204
|
.regen-skeleton:nth-child(4)::before,
|
|
198
205
|
.regen-skeleton:nth-child(4)::after { animation-delay: 0.4s; }
|
|
199
206
|
|
|
200
|
-
/*
|
|
201
|
-
|
|
207
|
+
/* ─────────────────────────────────────────────────────────────
|
|
208
|
+
LOADING SPINNER
|
|
209
|
+
───────────────────────────────────────────────────────────── */
|
|
210
|
+
|
|
211
|
+
.spinner {
|
|
212
|
+
width: 28px;
|
|
213
|
+
height: 28px;
|
|
214
|
+
border: 2.5px solid rgba(138,190,183,0.12);
|
|
215
|
+
border-top-color: var(--dk-accent);
|
|
216
|
+
border-radius: 50%;
|
|
217
|
+
animation: spin 0.7s linear infinite;
|
|
218
|
+
flex-shrink: 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* Spinner inside skeleton cards */
|
|
222
|
+
.option-skeleton .spinner {
|
|
202
223
|
position: absolute;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
224
|
+
top: 50%;
|
|
225
|
+
left: 50%;
|
|
226
|
+
transform: translate(-50%, -50%);
|
|
227
|
+
z-index: 2;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* Centered loading overlay for regenerate-all */
|
|
231
|
+
.regen-center {
|
|
232
|
+
position: absolute;
|
|
233
|
+
inset: 0;
|
|
209
234
|
display: flex;
|
|
235
|
+
flex-direction: column;
|
|
210
236
|
align-items: center;
|
|
211
|
-
|
|
237
|
+
justify-content: center;
|
|
238
|
+
gap: 14px;
|
|
239
|
+
z-index: 5;
|
|
240
|
+
pointer-events: none;
|
|
241
|
+
background: rgba(24,24,30,0.5);
|
|
242
|
+
backdrop-filter: blur(2px);
|
|
243
|
+
border-radius: 12px;
|
|
212
244
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
245
|
+
|
|
246
|
+
[data-theme="light"] .regen-center {
|
|
247
|
+
background: rgba(248,248,248,0.6);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.regen-center .spinner {
|
|
251
|
+
width: 36px;
|
|
252
|
+
height: 36px;
|
|
253
|
+
border-width: 3px;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.regen-center-text {
|
|
257
|
+
font-family: var(--dk-font-mono);
|
|
258
|
+
font-size: 12px;
|
|
259
|
+
color: var(--dk-accent-text);
|
|
260
|
+
letter-spacing: 0.3px;
|
|
220
261
|
}
|
|
221
262
|
|
|
222
263
|
/* Options grid regenerating state */
|
|
@@ -240,6 +281,49 @@
|
|
|
240
281
|
100% { opacity: 1; transform: translateY(0) scale(1); }
|
|
241
282
|
}
|
|
242
283
|
|
|
284
|
+
/* ─────────────────────────────────────────────────────────────
|
|
285
|
+
LAYOUT TOGGLE
|
|
286
|
+
───────────────────────────────────────────────────────────── */
|
|
287
|
+
|
|
288
|
+
.layout-toggle {
|
|
289
|
+
display: flex;
|
|
290
|
+
align-items: center;
|
|
291
|
+
gap: 1px;
|
|
292
|
+
background: rgba(var(--dk-ink),0.06);
|
|
293
|
+
border-radius: 6px;
|
|
294
|
+
padding: 2px;
|
|
295
|
+
margin-right: 8px;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.layout-btn {
|
|
299
|
+
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
justify-content: center;
|
|
302
|
+
width: 26px;
|
|
303
|
+
height: 22px;
|
|
304
|
+
border: none;
|
|
305
|
+
background: transparent;
|
|
306
|
+
border-radius: 4px;
|
|
307
|
+
color: var(--dk-text-hint);
|
|
308
|
+
cursor: pointer;
|
|
309
|
+
transition: background 0.15s, color 0.15s;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.layout-btn:hover {
|
|
313
|
+
background: rgba(var(--dk-ink),0.08);
|
|
314
|
+
color: var(--dk-text);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.layout-btn.active {
|
|
318
|
+
background: rgba(138,190,183,0.15);
|
|
319
|
+
color: var(--dk-accent-text);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.layout-btn svg {
|
|
323
|
+
width: 12px;
|
|
324
|
+
height: 12px;
|
|
325
|
+
}
|
|
326
|
+
|
|
243
327
|
/* ─────────────────────────────────────────────────────────────
|
|
244
328
|
MODEL BAR
|
|
245
329
|
───────────────────────────────────────────────────────────── */
|
|
@@ -468,6 +552,7 @@
|
|
|
468
552
|
font: 11px var(--dk-font-mono);
|
|
469
553
|
color: var(--dk-text-hint);
|
|
470
554
|
white-space: nowrap;
|
|
555
|
+
padding: 6px 12px;
|
|
471
556
|
}
|
|
472
557
|
|
|
473
558
|
.deck-save-status.saved {
|
package/form/css/layout.css
CHANGED
|
@@ -148,10 +148,17 @@ body {
|
|
|
148
148
|
───────────────────────────────────────────────────────────── */
|
|
149
149
|
|
|
150
150
|
.options { display: grid; column-gap: 20px; row-gap: 0; counter-reset: opt; }
|
|
151
|
+
.options.cols-4 { grid-template-columns: repeat(4, 1fr); }
|
|
151
152
|
.options.cols-3 { grid-template-columns: repeat(3, 1fr); }
|
|
152
153
|
.options.cols-2 { grid-template-columns: repeat(2, 1fr); }
|
|
153
154
|
.options.cols-1 { grid-template-columns: 1fr; }
|
|
154
155
|
|
|
156
|
+
/* Layout override - global toggle overrides per-slide classes */
|
|
157
|
+
.deck[data-layout="1"] .options { grid-template-columns: 1fr; }
|
|
158
|
+
.deck[data-layout="2"] .options { grid-template-columns: repeat(2, 1fr); }
|
|
159
|
+
.deck[data-layout="3"] .options { grid-template-columns: repeat(3, 1fr); }
|
|
160
|
+
.deck[data-layout="4"] .options { grid-template-columns: repeat(4, 1fr); }
|
|
161
|
+
|
|
155
162
|
.option {
|
|
156
163
|
border: 2px solid rgba(var(--dk-ink),0.12);
|
|
157
164
|
border-radius: 12px; overflow: hidden;
|
package/form/deck.html
CHANGED
|
@@ -44,6 +44,20 @@
|
|
|
44
44
|
<span class="deck-key"><kbd>←</kbd><kbd>→</kbd> Navigate</span>
|
|
45
45
|
<span class="deck-key"><kbd>1</kbd><kbd>2</kbd><kbd>3</kbd> Select</span>
|
|
46
46
|
<span class="deck-key"><kbd>Enter</kbd> Confirm</span>
|
|
47
|
+
<div class="layout-toggle" id="layout-toggle" role="toolbar" aria-label="Grid columns">
|
|
48
|
+
<button class="layout-btn" data-cols="1" type="button" title="1 column" aria-label="1 column" aria-pressed="false">
|
|
49
|
+
<svg viewBox="0 0 12 12"><rect x="3" y="1" width="6" height="10" rx="1" fill="currentColor"/></svg>
|
|
50
|
+
</button>
|
|
51
|
+
<button class="layout-btn" data-cols="2" type="button" title="2 columns" aria-label="2 columns" aria-pressed="false">
|
|
52
|
+
<svg viewBox="0 0 12 12"><rect x="1" y="1" width="4" height="10" rx="0.75" fill="currentColor"/><rect x="7" y="1" width="4" height="10" rx="0.75" fill="currentColor"/></svg>
|
|
53
|
+
</button>
|
|
54
|
+
<button class="layout-btn" data-cols="3" type="button" title="3 columns" aria-label="3 columns" aria-pressed="false">
|
|
55
|
+
<svg viewBox="0 0 12 12"><rect x="0.5" y="1" width="3" height="10" rx="0.5" fill="currentColor"/><rect x="4.5" y="1" width="3" height="10" rx="0.5" fill="currentColor"/><rect x="8.5" y="1" width="3" height="10" rx="0.5" fill="currentColor"/></svg>
|
|
56
|
+
</button>
|
|
57
|
+
<button class="layout-btn" data-cols="4" type="button" title="4 columns" aria-label="4 columns" aria-pressed="false">
|
|
58
|
+
<svg viewBox="0 0 12 12"><rect x="0" y="1" width="2.4" height="10" rx="0.5" fill="currentColor"/><rect x="3.2" y="1" width="2.4" height="10" rx="0.5" fill="currentColor"/><rect x="6.4" y="1" width="2.4" height="10" rx="0.5" fill="currentColor"/><rect x="9.6" y="1" width="2.4" height="10" rx="0.5" fill="currentColor"/></svg>
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
47
61
|
<span class="deck-key"><kbd class="mod-key">⌘</kbd><kbd>S</kbd> Save</span>
|
|
48
62
|
<button class="deck-save-btn" id="btn-save" type="button">Save</button>
|
|
49
63
|
<span class="deck-save-status" id="save-status" role="status" aria-live="polite">No unsaved changes</span>
|
package/form/js/deck-core.js
CHANGED
|
@@ -227,6 +227,78 @@ function initTheme() {
|
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
// ─── LAYOUT TOGGLE ───────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
const LAYOUT_KEY = "pi-deck-layout";
|
|
233
|
+
|
|
234
|
+
function getStoredLayout() {
|
|
235
|
+
try {
|
|
236
|
+
const value = localStorage.getItem(LAYOUT_KEY);
|
|
237
|
+
return value === "1" || value === "2" || value === "3" || value === "4" ? value : null;
|
|
238
|
+
} catch { return null; }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function setStoredLayout(value) {
|
|
242
|
+
try {
|
|
243
|
+
if (!value) {
|
|
244
|
+
localStorage.removeItem(LAYOUT_KEY);
|
|
245
|
+
} else {
|
|
246
|
+
localStorage.setItem(LAYOUT_KEY, value);
|
|
247
|
+
}
|
|
248
|
+
} catch {}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function applyLayout(cols) {
|
|
252
|
+
const deck = document.querySelector(".deck");
|
|
253
|
+
if (!deck) return;
|
|
254
|
+
if (cols) {
|
|
255
|
+
deck.dataset.layout = cols;
|
|
256
|
+
} else {
|
|
257
|
+
delete deck.dataset.layout;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function updateLayoutButtons(activeCols) {
|
|
262
|
+
const toggle = document.getElementById("layout-toggle");
|
|
263
|
+
if (!toggle) return;
|
|
264
|
+
toggle.querySelectorAll(".layout-btn").forEach((btn) => {
|
|
265
|
+
const isActive = btn.dataset.cols === activeCols;
|
|
266
|
+
btn.classList.toggle("active", isActive);
|
|
267
|
+
btn.setAttribute("aria-pressed", isActive ? "true" : "false");
|
|
268
|
+
// Update title to show "Auto" hint when clicking would reset
|
|
269
|
+
const cols = btn.dataset.cols;
|
|
270
|
+
btn.title = isActive ? `${cols} column${cols === "1" ? "" : "s"} (click for auto)` : `${cols} column${cols === "1" ? "" : "s"}`;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function initLayoutToggle() {
|
|
275
|
+
const stored = getStoredLayout();
|
|
276
|
+
if (stored) {
|
|
277
|
+
applyLayout(stored);
|
|
278
|
+
updateLayoutButtons(stored);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const toggle = document.getElementById("layout-toggle");
|
|
282
|
+
if (!toggle) return;
|
|
283
|
+
|
|
284
|
+
toggle.addEventListener("click", (event) => {
|
|
285
|
+
const btn = event.target.closest(".layout-btn");
|
|
286
|
+
if (!btn) return;
|
|
287
|
+
const cols = btn.dataset.cols;
|
|
288
|
+
const current = getStoredLayout();
|
|
289
|
+
if (cols === current) {
|
|
290
|
+
// Clicking active button toggles back to auto
|
|
291
|
+
setStoredLayout(null);
|
|
292
|
+
applyLayout(null);
|
|
293
|
+
updateLayoutButtons(null);
|
|
294
|
+
} else {
|
|
295
|
+
setStoredLayout(cols);
|
|
296
|
+
applyLayout(cols);
|
|
297
|
+
updateLayoutButtons(cols);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
230
302
|
// ─── SELECTION PERSISTENCE ────────────────────────────────────
|
|
231
303
|
|
|
232
304
|
const SELECTIONS_KEY = `pi-deck-${typeof deckData.sessionId === "string" ? deckData.sessionId : "unknown"}`;
|
package/form/js/deck-session.js
CHANGED
|
@@ -507,6 +507,7 @@ async function generateMore(button, slideId, input, countSelect) {
|
|
|
507
507
|
const skeletons = [];
|
|
508
508
|
for (let i = 0; i < count; i++) {
|
|
509
509
|
const skeleton = createElement("div", "option-skeleton");
|
|
510
|
+
skeleton.innerHTML = '<div class="spinner"></div>';
|
|
510
511
|
optionsGrid.appendChild(skeleton);
|
|
511
512
|
skeletons.push(skeleton);
|
|
512
513
|
}
|
|
@@ -563,7 +564,7 @@ async function regenerateSlide(button, slideId) {
|
|
|
563
564
|
const prompt = input ? input.value.trim() : "";
|
|
564
565
|
if (input) input.value = "";
|
|
565
566
|
|
|
566
|
-
// Create skeleton overlay
|
|
567
|
+
// Create skeleton overlay with centered spinner
|
|
567
568
|
const optionCount = slide.options?.length || 2;
|
|
568
569
|
const colClass = optionsGrid.classList.contains("cols-3") ? "cols-3" :
|
|
569
570
|
optionsGrid.classList.contains("cols-1") ? "cols-1" : "cols-2";
|
|
@@ -571,8 +572,9 @@ async function regenerateSlide(button, slideId) {
|
|
|
571
572
|
for (let i = 0; i < optionCount; i++) {
|
|
572
573
|
overlay.appendChild(createElement("div", "regen-skeleton"));
|
|
573
574
|
}
|
|
574
|
-
const
|
|
575
|
-
|
|
575
|
+
const center = createElement("div", "regen-center");
|
|
576
|
+
center.innerHTML = '<div class="spinner"></div><div class="regen-center-text">Regenerating options...</div>';
|
|
577
|
+
overlay.appendChild(center);
|
|
576
578
|
|
|
577
579
|
// Position overlay relative to options grid
|
|
578
580
|
optionsGrid.style.position = "relative";
|
|
@@ -670,6 +672,7 @@ function hideLoadingOverlay() {
|
|
|
670
672
|
|
|
671
673
|
function init() {
|
|
672
674
|
initTheme();
|
|
675
|
+
initLayoutToggle();
|
|
673
676
|
setMetaLabel();
|
|
674
677
|
renderSlides();
|
|
675
678
|
restoreSelections();
|
package/index.ts
CHANGED
|
@@ -88,7 +88,7 @@ const DeckParams = Type.Object(
|
|
|
88
88
|
slides: Type.Optional(
|
|
89
89
|
Type.String({
|
|
90
90
|
description:
|
|
91
|
-
"JSON string of deck config. Each slide has id, title, context?, columns? (1|2|3, omit for auto-layout), and options[]. " +
|
|
91
|
+
"JSON string of deck config. Each slide has id, title, context?, columns? (1|2|3|4, omit for auto-layout), and options[]. " +
|
|
92
92
|
"Each option has label, description?, aside?, recommended?, and either previewHtml (raw HTML string) or " +
|
|
93
93
|
"previewBlocks (array of typed blocks: {type:'html',content}, {type:'mermaid',content,theme?}, " +
|
|
94
94
|
"{type:'code',code,lang}, {type:'image',src,alt,caption?}). Exactly one of previewHtml or previewBlocks required per option.",
|
package/package.json
CHANGED
|
@@ -44,7 +44,7 @@ Build 1 decision per slide.
|
|
|
44
44
|
|
|
45
45
|
Provide 2-4 options per slide that are genuinely distinct in direction, not tiny variants.
|
|
46
46
|
|
|
47
|
-
Each slide supports an optional `columns` property (1, 2, or
|
|
47
|
+
Each slide supports an optional `columns` property (1, 2, 3, or 4) to override the auto-detected grid. **Omit `columns` by default** — the auto-layout picks 2 or 3 columns based on option count, which is correct for most content. Only override to `columns: 1` when options contain wide architecture diagrams or detailed code that genuinely needs full viewport width. Use `columns: 4` when presenting many small, comparable items (e.g., icon sets, color swatches).
|
|
48
48
|
|
|
49
49
|
Each slide supports an optional `context` property — a string displayed below the title that frames the decision for the user.
|
|
50
50
|
|