Repository navigation
Commit 3a9b847
feat: rewrite as TanStack Start app — a clearer HyperFrames-on-Cloudflare example (#10)
* feat: rewrite as TanStack Start app with TanStack AI generation
Full-stack rewrite of the template on TanStack Start (Vite +
@cloudflare/vite-plugin), replacing the hand-rolled Worker router and
static preview page:
- src/server.ts custom entry: Start fetch handler + RenderContainer DO
export (wrangler main now points here)
- /api/render, /api/preview, /r/<key> ported to Start server routes;
asset fetches use the request origin (dev assets binding is
same-origin-only)
- /api/generate rebuilt on TanStack AI: chat() + @tanstack/ai-openrouter
streams the composition to the browser over SSE; BYOK key travels in
forwardedProps via a custom useChat fetcher and is never persisted
- lint + self-heal moved to a client-driven loop: lintComposition server
function (@hyperframes/core/lint) after each assistant turn, fix
requests sent as follow-up chat turns (max 2, temp 0.3)
- React UI (routes/index.tsx + components) reproduces the original
preview page, adds live streaming view of the generated HTML
- scripts/build.mjs now runs via npm prepare-assets before vite
dev/build instead of wrangler build.command
* chore: commit worker-configuration.d.ts per Cloudflare guidance
* chore: commit generated routeTree.gen.ts
* fix: harden AI endpoints, surface errors honestly, add vitest coverage
Review fixes on top of the TanStack Start rewrite:
- Gate custom-HTML /api/render behind ENABLE_AI_GEN so public demos
can't run arbitrary HTML through the render container; the bundled
"click Render" path stays open
- Keep the BYOK key in React state only, never sessionStorage —
generated compositions execute same-origin in the player iframe and
could read origin storage; security note updated to match
- Drop empty preview_image_url that breaks the Deploy to Cloudflare
button (re-fix of 02b8a69)
- Surface server error messages: the useChat fetcher throws the JSON
error body instead of the library's bare status line, RenderControls
parses the error envelope, and a lint server-fn failure warns instead
of faking success
- Wrap the R2 put with contextual logging; serve only renders/* keys
from /r/$; validate message roles and clamp durationSec
- Extract pure encoding helpers (native Uint8Array.toBase64 with a
chunked fallback for Node 22) and add 16 vitest tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: log in with OpenRouter via OAuth PKCE instead of pasting a key
Adds a "Log in with OpenRouter" button that runs OpenRouter's OAuth PKCE
flow entirely client-side (src/lib/openrouter-oauth.ts): redirect to
openrouter.ai/auth with an S256 code challenge, then exchange the
one-time callback code for a runtime API key scoped to this app. No
client secret, no server involvement — the key stays in React memory
just like the pasted-key flow. Manual key entry remains as a fallback
behind a toggle.
The PKCE code verifier is the one sanctioned sessionStorage use: it only
exists across the redirect round-trip and is deleted (and ?code stripped
from the URL) before any generated composition can run.
Includes unit tests for the pure helpers (base64url, RFC 7636 appendix B
challenge vector, auth URL construction) and README updates.
* Moved exaples to above prompt
* docs: credit the spend-capped key, not memory placement, as the real protection
Review feedback on #10: memory-only key storage removes the trivial
storage read but is not an isolation boundary — same-origin generated
code can still reach window.parent and recover the key. Tighten the
code comment, UI security note, and README so adopters don't over-trust
where the key is stored.
* fix: address remaining review findings on #10 — durationSec clamp, OAuth remount safety
- Clamp durationSec to 120s. It's only a prompt hint, but an absurd
value could steer the model toward a very long composition that
inflates render time/cost on the deployer's account.
- Memoize the OAuth callback exchange for the page load. The one-time
code is consumed synchronously before the async key exchange resolves,
so a repeat effect run (React StrictMode double-effect, or an unmount/
remount mid-exchange) previously found the code gone and silently
dropped the minted key with no recovery. Repeat calls now await the
same promise and receive the key. Corrects the AiPanel comment that
claimed the second run was a no-op, and adds a regression test.
* fix: commit composition-manifest.json so a clean checkout typechecks
Review blocker on #10: 'npm run typecheck' failed from a fresh clone
with TS2307 because src/lib/server/assets.ts imports the manifest,
which was generated by prepare-assets and gitignored. Commit it —
matching routeTree.gen.ts and worker-configuration.d.ts — so bare
'tsc --noEmit' works with no build step. prepare-assets still rewrites
it on every dev/build, so composition changes show up as a git diff to
commit alongside them.
* ci: typecheck, tests, and manifest-freshness check on PRs
Review feedback on #10: the repo had no PR checks, so a typecheck that
only passed after a build step went unnoticed. Runs from a clean
checkout: npm ci, typecheck (no build first — the committed manifest
must suffice), vitest, then regenerates the manifest and fails on any
diff so composition changes can't land without it.
* ci: harden workflow per zizmor — pin actions by SHA, least privilege
All --pedantic findings fixed: checkout and setup-node pinned to the
commit SHAs of their latest releases (v7.0.1 / v7.0.0), read-only
permissions block, persist-credentials off, per-ref concurrency with
cancel-in-progress, and a job name.
* ci: run on Node 24 (current LTS)
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent b2ecc18 commit 3a9b847
37 files changed
Lines changed: 20509 additions & 2471 deletions
File tree
- .github/workflows
- public
- scripts
- src
- components
- lib
- server
- routes
- api
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
13 | 12 | | |
14 | | - | |
15 | | - | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
Large diffs are not rendered by default.
0 commit comments