# Changelog

New features, architectural changes and improvements to the template.

Release notes are curated from repository history. Dates reflect integration into the main branch; grouped entries are not semantic-version tags. Each entry links to its implementation and current developer guide.


## Auth and tenant security fixes

2026-10-16

Isolation gaps found before row-level security are closed, client IPs are trusted only where the deployment vouches for them, and failed sign-ins are counted.

<Callout title="Breaking: storage, admin and invitations">
  `STORAGE_PUBLIC_BASE_URL` is gone: every file URL is presigned, with an expiry capped by the files gateway, instead of a permanent public link. The admin session list no longer returns session tokens, and `admin.revokeUserSession` takes `userId` and `sessionId` instead of `sessionToken`. Accepting an Invitation now requires a verified email.
</Callout>

Client IP addresses, used by the Better Auth rate limiter, audit records and MCP JWKS limits, now come from one rule. On Workers the template reads `cf-connecting-ip`, set by Cloudflare’s edge. On a Node deployment behind a proxy, set `TRUSTED_PROXY_HEADER` (for example `x-forwarded-for`) and `TRUSTED_PROXIES` (hops from the right, default 1). Without them the socket address is used and client headers are ignored, so forged headers no longer pick a fresh rate-limit bucket.

Failed sign-ins and failed second factors are now counted: the rate limiter commits on its own connection, so the counter survives the rolled-back auth request. On Node the limiter has a small pool of its own, so concurrent sign-ins no longer hang waiting for a connection. A User removed from their personal Organization is no longer made its owner again on the next sign-in. A removed MCP signing key stops validating within 60 seconds. The per-request session memo is keyed by the presented cookie or bearer token, so an inner request with other credentials reads its own session.

Uploads recheck the uploader’s membership before committing a file, document submission checks ownership before locking the queue row, and integrations hide deliveries from Organizations the User has left. A saved billing customer link now wins over provider user hints, and the seed refuses non-local hosts. When another tab switches Organization or account, each open tab resets its query cache and reloads its routes instead of showing the previous identity’s rows.

- [Authentication](/docs/features/auth)
- [Environments](/docs/development/environments)
- [MCP server](/docs/features/mcp)
- [Files and uploads](/docs/features/files)

Sources: [isolation gaps](https://github.com/CarlosZiegler/template-cloudflare/commit/860fa26), [removed owners and failed credentials](https://github.com/CarlosZiegler/template-cloudflare/commit/608ad29), [trusted client IP](https://github.com/CarlosZiegler/template-cloudflare/commit/6bdc605), [limiter pool](https://github.com/CarlosZiegler/template-cloudflare/commit/f7b1958), [JWKS revocation](https://github.com/CarlosZiegler/template-cloudflare/commit/7d7c750), [session memo](https://github.com/CarlosZiegler/template-cloudflare/commit/503c0dc), [cross-tab sessions](https://github.com/CarlosZiegler/template-cloudflare/commit/cb08640).


## Database, Worker and web performance

2026-10-16

Tenant statements cost one round trip, the Worker runs next to the database, oRPC calls are batched and pages load their data on the server.

<Callout title="Action: set the Worker placement for your database">
  `apps/web/wrangler.jsonc` now sets `"placement": { "region": "aws:us-east-2" }`, the region of this template’s production database. A copy on another host sets its own region, or `"placement": { "mode": "smart" }` when the location is unknown. Placement moves only fetch handlers; static assets stay at the edge, and previews inherit the setting.
</Callout>

On the database side, row-level security costs no extra round trip: tenant settings and the statement are pipelined, row membership is checked once per statement rather than per row, and the identity functions are plpgsql so their plans are cached. The membership check is one system statement and the session is read once per request. A migration adds indexes for real filters and unindexed foreign keys, stores the document search vector as a generated column and drops redundant indexes. Todo list, board and lane renumbering, audit outbox flushes and queue scans use fewer statements or their indexes. An hourly cron job deletes rate-limit rows, sessions and verifications a day past expiry.

In the web app, oRPC calls made in the same tick go as one `/__batch__` request, sharing one Context and session read on the server. Queries no longer retry 4xx errors and retry network failures and 5xx at most twice. Settings, admin flags, files and integrations load their data in route loaders, and the dashboard renders its prefetched sections on the server through the new `todo.stats` aggregate. `todo.calendar` now returns at most 500 todos, with `truncated` set when there are more. React Compiler covers more web and mobile components, date-fns and files-sdk stay out of the client entry, thumbnail URLs are cached per Organization and the documents poll backs off.

Fixes from the same round: each board lane is counted once, large libraries show a document status for every file, and file preview URLs are always revoked.

- [Database and migrations](/docs/features/database)
- [API](/docs/features/api)
- [Scheduled jobs](/docs/features/scheduled-jobs)
- [Deployment](/docs/deploy)

Sources: [pipelining](https://github.com/CarlosZiegler/template-cloudflare/commit/39d14a3), [per-statement membership](https://github.com/CarlosZiegler/template-cloudflare/commit/83ccc4c), [indexes](https://github.com/CarlosZiegler/template-cloudflare/commit/95c14eb), [auth cleanup job](https://github.com/CarlosZiegler/template-cloudflare/commit/5914f2b), [oRPC batching](https://github.com/CarlosZiegler/template-cloudflare/commit/c9a0039), [dashboard stats](https://github.com/CarlosZiegler/template-cloudflare/commit/a5a5473), [server-rendered dashboard](https://github.com/CarlosZiegler/template-cloudflare/commit/27b8071), [placement](https://github.com/CarlosZiegler/template-cloudflare/commit/369401a).


## Row-level security per Organization

2026-10-16

PostgreSQL now enforces tenant isolation on every table. The template requires PostgreSQL 16 or newer, on any host, and verifies RLS on every migration.

<Callout title="Breaking: PostgreSQL 16 or newer">
  The foundation migration stops on PostgreSQL 15 or older. Its connection role must be able to create roles (`CREATEROLE` or a superuser); otherwise the migration prints the two statements an administrator runs once to create `app_tenant`. Apply the migrations with `pnpm --filter @template-cloudflare/db db:apply-pending` (`deploy-prod.yml` does this before the deploy).
</Callout>

Every table in `public` has row-level security. Procedures, assistant and MCP tools and the files gateway run as the `app_tenant` role for the session’s User and Active Organization, in one short transaction per statement. `app.org_id()` re-checks membership on every statement, so a removed Member sees nothing on the next query. Better Auth, cron, billing webhooks and admin keep the connection role through `asSystem(db, reason, fn)`, whose call sites are inventoried and checked by `pnpm lint`. The application’s `organizationId` filters stay as defence in depth.

The design uses plain PostgreSQL, so it works the same on Neon, Supabase, RDS, a self-hosted server or a local Docker database. The connection role may be a superuser, an owner with BYPASSRLS or a plain owner; tables never use `FORCE ROW LEVEL SECURITY`.

What changes for a project built on the template:

- `db:apply-pending` now ends with `db:verify-rls`, which checks the role, the identity functions, every policy and the exact grants. After a `pg_dump`/`pg_restore` into another host, grants and policies are lost: run `db:apply-pending` against the target, or `db:verify-rls [ENV_VAR] --repair`.
- Tenant identity is honoured only for roles listed in `app.session_roles`, seeded with the migration role. If the Worker connects with a different role, add it there, or tenant requests see no rows.
- Hyperdrive query caching must stay disabled. `pnpm setup --hyperdrive-id <id>` disables it, `pnpm doctor --connect` reports it, and the production deploy refuses to publish while it is on.
- A new table needs `pgTable.withRLS`, its `tenantPolicy(...)`, its grant in `packages/db/scripts/rls-baseline.mjs` and a custom migration. A new cross-tenant path needs a `SystemReason` and `node scripts/check-system-call-sites.mjs --write`.

Each tenant statement is pipelined: the identity settings and the statement travel in one round trip with a single Sync. Setting `DB_TENANT_PIPELINE=0` as a Worker var or secret restores explicit BEGIN/COMMIT without a code change. `pnpm --filter @template-cloudflare/db test:rls-matrix` (Docker) runs both modes on PostgreSQL 16 and 18 for every connection-role profile, including a real restore, and CI runs it as its own job.

Pull request previews now take the production connection path. When production uses Hyperdrive, each PR gets its own Hyperdrive with caching disabled, pointing at the PR’s database branch. Closing the PR deletes it, and the CI token needs Hyperdrive edit permission. Previews also deploy with their secrets in the same version (`wrangler deploy --secrets-file`), so a new preview Worker no longer fails env validation on its first upload. Only previews set `TENANCY_PROBE=1`, which mounts the `tenancy.probe` oracle; `scripts/check-production-config.mjs` refuses it in the production config. Before a production deploy that touches tenancy, run `node scripts/rls-preview-proof.mjs <previewUrl>`, which exits non-zero on any cross-tenant or pooled-connection leak.

- [ADR-0009: row-level security](/docs/adr/0009-row-level-security)
- [Tenant isolation](/docs/features/database#tenant-isolation) and [restoring or switching providers](/docs/features/database#restore-or-switch-providers)
- [Local PostgreSQL](/docs/development/setup#local-postgresql)
- [Deployment and previews](/docs/deploy)

Sources: [foundation](https://github.com/CarlosZiegler/template-cloudflare/commit/c50a66a), [tenant requests](https://github.com/CarlosZiegler/template-cloudflare/commit/a0f3bd0), [verify on apply](https://github.com/CarlosZiegler/template-cloudflare/commit/bd44355), [Hyperdrive caching](https://github.com/CarlosZiegler/template-cloudflare/commit/40aa79c), [pipelining](https://github.com/CarlosZiegler/template-cloudflare/commit/39d14a3), [preview-only probe](https://github.com/CarlosZiegler/template-cloudflare/commit/4e96220), [preview Hyperdrive and secrets](https://github.com/CarlosZiegler/template-cloudflare/commit/9fdc5fe).


## Earlier updates

- 2026-10-15: [AI assistant and one tool catalog](/docs/changelog/2026-10-15-assistant-and-agent-tools)
- 2026-10-15: [Dependency round, i18next fix and ReUI controls](/docs/changelog/2026-10-15-dependency-round-and-reui)
- 2026-10-15: [Developer documentation and changelog](/docs/changelog/2026-10-15-developer-docs-and-changelog)
- 2026-10-15: [Documents and large uploads](/docs/changelog/2026-10-15-documents-and-large-uploads)
- 2026-10-15: [Landing page redesign](/docs/changelog/2026-10-15-landing-redesign)
- 2026-10-15: [Landing sections and project skills](/docs/changelog/2026-10-15-landing-sections-and-project-skills)
- 2026-10-14: [Trace and code block primitives](/docs/changelog/2026-10-14-trace-and-code-blocks)
- 2026-10-13: [TanStack Start 1.0, OG images and durable imports](/docs/changelog/2026-10-13-start-og-and-imports)
- 2026-10-12: [Payments, native screens and a new landing page](/docs/changelog/2026-10-12-payments-and-native-experience)
- 2026-10-03: [Runtime and recovery hardening](/docs/changelog/2026-10-03-runtime-and-recovery)
- 2026-09-25: [Authentication foundations and performance](/docs/changelog/2026-09-25-auth-and-performance)
- 2026-09-24: [Platform dependency alignment](/docs/changelog/2026-09-24-platform-dependencies)
- 2026-09-03: [oRPC and Drizzle upgrades](/docs/changelog/2026-09-03-typed-api-upgrade)
- 2026-08-25: [Typed domain operations and a real dashboard](/docs/changelog/2026-08-25-typed-domain-and-dashboard)
- 2026-08-24: [Agent connections and native account security](/docs/changelog/2026-08-24-agent-connections-and-account-security)
- 2026-08-13: [Cloudflare email and shared UI](/docs/changelog/2026-08-13-email-and-ui)
- 2026-07-30: [An enforced client/server boundary](/docs/changelog/2026-07-30-client-boundary)
- 2026-07-24: [Native interface foundations](/docs/changelog/2026-07-24-native-foundation)
- 2026-07-13: [Administration and data workflows](/docs/changelog/2026-07-13-admin-and-data-workflows)
- 2026-06-10: [Setup, storage and localization](/docs/changelog/2026-06-10-setup-storage-and-i18n)
