From f2350cd478b04d271b510dcad2635241186f1f79 Mon Sep 17 00:00:00 2001 From: Nicole Haugen Date: Tue, 25 Aug 2026 13:07:41 -0500 Subject: [PATCH 01/18] Add live-CLI integration + fixture-drift tests for artifact adapter Complements artifact-cli.test.mjs (fixture round-trip) with three real-shape guards: * Live-CLI test invokes real `specify artifact list/info` on a scaffolded workspace and asserts wizard-contract fields (id/kind/stack, layer vocabulary, exactly-one-active). Skips when `specify` isn't on PATH. * Fixture-drift tests replay a committed snapshot of real CLI output and assert the field set the shape mapper reads is present. Guards against silent CLI shape changes without needing the binary in CI. * fixtures/README.md documents regeneration. Full suite: 211/211 pass. --- .../test/artifact-cli.integration.test.mjs | 145 +++++++ .../test/fixtures/README.md | 34 ++ .../test/fixtures/live-cli-info.json | 62 +++ .../test/fixtures/live-cli-list.json | 410 ++++++++++++++++++ 4 files changed, 651 insertions(+) create mode 100644 plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/artifact-cli.integration.test.mjs create mode 100644 plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/README.md create mode 100644 plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/live-cli-info.json create mode 100644 plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/live-cli-list.json diff --git a/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/artifact-cli.integration.test.mjs b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/artifact-cli.integration.test.mjs new file mode 100644 index 0000000..57f2361 --- /dev/null +++ b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/artifact-cli.integration.test.mjs @@ -0,0 +1,145 @@ +// Live-CLI integration tests for the artifact adapter. +// +// These tests invoke the real `specify` binary and assert the wizard-shape +// contract holds. They complement the fixture-based unit tests in +// artifact-cli.test.mjs by catching drift if the CLI's output shape changes +// underneath the wizard. +// +// Both tests skip cleanly when `specify` is not on PATH (CI without the CLI +// installed). Local dev and any CI job that installs the CLI will run them. + +import assert from "node:assert/strict"; +import { describe, test } from "node:test"; +import { execFileSync } from "node:child_process"; +import { mkdtempSync, rmSync, mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { buildCompositionFromCli } from "../composition/artifact-cli.mjs"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const FIXTURES_DIR = join(__dirname, "fixtures"); + +function hasSpecifyCli() { + try { + execFileSync("specify", ["--version"], { + shell: process.platform === "win32", + stdio: "ignore", + }); + return true; + } catch { + return false; + } +} + +const skipLive = !hasSpecifyCli(); + +describe("artifact-cli — live CLI", { skip: skipLive }, () => { + test("builds composition from real `specify artifact` output on a scaffolded workspace", async () => { + const root = mkdtempSync(join(tmpdir(), "speckit-live-")); + try { + mkdirSync(join(root, ".specify"), { recursive: true }); + writeFileSync(join(root, ".specify", "config.yml"), "version: 1\n"); + + const comp = await buildCompositionFromCli({ + workspaceRoot: root, + presetItems: [], + extensionItems: [], + }); + + assert.ok(Array.isArray(comp.artifacts)); + assert.ok(comp.artifacts.length > 0, "expected at least one built-in artifact"); + + for (const a of comp.artifacts) { + assert.ok(typeof a.id === "string" && a.id.length > 0, "id"); + assert.ok(["command", "template", "script", "hook"].includes(a.kind), `kind=${a.kind}`); + assert.ok(Array.isArray(a.stack) && a.stack.length > 0, "stack"); + for (const layer of a.stack) { + assert.ok( + ["core", "project", "preset", "extension"].includes(layer.layer), + `layer=${layer.layer}`, + ); + assert.equal(typeof layer.active, "boolean"); + assert.equal(typeof layer.hidden, "boolean"); + } + // CLI top-of-stack invariant: exactly one active per artifact. + assert.equal( + a.stack.filter((l) => l.active).length, + 1, + `expected exactly one active layer on ${a.id}`, + ); + } + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); +}); + +// Fixture drift detection — separate from the live test above so it can run +// even without `specify` on PATH, so long as a captured snapshot exists. +// Regenerate the snapshot with: +// specify artifact list --json > test/fixtures/live-cli-list.json +// specify artifact info --json (see capture note in that dir) +describe("artifact-cli — fixture drift", () => { + const listPath = join(FIXTURES_DIR, "live-cli-list.json"); + const infoPath = join(FIXTURES_DIR, "live-cli-info.json"); + const snapshotAvailable = existsSync(listPath) && existsSync(infoPath); + + test("captured list rows match the field set our shape mapper reads", { skip: !snapshotAvailable }, () => { + const rows = JSON.parse(readFileSync(listPath, "utf8")); + assert.ok(Array.isArray(rows) && rows.length > 0); + const requiredListFields = ["id", "name", "kind", "description"]; + for (const row of rows) { + for (const f of requiredListFields) { + assert.ok(Object.hasOwn(row, f), `list row ${row.id ?? "?"} missing field ${f}`); + } + assert.ok(["command", "template", "script"].includes(row.kind), `unexpected kind ${row.kind}`); + } + }); + + test("captured info rows carry the fields the wizard reads (id/name/kind/description/stack[])", { skip: !snapshotAvailable }, () => { + const infoMap = JSON.parse(readFileSync(infoPath, "utf8")); + const infoRows = Object.values(infoMap); + assert.ok(infoRows.length > 0, "expected at least one captured info row"); + + const requiredInfoFields = ["id", "name", "kind", "description", "stack"]; + const requiredStackFields = [ + "id", + "layer", + "sourceId", + "presetId", + "presetName", + "strategy", + "active", + "hidden", + "manifestPath", + "lookupId", + ]; + for (const info of infoRows) { + for (const f of requiredInfoFields) { + assert.ok(Object.hasOwn(info, f), `info ${info.id ?? "?"} missing field ${f}`); + } + assert.ok(Array.isArray(info.stack) && info.stack.length > 0); + for (const layer of info.stack) { + for (const f of requiredStackFields) { + assert.ok(Object.hasOwn(layer, f), `stack layer on ${info.id} missing field ${f}`); + } + // `layer` may be null (built-in) or a string. Guard vocabulary either way. + if (layer.layer !== null) { + assert.ok( + ["preset", "extension", "project"].includes(layer.layer), + `unexpected non-null layer value ${layer.layer}`, + ); + } + assert.equal(typeof layer.active, "boolean"); + assert.equal(typeof layer.hidden, "boolean"); + } + // Same top-of-stack invariant as the live test. + assert.equal( + info.stack.filter((l) => l.active).length, + 1, + `expected exactly one active layer on ${info.id}`, + ); + } + }); +}); diff --git a/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/README.md b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/README.md new file mode 100644 index 0000000..ada8976 --- /dev/null +++ b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/README.md @@ -0,0 +1,34 @@ +# Test fixtures + +## Live CLI snapshots + +`live-cli-list.json` and `live-cli-info.json` are point-in-time captures of the +real `specify artifact` CLI output. They're consumed by +`test/artifact-cli.integration.test.mjs`'s **fixture drift** suite to catch shape +regressions if the CLI's JSON contract changes. + +They are **not** consumed by the fixture-based unit tests in +`test/artifact-cli.test.mjs` — those tests use synthetic fixtures inline. + +### Regenerating + +Run from a workspace where `specify` is installed and a representative +composition is applied (any preset/extension mix works — the drift test only +checks field presence, not counts or content): + +```powershell +specify artifact list --json > test/fixtures/live-cli-list.json + +# For live-cli-info.json: capture one representative artifact of each kind +# (command, template, script) into a { "": } map. See the capture +# helper in .speckit-wizard/diffs/ or run manually: +$listObj = specify artifact list --json | ConvertFrom-Json +$sample = @{} +foreach ($kind in @("command","template","script")) { + $first = $listObj | Where-Object { $_.kind -eq $kind } | Select-Object -First 1 + if ($first) { + $sample[$first.id] = specify artifact info $first.id --json | ConvertFrom-Json + } +} +$sample | ConvertTo-Json -Depth 20 | Out-File test/fixtures/live-cli-info.json -Encoding utf8 +``` diff --git a/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/live-cli-info.json b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/live-cli-info.json new file mode 100644 index 0000000..fe68619 --- /dev/null +++ b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/live-cli-info.json @@ -0,0 +1,62 @@ +{ + "template:CHANGELOG": { + "description": "", + "id": "template:CHANGELOG", + "kind": "template", + "name": "CHANGELOG", + "stack": [ + { + "active": true, + "hidden": false, + "id": "template:CHANGELOG", + "layer": "preset", + "lookupId": "preset:screenwriting:template:CHANGELOG", + "manifestPath": ".specify/presets/screenwriting/preset.yml", + "presetId": "screenwriting", + "presetName": "Screenwriting", + "sourceId": "screenwriting", + "strategy": "replace" + } + ] + }, + "script:check-prerequisites": { + "description": "Consolidated prerequisite checking script", + "id": "script:check-prerequisites", + "kind": "script", + "name": "check-prerequisites", + "stack": [ + { + "active": true, + "hidden": false, + "id": "script:check-prerequisites", + "layer": null, + "lookupId": null, + "manifestPath": null, + "presetId": null, + "presetName": null, + "sourceId": null, + "strategy": "replace" + } + ] + }, + "command:speckit.agent-context.update": { + "description": "Refresh the managed Spec Kit section in the coding agent context file", + "id": "command:speckit.agent-context.update", + "kind": "command", + "name": "speckit.agent-context.update", + "stack": [ + { + "active": true, + "hidden": false, + "id": "command:speckit.agent-context.update", + "layer": "extension", + "lookupId": "extension:agent-context:command:speckit.agent-context.update", + "manifestPath": ".specify/extensions/agent-context/extension.yml", + "presetId": null, + "presetName": null, + "sourceId": "agent-context", + "strategy": "replace" + } + ] + } +} diff --git a/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/live-cli-list.json b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/live-cli-list.json new file mode 100644 index 0000000..c6eb225 --- /dev/null +++ b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/test/fixtures/live-cli-list.json @@ -0,0 +1,410 @@ +[ + { + "description": "Refresh the managed Spec Kit section in the coding agent context file", + "id": "command:speckit.agent-context.update", + "kind": "command", + "name": "speckit.agent-context.update" + }, + { + "description": "Analyze command — inspect the fleet fer consistency in pirate speak", + "id": "command:speckit.analyze", + "kind": "command", + "name": "speckit.analyze" + }, + { + "description": "Generate a professional writer bio from author information in the story bible (§ VIII Author Information). Two modes — short (50 words, query/pitch use) and long (150 words, press kit / website use). Writes bio to agent-file.md § VIII.", + "id": "command:speckit.bio", + "kind": "command", + "name": "speckit.bio" + }, + { + "description": "Interactive brainstorming session for any screenplay topic — spec, plan, characters, themes, world-building, locations, research, series, or timeline. Loads existing topic files and prior brainstorm notes as context, asks probing questions in a loop, and produces a brainstorm notes file, a patch to the topic file, or nothing if cancelled.", + "id": "command:speckit.brainstorm", + "kind": "command", + "name": "speckit.brainstorm" + }, + { + "description": "Checklist command — run a ship's inspection in pirate speak", + "id": "command:speckit.checklist", + "kind": "command", + "name": "speckit.checklist" + }, + { + "description": "Clarify command — interrogate the voyage manifest in pirate speak", + "id": "command:speckit.clarify", + "kind": "command", + "name": "speckit.clarify" + }, + { + "description": "Lean constitution - create or update project constitution", + "id": "command:speckit.constitution", + "kind": "command", + "name": "speckit.constitution" + }, + { + "description": "Post-draft continuity audit — story bible compliance in scenes, character state tracking, timeline coherence, location consistency, and world rule violations across all drafted Fountain files.", + "id": "command:speckit.continuity", + "kind": "command", + "name": "speckit.continuity" + }, + { + "description": "Assess the current codebase against the feature's spec, plan, and tasks, then append any remaining unbuilt work as new tasks to tasks.md so implement can complete it.", + "id": "command:speckit.converge", + "kind": "command", + "name": "speckit.converge" + }, + { + "description": "Generate self-coverage in industry format — logline, premise, structure, character, dialogue, and a Recommend/Consider/Pass verdict with rationale.", + "id": "command:speckit.coverage", + "kind": "command", + "name": "speckit.coverage" + }, + { + "description": "Export the screenplay to Fountain (assembled), Final Draft XML (.fdx), or PDF. Assembles all drafted scenes into a single file in scene order, generates a title page, and validates Fountain syntax before export.", + "id": "command:speckit.export", + "kind": "command", + "name": "speckit.export" + }, + { + "description": "Ingest script notes from any source — table read, development exec, coverage, or sensitivity reader — categorize by issue type, map to scene IDs, generate prioritized revision tasks in tasks.md. Closes the notes round as a proper workflow step.", + "id": "command:speckit.feedback", + "kind": "command", + "name": "speckit.feedback" + }, + { + "description": "Screenplay format compliance audit — slug line syntax, action line length, camera direction detection, parenthetical policy, transitions, title page, and Fountain export readiness.", + "id": "command:speckit.format", + "kind": "command", + "name": "speckit.format" + }, + { + "description": "Glossary management — add terms, resolve inconsistencies, update slug-line canonical forms, and audit all drafted scenes for terminology violations.", + "id": "command:speckit.glossary", + "kind": "command", + "name": "speckit.glossary" + }, + { + "description": "Workflow advisor — scans all project files to detect the current state of the screenplay and gives prioritized, opinionated recommendations for what to do next and why. Distinct from speckit.status (which reports numbers); this command reasons about the project and acts as a senior script editor guiding the session.", + "id": "command:speckit.help", + "kind": "command", + "name": "speckit.help" + }, + { + "description": "Lean implement - execute tasks from tasks.md", + "id": "command:speckit.implement", + "kind": "command", + "name": "speckit.implement" + }, + { + "description": "Interactive one-on-one conversation with an existing character, voiced by AI from their profile and known scene presence. Surfaces character psychology, subtext, and arc state through dialogue. Session can be exported as a summary note.", + "id": "command:speckit.interview", + "kind": "command", + "name": "speckit.interview" + }, + { + "description": "Generate or update editable scene outlines for author review before drafting — dramatic purpose, beat sequence, dialogue requirements, visual anchors, and story bible compliance.", + "id": "command:speckit.outline", + "kind": "command", + "name": "speckit.outline" + }, + { + "description": "Screenplay pacing analysis — emotional tension arc scoring, page count by act, scene length distribution, dialogue-to-action ratio, dead scenes, midpoint position, and act turn timing. Outputs a tension arc chart (Mermaid xychart) and a remediation task list.", + "id": "command:speckit.pacing", + "kind": "command", + "name": "speckit.pacing" + }, + { + "description": "Generate a pitch document from the screenplay brief and synopsis — logline, premise, tone/comps, character overview, story overview, writer's statement, and submission tracker.", + "id": "command:speckit.pitch", + "kind": "command", + "name": "speckit.pitch" + }, + { + "description": "Lean plan - create plan.md from the spec", + "id": "command:speckit.plan", + "kind": "command", + "name": "speckit.plan" + }, + { + "description": "Final line-pass on drafted scenes — action line compression, unfilmable removal, parenthetical audit, on-the-nose dialogue, voice consistency, and anti-pattern sweep.", + "id": "command:speckit.polish", + "kind": "command", + "name": "speckit.polish" + }, + { + "description": "Research tracking command for screenplay projects. Four modes — add (log a new research item), resolve (mark an item answered and capture the finding), check (scan drafted scenes for unsupported claims), and status (research dashboard showing open items ranked by story risk). Tracks authenticity flags for expert-visible errors.", + "id": "command:speckit.research", + "kind": "command", + "name": "speckit.research" + }, + { + "description": "Revise drafted scenes based on notes, checklist failures, continuity violations, or coverage feedback — structural rewrite, scene restructuring, and targeted fixes. Produces a versioned draft file with a diff summary.", + "id": "command:speckit.revise", + "kind": "command", + "name": "speckit.revise" + }, + { + "description": "Character voice testing and multi-role scene play-through. Modes — single character roleplay, two-character improv, voice-test (5 registers), play (multi-role beat-by-beat scene reading), and dialog (Dialog Workshop with Subtext Tracker). Captures insights and revised dialogue drafts as Fountain block comment revision notes.", + "id": "command:speckit.roleplay", + "kind": "command", + "name": "speckit.roleplay" + }, + { + "description": "Sensitivity and representation review — flags cultural misrepresentation, harmful tropes, and identity portrayal issues across drafted scenes. Also checks for broadcast and streaming platform content standard concerns. Read-only analysis with severity tiers (CRITICAL / WARNING / NOTE) and per-issue remediation guidance.", + "id": "command:speckit.sensitivity", + "kind": "command", + "name": "speckit.sensitivity" + }, + { + "description": "Series bible management — init the TV series bible before Episode 1, audit cross-episode continuity across all drafted episodes/seasons, sync the series bible after an episode is completed, and display a series-wide status dashboard. Operates on series/series-bible.md as the single authority for cross-episode canon.", + "id": "command:speckit.series", + "kind": "command", + "name": "speckit.series" + }, + { + "description": "Lean specify - create spec.md from a feature description", + "id": "command:speckit.specify", + "kind": "command", + "name": "speckit.specify" + }, + { + "description": "Script statistics — page count breakdown by act/scene/character; dialogue line counts; estimated screen time; action line voice signals (passive constructions, adverbs, filter language, weak verbs); dialogue/action balance with genre target bands; and export-ready metadata. Read-only.", + "id": "command:speckit.statistics", + "kind": "command", + "name": "speckit.statistics" + }, + { + "description": "Project dashboard — scan all drafted scenes, tasks, and checklists to produce a page-count table, status breakdown, and phase completion summary. Run at any time during the draft.", + "id": "command:speckit.status", + "kind": "command", + "name": "speckit.status" + }, + { + "description": "B-story and C-story management — add (register a new SP-NNN arc mid-draft), check (audit all subplot arcs for beat gaps, absence streaks, and unresolved dramatic questions), status (dashboard of arc health, draft coverage, and convergence load), and intersect (rebuild the Convergence Map from current plan.md and draft state). Works with subplots.md as the subplot authority; speckit.analyze and speckit.continuity both reference it.", + "id": "command:speckit.subplot", + "kind": "command", + "name": "speckit.subplot" + }, + { + "description": "Generate a submission-ready synopsis from the screenplay — one-page and full act-by-act for features; pilot synopsis and season arc overview for TV.", + "id": "command:speckit.synopsis", + "kind": "command", + "name": "speckit.synopsis" + }, + { + "description": "Lean tasks - create tasks.md from plan and spec", + "id": "command:speckit.tasks", + "kind": "command", + "name": "speckit.tasks" + }, + { + "description": "Tasks-to-issues command — file crew assignments at the GitHub port in pirate speak", + "id": "command:speckit.taskstoissues", + "kind": "command", + "name": "speckit.taskstoissues" + }, + { + "description": "Version management for screenplay drafts — list versions, compare two versions side by side (dialogue and action lines), restore a previous version, and tag the current version as a milestone.", + "id": "command:speckit.versions", + "kind": "command", + "name": "speckit.versions" + }, + { + "description": "", + "id": "template:CHANGELOG", + "kind": "template", + "name": "CHANGELOG" + }, + { + "description": "Agent file template written in pirate speak", + "id": "template:agent-file-template", + "kind": "template", + "name": "agent-file-template" + }, + { + "description": "15-point Save the Cat beat sheet with page targets and emotional-state notes, plus an 8-point Hauge alternative. Records the structural skeleton and emotional journey of the screenplay.", + "id": "template:beat-sheet-template", + "kind": "template", + "name": "beat-sheet-template" + }, + { + "description": "Character index: master roster with role, A/B/C-story function, first appearance, episode count (TV), and link to full character profile.", + "id": "template:characters-index-template", + "kind": "template", + "name": "characters-index-template" + }, + { + "description": "Screen character profile: psychological core, dialogue style with sample lines, on-screen physicality, casting note, scene entry behavior, B/C-story function, relationship dynamics, and writer summary.", + "id": "template:characters-template", + "kind": "template", + "name": "characters-template" + }, + { + "description": "Checklist template written in pirate speak", + "id": "template:checklist-template", + "kind": "template", + "name": "checklist-template" + }, + { + "description": "The Pirate Code — project constitution in pirate speak", + "id": "template:constitution-template", + "kind": "template", + "name": "constitution-template" + }, + { + "description": "Source template for .specify/memory/craft-rules.md, generated by speckit.constitution. Contains the universal screenwriting craft ruleset (format, scene, dialogue, structure) plus selectable style blocks.", + "id": "template:craft-rules-template", + "kind": "template", + "name": "craft-rules-template" + }, + { + "description": "TV episode outline: cold open, teaser, per-act breakdown with turning points, A/B/C-story beats per act, tag, and page-count targets. Standard writers' room document.", + "id": "template:episode-outline-template", + "kind": "template", + "name": "episode-outline-template" + }, + { + "description": "Script notes log: raw notes from table reads, development executives, or script readers. Categorized issues (Structure/Character/Pacing/Dialogue/Format) with severity, revision tasks, and resolution log.", + "id": "template:feedback-template", + "kind": "template", + "name": "feedback-template" + }, + { + "description": "Consistency reference: invented terms, proper nouns, world-specific capitalization rules, and a consistency log populated by speckit.polish and speckit.continuity.", + "id": "template:glossary-template", + "kind": "template", + "name": "glossary-template" + }, + { + "description": "Canonical location reference: per-location visual/atmospheric identity, time-of-day variations, character behavioral tells, production notes, and state log.", + "id": "template:locations-template", + "kind": "template", + "name": "locations-template" + }, + { + "description": "Screenplay pitch document: logline, series or film premise, tone and comp titles, character roster, pilot synopsis (TV) or act summary (feature), season arc (TV), sample episode titles, writer's statement, and submission tracker.", + "id": "template:pitch-deck-template", + "kind": "template", + "name": "pitch-deck-template" + }, + { + "description": "Implementation plan template written in pirate speak", + "id": "template:plan-template", + "kind": "template", + "name": "plan-template" + }, + { + "description": "Character relationship arcs: power balance tracker, communication pattern, arc beat sheet (establishing → rupture → midpoint → crisis → resolution), and A-story convergence map.", + "id": "template:relationships-template", + "kind": "template", + "name": "relationships-template" + }, + { + "description": "Research document: open questions, source notes, historical/technical findings, and resolved items that drive task generation.", + "id": "template:research-template", + "kind": "template", + "name": "research-template" + }, + { + "description": "Index-card-style scene breakdown: slug line, dramatic purpose, characters present, A/B/C-story function, props/locations needed, emotional shift, page range, and draft status.", + "id": "template:scene-card-template", + "kind": "template", + "name": "scene-card-template" + }, + { + "description": "Per-scene outline file: opening beat, causal beat sequence, character wants/gets, dialogue requirements (what must be deflected), visual/sensory anchors, thematic work, and story bible compliance notes. Status field (DRAFT/APPROVED/SKIP) gates AI drafting in speckit.implement.", + "id": "template:scene-outline-template", + "kind": "template", + "name": "scene-outline-template" + }, + { + "description": "TV series bible: series premise and engine, tone and format, character roster with arc by season, season arc map, episode format, world rules, network/platform target, and continuity constraint log.", + "id": "template:series-bible-template", + "kind": "template", + "name": "series-bible-template" + }, + { + "description": "Feature specification template written in pirate speak", + "id": "template:spec-template", + "kind": "template", + "name": "spec-template" + }, + { + "description": "B-story and C-story beat sheets: inciting incident through resolution, A-story intersection map, convergence map for multi-thread acts, and resolution checklist.", + "id": "template:subplots-template", + "kind": "template", + "name": "subplots-template" + }, + { + "description": "One-page pitch synopsis (250–350 words) and full synopsis (600–1000 words) in present tense. Feature: act-by-act summary, ending revealed. TV: pilot summary plus season arc overview.", + "id": "template:synopsis-template", + "kind": "template", + "name": "synopsis-template" + }, + { + "description": "Task list template written in pirate speak", + "id": "template:tasks-template", + "kind": "template", + "name": "tasks-template" + }, + { + "description": "Thematic contract: motif registry with planned visual occurrences and transformation arc, symbol tracker, act-by-act thematic work map, and drift log.", + "id": "template:themes-template", + "kind": "template", + "name": "themes-template" + }, + { + "description": "Story timeline: scene-by-scene chronology, elapsed time, flashback/flash-forward map, and continuity cross-references.", + "id": "template:timeline-template", + "kind": "template", + "name": "timeline-template" + }, + { + "description": "", + "id": "template:tutorial-script-template", + "kind": "template", + "name": "tutorial-script-template" + }, + { + "description": "World-building reference: setting rules, geography, culture, history, and in-world systems used during drafting.", + "id": "template:world-building-template", + "kind": "template", + "name": "world-building-template" + }, + { + "description": "Consolidated prerequisite checking script", + "id": "script:check-prerequisites", + "kind": "script", + "name": "check-prerequisites" + }, + { + "description": "Common functions and variables for all scripts", + "id": "script:common", + "kind": "script", + "name": "common" + }, + { + "description": "", + "id": "script:create-new-feature", + "kind": "script", + "name": "create-new-feature" + }, + { + "description": "", + "id": "script:resolve-template", + "kind": "script", + "name": "resolve-template" + }, + { + "description": "", + "id": "script:setup-plan", + "kind": "script", + "name": "setup-plan" + }, + { + "description": "", + "id": "script:setup-tasks", + "kind": "script", + "name": "setup-tasks" + } +] From 0a62ac8518438fa688239f807dd8ac11744c393d Mon Sep 17 00:00:00 2001 From: Nicole Haugen Date: Tue, 25 Aug 2026 13:39:56 -0500 Subject: [PATCH 02/18] Fix blank flash on wizard first-open (boot overlay race) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a warm cache the server's `bootAsync` reaches `phase: ready` before the browser's first paint. The old flow relied on JS to (a) populate the overlay content and (b) hide `main.app-body`, then almost immediately flipped the overlay to `is-hidden` in the same microtask cycle. The browser composited populate + hide into one frame and the user saw a blank body flip straight to the loaded app with no boot indicator. Three-part fix so the overlay is guaranteed to paint: * Static markup in `index.html` — pre-render the overlay panel with title + subtitle so it is visible from the very first paint, before any module fetch/parse. * CSS-level `main.app-body { visibility: hidden }` — no longer depends on JS running to keep the app body hidden underneath. * JS-side minimum visible time (`MIN_OVERLAY_MS = 450`) — even when the state fetch resolves in a single frame, the hide is deferred via `setTimeout` so the overlay stays up long enough to register. --- .../speckit-wizard-canvas/ui/boot.js | 32 +++++++++++++++++-- .../speckit-wizard-canvas/ui/index.html | 11 +++++-- .../speckit-wizard-canvas/ui/styles/boot.css | 8 +++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/boot.js b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/boot.js index 0197282..08784be 100644 --- a/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/boot.js +++ b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/boot.js @@ -39,6 +39,18 @@ let __bannerDismissedFor = null; // want to try the wizard anyway). Keyed by timestamp so a fresh failure // re-freezes the boot dialog instead of silently reusing this decision. let __continueAnywayFor = null; +// Timestamp (performance.now) at which the overlay first painted. Used to +// enforce a minimum visible time so a very-fast boot doesn't skip the +// overlay entirely — on a warm cache the `/api/state` fetch returns +// `boot.phase === "ready"` within a single paint frame, and without this +// guard the browser composites overlay-populated + overlay-hidden into +// one frame and the user sees a blank body flip straight to the app. +let __overlayShownAt = 0; +let __minVisibleTimer = null; +// Minimum time the overlay stays visible once first rendered. Long enough +// for the user to register that boot is happening; short enough not to +// feel like padding. +const MIN_OVERLAY_MS = 450; // Runtime dependencies the extension needs to fully function. Surfaced in // the in-wizard banner as a copy/paste-friendly install command. Keep in @@ -51,6 +63,7 @@ export function installBootOverlay({ token }) { if (!__root) return { handleBootMessage: () => {}, setInitialSnapshot: () => {} }; __appRootEl = document.querySelector("main.app-body"); if (__appRootEl) __appRootEl.style.visibility = "hidden"; + __overlayShownAt = performance.now(); render(); return { handleBootMessage, @@ -98,6 +111,21 @@ function render() { const shouldHideOverlay = bypassed || (__state?.phase === "ready" && !__depsError); if (shouldHideOverlay) { + // Enforce a minimum visible time. Without this, a warm-cache boot + // completes before the browser has a chance to paint the overlay + // content at all — the user sees a blank body flip straight to the + // loaded app with no boot indicator. See comment on + // MIN_OVERLAY_MS. + const elapsed = performance.now() - __overlayShownAt; + if (elapsed < MIN_OVERLAY_MS) { + if (!__minVisibleTimer) { + __minVisibleTimer = setTimeout(() => { + __minVisibleTimer = null; + render(); + }, MIN_OVERLAY_MS - elapsed); + } + return; + } if (!__root.classList.contains("is-hidden")) { __root.classList.add("is-hidden"); setTimeout(() => { @@ -108,7 +136,7 @@ function render() { const stillReady = __state?.phase === "ready" && !__depsError; if (__root && (stillBypassed || stillReady)) { __root.style.display = "none"; - if (__appRootEl) __appRootEl.style.visibility = ""; + if (__appRootEl) __appRootEl.style.visibility = "visible"; } }, 320); } @@ -128,7 +156,7 @@ function render() { const title = document.createElement("h1"); title.className = "boot-title"; - title.innerHTML = 'Starting Spec Kit Wizard'; + title.innerHTML = 'Starting Spec Kit Wizard - Dev'; panel.appendChild(title); const subtitle = document.createElement("p"); diff --git a/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/index.html b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/index.html index 442b2ae..40b8e23 100644 --- a/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/index.html +++ b/plugins/spec-kit-copilot-wizard/extensions/speckit-wizard-canvas/ui/index.html @@ -2,7 +2,7 @@ - Spec Kit Wizard + Spec Kit Wizard - Dev @@ -15,11 +15,16 @@ -
+
+
+

Starting Spec Kit Wizard - Dev

+

Preparing your project…

+
+
- Spec Kit Wizard + Spec Kit Wizard - Dev