You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
tstack-on-cf is the TanStack Start + Hono template on Cloudflare Workers (@cloudflare/vite-plugin, Neon Postgres via drizzle neon-http, three wrangler envs, Biome 2 + GritQL plugins, Vitest 4, semantic-release). The audit found a strong base — observability logs (100%) + traces (1%) enabled, compat-date auto-bump bot, weekly taze bumps, clean secrets hygiene, accurate llms.txt with a symlinked AGENTS.md, meta-tests that keep config/docs honest, and a completed workers-best-practices issue series (#5–#16) that must not be re-proposed. This PRD covers what remains.
Goals
Close the remaining Cloudflare-standards gaps (source maps, placement, Hyperdrive decision, env routing posture, CI-safe typegen).
Gate and automate the path to production (PR CI, CD, migration story, rollback guidance).
Make tests exercise the real runtime and clean up masked dead dependencies.
Non-goals
New product features or UI work.
Replacing Neon/Drizzle or the TanStack/Hono architecture.
Re-doing anything from the closed workers-best-practices series (secrets out of vars, observability, env blocks, onError, structured logging, /api routing, DB singleton, cf-typegen pre-push).
Workstream 1 — Cloudflare standards conformance
Enable source map upload. No upload_source_maps in wrangler.jsonc — production stack traces are minified in Workers Logs despite logs+traces being on. Add "upload_source_maps": true (top-level, inherited by envs).
Enable Smart Placement for staging/production. The Worker makes sequential round-trips to Neon pinned in eu-central-1 (src/db/setup.ts) — the canonical Smart Placement case. Add placement: { "mode": "smart" } and measure.
Decide Hyperdrive vs neon-http, and record it. Cloudflare's standard for external Postgres is a Hyperdrive binding (pooling + edge caching); the repo uses the neon-http fetch driver directly. Evaluate Hyperdrive (drizzle-orm/node-postgres) against neon-http and document the decision either way.
Give staging/production a routing posture.wrangler.jsonc:15-24 env blocks are name+vars only — no routes/custom_domain, no explicit workers_dev/preview_urls, despite the repo's own .claude/rules/cloudflare-deployment.md recommending custom_domain: true. Add commented placeholders + explicit posture per env.
Make cf-typegen CI-safe. Typegen currently depends on gitignored .dev.vars for secret inference, forcing SKIP_SIMPLE_GIT_HOOKS=1 in all three workflows and a deliberate typegen skip in compat-date.yml:96-101. Type the secrets explicitly in committed service-bindings.d.ts (interface Env extends BaseEnv { DATABASE_HOST: string; … }) — then typegen runs anywhere and the workarounds get deleted. (Also evaluate the new secrets.required wrangler config property as the declared source of truth.)
Workstream 2 — Deployment readiness
Add PR CI..github/workflows/ has only release.yml (post-merge), deps-update.yml, compat-date.yml; nothing validates pull requests, including the bots' own PRs. Add ci.yml on pull_request + push running lint:ci, types, test, knip, and vite build.
Add CD. Deploys run from a laptop with local wrangler auth (package.json:13-15). Add staged CD via cloudflare/wrangler-action (staging on main push, production on release tag, CLOUDFLARE_API_TOKEN secret) or Workers Builds; include the env-scoped build first.
Finish the per-env migration story. Only src/db/migrations/dev/ exists; .staging.vars/.production.vars are empty placeholders and migrations run manually via dotenvx. Wire db:migrate:<env> into the CD flow (migrate → deploy) or document the manual gate explicitly.
Document the auth/security posture of the API.src/hono/api/clients.ts is public unauthenticated CRUD with no rate limiting or CORS anywhere. For a template that's acceptable only if loud: add an auth middleware slot in src/hono/factory.ts (e.g. bearer/JWT) and a security-posture section in README.
Pin Node. No engines, no .nvmrc; CI floats on lts/*. Add both.
Add rollback/gradual-rollout guidance. Nothing in README about wrangler versions upload, gradual deployments, or wrangler rollback for incidents.
Workstream 3 — AI-readiness
Fix the dead /docs pointer. AGENTS.md:86-89 and README.md:437 declare /docs the single source of truth for business requirements; the directory doesn't exist. Create it with a stub or remove the claim.
Fix README drift (3 spots). README.md:155 shows compatibility_date: "2025-09-02" vs actual 2026-05-25; README.md:112 claims migrations/{dev,staging,production}/ but only dev/ exists; README.md:454 links a LICENSE file that doesn't exist despite the MIT claim. Fix all three, add the LICENSE, and consider extending the repo's meta-test pattern to enforce the wrangler snippet.
Add .mcp.json. Ship Cloudflare docs/bindings MCP servers (and optionally Neon MCP) as project-scoped config — the template already commits to agent-assisted workflows.
Close the Codex parity gap or declare it..codex/agents/ has 1 of the 3 .claude/agents/ (only mvp-e); hooks are mirrored but agents aren't. Mirror them or state the supported-agent matrix (same for the absent .cursor/).
Workstream 4 — Maintainability
Make tests exercise the real runtime. 10 files / ~27 cases, all config-heavy: no @cloudflare/vitest-pool-workers (so src/server.ts dispatch, env wiring, and SSR never run in workerd) and zero component tests (vitest.config.ts doesn't even set jsdom). Add a Vitest workspace: workers-pool project for server/Hono + jsdom project for components — or delete the unused testing deps.
Remove dead devDependencies masked by knip config.web-vitals, @tanstack/react-form, @testing-library/dom, @testing-library/react have zero usages but are hidden via knip.jsonignoreDependencies; knip also ignores all of src/db/** and src/core/errors.ts. Remove the deps and shrink the ignore list.
Create a major-upgrade cadence. Vite is exact-pinned at 7.1.2 with Vite 8.2.1 out; taze minor mode never proposes majors and nothing tracks them (also pending: Biome 2.5.x, TS 7/tsgo evaluation). Add a scheduled majors report that opens/updates an issue.
Explain pnpm-workspace.yaml. It exists solely for pnpm 10 allowBuilds with no packages: key — add a comment so humans and agents stop assuming a monorepo.
PRD: Bring tstack-on-cf to current Cloudflare standards (deploy readiness, AI-readiness, maintainability)
Audit date: 2026-08-07 · Baseline: Cloudflare Workers Best Practices guide (2026-02-15), Wrangler v4 standards, Workers Logs/Traces/Source Maps, Smart Placement, Hyperdrive, versioned deployments, vitest-pool-workers, llms.txt/AGENTS.md agent conventions.
Background
tstack-on-cf is the TanStack Start + Hono template on Cloudflare Workers (
@cloudflare/vite-plugin, Neon Postgres via drizzleneon-http, three wrangler envs, Biome 2 + GritQL plugins, Vitest 4, semantic-release). The audit found a strong base — observability logs (100%) + traces (1%) enabled, compat-date auto-bump bot, weekly taze bumps, clean secrets hygiene, accuratellms.txtwith a symlinked AGENTS.md, meta-tests that keep config/docs honest, and a completed workers-best-practices issue series (#5–#16) that must not be re-proposed. This PRD covers what remains.Goals
/docspointer, README drift, missing LICENSE) and extend agent tooling (.mcp.json, Codex parity).Non-goals
/apirouting, DB singleton, cf-typegen pre-push).Workstream 1 — Cloudflare standards conformance
upload_source_mapsinwrangler.jsonc— production stack traces are minified in Workers Logs despite logs+traces being on. Add"upload_source_maps": true(top-level, inherited by envs).src/db/setup.ts) — the canonical Smart Placement case. Addplacement: { "mode": "smart" }and measure.neon-httpfetch driver directly. Evaluate Hyperdrive (drizzle-orm/node-postgres) against neon-http and document the decision either way.wrangler.jsonc:15-24env blocks are name+vars only — noroutes/custom_domain, no explicitworkers_dev/preview_urls, despite the repo's own.claude/rules/cloudflare-deployment.mdrecommendingcustom_domain: true. Add commented placeholders + explicit posture per env.cf-typegenCI-safe. Typegen currently depends on gitignored.dev.varsfor secret inference, forcingSKIP_SIMPLE_GIT_HOOKS=1in all three workflows and a deliberate typegen skip incompat-date.yml:96-101. Type the secrets explicitly in committedservice-bindings.d.ts(interface Env extends BaseEnv { DATABASE_HOST: string; … }) — then typegen runs anywhere and the workarounds get deleted. (Also evaluate the newsecrets.requiredwrangler config property as the declared source of truth.)Workstream 2 — Deployment readiness
.github/workflows/has onlyrelease.yml(post-merge),deps-update.yml,compat-date.yml; nothing validates pull requests, including the bots' own PRs. Addci.ymlonpull_request+pushrunning lint:ci, types, test, knip, andvite build.package.json:13-15). Add staged CD viacloudflare/wrangler-action(staging on main push, production on release tag,CLOUDFLARE_API_TOKENsecret) or Workers Builds; include the env-scoped build first.src/db/migrations/dev/exists;.staging.vars/.production.varsare empty placeholders and migrations run manually via dotenvx. Wiredb:migrate:<env>into the CD flow (migrate → deploy) or document the manual gate explicitly.src/hono/api/clients.tsis public unauthenticated CRUD with no rate limiting or CORS anywhere. For a template that's acceptable only if loud: add an auth middleware slot insrc/hono/factory.ts(e.g. bearer/JWT) and a security-posture section in README.engines, no.nvmrc; CI floats onlts/*. Add both.wrangler versions upload, gradual deployments, orwrangler rollbackfor incidents.Workstream 3 — AI-readiness
/docspointer. AGENTS.md:86-89 and README.md:437 declare/docsthe single source of truth for business requirements; the directory doesn't exist. Create it with a stub or remove the claim.compatibility_date: "2025-09-02"vs actual2026-05-25; README.md:112 claimsmigrations/{dev,staging,production}/but onlydev/exists; README.md:454 links a LICENSE file that doesn't exist despite the MIT claim. Fix all three, add the LICENSE, and consider extending the repo's meta-test pattern to enforce the wrangler snippet..mcp.json. Ship Cloudflare docs/bindings MCP servers (and optionally Neon MCP) as project-scoped config — the template already commits to agent-assisted workflows..codex/agents/has 1 of the 3.claude/agents/(only mvp-e); hooks are mirrored but agents aren't. Mirror them or state the supported-agent matrix (same for the absent.cursor/).Workstream 4 — Maintainability
@cloudflare/vitest-pool-workers(sosrc/server.tsdispatch, env wiring, and SSR never run in workerd) and zero component tests (vitest.config.tsdoesn't even set jsdom). Add a Vitest workspace: workers-pool project for server/Hono + jsdom project for components — or delete the unused testing deps.web-vitals,@tanstack/react-form,@testing-library/dom,@testing-library/reacthave zero usages but are hidden viaknip.jsonignoreDependencies; knip also ignores all ofsrc/db/**andsrc/core/errors.ts. Remove the deps and shrink the ignore list.minormode never proposes majors and nothing tracks them (also pending: Biome 2.5.x, TS 7/tsgo evaluation). Add a scheduled majors report that opens/updates an issue.pnpm-workspace.yaml. It exists solely for pnpm 10allowBuildswith nopackages:key — add a comment so humans and agents stop assuming a monorepo..example.varsnaming with wrangler's.dev.vars.exampleconvention; enableverbatimModuleSyntax(Biome already enforcesuseImportType).Prioritization
upload_source_maps; Smart Placement; migration story; README drift + LICENSE;/docspointer; workers-pool tests.mcp.json; Codex parity; dead deps; majors cadence; Node pinAcceptance criteria
pnpm run cf-typegenproduces identical output with or without.dev.varspresent; theSKIP_SIMPLE_GIT_HOOKSworkarounds are gone from workflows.wrangler.jsonchasupload_source_mapsand Smart Placement on server envs; a written decision exists for Hyperdrive vs neon-http.Out of scope
Executing the Vite 8 / TS 7 majors (tracked by the new cadence), replacing Neon, building real auth (only the middleware slot + docs are in scope).
References