';
+ fs.writeFileSync(path.join(CONTENT_DIR, 'alpine-fragment.html'), fragment);
+ await sleep(300);
+
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
+ assert(res.body.includes('x-data="{ open: false }"'), 'Should preserve x-data');
+ assert(res.body.includes('@click="open = !open"'), 'Should preserve @click');
+ assert(res.body.includes('x-show="open"'), 'Should preserve x-show');
+ assert(res.body.includes('/vendor/alpine.js'), 'Should include Alpine script');
+ });
+```
+
+- [ ] **Step 2: Run the failing tests**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+node tests/brainstorm-server/server.test.js
+```
+
+Expected: FAIL because `/vendor/alpine.js` returns 404 and the frame does not include Alpine yet.
+
+- [ ] **Step 3: Implement exact vendor serving**
+
+In `skills/brainstorming/scripts/server.cjs`, add these constants after `helperInjection`:
+
+```js
+const ALPINE_VENDOR_PATH = path.join(__dirname, 'vendor', 'alpine.js');
+
+function loadVendorFile(filePath, name) {
+ try {
+ return fs.readFileSync(filePath);
+ } catch (error) {
+ throw new Error(
+ `Failed to load vendored ${name} at ${filePath}; ` +
+ 'run the refresh command in skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md. ' +
+ error.message
+ );
+ }
+}
+
+const VENDOR_FILES = new Map([
+ ['/vendor/alpine.js', {
+ content: loadVendorFile(ALPINE_VENDOR_PATH, 'Alpine'),
+ contentType: 'application/javascript; charset=utf-8'
+ }]
+]);
+```
+
+Add these helpers after `getNewestScreen()`:
+
+```js
+function parseRequestUrl(req) {
+ return new URL(req.url, 'http://localhost');
+}
+
+function serveVendorFile(requestUrl, res) {
+ const vendorFile = VENDOR_FILES.get(requestUrl.pathname);
+ if (!vendorFile) {
+ res.writeHead(404);
+ res.end('Not found');
+ return;
+ }
+
+ res.writeHead(200, { 'Content-Type': vendorFile.contentType });
+ res.end(vendorFile.content);
+}
+```
+
+Change the start of `handleRequest(req, res)` to parse once and use `pathname`:
+
+```js
+function handleRequest(req, res) {
+ touchActivity();
+ const requestUrl = parseRequestUrl(req);
+
+ if (req.method === 'GET' && requestUrl.pathname === '/') {
+```
+
+Add the vendor branch before `/files/`:
+
+```js
+ } else if (req.method === 'GET' && requestUrl.pathname.startsWith('/vendor/')) {
+ serveVendorFile(requestUrl, res);
+ } else if (req.method === 'GET' && requestUrl.pathname.startsWith('/files/')) {
+ const fileName = requestUrl.pathname.slice(7);
+```
+
+Keep the rest of the `/files/` branch unchanged except that it now uses `fileName` from `requestUrl.pathname`.
+
+- [ ] **Step 4: Inject Alpine from the frame template**
+
+In `skills/brainstorming/scripts/frame-template.html`, add this script tag immediately before ``:
+
+```html
+
+```
+
+Change the indicator copy to:
+
+```html
+ Interact with the mockup, then return to the terminal
+```
+
+- [ ] **Step 5: Run the server tests**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+node tests/brainstorm-server/server.test.js
+```
+
+Expected: `PASS` and `0 failed`.
+
+- [ ] **Step 6: Commit Tasks 1 and 2**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+git add \
+ skills/brainstorming/scripts/server.cjs \
+ skills/brainstorming/scripts/frame-template.html \
+ skills/brainstorming/scripts/vendor/alpine.js \
+ skills/brainstorming/scripts/vendor/alpine.provenance.json \
+ skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md \
+ tests/brainstorm-server/server.test.js
+git commit -m "feat: add Alpine to visual companion runtime"
+```
+
+## Task 3: Preserve Alpine Through Codex Plugin Sync
+
+**Files:**
+- Modify: `scripts/sync-to-codex-plugin.sh`
+- Modify: `tests/codex-plugin-sync/test-sync-to-codex-plugin.sh`
+
+- [ ] **Step 1: Add failing sync fixture coverage**
+
+In `write_upstream_fixture()`, extend the `mkdir -p` block with:
+
+```bash
+ "$repo/skills/brainstorming/scripts/vendor" \
+```
+
+After the example skill fixture, add:
+
+```bash
+ cat > "$repo/skills/brainstorming/scripts/server.cjs" <<'EOF'
+console.log('fixture server')
+EOF
+
+ cat > "$repo/skills/brainstorming/scripts/helper.js" <<'EOF'
+window.fixtureHelper = true
+EOF
+
+ cat > "$repo/skills/brainstorming/scripts/frame-template.html" <<'EOF'
+
+EOF
+
+ printf 'fixture alpine\n' > "$repo/skills/brainstorming/scripts/vendor/alpine.js"
+
+ cat > "$repo/skills/brainstorming/scripts/vendor/alpine.provenance.json" <<'EOF'
+{"name":"alpinejs","version":"3.15.12","localPath":"skills/brainstorming/scripts/vendor/alpine.js","sha256":"fixture","approvalArtifact":"SUP-215"}
+EOF
+
+ cat > "$repo/skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md" <<'EOF'
+# Third-Party Notices
+
+Alpine.js fixture notice.
+EOF
+```
+
+Add these paths to the `git -C "$repo" add` list:
+
+```bash
+ skills/brainstorming/scripts/server.cjs \
+ skills/brainstorming/scripts/helper.js \
+ skills/brainstorming/scripts/frame-template.html \
+ skills/brainstorming/scripts/vendor/alpine.js \
+ skills/brainstorming/scripts/vendor/alpine.provenance.json \
+ skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md \
+```
+
+In `write_synced_destination_fixture()`, extend the `mkdir -p` block with:
+
+```bash
+ "$repo/plugins/superpowers/skills/brainstorming/scripts/vendor" \
+```
+
+Add the same fixture files under `plugins/superpowers/skills/brainstorming/scripts/`, then add those paths to the destination `git add` list.
+
+Add these preview assertions after `Preview reflects dirty tracked destination file`:
+
+```bash
+ assert_contains "$preview_section" "skills/brainstorming/scripts/server.cjs" "Preview includes skill-local server runtime"
+ assert_contains "$preview_section" "skills/brainstorming/scripts/helper.js" "Preview includes skill-local helper runtime"
+ assert_contains "$preview_section" "skills/brainstorming/scripts/frame-template.html" "Preview includes skill-local frame template"
+ assert_contains "$preview_section" "skills/brainstorming/scripts/vendor/alpine.js" "Preview includes vendored Alpine"
+ assert_contains "$preview_section" "skills/brainstorming/scripts/vendor/alpine.provenance.json" "Preview includes Alpine provenance"
+ assert_contains "$preview_section" "skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md" "Preview includes Alpine notice"
+```
+
+Add these no-op fixture path variables near `noop_openai_metadata_path`:
+
+```bash
+ local noop_alpine_path
+ local noop_alpine_provenance_path
+ local noop_alpine_notice_path
+```
+
+Assign them after `noop_openai_metadata_path=...`:
+
+```bash
+ noop_alpine_path="$noop_apply_dest/plugins/superpowers/skills/brainstorming/scripts/vendor/alpine.js"
+ noop_alpine_provenance_path="$noop_apply_dest/plugins/superpowers/skills/brainstorming/scripts/vendor/alpine.provenance.json"
+ noop_alpine_notice_path="$noop_apply_dest/plugins/superpowers/skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md"
+```
+
+Add these no-op assertions after the OpenAI metadata assertion:
+
+```bash
+ assert_file_equals "$noop_alpine_path" "fixture alpine" "Clean no-op local apply preserves vendored Alpine"
+ assert_file_equals "$noop_alpine_provenance_path" "{\"name\":\"alpinejs\",\"version\":\"3.15.12\",\"localPath\":\"skills/brainstorming/scripts/vendor/alpine.js\",\"sha256\":\"fixture\",\"approvalArtifact\":\"SUP-215\"}" "Clean no-op local apply preserves Alpine provenance"
+ assert_contains "$(cat "$noop_alpine_notice_path")" "Alpine.js fixture notice." "Clean no-op local apply preserves Alpine notice"
+```
+
+Add this source assertion near the existing source assertions:
+
+```bash
+ assert_contains "$script_source" "Vendored third-party code included in this sync" "Source calls out vendored third-party code in sync PR body"
+```
+
+- [ ] **Step 2: Run the failing sync test**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh
+```
+
+Expected: FAIL on the source assertion because the sync PR body does not mention vendored third-party code yet.
+
+- [ ] **Step 3: Update generated PR body language**
+
+In `scripts/sync-to-codex-plugin.sh`, add this helper before
+`if [[ $BOOTSTRAP -eq 1 ]]; then` in the commit/PR section. Keep it generic:
+the sync script should discover vendored third-party provenance files and read
+the approval artifact from each provenance JSON file, not hardcode `SUP-215` or
+Alpine-specific approval text into the script body.
+
+```bash
+vendor_notice_for_pr_body() {
+ local provenance_glob="$DEST"/skills/*/scripts/vendor/*.provenance.json
+
+ if ! compgen -G "$provenance_glob" > /dev/null; then
+ return 0
+ fi
+
+ python3 - "$DEST" <<'PY'
+import glob
+import json
+import os
+import sys
+
+dest = sys.argv[1]
+provenance_files = sorted(glob.glob(os.path.join(dest, "skills", "*", "scripts", "vendor", "*.provenance.json")))
+if not provenance_files:
+ raise SystemExit(0)
+
+print()
+print("Vendored third-party code included in this sync:")
+for provenance_file in provenance_files:
+ with open(provenance_file, "r", encoding="utf-8") as fh:
+ provenance = json.load(fh)
+
+ rel_provenance = os.path.relpath(provenance_file, dest)
+ rel_vendor_dir = os.path.dirname(rel_provenance)
+ basename = os.path.basename(provenance_file).removesuffix(".provenance.json")
+ local_path = provenance.get("localPath") or os.path.join(rel_vendor_dir, f"{basename}.js")
+ notice_path = os.path.join(rel_vendor_dir, "THIRD_PARTY_NOTICES.md")
+ name = provenance.get("name", "unknown")
+ version = provenance.get("version", "unknown")
+ approval = provenance.get("approvalArtifact", "not recorded")
+ sha256 = provenance.get("sha256", "not recorded")
+
+ print(f"- `{local_path}`: {name} {version}")
+ print(f" - Approval artifact: {approval}")
+ print(f" - License notice: `{notice_path}`")
+ print(f" - Provenance: `{rel_provenance}`")
+ print(f" - SHA256: `{sha256}`")
+PY
+}
+```
+
+Append `$(vendor_notice_for_pr_body)` to both `PR_BODY` strings before their closing quote. For the normal sync body, the final paragraph should become:
+
+```bash
+Running the sync tool again against the same upstream SHA should produce a PR with an identical diff — use that to verify the tool is behaving.$(vendor_notice_for_pr_body)"
+```
+
+For the bootstrap body, the final paragraph should become:
+
+```bash
+This is a one-time bootstrap. Subsequent syncs will be normal (non-bootstrap) runs using the same tracked upstream plugin files.$(vendor_notice_for_pr_body)"
+```
+
+- [ ] **Step 4: Run the sync test**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+bash tests/codex-plugin-sync/test-sync-to-codex-plugin.sh
+```
+
+Expected: `PASS`.
+
+- [ ] **Step 5: Commit Task 3**
+
+Run:
+
+```bash
+cd /Users/drewritter/prime-rad/superpowers
+git add scripts/sync-to-codex-plugin.sh tests/codex-plugin-sync/test-sync-to-codex-plugin.sh
+git commit -m "test: cover Alpine in Codex plugin sync"
+```
+
+## Task 4: Update Visual Companion Guidance
+
+**Files:**
+- Modify: `skills/brainstorming/visual-companion.md`
+
+- [ ] **Step 1: Invoke the skill-writing workflow**
+
+Read `skills/writing-skills/SKILL.md` before editing `visual-companion.md`.
+
+- [ ] **Step 2: Update the selection-first copy**
+
+Change the `How It Works` paragraph to:
+
+```markdown
+The server watches a directory for HTML files and serves the newest one to the browser. You write HTML content to `screen_dir`, the user tries the mockup in their browser, and they respond in the terminal. Use `[data-choice]` only when you are deliberately asking the user to pick among named A/B/C visual options.
+```
+
+Change Loop step 2 to:
+
+```markdown
+2. **Tell user what to expect and end your turn:**
+ - Remind them of the URL (every step, not just first)
+ - Give a brief text summary of what's on screen (e.g., "Showing an interactive meal-planning mockup with tabs and an editable grocery list")
+ - Ask them to respond in the terminal: "Take a look, try the mockup, and tell me what feels right or wrong."
+ - If the screen is a deliberate A/B/C choice, also say: "Click an option if you'd like; your terminal feedback is still the source of truth."
+```
+
+- [ ] **Step 3: Add compact Alpine guidance before the current minimal example**
+
+Insert this section before `**Minimal example:**`:
+
+````markdown
+## Interactive Mockups With Alpine
+
+Frame-wrapped fragments automatically load Alpine.js. Use Alpine when visible interaction is central to the design question: tabs, toggles, accordions, modal open/close, wizard next/back, lightweight form validation, or simple add/remove list behavior.
+
+Keep it illustrative. Do not build a fake application just because realistic chrome includes many controls. If an interaction is not part of the question, render that area as passive content.
+
+```html
+
+
+
+
+
+
+
+
Week plan
+
Three realistic meals are enough for the mockup.
+
+
+
+
Grocery list
+
+
+
+
+
+
+
+
+
+```
+
+Rules:
+
+- Write content fragments by default; do not add an Alpine `` to
+ `frame-template.html`.
+- Keep the existing helper server-injected from `server.cjs` into every served
+ page, including waiting pages and full HTML documents.
+- Do not automatically inject Alpine into waiting pages or full HTML documents.
+ Full documents may include their own scripts, including `/vendor/alpine.js`,
+ when they need complete control.
+- Update the frame's default indicator copy from a selection-specific prompt to
+ neutral language such as "Interact with the mockup, then return to the
+ terminal." Preserve the helper's selected-choice update behavior when a
+ deliberate `[data-choice]` is clicked.
+
+Required runtime invariant:
+
+- By the time `DOMContentLoaded` fires for a served frame-wrapped fragment,
+ every `x-data` block in that fragment has been evaluated and `x-show` /
+ `@click` directives are bound.
+- The existing helper must still connect to the WebSocket server, reload on
+ screen changes, and capture deliberate `[data-choice]` clicks.
+- The helper must not depend on Alpine.
+
+Expected served fragment order:
+
+1. Page/frame HTML
+2. Alpine script with `defer`
+3. Existing helper injection
+
+Because `defer` changes execution order, the implementation should test the
+runtime behavior rather than only checking byte order in the served HTML.
+
+V1 guarantees automatic Alpine support only for normal frame-wrapped fragments.
+The common agent path should remain fragments; do not require robust
+full-document Alpine injection in SUP-215.
+
+### Codex Plugin Sync
+
+The root sync script already uses anchored root-level excludes, so `/scripts/`
+does not match nested skill-local paths like
+`skills/brainstorming/scripts/vendor/alpine.js`. SUP-215 should preserve that
+behavior rather than changing the exclusion model.
+
+The sync script does need one user-visible change: generated Codex plugin PR
+bodies should surface the vendored third-party code when the synced diff
+includes `skills/brainstorming/scripts/vendor/alpine.js`. The PR body should
+call out the approval artifact, license notice, and SHA256 provenance instead
+of presenting the sync as an opaque tracked-file copy.
+
+### Mockup Authoring Guidance
+
+Update `visual-companion.md` so agents treat Alpine as available by default.
+
+The key instruction:
+
+> If a visual mockup includes something that looks clickable, editable, or
+> selectable to a user, make it work only when that interaction is part of the
+> current design question. Otherwise, render it visibly as passive non-control
+> content or keep the behavior minimal and illustrative.
+
+The guide should lead with an Alpine-backed interactive mockup example before
+the existing selection-card examples. Existing `data-choice` examples should be
+kept but clearly labeled as deliberate A/B choice affordances, not normal UI
+controls.
+
+Keep the guide compact. It should include one concise Alpine example and a
+terse do/don't checklist, not a cookbook of separate snippets for every UI
+pattern.
+
+Common Alpine patterns the example or checklist may reference:
+
+- tabs and sidebar navigation
+- modal/dialog open and close
+- accordion expand/collapse
+- form input and lightweight validation
+- multi-step wizard navigation
+- toggle/switch state
+- simple list add/remove/edit behavior
+- toast or inline success feedback
+
+Controls that should work when they are central to the current visual question:
+
+- tabs and sidebar/nav items
+- buttons that imply state changes
+- toggles and switches
+- form fields and submit buttons
+- modal/dialog triggers
+- accordion headers
+- wizard next/back controls
+- add/edit/delete list actions
+
+Boundaries:
+
+These are authoring rules enforced by agent discipline, skill guidance, human
+review, and eval evidence. They are not enforced by the server, frame template,
+or vendored Alpine in V1. If runtime enforcement becomes necessary, that should
+be a follow-up hardening task, likely involving CSP and a revisit of the Alpine
+CSP build.
+
+- No fake backend calls.
+- No network requests.
+- No localStorage/sessionStorage persistence.
+- No complex application logic beyond what the mockup needs to communicate.
+- No interactivity that is not visually implied by the mockup.
+- Do not build full add/edit/delete/search/wizard behavior merely because those
+ controls appear in a realistic product screen. If the question is about visual
+ hierarchy, surrounding app chrome can be passive.
+- No script tags for Alpine; the frame provides it.
+- Do not put exploratory Alpine controls inside `[data-choice]` containers
+ unless the click is intended to select that choice. Use a separate choice
+ affordance or `@click.stop` where appropriate.
+- Replace existing network-positive guidance such as loading live Unsplash
+ images. If real images matter, use project-provided local assets through the
+ existing `/files/` route or choose a simple local placeholder.
+
+### Sample Data Policy
+
+Do not ship canned sample fixtures.
+
+When a mockup represents data, the agent should create 2-5 compact, realistic,
+domain-specific records. The records should match the product being discussed.
+A family meal-planning tool should not show generic SaaS users; a workshop
+scheduling app should show realistic sessions, facilitators, rooms, or dates.
+
+Put records in Alpine `x-data` only when interaction needs state, such as
+filtering, editing, adding, selecting, or stepping through records. If the data
+is only presentational, render it directly as HTML.
+
+This keeps mockups grounded in the user's idea and avoids every screen
+collapsing into the same dashboard template.
+
+### Feedback and Events
+
+V1 keeps the current feedback model unchanged.
+
+- The terminal remains the primary feedback channel.
+- Existing `[data-choice]` click capture remains supported.
+- Alpine interactions are for user understanding, not automatic telemetry.
+- Default guide and frame language should say "try/interact with the mockup,
+ then respond in the terminal," not "click an option" unless the screen is
+ explicitly asking for an A/B/C choice.
+- Use `data-choice` only when asking the user to choose among named options the
+ agent should read on the next turn.
+- Do not instrument ordinary tabs, forms, toggles, modals, or list interactions
+ as choice events.
+- Do not add broad interaction streaming in V1.
+- Do not ask agents to wire new `brainstorm.feedback(...)` calls in V1.
+
+This avoids expanding context with noisy interaction logs. The user can freely
+poke at a mockup, then tell the agent what worked or did not work.
+
+## V2 Follow-Up
+
+After dogfooding Alpine-backed mockups, revisit the old selection-oriented
+event model.
+
+Possible V2 direction:
+
+- Remove or de-emphasize the selection-specific helper code.
+- Replace it with a general ephemeral interaction stream file.
+- Keep that stream out of default context; agents should read it only when it is
+ useful.
+- Clear the stream when a new screen is pushed and/or when the server stops.
+
+Do not implement this in SUP-215. The point of V1 is to learn whether Alpine
+improves visual brainstorming before changing the feedback model.
+
+## Security and Trust Boundary
+
+Superpowers visual companion is not Brainstorm.
+
+Brainstorm renders user-generated artifacts inside a multi-user web
+application, so CSP and iframe sandboxing are product security boundaries.
+Superpowers runs a local helper server inside the user's coding harness. The
+server binds to `127.0.0.1` by default, and the user has already authorized the
+agent to write local files and run local commands.
+
+The relevant V1 guardrails are:
+
+- keep the default bind host as localhost-only
+- vendor Alpine instead of fetching it from a CDN at runtime
+- serve only known vendored files
+- prohibit network requests in generated mockups
+- prohibit storage-based persistence in generated mockups
+
+CSP and iframe sandboxing can be revisited if local usage reveals a concrete
+need.
+
+## Testing
+
+Extend the existing brainstorm server tests.
+
+Required coverage:
+
+- `/vendor/alpine.js` returns the vendored Alpine script with a JavaScript
+ content type.
+- `/vendor/alpine.js?v=` returns the same vendored script.
+- Unknown, nested, and traversal-ish vendor paths return 404, including encoded
+ traversal attempts.
+- Frame-wrapped fragments include the Alpine script automatically.
+- Existing helper injection still occurs.
+- Waiting pages and full HTML documents continue to receive helper injection
+ and do not receive automatic Alpine injection.
+- Existing `[data-choice]` click capture still writes `state/events`.
+- A fragment containing Alpine attributes is served without stripping or
+ escaping those attributes.
+- Vendored Alpine provenance verification recomputes the SHA256 and checks the
+ required metadata and notice files.
+
+Do not pretend the existing `tests/brainstorm-server/server.test.js` harness can
+prove Alpine runtime behavior. It is an HTTP/WebSocket test harness and does not
+execute browser DOM events or Alpine directives. Runtime behaviors such as
+`x-show`, `@click`, and `@click.stop` must be covered by a real browser test if
+one is added, or by manual dogfood evidence in the PR.
+
+Codex plugin sync coverage:
+
+- Update `tests/codex-plugin-sync/test-sync-to-codex-plugin.sh` so the fixture
+ includes the visual companion runtime files:
+ `skills/brainstorming/scripts/server.cjs`,
+ `skills/brainstorming/scripts/helper.js`,
+ `skills/brainstorming/scripts/frame-template.html`,
+ `skills/brainstorming/scripts/vendor/alpine.js`,
+ `skills/brainstorming/scripts/vendor/alpine.provenance.json`, and
+ `skills/brainstorming/scripts/vendor/THIRD_PARTY_NOTICES.md`.
+- Assert that dry-run preview includes those nested skill-local runtime files.
+- Assert that the no-op synced destination fixture contains those files, so the
+ test proves root `/scripts/` exclusion does not remove
+ `skills/brainstorming/scripts/`.
+- If a positive changed-apply fixture is added, assert that the applied
+ destination contains the vendored Alpine file and provenance files.
+- Update `scripts/sync-to-codex-plugin.sh` PR body generation so any downstream
+ Codex plugin PR carrying `skills/brainstorming/scripts/vendor/alpine.js`
+ explicitly calls out the vendored third-party code, approval artifact,
+ license notice, and SHA256 provenance.
+
+Skill behavior coverage:
+
+- Use `superpowers:writing-skills` for the `visual-companion.md` behavior
+ change.
+- Include adversarial pressure-test evidence in the implementation PR: initial
+ prompt, environment, eval count, observed output, and whether the output met
+ expectations.
+- Cover at least this matrix:
+ - Interactive mockup without `data-choice`: uses Alpine directives, omits an
+ Alpine script tag, includes compact domain-specific sample data when useful,
+ avoids backend/storage/network behavior, and asks the user to respond in the
+ terminal.
+ - Deliberate A/B choice: preserves `data-choice` for named options and keeps
+ the choice semantics clear.
+ - Static visual: uses no Alpine when interactivity is not useful.
+ - Busy dashboard or app shell: limits interactivity to the design question and
+ does not build a fake mini-application.
+ - Image-heavy mockup that previously might have used a live Unsplash URL: now
+ uses a `/files/` local asset or a local placeholder, with
+ before/after evidence for the guidance change.
+
+Manual dogfood check:
+
+1. Start the visual companion with `scripts/start-server.sh --project-dir`.
+2. Write a normal fragment that uses `x-data`, `@click`, and `x-show`.
+3. Open the local URL.
+4. Confirm Alpine initializes with no console errors.
+5. Confirm `@click` changes state and `x-show` toggles visibility.
+6. Confirm the interaction works without the agent adding an Alpine script tag.
+7. Confirm a nested Alpine control using `@click.stop` near a `[data-choice]`
+ surface does not produce an unintended extra choice event.
+8. Confirm the terminal remains the feedback path.
+
+If adding an automated browser dependency is too heavy for SUP-215, this
+browser proof can be manual PR evidence rather than a new test dependency.
+
+## Rollout
+
+V1 is an experiment, but it should still ship cleanly:
+
+- Keep changes contained to the brainstorming skill runtime, guide, and tests.
+- Do not change the visual companion startup flow.
+- Do not create a new mode in the user-facing language.
+- Describe the behavior as "interactive mockups" or "Alpine-backed mockups,"
+ not as a separate artifact/prototype system.
+- Include the maintainer-approved dependency exception and third-party
+ provenance in the PR.
+- Include real browser dogfood evidence that Alpine initializes and runs.
+- Include skill-behavior evidence that the updated guidance changes agent
+ output, not just server bytes.
+- Include the PR base in the review notes. The SUP-215 PR should show a focused
+ diff against its chosen base.
+- After dogfooding, decide whether SUP-215 should be followed by a V2 ticket
+ for event-stream cleanup.
diff --git a/scripts/sync-to-codex-plugin.sh b/scripts/sync-to-codex-plugin.sh
index 8c91b4cd..f4908365 100755
--- a/scripts/sync-to-codex-plugin.sh
+++ b/scripts/sync-to-codex-plugin.sh
@@ -415,6 +415,53 @@ fi
git add "$DEST_REL"
+vendor_notice_for_pr_body() {
+ local provenance_glob="$DEST"/skills/*/scripts/vendor/*.provenance.json
+
+ if ! compgen -G "$provenance_glob" > /dev/null; then
+ return 0
+ fi
+
+ command -v python3 >/dev/null || die "python3 not found in PATH"
+ python3 - "$DEST" <<'PY'
+import glob
+import json
+import os
+import sys
+
+dest = sys.argv[1]
+provenance_files = sorted(glob.glob(os.path.join(dest, "skills", "*", "scripts", "vendor", "*.provenance.json")))
+if not provenance_files:
+ raise SystemExit(0)
+
+print()
+print()
+print("Vendored third-party code included in this sync:")
+for provenance_file in provenance_files:
+ with open(provenance_file, "r", encoding="utf-8") as fh:
+ provenance = json.load(fh)
+
+ rel_provenance = os.path.relpath(provenance_file, dest)
+ rel_vendor_dir = os.path.dirname(rel_provenance)
+ basename = os.path.basename(provenance_file)
+ suffix = ".provenance.json"
+ if basename.endswith(suffix):
+ basename = basename[:-len(suffix)]
+ local_path = provenance.get("localPath") or os.path.join(rel_vendor_dir, f"{basename}.js")
+ notice_path = os.path.join(rel_vendor_dir, "THIRD_PARTY_NOTICES.md")
+ name = provenance.get("name", "unknown")
+ version = provenance.get("version", "unknown")
+ approval = provenance.get("approvalArtifact", "not recorded")
+ sha256 = provenance.get("sha256", "not recorded")
+
+ print(f"- `{local_path}`: {name} {version}")
+ print(f" - Approval artifact: {approval}")
+ print(f" - License notice: `{notice_path}`")
+ print(f" - Provenance: `{rel_provenance}`")
+ print(f" - SHA256: `{sha256}`")
+PY
+}
+
if [[ $BOOTSTRAP -eq 1 ]]; then
COMMIT_TITLE="bootstrap superpowers v$UPSTREAM_VERSION from upstream main @ $UPSTREAM_SHORT"
PR_BODY="Initial bootstrap of the superpowers plugin from upstream \`main\` @ \`$UPSTREAM_SHORT\` (v$UPSTREAM_VERSION).
@@ -424,7 +471,7 @@ Creates \`plugins/superpowers/\` by copying the tracked plugin files from upstre
Run via: \`scripts/sync-to-codex-plugin.sh --bootstrap\`
Upstream commit: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
-This is a one-time bootstrap. Subsequent syncs will be normal (non-bootstrap) runs using the same tracked upstream plugin files."
+This is a one-time bootstrap. Subsequent syncs will be normal (non-bootstrap) runs using the same tracked upstream plugin files.$(vendor_notice_for_pr_body)"
else
COMMIT_TITLE="sync superpowers v$UPSTREAM_VERSION from upstream main @ $UPSTREAM_SHORT"
PR_BODY="Automated sync from superpowers upstream \`main\` @ \`$UPSTREAM_SHORT\` (v$UPSTREAM_VERSION).
@@ -434,7 +481,7 @@ Copies the tracked plugin files from upstream, including the committed Codex man
Run via: \`scripts/sync-to-codex-plugin.sh\`
Upstream commit: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
-Running the sync tool again against the same upstream SHA should produce a PR with an identical diff — use that to verify the tool is behaving."
+Running the sync tool again against the same upstream SHA should produce a PR with an identical diff — use that to verify the tool is behaving.$(vendor_notice_for_pr_body)"
fi
git commit --quiet -m "$COMMIT_TITLE
diff --git a/skills/brainstorming/scripts/frame-template.html b/skills/brainstorming/scripts/frame-template.html
index 6325ef91..7a3c8ec8 100644
--- a/skills/brainstorming/scripts/frame-template.html
+++ b/skills/brainstorming/scripts/frame-template.html
@@ -193,6 +193,7 @@
.mock-button { background: var(--accent); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.85rem; }
.mock-input { background: var(--bg-primary); border: 1px solid var(--border); border-radius: 6px; padding: 0.5rem; width: 100%; }
+
@@ -207,7 +208,7 @@
- Click an option above, then return to the terminal
+ Interact with the mockup, then return to the terminal