WIP: Landing redesign + the real editor as the hero demo#65
WIP: Landing redesign + the real editor as the hero demo#65cameronapak wants to merge 4 commits into
Conversation
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>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis 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. ChangesLanding Page and Anonymous Demo
Estimated code review effort: 4 (Complex) | ~60 minutes Repository Config and Skill Docs Housekeeping
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
landing/package.json (1)
14-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
shadcnCLI listed as a runtime dependency.
shadcnis a code-gen/scaffolding CLI, not something imported at runtime. Consider moving it todevDependenciesto 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 valueDuplicate, inconsistently-resolved
APP_URLacross landing components.This hardcodes
APP_URL, whileHeroOutlineEmbed.tsxresolves the same concept fromimport.meta.env.VITE_APP_URLwith 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(andGITHUB_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 valuePrefer typed
ImportMetaEnvover a manual cast.
(import.meta.env as Record<string, string | undefined>)bypasses Vite's env typing. DeclaringVITE_APP_URLinvite-env.d.ts'sImportMetaEnvinterface (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 winStylelint config doesn't understand Tailwind v4 at-rules.
The
@custom-variantand@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-tailwindcssor thepostcss-html/tailwindcsscustom 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
⛔ Files ignored due to path filters (1)
landing/bun.lockis 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.gitignoreDESIGN.mdPRODUCT.mdlanding/.gitignorelanding/package.jsonlanding/src/components/HeroOutlineEmbed.tsxlanding/src/components/Kbd.tsxlanding/src/components/Landing.tsxlanding/src/components/ui/button-variants.tslanding/src/components/ui/button.tsxlanding/src/components/ui/card.tsxlanding/src/components/ui/separator.tsxlanding/src/lib/utils.tslanding/src/routeTree.gen.tslanding/src/router.tsxlanding/src/routes/__root.tsxlanding/src/routes/index.tsxlanding/src/styles.csslanding/src/vite-env.d.tslanding/tsconfig.jsonlanding/vite.config.tslanding/wrangler.jsoncskills-lock.jsonsrc/components/Header.tsxsrc/data/demo-backend.tssrc/data/demo-seed.tssrc/routes/__root.tsxsrc/styles.cssworker/index.ts
| /** 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 |
There was a problem hiding this comment.
🔒 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 . || trueRepository: 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>
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: patchesfetch+WebSocket, serves/api/nodes//api/kv//api/syncfrom aMap, deny-all 404 for every other/apiroute. 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-schemaNode, so a new required field is a compile error, not a blank demo.__root.tsx—AuthGateskips the session gate in demo mode (hook-free branch); rendershtml.demo-embed.worker/index.ts— CSP hardening: shell getsframe-ancestors 'self'; only the?demodocument addshttps://dotflowy.com.DEMO_FRAME_ANCESTORSenv var for local dev (.dev.vars).landing/HeroOutlineEmbed.tsx— lazy iframe (IntersectionObserver + skeleton, zero new deps); oldHeroOutlineDemodeleted.Gotchas encoded
overflow: hidden), so scroll passes through the hero.classList.add— React's first commit replaces the wholeclassattribute.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
/apiroutes 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
Bug Fixes
Documentation