Skip to content

feat(fork): rebrand T3 Code strings at build time - #60

Merged
jamesainslie merged 3 commits into
mainfrom
lathe-brand-layer
Oct 10, 2026
Merged

jamesainslie merged 3 commits into
mainfrom
lathe-brand-layer

Conversation

@jamesainslie

Copy link
Copy Markdown
Owner

About 400 user-visible "T3 Code" strings live in upstream-owned lines across web, desktop, server and packages. The fork's merge rule allows an upstream file only one import and one-line swaps, so rebranding them in source would make every upstream sync conflict. This rebrands the built product instead and fails CI when upstream branding leaks.

How

  • scripts/lib/forkBrand.ts (fork-owned) rewrites only string literals, template-literal static parts and JSX text with the oxc parser that vite-plus already exports. Identifiers, module specifiers, keys, comments, regex literals and type positions are untouched; replacements never span a line and a source map keeps columns right. "T3 Code" becomes FORK_IDENTITY.productBaseName.
  • Hooked into the web Vite config (dev and build, plus index.html), all desktop pack entries, and the server pack (which also feeds the CLI executable). Server dev runs unbundled source, so its dev script loads a Node load hook. Skipped under Vitest, where upstream tests assert upstream strings.
  • Modules are filtered by path and by containing the token before parsing; rebranding every source file takes about 350ms.

Kept as T3 Code (each with a reason in FORK_BRAND_EXCEPTIONS)

Codex clientInfo and the ChatGPT agent_name_hint (vendor-facing identities), the triage playbook (must match upstream byte for byte), the GNOME extension name, "T3 Code Relay API" (upstream's API title) and the "fork of T3 Code" attribution. Tests also pin strings no substitution may ever reach: T3 Connect and the t3.codes hosts, T3CODE_*, refs/t3/, branch prefixes, storage keys, @t3tools/, MCP names and tools, wire paths, t3.json, repo slugs.

Leak check

scripts/brand-leak-check.ts scans built output with an allowlist derived from the same exceptions and names file, line and context. Wired into ci-fork.yml after the bundle verification and into release-fork.yml right after the JS bundle build, before packaging.

Verification

  • forkBrand.test.ts and brand-leak-check.test.ts: 32 tests (rewrites, every skipped syntax, every exception, longest match, untouched files, fixture leaks). Upstream web tests asserting "T3 Code" still pass.
  • Typecheck of scripts, web, desktop and server; lint, format and knip on changed files.
  • Real builds (web, server pack, desktop pack), then the leak check: 0 leaks in 2354 files, against 311 in an unbranded build. 336 replacements in 153 files.
  • Dev: a Vite dev server served rebranded code; server dev printed "Lathe (Dev)".

Not covered

docs/ markdown, the marketing site, the mobile app, the GNOME extension's own files, and strings assembled from parts. Choices worth a look: the new-project README footer links to the fork repo, checkpoint commits are authored "Lathe" (email unchanged), and the MCP server's display name (not its t3-code key) becomes Lathe.

Upstream-owned files still say "T3 Code" in about 340 user-visible
strings. Swapping them by hand would make every upstream sync conflict,
so a fork-owned bundler plugin rewrites them in the built product.

scripts/lib/forkBrand.ts parses each module with the oxc parser that
vite-plus exports and rewrites only string literals, template quasis
and JSX text, longest substitution key first. Identifiers, specifiers,
property keys, comments, regex literals and types are untouched, and a
source map keeps stack traces exact. Modules without the token are
never parsed. Exceptions, each with a reason, keep vendor-facing client
identities, the GNOME extension name, the relay API title, the upstream
attribution and the triage playbook as upstream has them.

The web, desktop and server bundling configs each add the plugin, so
dev servers and watch builds show what ships. The server's unbundled
dev mode gets the same rewrite from a Node load hook. Vitest skips it.
scripts/brand-leak-check.ts scans built output for "T3 Code" that no
brand exception covers and names the file, position and context of
each leak. It derives its allowlist from the brand layer's exceptions,
and skips source maps, binaries and JavaScript comments.

CI runs it after the desktop pipeline build, and the release workflow
runs it on the JS bundle before that bundle is uploaded for packaging.
Covers why the product is rebranded at build time, how to add an
exception, the leak check, and what the layer does not reach.
@jamesainslie
jamesainslie merged commit 2baf064 into main Oct 10, 2026
7 checks passed
jamesainslie added a commit that referenced this pull request Oct 16, 2026
Upstream now names Codex's app-server client once, in a CODEX_CLIENT_NAME
constant used for clientInfo and the originator override. The brand
layer's exceptions only matched object properties, so the build would
have rebranded the identity OpenAI sees. Property exceptions now also
match a variable's initializer, in the rebrand step and in the bundle
leak check, and the Codex exception names the constant.

The triage playbook goes back to upstream's text: the brand layer keeps
it byte-identical to upstream and pointed at upstream's issue tracker,
a decision from #60 that the sync had reversed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant