Skip to content

WIP: Landing redesign + the real editor as the hero demo#65

Draft
cameronapak wants to merge 4 commits into
mainfrom
landing-page
Draft

WIP: Landing redesign + the real editor as the hero demo#65
cameronapak wants to merge 4 commits into
mainfrom
landing-page

Conversation

@cameronapak

@cameronapak cameronapak commented Jul 4, 2026

Copy link
Copy Markdown
Owner

What

Two commits: the landing redesign (mirror the app, demote AI to a quiet helper), then the hero's toy outline replaced with the real OutlineEditor, embedded via iframe in an anonymous, in-memory demo mode (?demo=1).

How

  • src/data/demo-backend.ts — browser port of the e2e Worker/DO mock: patches fetch + WebSocket, serves /api/nodes / /api/kv / /api/sync from a Map, deny-all 404 for every other /api route. Nothing touches the network; resets on reload.
  • src/data/demo-seed.ts — curated marketing outline (tags + color, todos, rich link, one provenance-sparkle node). Typed as the shared wire-schema Node, so a new required field is a compile error, not a blank demo.
  • __root.tsxAuthGate skips the session gate in demo mode (hook-free branch); renders html.demo-embed.
  • worker/index.ts — CSP hardening: shell gets frame-ancestors 'self'; only the ?demo document adds https://dotflowy.com. DEMO_FRAME_ANCESTORS env var for local dev (.dev.vars).
  • landing/HeroOutlineEmbed.tsx — lazy iframe (IntersectionObserver + skeleton, zero new deps); old HeroOutlineDemo deleted.

Gotchas encoded

  • Cross-origin iframes never chain overscroll to the embedder → demo doc is non-scrollable (overflow: hidden), so scroll passes through the hero.
  • The class must be React-rendered, not classList.add — React's first commit replaces the whole class attribute.
  • wrangler dev's custom-domain simulation hides the real hostname → framing allowlist is env-var, not hostname-sniffed.

Security

Demo is provably anonymous: no session, no cookie-authed request, unknown /api routes stubbed to 404 by construction. The authenticated app stays unframable cross-origin.

Tested

typecheck, typecheck:worker, lint, 280 unit tests pass. Verified live: localhost:3100 landing framing the :8787 Worker — editing, tags, todos, zoom, and scroll pass-through all work.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new marketing landing page with a live embedded demo, feature highlights, and clear calls to action.
    • Introduced a standalone landing site setup with its own build, routing, and deployment configuration.
  • Bug Fixes

    • Improved demo mode behavior so the embedded experience loads without auth prompts or distracting app controls.
    • Added scroll handling to keep the embedded demo from trapping page scrolling.
  • Documentation

    • Expanded product, design, and wayfinding documentation for clearer product and workflow guidance.

cameronapak and others added 3 commits July 4, 2026 03:12
Rip out the fabricated blue→purple gradient brand the prior build
invented (gradient CTAs, gradient-clipped "yours", ambient glow) — it
diverged from the app's real grayscale system and read as AI slop. The
landing now mirrors src/styles.css exactly: zero gradients, grayscale
buttons (--primary), one solid mirror-blue accent on text only.

Reframe the agent section from a drenched "your AI edits your outline"
spotlight into a quiet "you stay the author" beat: a static outline
where one node wears the app's real muted-sparkle provenance mark. AI
reads as a helper, marked for trust, not the star.

Also: differentiate the three feature blocks with product-native
artifacts, one hero kicker instead of one-per-section, de-em-dash the
copy, and fix the GitHub handle (cameronapak, was a 404).

Adds PRODUCT.md + DESIGN.md capturing the brand + visual system.
Verified in-browser across desktop light/dark and 390px mobile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The landing hero now iframes the actual app (?demo=1) instead of the
hand-rolled toy. Demo mode bypasses AuthGate and swaps the transport for
an in-memory port of the e2e Worker/DO mock (fetch + WebSocket
monkeypatch), seeded with curated marketing content. The Worker stamps
frame-ancestors so only dotflowy.com may frame the demo document, and
demo mode renders html.demo-embed (non-scrollable doc) so the iframe
never traps the visitor's scroll.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7dad1a9-b289-44ea-930f-989dab6ad247

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a standalone landing marketing site (Vite/TanStack Start) with UI components and a lazy-loaded live demo iframe, an in-memory anonymous demo backend intercepting fetch/WebSocket calls, app-side demo-mode gating and CSP hardening in the worker, DESIGN.md/PRODUCT.md specs, plus unrelated skill docs and config housekeeping.

Changes

Landing Page and Anonymous Demo

Layer / File(s) Summary
Brand and product specification docs
DESIGN.md, PRODUCT.md
Adds design token system, component conventions, brand positioning, users, anti-references, and accessibility requirements documents.
Landing app scaffolding, routing, and build config
landing/.gitignore, landing/package.json, landing/tsconfig.json, landing/vite.config.ts, landing/wrangler.jsonc, landing/src/vite-env.d.ts, landing/src/routeTree.gen.ts, landing/src/router.tsx, landing/src/routes/__root.tsx, landing/src/routes/index.tsx, landing/src/styles.css
Sets up the landing project's build tooling, TanStack routing, and global Tailwind design-token styles.
Landing UI components and hero embed
landing/src/components/Landing.tsx, landing/src/components/HeroOutlineEmbed.tsx, landing/src/components/Kbd.tsx, landing/src/components/ui/*, landing/src/lib/utils.ts
Implements the marketing page sections and a lazily-mounted iframe embedding the live app demo.
Anonymous in-memory demo backend
src/data/demo-backend.ts, src/data/demo-seed.ts
Implements a fake sync WebSocket and REST endpoint interception backed by in-memory store/kv, plus curated seed data.
App-side demo mode gating and CSP hardening
src/components/Header.tsx, src/routes/__root.tsx, src/styles.css, worker/index.ts, .dev.vars.example
Gates header UI and auth flow in demo mode, installs the demo backend, and hardens the CSP frame-ancestors directive for the embed.

Estimated code review effort: 4 (Complex) | ~60 minutes

Repository Config and Skill Docs Housekeeping

Layer / File(s) Summary
Wayfinder skill documentation and lock updates
.agents/skills/setup-matt-pocock-skills/issue-tracker-*.md, skills-lock.json, .claude/skills/setup-matt-pocock-skills
Adds "Wayfinding operations" guidance to issue-tracker skill docs, refreshes the lock hash, removes an unused skill entry, and symlinks the skills path.
Editor and repo config tweaks
.claude/launch.json, .gitignore
Adds a landing dev run configuration and ignores ShipStudio-related metadata directories.

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant HeroOutlineEmbed
  participant DemoIframe
  participant DemoBackend as installDemoBackend()
  participant Worker as worker/index.ts

  Visitor->>HeroOutlineEmbed: scrolls hero panel into view
  HeroOutlineEmbed->>DemoIframe: mount iframe with ?demo=1
  DemoIframe->>DemoBackend: installDemoBackend() patches fetch/WebSocket
  DemoIframe->>DemoBackend: fetch /api/nodes, /api/kv
  DemoBackend-->>DemoIframe: sandboxed in-memory response
  DemoIframe->>Worker: request static asset with ?demo
  Worker-->>DemoIframe: response with frame-ancestors CSP allowing dotflowy.com
Loading

Possibly related PRs

  • cameronapak/dotflowy#12: Both PRs modify src/components/Header.tsx's right-side action cluster rendering logic.
  • cameronapak/dotflowy#13: Both PRs modify the same non-/api/* request handling path in worker/index.ts's main fetch handler.
  • cameronapak/dotflowy#33: Both PRs modify the same issue-tracker skill documentation files under .agents/skills/setup-matt-pocock-skills/.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: a landing page redesign and replacing the hero mock with the real editor demo.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch landing-page

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cameronapak cameronapak changed the title Landing redesign + the real editor as the hero demo WIP: Landing redesign + the real editor as the hero demo Jul 4, 2026
@cameronapak cameronapak marked this pull request as draft July 4, 2026 09:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
landing/package.json (1)

14-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

shadcn CLI listed as a runtime dependency.

shadcn is a code-gen/scaffolding CLI, not something imported at runtime. Consider moving it to devDependencies to keep the production dependency graph accurate.

♻️ Proposed fix
   "dependencies": {
     "`@base-ui/react`": "^1.6.0",
     "`@fontsource-variable/geist`": "^5.2.9",
     "`@fontsource-variable/geist-mono`": "^5.2.8",
     "`@tanstack/react-router`": "^1.170.16",
     "`@tanstack/react-start`": "^1.168.26",
     "class-variance-authority": "^0.7.1",
     "cnfast": "^0.0.8",
     "lucide-react": "^1.21.0",
     "react": "^19.0.0",
     "react-dom": "^19.0.0",
-    "shadcn": "^4.12.0",
     "tw-animate-css": "^1.4.0"
   },
   "devDependencies": {
     "`@tailwindcss/vite`": "^4.3.1",
     "`@types/react`": "^19.0.0",
     "`@types/react-dom`": "^19.0.0",
     "`@vitejs/plugin-react`": "^6.0.2",
+    "shadcn": "^4.12.0",
     "tailwindcss": "^4.3.1",
     "typescript": "^5.6.0",
     "vite": "^8.0.16",
     "wrangler": "^4.104.0"
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/package.json` around lines 14 - 27, The package manifest currently
lists the shadcn CLI under runtime dependencies even though it is only used for
scaffolding/code generation. Move shadcn from the dependencies section to
devDependencies in package.json so the landing app’s production dependency graph
stays accurate; keep the existing package entry and adjust only its dependency
classification.
landing/src/components/Landing.tsx (1)

11-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate, inconsistently-resolved APP_URL across landing components.

This hardcodes APP_URL, while HeroOutlineEmbed.tsx resolves the same concept from import.meta.env.VITE_APP_URL with a fallback. In local dev with the env var overridden, the hero iframe would point at the local app while Nav/Hero/Footer links still point at production — confusing during local testing.

Consider extracting a single shared APP_URL (and GITHUB_URL) constant, sourced from the env override, into a shared module both components import.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/Landing.tsx` around lines 11 - 12, The landing
components are using duplicate `APP_URL` values with different sources, which
can cause links and the hero iframe to disagree in local overrides. Move
`APP_URL` and `GITHUB_URL` into a shared module and have `Landing` and
`HeroOutlineEmbed` import the same resolved constants, using
`import.meta.env.VITE_APP_URL` with the existing fallback so all landing links
stay consistent across environments.
landing/src/components/HeroOutlineEmbed.tsx (1)

7-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer typed ImportMetaEnv over a manual cast.

(import.meta.env as Record<string, string | undefined>) bypasses Vite's env typing. Declaring VITE_APP_URL in vite-env.d.ts's ImportMetaEnv interface (scaffolded in this same PR layer) would give this a proper type without the inline assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/components/HeroOutlineEmbed.tsx` around lines 7 - 9, The APP_URL
env lookup in HeroOutlineEmbed should not use a manual Record cast on
import.meta.env; instead, rely on Vite’s typed ImportMetaEnv. Add VITE_APP_URL
to the ImportMetaEnv interface in vite-env.d.ts and then reference
import.meta.env.VITE_APP_URL directly in HeroOutlineEmbed so the constant is
properly typed without the inline assertion.
landing/src/styles.css (1)

1-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stylelint config doesn't understand Tailwind v4 at-rules.

The @custom-variant and @theme "unknown at-rule" errors are false positives — both are valid Tailwind CSS v4 CSS-first configuration directives, not SCSS. The lint failures stem from stylelint lacking a Tailwind v4-aware syntax/plugin (e.g., stylelint-config-tailwindcss or the postcss-html/tailwindcss custom syntax), not from a code defect. Worth fixing the lint config so CI doesn't perpetually flag valid Tailwind syntax.

Also applies to: 7-7, 15-45

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@landing/src/styles.css` around lines 1 - 5, The reported unknown at-rule
errors are false positives from Stylelint not recognizing Tailwind v4 CSS-first
directives. Update the Stylelint setup to use a Tailwind v4-aware config/syntax
(for example, a Tailwind-specific Stylelint config or custom syntax/plugin) so
`@custom-variant` and `@theme` are parsed correctly. Check the Stylelint
configuration used for the stylesheet entrypoint and ensure it handles Tailwind
directives consistently across the affected CSS files.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@worker/index.ts`:
- Around line 61-67: The CSP gate for demo framing in worker/index.ts does not
cover the `/embed` demo route handled by src/data/demo-backend.ts, so the
landing iframe will be blocked on that path. Update the frame-ancestors logic in
the demo CSP branch to treat `/embed` the same as the anonymous demo document
and allow `https://dotflowy.com` when that route is used, using the existing
demo framing config symbols around DEMO_FRAME_ANCESTORS and the CSP builder.

---

Nitpick comments:
In `@landing/package.json`:
- Around line 14-27: The package manifest currently lists the shadcn CLI under
runtime dependencies even though it is only used for scaffolding/code
generation. Move shadcn from the dependencies section to devDependencies in
package.json so the landing app’s production dependency graph stays accurate;
keep the existing package entry and adjust only its dependency classification.

In `@landing/src/components/HeroOutlineEmbed.tsx`:
- Around line 7-9: The APP_URL env lookup in HeroOutlineEmbed should not use a
manual Record cast on import.meta.env; instead, rely on Vite’s typed
ImportMetaEnv. Add VITE_APP_URL to the ImportMetaEnv interface in vite-env.d.ts
and then reference import.meta.env.VITE_APP_URL directly in HeroOutlineEmbed so
the constant is properly typed without the inline assertion.

In `@landing/src/components/Landing.tsx`:
- Around line 11-12: The landing components are using duplicate `APP_URL` values
with different sources, which can cause links and the hero iframe to disagree in
local overrides. Move `APP_URL` and `GITHUB_URL` into a shared module and have
`Landing` and `HeroOutlineEmbed` import the same resolved constants, using
`import.meta.env.VITE_APP_URL` with the existing fallback so all landing links
stay consistent across environments.

In `@landing/src/styles.css`:
- Around line 1-5: The reported unknown at-rule errors are false positives from
Stylelint not recognizing Tailwind v4 CSS-first directives. Update the Stylelint
setup to use a Tailwind v4-aware config/syntax (for example, a Tailwind-specific
Stylelint config or custom syntax/plugin) so `@custom-variant` and `@theme` are
parsed correctly. Check the Stylelint configuration used for the stylesheet
entrypoint and ensure it handles Tailwind directives consistently across the
affected CSS files.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 71cae453-3152-49ae-b91c-b379e5e232b5

📥 Commits

Reviewing files that changed from the base of the PR and between f850894 and 2110627.

⛔ Files ignored due to path filters (1)
  • landing/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (35)
  • .agents/skills/setup-matt-pocock-skills/issue-tracker-github.md
  • .agents/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md
  • .agents/skills/setup-matt-pocock-skills/issue-tracker-local.md
  • .claude/launch.json
  • .claude/skills/setup-matt-pocock-skills
  • .dev.vars.example
  • .gitignore
  • DESIGN.md
  • PRODUCT.md
  • landing/.gitignore
  • landing/package.json
  • landing/src/components/HeroOutlineEmbed.tsx
  • landing/src/components/Kbd.tsx
  • landing/src/components/Landing.tsx
  • landing/src/components/ui/button-variants.ts
  • landing/src/components/ui/button.tsx
  • landing/src/components/ui/card.tsx
  • landing/src/components/ui/separator.tsx
  • landing/src/lib/utils.ts
  • landing/src/routeTree.gen.ts
  • landing/src/router.tsx
  • landing/src/routes/__root.tsx
  • landing/src/routes/index.tsx
  • landing/src/styles.css
  • landing/src/vite-env.d.ts
  • landing/tsconfig.json
  • landing/vite.config.ts
  • landing/wrangler.jsonc
  • skills-lock.json
  • src/components/Header.tsx
  • src/data/demo-backend.ts
  • src/data/demo-seed.ts
  • src/routes/__root.tsx
  • src/styles.css
  • worker/index.ts

Comment thread worker/index.ts
Comment on lines +61 to +67
/** Extra space-separated CSP `frame-ancestors` sources allowed to embed the
* anonymous demo document (`?demo=1`) — local-only, set in `.dev.vars` (e.g.
* `http://localhost:3100` for the landing dev server). Hostname sniffing
* can't gate this instead: `wrangler dev`'s custom-domain simulation rewrites
* the request URL to the prod domain (same gotcha as BETTER_AUTH_URL). Unset
* in prod, where only https://dotflowy.com may frame the demo. */
DEMO_FRAME_ANCESTORS?: string

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether any route/component currently references the /embed path.
rg -n "'/embed'|\"/embed\"|/embed" --type=ts --type=tsx -g '!**/node_modules/**'

Repository: cameronapak/dotflowy

Length of output: 186


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect relevant files and search for /embed and CSP handling.
git ls-files 'worker/index.ts' 'src/data/demo-backend.ts' | xargs -r -I{} sh -c 'echo "### {}"; wc -l "{}"; echo'

echo '### worker/index.ts'
sed -n '1,220p' worker/index.ts

echo '### src/data/demo-backend.ts'
sed -n '1,220p' src/data/demo-backend.ts

echo '### Search for /embed references'
rg -n --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' '/embed|frame-ancestors|Content-Security-Policy|demo=1|isDemoMode' .

Repository: cameronapak/dotflowy

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '### worker/index.ts (CSP branch)'
sed -n '512,542p' worker/index.ts

echo '### CSP-related repo files'
git ls-files | rg '(^|/)(_headers|_redirects|wrangler\.toml|package\.json|vite\.config\.(ts|js)|\.html$|\.md$|\.css$)'
echo '### Explicit CSP mentions'
rg -n --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' 'Content-Security-Policy|frame-ancestors|demo=1|demo mode|_headers' worker src landing .github . || true

Repository: cameronapak/dotflowy

Length of output: 50378


Allow /embed in the demo CSP gate worker/index.ts:520-536 src/data/demo-backend.ts already treats /embed as demo mode, so this CSP branch should allow https://dotflowy.com for that path too; otherwise the landing iframe will fail when the dedicated route is used.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@worker/index.ts` around lines 61 - 67, The CSP gate for demo framing in
worker/index.ts does not cover the `/embed` demo route handled by
src/data/demo-backend.ts, so the landing iframe will be blocked on that path.
Update the frame-ancestors logic in the demo CSP branch to treat `/embed` the
same as the anonymous demo document and allow `https://dotflowy.com` when that
route is used, using the existing demo framing config symbols around
DEMO_FRAME_ANCESTORS and the CSP builder.

The landing hero now iframes the actual app (?demo=1) instead of the
hand-rolled toy. Demo mode bypasses AuthGate and swaps the transport for
an in-memory port of the e2e Worker/DO mock (fetch + WebSocket
monkeypatch), seeded with curated marketing content. The Worker stamps
frame-ancestors so only dotflowy.com may frame the demo document, and
demo mode renders html.demo-embed (non-scrollable doc) so the iframe
never traps the visitor's scroll.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant