# 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.


## Invitations are not a mail relay

2026-10-29

Invitation emails carry a fixed subject and attributed, link-free user text; only verified owners and admins may send, with daily caps and a rate limit; Reply-To and one product name for every email.

Organization invitations could be used as an open relay: any signed-in account could create an Organization and make the app mail any address, with the Organization name as the subject. The subject is now a constant built from the product name, and the inviter's and Organization's names appear in the body as attributed plain text (`Name (verified address) invited you to join the organization "…"`), HTML-escaped, length-capped and with link-shaped tokens defanged so no client auto-links them. Every template applies the same treatment to names, and the security notice prints a readable UTC timestamp ([Email](/docs/features/email#user-controlled-text), [commit e951425](https://github.com/CarlosZiegler/template-cloudflare/commit/e9514250)).

Sending now requires a verified inviter email even when `AUTH_SKIP_EMAIL_VERIFICATION` is `true`, and is limited to 20 invitations per inviter and 50 per Organization per rolling day, 50 pending invitations per Organization, and 10 `invite-member` requests per 10 minutes per client IP. The default owner, admin and member roles are written down in the plugin configuration. Refusals are typed codes translated in en, de and fr ([Authentication and Organizations](/docs/features/auth#invitations), [commit 88e8671](https://github.com/CarlosZiegler/template-cloudflare/commit/88e86714)). The web invite dialog and the mobile invite group show the verify-your-email state with a button that sends the verification email ([commit b9e63fd](https://github.com/CarlosZiegler/template-cloudflare/commit/b9e63fd1)).

`EMAIL_REPLY_TO` sets a Reply-To on every message through the Worker binding, Cloudflare REST and Resend, and the product name every email prints comes from `EMAIL_FROM`'s display name; the hard-coded fallback is gone. The email guide documents the SPF, DKIM and DMARC records the sending domain needs for either provider ([commit 8f90bc5](https://github.com/CarlosZiegler/template-cloudflare/commit/8f90bc5d)).


## MCP Apps widget, approval status and plugin marketplaces

2026-10-29

The todo widget runs the MCP Apps handshake so Claude and ChatGPT render it, agents can read the outcome of an approval and follow an absolute link to it, the server announces instructions and annotations, and the agent plugin installs from Claude Code, Codex, Cursor and Grok with production URLs.

The `todo_list` widget now renders in hosts that follow the MCP Apps specification. The view performs the `ui/initialize` → `ui/notifications/initialized` handshake the specification requires before a host may send tool data, then renders `ui/notifications/tool-result`; it keeps reading `window.openai.toolOutput` for ChatGPT builds that still inject it. There is one resource, `ui://widget/todos/v2` with `text/html;profile=mcp-app` and an explicit empty CSP; `openai/outputTemplate` is the ChatGPT alias of `_meta.ui.resourceUri` and points at the same URI, so the `text/html+skybridge` duplicate is gone. A jsdom harness plays the host side in the test suite.

The MCP server says what it is. `initialize` names it after the OAuth issuer host instead of the template, returns `instructions` that summarize the approval flow, and declares `listChanged: false` for tools, resources and prompts, since a server built per request never notifies. Every tool sets all four annotations (reads are read-only and idempotent; nothing is destructive or open-world) and write tools publish the approval envelope as their `outputSchema`.

Agents can follow an approval to its end. A write call returns an absolute `approvalUrl`, `/integrations?approval=<id>` on the deployment that served it, which opens Integrations on that record. The new read-only `approval_status` tool reports whether the user executed (with the stored result), denied or let the request expire; the catalog has sixteen tools. The OAuth provider offers the `email` scope so UserInfo returns `email` and `email_verified`, and `/.well-known/openai-apps-challenge` serves the `OPENAI_APPS_CHALLENGE_TOKEN` Worker var during an OpenAI plugin review (404 while unset).

The agent plugin package is installable again. The Cursor and Grok marketplaces pointed at a folder that had been renamed, every manifest used the workers.dev origin instead of the production domain, and `plugin.json` carried a top-level `logo` the Agent Plugins 1.0.0 schema rejects. Listing copy and logo moved under `extensions["com.openai"].interface`, `mcp.json` declares the OAuth client, and new `.claude-plugin/marketplace.json` and `.agents/plugins/marketplace.json` files make it installable from Claude Code, the ChatGPT desktop app and Codex. The connection guide describes ChatGPT's current path, chatgpt.com/plugins → + → Add custom MCP server.

- [MCP and agent connections](/docs/features/mcp)
- [Connect your agent](/docs/connect-your-agent)
- [ADR-0007](/docs/adr/0007-ai-assistant-and-tool-catalog)

Sources: [widget and server](https://github.com/CarlosZiegler/template-cloudflare/commit/f09f9ddc), [approval status](https://github.com/CarlosZiegler/template-cloudflare/commit/19ff59ad), [challenge route and email scope](https://github.com/CarlosZiegler/template-cloudflare/commit/bebc6578), [plugin marketplaces](https://github.com/CarlosZiegler/template-cloudflare/commit/309f43e4).


## One URL per language

2026-10-29

Public pages live at /de/… and /fr/… instead of ?lang=, with a leaf config for slugs, redirects for the old URLs, a first-visit redirect at / and per-language sitemap entries.

The landing, privacy, terms, posts and profiles exist once per language at one path each: the default language at the clean URL, the others under their slug (`/privacy`, `/de/privacy`, `/fr/privacy`). Each is its own indexable page with a self canonical, hreflang for every language plus `x-default`, `og:locale`, `<html lang>` and a share image in its language; share links carry their language without a query. The route files are not duplicated: they moved under `routes/{-$locale}/`, TanStack Router's optional segment ([SEO and discovery](/docs/features/seo), [commit](https://github.com/CarlosZiegler/template-cloudflare/commit/8c3b6384)).

The languages, their URL slugs and optional translated segments are one leaf, `packages/intl/src/locale.config.ts`; a project maps `pt-BR` to `/br` there, and the pure path functions in `locale-paths.ts` (split, localize, canonical form, region-tag matching for `?lang=` and `Accept-Language`) read it. `getSSRLanguage` takes the language from the path on public pages and from the cookie everywhere else ([Internationalization](/docs/features/i18n), [commit](https://github.com/CarlosZiegler/template-cloudflare/commit/dfd319eb)).

The old URLs keep working: `?lang=de`, a region tag (`?lang=de-DE`, `?lang=de_AT`), `/en/privacy` and `/DE/privacy` answer a 301 to the path form with the rest of the query kept; an unknown language goes to the default page. A first human visit to `/` follows the locale cookie, then `Accept-Language`, with a 302 and `Vary: Accept-Language, Cookie`; crawlers, requests without `Accept-Language`, prefixed URLs and POST are never redirected (`rootLocaleRedirect` in the config). The language switcher on public pages is a list of links to the current page in each language and remembers the choice in the cookie; the header, footer, legal links and consent banner link within the page's language, and links to sign-in and the app stay unprefixed. The Markdown copies follow the same paths (`/de/privacy.md`; `Accept: text/markdown` on `/de` returns `/de/index.md`), the sitemap lists every language's URL with `xhtml:link` alternates and `llms.txt` names the per-language documents ([Agent-readable pages](/docs/features/agent-pages)).


## Earlier updates

- 2026-10-29: [Share cards, Markdown negotiation and one site identity](/docs/changelog/2026-10-29-seo-agent-readiness)
- 2026-10-27: [TanStack AI 1.0, password forms and iPhone Duo](/docs/changelog/2026-10-27-deps-password-forms-iphone-duo)
- 2026-10-19: [Agent skills from upstream and the /pr template](/docs/changelog/2026-10-19-agent-skills-update)
- 2026-10-19: [Ponytail from the official plugin](/docs/changelog/2026-10-19-ponytail-plugin)
- 2026-10-17: [One folder per backend feature, tagged errors and written rules](/docs/changelog/2026-10-17-backend-structure)
- 2026-10-17: [One folder per mobile feature, and the API aliases are gone](/docs/changelog/2026-10-17-mobile-structure)
- 2026-10-17: [The web app is organized by feature, like the backend](/docs/changelog/2026-10-17-web-structure)
- 2026-10-16: [Auth and tenant security fixes](/docs/changelog/2026-10-16-auth-and-security-fixes)
- 2026-10-16: [Database, Worker and web performance](/docs/changelog/2026-10-16-performance-and-placement)
- 2026-10-16: [Row-level security per Organization](/docs/changelog/2026-10-16-row-level-security)
- 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)
