vibespot 1.6.4 → 1.7.7

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.
@@ -86,6 +86,10 @@
86
86
  <a class="doc-nav__link doc-nav__link--sub" href="#intent-types">Intents</a>
87
87
  <a class="doc-nav__link doc-nav__link--sub" href="#prompt-tips">Prompt Tips</a>
88
88
  <a class="doc-nav__link doc-nav__link--sub" href="#brand-assets">Brand Assets</a>
89
+ <a class="doc-nav__link" href="#checkpoints">Checkpoints</a>
90
+ <a class="doc-nav__link doc-nav__link--sub" href="#checkpoints-seams">The Three Seams</a>
91
+ <a class="doc-nav__link doc-nav__link--sub" href="#checkpoints-card">The Checkpoint Card</a>
92
+ <a class="doc-nav__link doc-nav__link--sub" href="#checkpoints-oneshot">One-Shot Escape Hatch</a>
89
93
  <a class="doc-nav__link" href="#plan-mode">Plan Mode</a>
90
94
  <a class="doc-nav__link doc-nav__link--sub" href="#plan-mode-when">When to Use</a>
91
95
  <a class="doc-nav__link doc-nav__link--sub" href="#plan-mode-flow">The Flow</a>
@@ -954,12 +958,49 @@ testimonial carousel, and a minimal footer with social links.</code></pre>
954
958
  </ul>
955
959
  </div>
956
960
 
961
+ <!-- ============================================================
962
+ Section: Checkpoints
963
+ ============================================================ -->
964
+ <div class="doc-section" id="checkpoints">
965
+ <h2 id="checkpoints-heading">Checkpoints <a href="#checkpoints" class="doc-anchor">#</a></h2>
966
+ <p class="doc-subtitle">The default generation flow. Instead of running all four pipeline stages in one blocking shot, vibeSpot pauses at cheap seams and shows you a review card <em>before</em> spending tokens on the expensive parallel module build &mdash; so you steer the look and the skeleton while changes are still free.</p>
967
+
968
+ <p>The <a href="#agentic-pipeline">agentic pipeline</a> has a natural cost cliff: the design system (Stage 2a) and the module plan (Stage 2b) are a handful of cheap calls, but the module build (Stage 3) fans out one generation per module in parallel &mdash; the bulk of the spend and the wait. Checkpoints park the pipeline at the seams before that cliff. You see what the AI decided, adjust it, and only then pay for the build. Each gate is rendered from data the pipeline already produced, so showing the card costs nothing extra.</p>
969
+
970
+ <img class="doc-screenshot" style="max-width:360px" src="screenshots/checkpoint-card.png" alt="Design checkpoint card in the chat panel: a DESIGN CHECKPOINT badge, a rendered hero preview, the palette swatches and heading/body type specimen, the estimated build cost, and Approve / Steer / Skip / Cancel buttons">
971
+
972
+ <h3 id="checkpoints-seams">The Three Seams</h3>
973
+ <p>For a from-scratch page, checkpoints fire in order: <strong>brand intake &rarr; design &rarr; structure &rarr; build</strong>. Modifications to an existing page, email generation, and plan-approved runs build straight through without gating.</p>
974
+ <ol class="doc-steps">
975
+ <li><strong>Brand intake.</strong> When you ask for a new page and the theme has no style system yet, vibeSpot asks first instead of guessing: <strong>bring your brand</strong> (paste colors, drop in CSS/code, point at an existing theme or a live URL, describe your voice) or <strong>let it choose</strong>. What you provide seeds the <code>:root</code> design tokens that feed the design checkpoint. Skipped when the theme already has a style system.</li>
976
+ <li><strong>Design checkpoint.</strong> After the design-system stage, the pipeline parks and shows a <strong>design card</strong>: palette swatches, a heading/body type specimen, and one representative hero rendered straight from the design tokens. No extra model call &mdash; it's a deterministic preview of the tokens the build will use.</li>
977
+ <li><strong>Structure checkpoint.</strong> After the module planner, the pipeline parks again and shows the planned modules as an <strong>editable outline</strong>. Reorder rows (&#9650;/&#9660;), cut them (&#10005;), rename inline, or add a section &mdash; all client-side, no model call. The build honors exactly the outline you keep: a reorder or rename preserves that module's planned brief; an added row gets a sensible default brief.</li>
978
+ </ol>
979
+
980
+ <h3 id="checkpoints-card">The Checkpoint Card</h3>
981
+ <p>Every checkpoint card offers the same four actions, plus a cost estimate:</p>
982
+ <ul>
983
+ <li><strong>Approve</strong> &mdash; accept what's shown and continue to the next seam (or the build, at the last gate).</li>
984
+ <li><strong>Steer</strong> &mdash; type a note and vibeSpot re-runs <em>only</em> that stage with your guidance, then re-previews. Cheap to iterate, because you're redoing one stage, not the whole page.</li>
985
+ <li><strong>Skip</strong> &mdash; build now and suppress any remaining gates for this run. Skipping at the design gate suppresses the structure gate too.</li>
986
+ <li><strong>Cancel</strong> &mdash; drop the run. Nothing is written.</li>
987
+ </ul>
988
+ <p>Each card also shows the <strong>estimated cost of the build it gates</strong>, computed from spend-so-far via the per-page <a href="#observability">cost tracker</a> &mdash; so you know what Approve will cost before you click it. (CLI engines report no usage, so the estimate is omitted for them.)</p>
989
+ <div class="doc-callout doc-callout--tip">
990
+ <div class="doc-callout__label">&#128268; Crash-safe by design</div>
991
+ <p>A parked checkpoint isn't a paused, open promise &mdash; the pipeline <em>returns</em> at the gate and your resolution re-enters it via a resume token. If the server restarts while a gate is open, the run is simply dropped and you re-send your prompt, rather than leaving a hung generation behind.</p>
992
+ </div>
993
+
994
+ <h3 id="checkpoints-oneshot">One-Shot Escape Hatch</h3>
995
+ <p>Checkpoints are <strong>on by default</strong> for from-scratch page generation. When you already know what you want and don't need to review, the <strong>one-shot</strong> button next to send runs the whole pipeline in one go &mdash; the previous one-call behaviour, no gates. It's a per-message choice: tap one-shot for this prompt, or hit send to go through the checkpoints.</p>
996
+ </div>
997
+
957
998
  <!-- ============================================================
958
999
  Section: Plan Mode
959
1000
  ============================================================ -->
960
1001
  <div class="doc-section" id="plan-mode">
961
1002
  <h2 id="plan-mode-heading">Plan Mode <a href="#plan-mode" class="doc-anchor">#</a></h2>
962
- <p class="doc-subtitle">A deliberation phase that runs <em>before</em> any code is generated. Plan mode turns vague briefs into well-scoped plans through guided dialogue, then runs the agentic pipeline against the approved plan as a high-fidelity design brief.</p>
1003
+ <p class="doc-subtitle">A deliberation phase that runs <em>before</em> any code is generated. Plan mode turns vague briefs into well-scoped plans through guided dialogue, then runs the agentic pipeline against the approved plan as a high-fidelity design brief. It is the heaviest variant of the <a href="#checkpoints">checkpoint</a> flow &mdash; a full written plan you approve, rather than a single review card.</p>
963
1004
 
964
1005
  <p>Without plan mode, vibeSpot generates immediately on every chat message &mdash; fast, but unforgiving on vague prompts. The AI guesses at audience, content, and structure, and you find out what it guessed only after a full generation cycle. Plan mode flips the order: the AI asks targeted questions first, surfaces what's missing, and only generates after you've reviewed and approved a written plan. The plan is markdown, persisted to <code>{theme}/.vibespot/plan.md</code>, and reviewable in a dedicated tab in the right pane.</p>
965
1006
 
@@ -1010,6 +1051,11 @@ testimonial carousel, and a minimal footer with social links.</code></pre>
1010
1051
  </ul>
1011
1052
  <p>The plan persists across vibeSpot restarts. Open the same theme weeks later, toggle plan mode on, and your in-progress plan is right where you left it.</p>
1012
1053
 
1054
+ <div class="doc-callout doc-callout--info">
1055
+ <div class="doc-callout__label">&#128279; One primitive under the hood</div>
1056
+ <p>Plan approval now runs on the same park-and-re-enter machinery as the design and structure <a href="#checkpoints">checkpoints</a>. <strong>Approve plan</strong> and <strong>Discard &amp; start over</strong> resolve through the shared <code>checkpoint_resolve</code> path (approve &rarr; build, cancel &rarr; discard), so plan mode, the design gate, and the structure gate are three faces of one gating model rather than separate code paths. A plan-approved run builds straight through &mdash; it doesn't re-gate at the design or structure seams, because the plan already covered that deliberation.</p>
1057
+ </div>
1058
+
1013
1059
  <div class="doc-callout doc-callout--tip">
1014
1060
  <div class="doc-callout__label">&#10024; Plan mode + brand assets</div>
1015
1061
  <p>If your theme has a styleguide, brand voice, or product context loaded, the plan-mode AI uses them automatically. The plan will reference the right colors and tone without you re-stating them. After approval, the agentic pipeline gets the plan <em>and</em> the brand assets &mdash; double the alignment.</p>
@@ -1208,7 +1254,7 @@ testimonial carousel, and a minimal footer with social links.</code></pre>
1208
1254
  <li><strong>Span</strong> &mdash; one pipeline stage: intent analysis, design system, module planning, and module development.</li>
1209
1255
  <li><strong>Generation</strong> &mdash; one model call. The N parallel module calls (plus any retries) nest under the single module-development span.</li>
1210
1256
  </ul>
1211
- <p>A full page reads as <code>trace &rarr; stage spans &rarr; generations</code>, with token cost summing up the tree.</p>
1257
+ <p>A full page reads as <code>trace &rarr; stage spans &rarr; generations &rarr; output</code>, with token cost summing up the tree. The trace carries a compact <em>output</em> summary (the modules it produced), so the Traces list shows a result preview at a glance, and each generation links to the managed <em>prompt</em> version that produced it &mdash; so you can break cost, latency, and quality down per prompt version in Langfuse.</p>
1212
1258
 
1213
1259
  <h4 id="langfuse-instance">Get a Langfuse instance</h4>
1214
1260
  <p>You need somewhere to send the traces. Pick one: Langfuse's hosted cloud (fastest to start) or your own self-hosted install (your data stays on your infrastructure). Either way you end up with a <strong>public key</strong>, a <strong>secret key</strong>, and a <strong>host URL</strong> &mdash; the three things vibeSpot needs in the next step.</p>
@@ -1500,7 +1546,7 @@ docker cp vibespot:/home/vibespot/vibespot-themes ./backup-themes</code></pre>
1500
1546
  </ul>
1501
1547
  <div class="doc-callout doc-callout--tip">
1502
1548
  <div class="doc-callout__label">&#9888;&#65039; Single instance &amp; auth</div>
1503
- <p>vibeSpot keeps active sessions in memory &mdash; run a single instance (no horizontal scaling without sticky sessions). It runs as a non-root user and has <strong>no built-in authentication</strong>. For internet-facing deployments, put an authenticating proxy (OAuth2 Proxy, Authelia, Cloudflare Access) in front. Persistence is filesystem-only on the named volumes &mdash; there is no database to run.</p>
1549
+ <p>vibeSpot keeps active sessions in memory &mdash; run a single instance (no horizontal scaling without sticky sessions). It runs as a non-root user and has <strong>no built-in authentication</strong>. For internet-facing deployments, enable the bundled <strong>Azure Entra SSO gate</strong> (the <code>docker-compose.auth.yml</code> overlay) or put your own authenticating proxy (Authelia, Cloudflare Access) in front. Persistence is filesystem-only on the named volumes &mdash; there is no database to run.</p>
1504
1550
  </div>
1505
1551
  </div>
1506
1552
 
package/ui/index.html CHANGED
@@ -542,7 +542,10 @@
542
542
  <button class="chat__input-icon" id="btn-starter-templates" type="button" title="Templates" aria-label="Templates">
543
543
  <svg class="icon icon--sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>
544
544
  </button>
545
- <button class="chat__send" id="chat-send" type="button" title="Send (Enter)" aria-label="Send message">
545
+ <button class="chat__send chat__send--oneshot" id="chat-send-oneshot" type="button" title="One-shot — skip the design checkpoint and build the whole page in one go" aria-label="One-shot (skip checkpoints)">
546
+ <svg class="icon icon--sm" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M13 2L4.5 12.5h6L9 22l8.5-10.5h-6L13 2z"/></svg>
547
+ </button>
548
+ <button class="chat__send" id="chat-send" type="button" title="Send (Enter) — pauses at the design checkpoint" aria-label="Send message">
546
549
  <svg class="icon icon--md" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M3 12L21 4L13 22L11 14L3 12Z"/></svg>
547
550
  </button>
548
551
  </div>
package/ui/plan.js CHANGED
@@ -561,8 +561,9 @@
561
561
  }
562
562
  // Collapse plan sidebar so user watches modules generate.
563
563
  hidePlanView();
564
- // Server flips planMode off + runs the agentic pipeline.
565
- ws.send(JSON.stringify({ type: "plan_approve" }));
564
+ // Resolve the parked "plan" checkpoint plan mode is unified onto the
565
+ // checkpoint primitive (VIB-1880). Server flips planMode off + builds.
566
+ ws.send(JSON.stringify({ type: "checkpoint_resolve", kind: "plan", action: "approve" }));
566
567
  // Local state will be refreshed by the next init or modules_updated event.
567
568
  planModeActive = false;
568
569
  window.planModeActive = false;
@@ -573,7 +574,9 @@
573
574
  function discardPlan() {
574
575
  if (!confirm("Discard the current plan and exit plan mode?")) return;
575
576
  if (typeof ws !== "undefined" && ws && ws.readyState === 1) {
576
- ws.send(JSON.stringify({ type: "plan_discard" }));
577
+ // Cancel the parked "plan" checkpoint (VIB-1880); falls back to plan_discard
578
+ // semantics server-side. HTTP fallback below for a dropped socket.
579
+ ws.send(JSON.stringify({ type: "checkpoint_resolve", kind: "plan", action: "cancel" }));
577
580
  } else {
578
581
  // Fallback to HTTP
579
582
  fetch("/api/plan/discard", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" });
package/ui/styles.css CHANGED
@@ -8216,3 +8216,250 @@ body { display: flex; }
8216
8216
  display: none;
8217
8217
  }
8218
8218
  }
8219
+
8220
+ /* ---------------------------------------------------------------------------
8221
+ Checkpoint gate card (VIB-1877) — design preview before the module build.
8222
+ --------------------------------------------------------------------------- */
8223
+ .checkpoint {
8224
+ border: 1px solid var(--border-hover);
8225
+ border-radius: var(--radius);
8226
+ background: var(--bg-elevated);
8227
+ padding: var(--space-4);
8228
+ display: flex;
8229
+ flex-direction: column;
8230
+ gap: var(--space-3);
8231
+ max-width: 520px;
8232
+ }
8233
+ .checkpoint__head {
8234
+ display: flex;
8235
+ align-items: center;
8236
+ gap: var(--space-2);
8237
+ flex-wrap: wrap;
8238
+ }
8239
+ .checkpoint__badge {
8240
+ font-size: var(--text-xs);
8241
+ font-weight: 600;
8242
+ letter-spacing: 0.08em;
8243
+ text-transform: uppercase;
8244
+ color: var(--accent);
8245
+ background: var(--accent-dim);
8246
+ border-radius: var(--radius-pill);
8247
+ padding: 3px 9px;
8248
+ }
8249
+ .checkpoint__headline {
8250
+ font-family: var(--font-display);
8251
+ font-size: var(--text-md);
8252
+ color: var(--text);
8253
+ }
8254
+ .checkpoint__hero {
8255
+ border-radius: var(--radius-sm);
8256
+ overflow: hidden;
8257
+ border: 1px solid var(--border);
8258
+ background: #000;
8259
+ }
8260
+ .checkpoint__hero-frame {
8261
+ display: block;
8262
+ width: 100%;
8263
+ height: 230px;
8264
+ border: 0;
8265
+ }
8266
+ .checkpoint__tokens {
8267
+ display: flex;
8268
+ flex-wrap: wrap;
8269
+ gap: var(--space-4);
8270
+ align-items: flex-start;
8271
+ }
8272
+ .checkpoint__palette {
8273
+ display: flex;
8274
+ flex-wrap: wrap;
8275
+ gap: var(--space-2);
8276
+ flex: 1 1 220px;
8277
+ }
8278
+ .checkpoint__swatch {
8279
+ display: flex;
8280
+ align-items: center;
8281
+ gap: 6px;
8282
+ font-size: var(--text-sm);
8283
+ color: var(--text-dim);
8284
+ }
8285
+ .checkpoint__swatch-chip {
8286
+ width: 16px;
8287
+ height: 16px;
8288
+ border-radius: 4px;
8289
+ border: 1px solid var(--border-hover);
8290
+ display: inline-block;
8291
+ }
8292
+ .checkpoint__type {
8293
+ display: flex;
8294
+ flex-direction: column;
8295
+ gap: 2px;
8296
+ }
8297
+ .checkpoint__type-row {
8298
+ font-size: var(--text-lg);
8299
+ color: var(--text);
8300
+ line-height: 1.2;
8301
+ }
8302
+ .checkpoint__type-row--body {
8303
+ font-size: var(--text-base);
8304
+ color: var(--text-dim);
8305
+ }
8306
+ .checkpoint__muted { color: var(--text-dim); font-size: var(--text-sm); }
8307
+ .checkpoint__meta { font-size: var(--text-sm); color: var(--text-dim); }
8308
+ .checkpoint__cost { color: var(--text-dim); }
8309
+ .checkpoint__steer-input {
8310
+ width: 100%;
8311
+ resize: vertical;
8312
+ background: var(--bg-input);
8313
+ border: 1px solid var(--border-hover);
8314
+ border-radius: var(--radius-sm);
8315
+ color: var(--text);
8316
+ font-family: var(--font);
8317
+ font-size: var(--text-base);
8318
+ padding: var(--space-2);
8319
+ }
8320
+ .checkpoint__actions {
8321
+ display: flex;
8322
+ flex-wrap: wrap;
8323
+ gap: var(--space-2);
8324
+ }
8325
+ .checkpoint__btn { font-size: var(--text-base); }
8326
+ .checkpoint__btn:disabled { opacity: 0.5; cursor: default; }
8327
+
8328
+ /* Structure checkpoint (VIB-1879) — editable module outline. */
8329
+ .checkpoint__narrative {
8330
+ font-size: var(--text-sm);
8331
+ color: var(--text-dim);
8332
+ line-height: 1.5;
8333
+ }
8334
+ .checkpoint__outline {
8335
+ display: flex;
8336
+ flex-direction: column;
8337
+ gap: var(--space-2);
8338
+ }
8339
+ .checkpoint__row {
8340
+ display: flex;
8341
+ align-items: center;
8342
+ gap: var(--space-2);
8343
+ background: var(--bg-input);
8344
+ border: 1px solid var(--border);
8345
+ border-radius: var(--radius-sm);
8346
+ padding: 6px 8px;
8347
+ }
8348
+ .checkpoint__row-move {
8349
+ display: flex;
8350
+ flex-direction: column;
8351
+ gap: 1px;
8352
+ }
8353
+ .checkpoint__row-btn {
8354
+ border: 0;
8355
+ background: transparent;
8356
+ color: var(--text-dim);
8357
+ cursor: pointer;
8358
+ line-height: 1;
8359
+ font-size: 9px;
8360
+ padding: 1px 2px;
8361
+ border-radius: 3px;
8362
+ }
8363
+ .checkpoint__row-btn:hover { color: var(--text); background: var(--bg-elevated); }
8364
+ .checkpoint__row-main {
8365
+ display: flex;
8366
+ flex-direction: column;
8367
+ gap: 1px;
8368
+ flex: 1 1 auto;
8369
+ min-width: 0;
8370
+ }
8371
+ .checkpoint__row-name {
8372
+ background: transparent;
8373
+ border: 1px solid transparent;
8374
+ border-radius: var(--radius-sm);
8375
+ color: var(--text);
8376
+ font-family: var(--font-mono, monospace);
8377
+ font-size: var(--text-base);
8378
+ padding: 2px 4px;
8379
+ width: 100%;
8380
+ }
8381
+ .checkpoint__row-name:hover { border-color: var(--border-hover); }
8382
+ .checkpoint__row-name:focus {
8383
+ outline: none;
8384
+ border-color: var(--accent);
8385
+ background: var(--bg);
8386
+ }
8387
+ .checkpoint__row-desc {
8388
+ font-size: var(--text-xs);
8389
+ color: var(--text-dim);
8390
+ padding: 0 4px;
8391
+ overflow: hidden;
8392
+ text-overflow: ellipsis;
8393
+ white-space: nowrap;
8394
+ }
8395
+ .checkpoint__row-remove {
8396
+ border: 0;
8397
+ background: transparent;
8398
+ color: var(--text-dim);
8399
+ cursor: pointer;
8400
+ font-size: var(--text-base);
8401
+ padding: 2px 6px;
8402
+ border-radius: var(--radius-sm);
8403
+ flex: 0 0 auto;
8404
+ }
8405
+ .checkpoint__row-remove:hover { color: var(--danger, #e5484d); background: var(--bg-elevated); }
8406
+ .checkpoint__add {
8407
+ align-self: flex-start;
8408
+ font-size: var(--text-sm);
8409
+ }
8410
+
8411
+ /* One-shot send button — slightly dimmer than the primary send. */
8412
+ .chat__send--oneshot {
8413
+ opacity: 0.7;
8414
+ }
8415
+ .chat__send--oneshot:hover:not(:disabled) {
8416
+ opacity: 1;
8417
+ }
8418
+
8419
+ /* Brand-intake gate (VIB-1878) — front-of-flow ask-back card. */
8420
+ .brand-intake__choices {
8421
+ display: flex;
8422
+ flex-direction: column;
8423
+ gap: var(--space-2);
8424
+ margin: var(--space-3) 0;
8425
+ }
8426
+ .brand-intake__choice {
8427
+ display: flex;
8428
+ flex-direction: column;
8429
+ align-items: flex-start;
8430
+ gap: 2px;
8431
+ text-align: left;
8432
+ padding: var(--space-3);
8433
+ height: auto;
8434
+ }
8435
+ .brand-intake__choice-title { font-weight: 600; }
8436
+ .brand-intake__choice-hint { font-size: var(--text-sm); opacity: 0.75; }
8437
+ .brand-intake__form {
8438
+ display: flex;
8439
+ flex-direction: column;
8440
+ gap: var(--space-2);
8441
+ margin: var(--space-3) 0;
8442
+ }
8443
+ .brand-intake__field {
8444
+ display: flex;
8445
+ flex-direction: column;
8446
+ gap: 4px;
8447
+ }
8448
+ .brand-intake__label { font-size: var(--text-sm); font-weight: 600; }
8449
+ .brand-intake__input {
8450
+ width: 100%;
8451
+ font-family: inherit;
8452
+ font-size: var(--text-base);
8453
+ padding: var(--space-2);
8454
+ border: 1px solid var(--border);
8455
+ border-radius: var(--radius-sm);
8456
+ background: var(--surface);
8457
+ color: var(--text);
8458
+ resize: vertical;
8459
+ }
8460
+ .brand-intake__form-actions {
8461
+ display: flex;
8462
+ gap: var(--space-2);
8463
+ margin-top: var(--space-1);
8464
+ }
8465
+ .brand-intake__top-actions { margin-top: var(--space-2); }
Binary file