---
phase: 03-public-launch
plan: 01
subsystem: video-homepage-connect
tags: [dog-02, dog-03, homepage, connect, flagship-video, launch]
type: execute
wave: 1
depends_on: []
files_modified:
  - app/components/VideoHero.js
  - app/page.js
  - app/connect/page.js
  - app/connect/ConnectGuideClient.js
  - app/guides/claude-code/page.js
  - README.md
  - next.config.js
  - __tests__/unit/homepage-hero.test.jsx
  - __tests__/unit/homepage-rejected-framings.test.jsx
  - __tests__/unit/connect-runbook.test.jsx
  - __tests__/unit/video-hero.test.jsx
  - scripts/check-screencast-backfilled.mjs
autonomous: false
requirements: [DOG-02, DOG-03]
nyquist_compliant: true
wave_0_complete: true

user_setup:
  - service: loom-or-youtube
    why: "Host the ≤3-minute flagship demo video on an incognito-accessible surface (D-10)"
    env_vars: []
    dashboard_config:
      - task: "Record walkthrough per .planning/phases/02-claude-code-beachhead/02-01-SUMMARY.md §5 runbook (Discord bot setup + 9-segment recording recipe)"
        location: "Loom (set visibility to Public — NOT default 'anyone with link') OR YouTube (set visibility to Unlisted)"
      - task: "Verify video URL loads in incognito browser from a different IP (phone hotspot) with no captcha, no workspace-auth wall"
        location: "Chrome / Safari incognito + mobile browser"

must_haves:
  truths:
    - "A visitor loading dashclaw.io sees a ≤3-minute flagship demo video embedded above the fold in the hero"
    - "The hero headline mentions Claude Code and speaks in terse-technical voice (D-12)"
    - "None of the four rejected framings (homelab, enterprise compliance, generic AI governance, multi-agent platform) appear in the rendered hero HTML (D-13)"
    - "The hero CTA order is Watch demo → Install → Star on GitHub (D-14)"
    - "A visitor loading /connect sees a single-page copy-paste runbook with no multi-step wizard (D-15)"
    - "/connect still provides inline workspace-token generation (HostedProvision surfaces intact)"
    - "All 4 <SCREENCAST_URL> placeholders (2 raw in README.md + 1 raw + 1 HTML-entity-encoded in app/guides/claude-code/page.js) are backfilled with the published video URL"
    - "Browser does NOT log CSP violations when loading the hero video iframe (frame-src permits loom.com + youtube-nocookie.com)"
  artifacts:
    - path: "app/components/VideoHero.js"
      provides: "Loom/YouTube iframe embed with URL-host allowlist"
      min_lines: 20
      exports: ["default"]
    - path: "app/page.js"
      provides: "Rewritten hero — Claude Code lead, video embed, CTA order Watch→Install→Star"
      contains: "VideoHero"
    - path: "app/connect/page.js"
      provides: "Single-page copy-paste runbook (D-15) preserving HostedProvisionSection"
    - path: "next.config.js"
      provides: "CSP frame-src directive permitting loom.com + youtube-nocookie.com"
      contains: "frame-src"
    - path: "__tests__/unit/homepage-hero.test.jsx"
      provides: "Hero presence tests — video iframe, Claude Code headline, CTA order"
    - path: "__tests__/unit/homepage-rejected-framings.test.jsx"
      provides: "Negative assertions — no homelab/enterprise/compliance/control-plane/any-agent-framework in hero"
    - path: "__tests__/unit/connect-runbook.test.jsx"
      provides: "Single-page runbook structure — no multi-step state machine in rendered /connect"
    - path: "__tests__/unit/video-hero.test.jsx"
      provides: "VideoHero allowlist — Loom + youtube-nocookie accepted, other hosts throw"
    - path: "scripts/check-screencast-backfilled.mjs"
      provides: "Script asserting zero <SCREENCAST_URL> placeholders remain (raw + entity-encoded forms)"
  key_links:
    - from: "app/page.js"
      to: "app/components/VideoHero.js"
      via: "import + render"
      pattern: "import.*VideoHero|<VideoHero"
    - from: "app/page.js"
      to: "/connect"
      via: "Install CTA href"
      pattern: "href=\"/connect\""
    - from: "app/page.js"
      to: "github.com/ucsandman/DashClaw"
      via: "Star CTA href"
      pattern: "github.com.*DashClaw"
    - from: "next.config.js"
      to: "loom.com + youtube-nocookie.com"
      via: "CSP frame-src directive"
      pattern: "frame-src.*(loom|youtube-nocookie)"
---

<objective>
Ship the flagship ≤3-minute demo video, rewrite the homepage hero around the Claude Code beachhead, rewrite `/connect` as a single-page copy-paste runbook, and fix the CSP gap that currently blocks iframe embeds. This is the asset bundle for Phase 3 — three surfaces sharing the same voice and the same video URL.

**Purpose:** DOG-02 + DOG-03 are the atomic deliverable for the launch. The homepage is built around the video; `/connect` is the next surface a convinced visitor lands on. All three must go live together and incognito-verify before 03-02 publishes the Show HN post (hard ordering constraint — HN URL-change after submission is rank-killing per Pitfall 1).

**Output:**
- New `app/components/VideoHero.js` iframe wrapper with allowlist
- Rewritten `app/page.js` hero (Claude Code lead, video embed, CTA order Watch→Install→Star, rejected framings removed)
- Rewritten `app/connect/page.js` as a single-page runbook (HostedProvision preserved; ConnectGuideClient trimmed or inlined)
- `next.config.js` CSP `frame-src` directive permitting Loom + YouTube
- 4 `<SCREENCAST_URL>` placeholders backfilled (2 in README.md, 1 raw + 1 HTML-entity-encoded in app/guides/claude-code/page.js)
- Wave-0 test scaffolds (hero presence + rejected framings negative assertions + connect single-page + VideoHero allowlist)
- `scripts/check-screencast-backfilled.mjs` guardrail
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/STATE.md
@.planning/phases/03-public-launch/03-CONTEXT.md
@.planning/phases/03-public-launch/03-RESEARCH.md
@.planning/phases/03-public-launch/03-VALIDATION.md
@.planning/phases/02-claude-code-beachhead/02-01-SUMMARY.md
@.planning/phases/02-claude-code-beachhead/02-03-SUMMARY.md
@CLAUDE.md
@.impeccable.md
@docs/homepage-draft-claude-code.md
@README.md
@app/page.js
@app/connect/page.js
@app/connect/ConnectGuideClient.js
@app/connect/HostedProvisionSection.js
@app/connect/HostedProvisionClient.jsx
@app/guides/claude-code/page.js
@next.config.js
@app/globals.css

<interfaces>
<!-- Contracts executors use without exploring the codebase -->

From app/connect/HostedProvisionSection.js (KEEP — D-15 wants inline workspace token):
```javascript
// default export — server component rendering workspace-token provisioning UI.
export default function HostedProvisionSection(): JSX.Element
```

From app/connect/HostedProvisionClient.jsx (KEEP — client island for token reveal):
```javascript
// default export — client component, 'use client', accepts provisioning state
export default function HostedProvisionClient(props): JSX.Element
```

From app/connect/ConnectGuideClient.js (247 lines — TRIM heavily or INLINE):
```javascript
// Currently renders wizard-adjacent step-by-step guide. D-15 says single-page runbook.
// Keep the content (command + token paste + Discord 3-step), flatten the wizard framing.
export default function ConnectGuideClient({ content }: { content: ConnectGuideContent }): JSX.Element
```

From scripts/check-readme-lead.mjs (EXISTING gate — must continue to pass):
```javascript
// Asserts README first 50 lines contain "Claude Code" + "/guides/claude-code" link.
// Plan 03-01 preserves both (README lead was shipped in 02-03).
```

From .env.example (already documents 5 Discord vars — no new env needed for Plan 03-01):
```bash
DISCORD_BOT_TOKEN=
DISCORD_APPROVER_ORG_ID=
DISCORD_PUBLIC_KEY=
DISCORD_APPROVER_USER_ID=
DISCORD_APPLICATION_ID=
```

Embed URL shapes (from RESEARCH Pattern 2):
- Loom: https://www.loom.com/embed/<VIDEO_ID>  (NOT /share/)
- YouTube: https://www.youtube-nocookie.com/embed/<VIDEO_ID>  (privacy-enhanced)

CSP directive to ADD to next.config.js csp array (after "frame-ancestors 'none'"):
```javascript
"frame-src 'self' https://www.loom.com https://www.youtube-nocookie.com"
```

Placeholder locations (verified 2026-04-22):
- README.md:8 — <a href="<SCREENCAST_URL>">...demo-gif2.gif...</a>
- README.md:19 — <a href="<SCREENCAST_URL>">Watch the 3-min walkthrough</a>
- app/guides/claude-code/page.js:104 — note: 'Screencast: <SCREENCAST_URL>'
- app/guides/claude-code/page.js:249 — HTML-entity: `&lt;SCREENCAST_URL&gt;`
</interfaces>

<design_context>
Per .impeccable.md (HARD GATE for any UI change):
- Brand orange = SIGNAL ONLY (hero decoration violates tiebreaker #2 — no orange on the hero frame itself; video iframe border uses border-border-hover tokens)
- NEVER hardcode hex — use CSS tokens from app/globals.css exclusively
- Pre-existing `bg-[#0a0a0a]` at app/guides/claude-code/page.js:204 is INFO-level carryover from commit 936a2030 — do NOT touch (surgical-change rule per Pitfall 7)
- Developer-reader first (tiebreaker #7) — homepage copy reads like README lead, not marketing
- WCAG 2.1 AA floor
- Four anti-references guardrail (no marketing-SaaS gradient-glow vibes)

Hero copy voice (D-12 locked):
- Terse + technical
- ≤8 words in headline
- Sample direction: "The approval layer for Claude Code." / "Your coding agent, with a pause button."
- Final wording at executor's discretion; voice + structural rules + negative assertions are locked
</design_context>

<assumption_resolution>
**A1 RESOLVED:** Blog location is this monorepo. Verified: no `app/blog/` directory exists yet; no `../dashclaw-website` sibling repo exists. Plan 03-02 creates `app/blog/claude-code-beachhead/page.js` as a simple segment route (follows `app/guides/claude-code/page.js` SSR pattern — no MDX dependency). Plan 03-01 does NOT touch blog.

**Scope note:** 03-01 is purely video + homepage + /connect + CSP. Blog post is 03-02. `/pricing`, `requireTier`, and monetization counter are 03-03.
</assumption_resolution>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Wave-0 test scaffolds + VideoHero component + CSP frame-src + placeholder-backfill script (RED)</name>
  <files>app/components/VideoHero.js, next.config.js, scripts/check-screencast-backfilled.mjs, __tests__/unit/homepage-hero.test.jsx, __tests__/unit/homepage-rejected-framings.test.jsx, __tests__/unit/connect-runbook.test.jsx, __tests__/unit/video-hero.test.jsx</files>
  <read_first>
    - `.planning/phases/03-public-launch/03-RESEARCH.md` §Pattern 2 (VideoHero code example), §Pattern 3 (hero structure), §Pitfall 3 (CSP frame-src), §Validation Architecture (Wave 0 gaps)
    - `.planning/phases/03-public-launch/03-CONTEXT.md` decisions D-09 through D-15 (hero + CTA + runbook locks)
    - `.impeccable.md` (token-first, brand orange as signal)
    - `app/page.js` lines 1-150 (existing hero structure to replace)
    - `app/connect/page.js` full file (structure to preserve HostedProvisionSection around)
    - `app/components/HeroScreenshot.js` (existing hero-adjacent component pattern)
    - `__tests__/unit/connect-discoverability.test.js` (existing /connect test pattern to mirror)
    - `next.config.js` (CSP array at lines 20-35)
    - `README.md` (2 placeholder locations) + `app/guides/claude-code/page.js` (2 more, 1 HTML-entity-encoded)
    - `scripts/check-readme-lead.mjs` (existing script style to mirror for check-screencast-backfilled.mjs)
  </read_first>
  <behavior>
    **VideoHero component (new):**
    - Test `video-hero.test.jsx`: accepts `src="https://www.loom.com/embed/abc123"` → renders `<iframe>` with that src
    - Test: accepts `src="https://www.youtube-nocookie.com/embed/abc123"` → renders `<iframe>` with that src
    - Test: throws (or returns null with console error) when src host is NOT Loom or youtube-nocookie (SSRF mitigation T-03-01)
    - Test: iframe has `allowFullScreen` + `title` attributes (a11y)

    **Hero presence (homepage-hero.test.jsx):**
    - Test: rendered HTML contains `<iframe` with src matching `/(loom\.com|youtube-nocookie\.com)\/embed/`
    - Test: rendered HTML headline (first `<h1>`) contains "Claude Code" (case-insensitive)
    - Test: CTA order — first primary CTA references "Watch" or "Demo" (video), second references "Install" or "Connect" (pointing /connect or /guides/claude-code), third references "Star" (pointing github.com)
    - Test: headline length ≤ 60 characters (enforces terse voice — ≤8 words ≈ ≤60 chars)

    **Rejected framings negative assertions (homepage-rejected-framings.test.jsx):**
    - Test: rendered HTML does NOT contain (case-insensitive) the strings: "homelab", "SOC 2", "SOC2", "compliance team", "control plane for agents", "policy-as-code for AI", "works with any agent framework", "enterprise compliance"
    - Test: rendered HTML does NOT contain the current shipped phrase "policy firewall for AI agents" (from app/page.js:42 — generic AI governance abstraction per D-13)

    **Single-page runbook (connect-runbook.test.jsx):**
    - Test: `/connect` rendered HTML does NOT contain "Step 1 of" / "Step 2 of" / multi-step wizard patterns
    - Test: `/connect` rendered HTML DOES contain a workspace-token generation UI marker (HostedProvisionSection preserved — assert the section heading text or a known data-testid)
    - Test: `/connect` rendered HTML contains all three runbook elements: paste-install-command, paste-workspace-token, configure-Discord (assert via keyword presence — "npm install", "DASHCLAW_API_KEY" or "workspace token", "Discord")

    **CSP frame-src (no test file — manual curl + browser verification):**
    - Modify `next.config.js` csp array: add `"frame-src 'self' https://www.loom.com https://www.youtube-nocookie.com"` AFTER the existing `"frame-ancestors 'none'"` line. These are separate CSP directives — frame-ancestors governs who can frame us; frame-src governs who we can frame.

    **Placeholder-backfill script (scripts/check-screencast-backfilled.mjs):**
    - Reads README.md + app/guides/claude-code/page.js
    - Counts occurrences of both `<SCREENCAST_URL>` (raw) AND `&lt;SCREENCAST_URL&gt;` (HTML-entity-encoded form — per Phase 2 02-01-SUMMARY section 3 warning)
    - Exits 0 if zero total matches; exits 1 with clear error naming each remaining location otherwise
    - Style: mirror `scripts/check-readme-lead.mjs` (ES module, readFileSync, process.exit)
  </behavior>
  <action>
    1. **Create `app/components/VideoHero.js`** per RESEARCH §Pattern 2 code example:
       - Accepts `src` + `title` props
       - Hostname allowlist: `loom.com` and `youtube-nocookie.com` only
       - Returns `<iframe>` with className using CSS tokens (`border-border-hover`, `rounded-xl`, aspect-video) — NO hardcoded hex
       - On non-allowed host: throw `Error('VideoHero: src must be Loom or YouTube URL')` (SSRF mitigation, T-03-01)
    2. **Modify `next.config.js`** (inside csp array, AFTER `"frame-ancestors 'none'"`): insert exactly one line: `"frame-src 'self' https://www.loom.com https://www.youtube-nocookie.com",`
    3. **Create `scripts/check-screencast-backfilled.mjs`** — ES module mirroring `scripts/check-readme-lead.mjs` style. Uses `readFileSync` on both README.md and app/guides/claude-code/page.js; applies regex `/(&lt;|<)SCREENCAST_URL(&gt;|>)/g` (covers both raw and HTML-entity forms); reports each location line number on failure.
    4. **Create 4 test files** per `<behavior>` above. All use `@testing-library/react` + `render()` + string/regex assertions on `document.body.innerHTML`. Use `describe.skip` or `it.skip` wrappers for test fixtures that depend on components not yet modified in Tasks 2-3 (they'll flip to `it` in Task 4 verification). Alternatively, these can be RED (failing) on first run — the plan's GREEN wave is Task 4's full-suite verification.
    5. Run `npm test -- --run __tests__/unit/video-hero.test.jsx` — PASSES (Task 1 completes VideoHero).
    6. Run `npm test -- --run __tests__/unit/homepage-hero __tests__/unit/homepage-rejected-framings __tests__/unit/connect-runbook` — expected to FAIL (RED state — homepage + /connect not rewritten yet). Capture output as RED baseline.
    7. Run `node scripts/check-screencast-backfilled.mjs` — expected to FAIL with 4 remaining matches (2 in README, 2 in guides page). Capture as RED baseline.
    8. Commit: `test(03-01): add wave-0 scaffolds + VideoHero + CSP frame-src + placeholder-backfill script (RED)`

    **Threat mitigation:** T-03-01-01 CSP gap addressed (frame-src directive added). T-03-01 SSRF via embed URL addressed (VideoHero hostname allowlist + throw on non-allowed host).
  </action>
  <verify>
    <automated>npm test -- --run __tests__/unit/video-hero.test.jsx 2>&1 | tail -20</automated>
    Expected: video-hero test passes. Other Wave-0 tests fail (RED) — this is the expected RED baseline.

    Supplementary manual check (no automated equivalent needed — CSP is a header, browser-verified in Task 4):
    - `grep -n "frame-src" next.config.js` returns one line
    - `node scripts/check-screencast-backfilled.mjs` exits 1 with 4 remaining (RED baseline)
  </verify>
  <done>
    - `app/components/VideoHero.js` exists, exports default, has hostname allowlist
    - `next.config.js` CSP array contains `frame-src 'self' https://www.loom.com https://www.youtube-nocookie.com`
    - `scripts/check-screencast-backfilled.mjs` exists, ES module style, exits 1 when placeholders present
    - 4 test files exist under `__tests__/unit/` (homepage-hero, homepage-rejected-framings, connect-runbook, video-hero)
    - `npm test -- --run __tests__/unit/video-hero.test.jsx` PASSES
    - Other 3 test files are RED (expected — target components not rewritten yet; will GREEN in Task 3)
    - Commit lands with `test(03-01):` prefix
  </done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Rewrite app/page.js hero + /connect runbook → GREEN the homepage + connect tests</name>
  <files>app/page.js, app/connect/page.js, app/connect/ConnectGuideClient.js</files>
  <read_first>
    - `docs/homepage-draft-claude-code.md` (806-word Phase 2 handoff — re-verify each concrete claim against currently-shipped state per Pitfall 8)
    - `README.md` lines 1-40 (currently-shipped install block + Claude-Code-first lead; homepage 3-step install must match)
    - `app/guides/claude-code/page.js` (ensure hero CTA pointing here still resolves)
    - `app/page.js` full file (current hero to replace + lower-fold sections to preserve if trimming)
    - `app/connect/page.js` + `ConnectGuideClient.js` + `HostedProvisionSection.js` + `HostedProvisionClient.jsx` (Pitfall 9: DO NOT delete HostedProvision — it provides D-15's inline workspace-token UX)
    - `app/landingData.js` (shared landing content — trim references that collide with rejected framings)
    - `.impeccable.md` (token-first, brand orange as signal)
    - `.planning/phases/03-public-launch/03-CONTEXT.md` decisions D-12 (voice), D-13 (rejected framings list), D-14 (CTA order Watch→Install→Star), D-15 (single-page runbook)
    - RED test output from Task 1 (the failing assertions define what GREEN looks like)
  </read_first>
  <behavior>
    After this task:
    - `__tests__/unit/homepage-hero.test.jsx` PASSES (iframe embed present, Claude Code headline, CTA order correct, headline ≤60 chars)
    - `__tests__/unit/homepage-rejected-framings.test.jsx` PASSES (none of the 8 rejected strings appear in rendered HTML)
    - `__tests__/unit/connect-runbook.test.jsx` PASSES (no multi-step wizard markers; HostedProvision workspace-token UI present; three runbook elements present)
    - `scripts/check-readme-lead.mjs` STILL PASSES (non-regression — Claude Code lead preserved)
    - `scripts/check-screencast-backfilled.mjs` still RED (backfill is Task 3 — requires actual video URL from human checkpoint)
    - Full suite `npm test` — no new failures introduced in unrelated files
  </behavior>
  <action>
    1. **Rewrite `app/page.js` hero section** (lines ~24-150 approximately; preserve lower-fold sections that aren't hero):
       - **Remove** from hero: the `"Open-source AI governance runtime"` pill badge (generic governance abstraction — D-13); the `"DashClaw is the policy firewall for AI agents."` tagline (generic governance — D-13); the `"Works with Claude Managed Agents, Claude Code, OpenAI, LangChain, CrewAI, AutoGen, Codex, Gemini CLI, and any custom agent."` line (multi-agent platform positioning — D-13); the "Intercept agent actions before they reach production." headline (replace with Claude-Code-specific)
       - **Replace headline** with Claude-Code-specific + terse-technical phrase (≤ 60 chars / ≤ 8 words). Voice direction (D-12): `"The approval layer for Claude Code."` or `"Your coding agent, with a pause button."` or `"Govern Claude Code before it runs rm -rf."` — executor picks one that satisfies voice constraints. Subhead (1 sentence) explains the behavior: something like `"Intercept risky commands. Approve from your phone. Keep an audit trail."` using token-first styling (`text-text-secondary`, no brand orange).
       - **Add `<VideoHero>` embed** above the fold, under the headline + subhead. Use a temporary placeholder src like `"https://www.loom.com/embed/PLACEHOLDER_VIDEO_ID"` — the real URL lands via Task 3 after human-action recording checkpoint. Document the placeholder in a code comment so Task 3 knows where to update.
       - **Replace CTA block** with three buttons in exact order (D-14):
         1. `Watch demo →` (primary — scrolls to or focuses the VideoHero, OR is a prominent anchor reinforcing the embed above — executor's UX choice; must contain "Watch" text)
         2. `Install →` (href="/connect" — must contain "Install" text)
         3. `Star on GitHub →` (href="https://github.com/ucsandman/DashClaw" — must contain "Star" text + target="_blank" + rel="noopener noreferrer")
       - **Preserve** lower-fold sections (feature grid, platform coverage, signals, etc.) — surgical-change rule; only hero is rewritten
       - **Remove** the current `<div>` containing demo-gif2.gif (lines ~103-113) because VideoHero supersedes it in the hero. If the GIF is referenced elsewhere on the page (lower fold), leave that reference alone.
       - **Styling:** all new classes use CSS tokens (border-border-hover, text-text-primary, bg-surface-primary) — NO hardcoded hex. Brand orange (`text-brand`, `bg-brand`) reserved for the one signal element per `.impeccable.md` tiebreaker #2 — likely the CTA chevron or a single accent; do NOT paint the whole hero orange.

    2. **Rewrite `app/connect/page.js`** per D-15 (single-page copy-paste runbook):
       - **Preserve**: `HostedProvisionSection` component (provides inline workspace-token generation — the UX D-15 actually wants). Keep its import and render position near the top.
       - **Preserve**: page-level layout (PublicNavbar, PublicFooter, main container).
       - **Inline or trim**: `<ConnectGuideClient content={content} />` is the wizard-adjacent framing per Pitfall 9. Two acceptable approaches (executor picks):
         - (a) Trim `ConnectGuideClient.js` internally to render a linear runbook (no step tabs, no collapsible advanced sections, no "Step N of M" framing). Rename `ConnectGuideClient` if the wizard framing is baked into the component name. Keep `getConnectGuideContent` intact.
         - (b) Inline a minimal runbook directly in `app/connect/page.js` after HostedProvisionSection: three labeled blocks (1) paste install command + `DASHCLAW_API_KEY` env, (2) paste workspace token (auto-generated by HostedProvision above), (3) configure Discord bot — link to `/guides/claude-code` for the 3-step bot setup details. Remove the ConnectGuideClient import.
       - **Remove** or **collapse** the lower sections if they add wizard-flavored complexity:
         - "MCP Server — Zero Code Path" block (lines ~43-118): this block is useful but competes with the Claude Code primary runbook. Either (a) move below the primary runbook as a single collapsed-by-default section, or (b) keep but demote visually (smaller heading, less prominent framing). Executor's call — the goal is the PRIMARY top-to-bottom path is Claude-Code-first, ≤5 min, D-15-compliant.
         - "Framework Guides" grid (lines ~178-202): keep but demote — it's useful context but not the runbook. Move below the approval channels section.
       - **Do NOT** touch `HostedProvisionSection.js` or `HostedProvisionClient.jsx` or `hostedTemplates.js` (per Pitfall 9 — these provide the actual token UX)

    3. **If ConnectGuideClient.js is modified**: preserve public API (`export default function ConnectGuideClient({ content })`) so other imports don't break. If fully replaced by inline logic in page.js, delete the file ONLY after grepping `grep -rn "ConnectGuideClient" app` to confirm no other importers exist.

    4. **Verify tests GREEN**:
       ```
       npm test -- --run __tests__/unit/homepage-hero __tests__/unit/homepage-rejected-framings __tests__/unit/connect-runbook
       ```
       All three files must pass.

    5. **Non-regression check**:
       ```
       node scripts/check-readme-lead.mjs
       ```
       Still passes (README.md Claude-Code-first lead shipped in 02-03 is preserved by this plan).

    6. Commit: `feat(03-01): rewrite homepage hero + /connect runbook (DOG-03)`

    **Threat mitigation:** T-03-01-02 (hero copy leaks rejected framings) addressed by homepage-rejected-framings.test.jsx passing. T-03-01-03 voice-compliance verified by homepage-hero.test.jsx headline-length + Claude Code mention.
  </action>
  <verify>
    <automated>npm test -- --run __tests__/unit/homepage-hero __tests__/unit/homepage-rejected-framings __tests__/unit/connect-runbook && node scripts/check-readme-lead.mjs</automated>
  </verify>
  <done>
    - `app/page.js` hero rewritten: Claude Code headline ≤8 words, VideoHero embed, CTA order Watch→Install→Star, rejected framings absent
    - `app/connect/page.js` single-page runbook: HostedProvisionSection preserved, wizard framing removed or trimmed, three runbook elements (install, token, Discord) top-to-bottom
    - `npm test -- --run __tests__/unit/homepage-hero __tests__/unit/homepage-rejected-framings __tests__/unit/connect-runbook` all PASS
    - `scripts/check-readme-lead.mjs` exits 0 (non-regression)
    - No hardcoded hex introduced (grep `#[0-9a-fA-F]{6}` app/page.js app/connect/page.js against baseline returns no NEW matches)
    - VideoHero src is currently a placeholder; Task 3 backfills with real URL after recording checkpoint
  </done>
</task>

<task type="checkpoint:human-action" gate="blocking">
  <name>Task 3: CHECKPOINT — Record flagship video + publish + verify incognito</name>
  <what-built>
    Tasks 1-2 have built:
    - `VideoHero` component ready to accept a Loom/YouTube URL
    - Homepage hero embedding VideoHero with a placeholder src
    - /connect rewritten as single-page runbook
    - CSP frame-src permits Loom + YouTube embeds
    - 4 `<SCREENCAST_URL>` placeholders in README.md + app/guides/claude-code/page.js still present (NOT yet backfilled — requires real URL)
    - `scripts/check-screencast-backfilled.mjs` available to verify zero placeholders remain

    The technical infrastructure is ready. What remains is the human-only work: record the video, publish it, verify it loads incognito.
  </what-built>
  <how-to-verify>
    **This is a human-action checkpoint.** Claude cannot record video, upload to Loom/YouTube, or browse from a phone hotspot incognito. Wes performs steps 1-4; Claude resumes at step 5.

    **1. Pre-flight (env sanity check per Pitfall 4 — BUG-04 lesson):**
       ```bash
       echo $DASHCLAW_BASE_URL     # must be unset or point at real prod (NOT localhost:3000)
       echo $DASHCLAW_API_KEY      # must match real prod key
       curl -s $DASHCLAW_BASE_URL/api/health | jq .version  # must NOT be "demo"
       ```
       Close any `dashclaw-demo` Docker containers before recording.

    **2. Discord bot setup** (if not already done — one-time, ~10 min). Follow the 6-step runbook in `.planning/phases/02-claude-code-beachhead/02-01-SUMMARY.md` §5 step 1 exactly:
       - https://discord.com/developers/applications → New Application
       - Bot tab → Reset Token → copy `DISCORD_BOT_TOKEN`
       - Copy `APPLICATION ID`, `PUBLIC KEY`, your own Discord user ID
       - OAuth2 URL Generator → install to throwaway server
       - Populate 5 env vars in `.env.local`
       - Developer Portal → Interactions Endpoint URL → `https://<deployment>/api/discord/interactions` → wait for green checkmark

    **3. Record walkthrough** (Windows/WSL, ≤3:00 per D-09 — tighter than Phase 2's ≤5:00):
       - Follow 9-segment recipe in `.planning/phases/02-claude-code-beachhead/02-01-PLAN.md` Task 2 `<what-built>` block
       - Trim setup preamble for the public cut (≤3:00 target)
       - Raw over polished. No slides. Real codebase. Real phone tap. Real Claude Code resume.
       - Scrub frames for any secret exposure (DISCORD_BOT_TOKEN, DASHCLAW_API_KEY, or similar) — T-03-02-01 mitigation

    **4. Publish + verify incognito** (per D-10 + Pitfall 2):
       - Upload to Loom → set visibility to **Public** (NOT the default "anyone with link") OR upload to YouTube → set to **Unlisted**
       - Copy the embed URL in correct shape:
         - Loom: `https://www.loom.com/embed/<VIDEO_ID>` (NOT /share/)
         - YouTube: `https://www.youtube-nocookie.com/embed/<VIDEO_ID>` (privacy-enhanced, cookieless)
       - Also note the watch URL (Loom /share/ or YouTube /watch?v=) for README anchor hrefs
       - **Verify in incognito from phone hotspot** (different IP): URL loads, no captcha, no workspace-auth wall, video plays

    **5. Provide the URLs to Claude** to resume execution:
       - Embed URL (used in `<VideoHero src={...}>`)
       - Watch URL (used in README anchor hrefs — the `href="<SCREENCAST_URL>"` spots are links the reader clicks)

    **Pass criteria (all must be TRUE before resuming):**
    - Video duration ≤ 3:00
    - Incognito URL loads without captcha
    - No secrets visible in any frame
    - Phone-to-resolution observable (real Discord approval, real Claude Code resume)
  </how-to-verify>
  <resume-signal>
    Provide both URLs in this format:

    ```
    EMBED_URL: https://www.loom.com/embed/XXXXX
    WATCH_URL: https://www.loom.com/share/XXXXX
    ```

    (or the YouTube equivalents)

    Alternative resume signals:
    - `ship placeholder again` — defer Task 4 backfill (NOT recommended — Phase 3 goal requires DOG-02)
    - `abort phase 3` — halt before Task 4
  </resume-signal>
  <files>N/A — human-action checkpoint. Wes records + publishes video; no files modified by Claude during this task.</files>
  <action>
    Claude halts and surfaces the &lt;what-built&gt; and &lt;how-to-verify&gt; blocks to the operator. No files modified, no commands executed. Claude resumes only on the operator's &lt;resume-signal&gt; (EMBED_URL + WATCH_URL, or one of the alternatives). On `ship placeholder again`: skip Task 4 backfill; on `abort phase 3`: halt without further work.
  </action>
  <verify>
    <automated>N/A — no automation possible for video recording + human-hosted publish + phone-hotspot incognito verification. Pass criteria live in &lt;how-to-verify&gt; above and are operator-confirmed via resume-signal.</automated>
  </verify>
  <done>
    Operator provides EMBED_URL + WATCH_URL in the resume-signal format, OR explicitly signals `ship placeholder again` / `abort phase 3`. Claude resumes Task 4 with the provided URLs or halts per alternative signal.
  </done>
</task>

<task type="auto">
  <name>Task 4: Backfill 4 <SCREENCAST_URL> placeholders + flip VideoHero src + verify full suite GREEN</name>
  <files>app/page.js, README.md, app/guides/claude-code/page.js</files>
  <read_first>
    - Resume-signal from Task 3 containing `EMBED_URL` (Loom `/embed/` or YouTube-nocookie `/embed/`) and `WATCH_URL` (Loom `/share/` or YouTube `/watch?v=`)
    - `.planning/phases/02-claude-code-beachhead/02-01-SUMMARY.md` §3 (6-step backfill procedure — honor both raw and HTML-entity-encoded forms)
    - `README.md:8,19` — both contain `<SCREENCAST_URL>` as a literal string inside `href="..."`
    - `app/guides/claude-code/page.js:104` — `note: 'Screencast: <SCREENCAST_URL>'` (raw)
    - `app/guides/claude-code/page.js:249` — `Screencast: &lt;SCREENCAST_URL&gt;` (HTML-entity-encoded — DO NOT miss this one; naive grep against `<SCREENCAST_URL>` misses it per Phase 2 close state)
    - `scripts/check-screencast-backfilled.mjs` (handles both forms)
  </read_first>
  <action>
    1. **Flip VideoHero src in `app/page.js`**: replace the placeholder src (`https://www.loom.com/embed/PLACEHOLDER_VIDEO_ID`) with the real `EMBED_URL` from Task 3 resume signal.
    2. **Backfill `README.md`** (2 locations):
       - Line ~8: `<a href="<SCREENCAST_URL>">` → `<a href="<WATCH_URL>">` (user-clickable link, so use watch URL not embed URL)
       - Line ~19: `<a href="<SCREENCAST_URL>">Watch the 3-min walkthrough` → `<a href="<WATCH_URL>">Watch the 3-min walkthrough`
    3. **Backfill `app/guides/claude-code/page.js`** (2 locations, both forms):
       - Line ~104: `note: 'Screencast: <SCREENCAST_URL>'` → `note: 'Screencast: <WATCH_URL>'` (raw form — simple text replacement)
       - Line ~249: `Screencast: &lt;SCREENCAST_URL&gt;` → `Screencast: <WATCH_URL>` (HTML-entity form — executor must replace the full `&lt;SCREENCAST_URL&gt;` token with the plain watch URL; surrounding text/JSX preserved)
    4. **Verify zero placeholders remain**:
       ```bash
       node scripts/check-screencast-backfilled.mjs   # exits 0
       grep -c "SCREENCAST_URL" README.md              # returns 0
       grep -cE "SCREENCAST_URL|&lt;SCREENCAST_URL&gt;" app/guides/claude-code/page.js  # returns 0
       ```
    5. **Full-suite verification** (plan-level — per feedback memory `feedback_full_test_suite_in_plan_verification.md`):
       ```bash
       npm test -- --run   # no targeted pattern — full suite
       ```
       All 1690+ existing tests plus the 4 new Wave-0 tests must pass.
    6. **Static guardrails** (non-regression):
       ```bash
       node scripts/check-readme-lead.mjs      # exits 0
       node scripts/check-screencast-backfilled.mjs  # exits 0 (new in this plan)
       npm run route-sql:check                 # baseline held (this plan adds zero raw SQL)
       npm run openapi:check                   # baseline held (this plan adds zero API routes — 03-03 adds the counter route)
       npm run api:inventory:check             # baseline held
       npm run docs:check                      # baseline held
       ```
    7. **Manual incognito re-verification** (belt-and-suspenders, before 03-02's HN submission): Wes loads dashclaw.io in incognito on a different IP; video iframe renders, CSP violations NONE in console. **This is pre-requirement for launching 03-02 — do NOT start 03-02's HN publication task until this check passes.** Surface as explicit resume-note in the SUMMARY.
    8. Commit (bundled with backfill): `docs(03-01): backfill CCI-05 screencast URLs + flip VideoHero to live video (DOG-02)`

    **Threat mitigation:** T-03-01-01 (CSP gap) fully mitigated — manual incognito check at step 7 verifies the iframe actually renders (not just that the directive exists). T-03-01-02 (HN URL-change penalty) mitigated by the step-7 gate before 03-02 starts.
  </action>
  <verify>
    <automated>node scripts/check-screencast-backfilled.mjs && node scripts/check-readme-lead.mjs && npm test -- --run</automated>
  </verify>
  <done>
    - `app/page.js` VideoHero src is the real embed URL (no PLACEHOLDER)
    - All 4 `<SCREENCAST_URL>` placeholder instances (raw + HTML-entity-encoded) replaced with real watch URL
    - `scripts/check-screencast-backfilled.mjs` exits 0
    - Full suite `npm test -- --run` passes (1690+ tests + 4 new scaffolds)
    - `scripts/check-readme-lead.mjs` exits 0
    - `route-sql:check`, `openapi:check`, `api:inventory:check`, `docs:check` all pass (baseline held — this plan adds zero API routes + zero raw SQL)
    - Manual incognito verification (step 7) captured in the SUMMARY as a pre-requirement for 03-02's HN submission
    - Commit lands as `docs(03-01): backfill CCI-05 screencast URLs + flip VideoHero to live video (DOG-02)`
  </done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Browser → dashclaw.io | Public visitor loads hero + /connect; CSP governs what the page can embed/connect to |
| dashclaw.io → Loom/YouTube | Hero iframe loads third-party video — CSP frame-src permits exactly two hosts; VideoHero component enforces allowlist at React render time |
| Video frames → public web | Every recorded frame is public — any secret in-frame leaks globally |
| HN submission → dashclaw.io URL | HN algorithm treats the submitted URL as canonical; URL-change after submission kills ranking |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-03-01-01 | Tampering / DoS | Hero video iframe blocked by CSP | mitigate | Task 1 adds `frame-src 'self' https://www.loom.com https://www.youtube-nocookie.com` to next.config.js csp array. Task 4 step 7 manual incognito re-verification confirms iframe actually renders (not just directive present) |
| T-03-01-02 | Tampering (reputational) | HN URL-change penalty if homepage not live | mitigate | Hard ordering constraint: Task 4 manual incognito verification (step 7) is a pre-requirement for 03-02's HN publication task. Documented in SUMMARY resume-notes |
| T-03-01-03 | Information disclosure (brand) | Hero copy leaks rejected framings | mitigate | `__tests__/unit/homepage-rejected-framings.test.jsx` asserts 8 negative patterns (homelab, SOC 2, compliance team, control plane for agents, policy-as-code for AI, works with any agent framework, enterprise compliance, policy firewall for AI agents) are absent from rendered HTML. Full suite gate in Task 4 |
| T-03-01-04 | SSRF / Tampering | VideoHero accepts arbitrary iframe src | mitigate | VideoHero component throws on any host not in `{loom.com, youtube-nocookie.com}` allowlist. Unit test `__tests__/unit/video-hero.test.jsx` asserts throw behavior |
| T-03-01-05 | Information disclosure (operator secrets) | Recorded video frames contain secrets | mitigate | Task 3 checkpoint step 3 explicitly requires frame scrub; Task 3 step 1 env sanity check (Pitfall 4 / BUG-04 lesson) ensures recording happens against the real prod instance, not a leak-prone demo container |
| T-03-01-06 | Tampering (HTML escaping) | HTML-entity-encoded placeholder missed by naive grep | mitigate | `scripts/check-screencast-backfilled.mjs` regex handles both raw and HTML-entity-encoded forms. Task 4 step 4 runs the script explicitly |
| T-03-01-07 | Spoofing / Clickjacking | Homepage framed into a phishing page | accept | Already mitigated pre-Phase-3 by `X-Frame-Options: DENY` (next.config.js:42) + `frame-ancestors 'none'` in CSP. Phase 3 does not weaken these |
</threat_model>

<verification>
Plan-level verification (after Task 4 commit):

```bash
# Wave-0 + new scaffolds all GREEN
npm test -- --run

# Placeholder backfill verified — both raw + HTML-entity forms
node scripts/check-screencast-backfilled.mjs

# README Claude-Code-first lead still holds (non-regression from 02-03)
node scripts/check-readme-lead.mjs

# Static guardrails — baseline held (this plan adds zero API routes + zero raw SQL)
npm run route-sql:check
npm run openapi:check
npm run api:inventory:check
npm run docs:check

# Visual + manual (step 7 of Task 4) — Wes loads dashclaw.io incognito from phone hotspot
# Verifies: iframe renders, video plays, CSP violations NONE in DevTools console
# This is the gate for 03-02 HN publication task
```

**Wave-transition gate (entering Wave 2 / 03-02):**
- Manual incognito check on dashclaw.io PASSES before 03-02's HN publication task starts
- This is captured in 03-01-SUMMARY as an explicit precondition for 03-02
</verification>

<success_criteria>
1. Flagship demo video ≤3:00 recorded, published on Loom Public OR YouTube Unlisted, loads in incognito from phone hotspot with no captcha (DOG-02 D-08, D-09, D-10)
2. Video embedded at 3 locations: homepage hero above-the-fold (`app/page.js`), `/guides/claude-code` page, README.md (D-11)
3. Homepage hero rewritten: Claude Code headline ≤ 60 chars / ≤ 8 words, terse-technical voice, video embed above the fold, CTA order Watch demo → Install → Star on GitHub (D-12, D-14)
4. Homepage rejected framings removed: zero matches for homelab / SOC 2 / compliance team / control plane for agents / policy-as-code for AI / works with any agent framework / enterprise compliance / policy firewall for AI agents in the hero HTML (D-13)
5. `/connect` is a single-page copy-paste runbook — no multi-step wizard framing — with inline workspace-token generation via HostedProvisionSection preserved (D-15)
6. CSP frame-src directive permits loom.com + youtube-nocookie.com; iframe renders without browser CSP violation
7. 4 `<SCREENCAST_URL>` placeholders (2 in README.md, 1 raw + 1 HTML-entity in app/guides/claude-code/page.js) all backfilled with real watch URL — closes Phase 2 CCI-01 + CCI-05 open gaps
8. Full test suite `npm test -- --run` passes with zero new failures (1690+ existing + 4 new Wave-0 scaffolds)
9. All static guardrails held at baseline: `route-sql:check`, `openapi:check`, `api:inventory:check`, `docs:check`, `check-readme-lead.mjs`
10. No hardcoded hex introduced in modified files (CSS tokens only); pre-existing `bg-[#0a0a0a]` at app/guides/claude-code/page.js:204 untouched per surgical-change rule
</success_criteria>

<output>
After completion, create `.planning/phases/03-public-launch/03-01-SUMMARY.md` documenting:
- Video URLs (EMBED + WATCH) chosen and published
- Confirmation of incognito loading (step 7 check)
- All 4 placeholder locations backfilled
- Explicit precondition for 03-02: `manual_incognito_verified: true` flag for HN publication gating
- Phase 2 CCI-01 + CCI-05 open gaps now closed (update REQUIREMENTS.md traceability table from Partial → Complete)
- Livingcode auto-regen artifacts committed alongside code changes (per CLAUDE.md §Generated Artifacts)
</output>
