Skip to content

feat: let an environment publish themes as a file - #187

Merged
rynfar merged 3 commits into
pylonfrom
upstream/2026-08-29-environment-themes
Aug 30, 2026
Merged

feat: let an environment publish themes as a file#187
rynfar merged 3 commits into
pylonfrom
upstream/2026-08-29-environment-themes

Conversation

@rynfar

@rynfar rynfar commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Problem

Themes lived only in each client's local storage. There was no way for a machine to provide a theme — for a team to ship one, or for a Linux desktop to keep Pylon in step with the system theme — and no way for an environment to set a default.

Fix

Adopts upstream #8569 (c1c2d5401) from the f94a0d646..1f8ed54ad T3 review, plus one adaptation commit and one doc fix.

A machine drops JSON theme files into <stateDir>/themes/. The server watches that directory (debounced, capped at 32 files / 32 KB each / 192 KB total, symlinks refused via O_NOFOLLOW, built-in and reserved ids rejected) and streams the set to subscribers as a new environmentThemesUpdated event on subscribeServerConfig. Web and desktop render each as a non-editable card in the theme library with a Duplicate action. A new t3 theme set|clear|show CLI records defaultTheme in settings.json; clients adopt it once per generation, with the guard persisted so a reload never re-applies.

Compatibility: the event is emitted only to subscribers that pass environmentThemes: true, so older clients see an unchanged payload; the client projection strips themes on reconnect to a server without the capability. Mobile does not request the stream and the user doc says so.

Pylon adaptations

  • Theme file version. Upstream's EnvironmentThemeFile accepts only version: 1; Pylon's exporter has written version: 2 since PR fix(clients): restore upstream status visuals #172. Without the change a theme exported from Pylon was rejected by both the watcher and t3 theme set <file>. The contract now accepts [1, 2], with tests on the watcher and CLI paths for a v2 file and for a v1 file carrying a retired role (which still loads because lenientThemeColorOverrides drops unknown roles).
  • docs/internals/glossary.md link refs [25]/[26] were already taken in Pylon; upstream's became [27]/[28]. All 28 refs verified to resolve.
  • Nine "T3 Code" strings, "T3 Code Mobile", and ~/.t3/userdata/themes/ rewritten across the CLI help, user doc, glossary, and contract JSDoc. t3 theme and T3CODE_HOME precedence are compatibility identifiers and stay.
  • The user doc scopes ~/.pylon-code/userdata/themes/ to a server the Pylon app started, and says a command-line server uses the base directory it was given. That is because resolveBaseDir still defaults to ~/.t3 for a CLI-started server while the desktop resolves ~/.pylon-code — a pre-existing fork-wide inconsistency this feature is the first user doc to depend on. Maintainer decision needed, separately: change the CLI default fork-wide, or leave it as compatibility behaviour.

Verification

  • vp test run across the nine specified files plus cli/theme.test.ts — 281 passed, 1 failed. The failure is upstream's environmentTheme.test.ts > "streams a set for every change to the directory", which waits on a real fs.watch event; fs.watch delivers no events on this macOS host for any directory (confirmed with plain Node, sandboxed and not), and the test file is byte-identical to upstream. CI is Ubuntu/inotify and should be green — confirm on the first run.
  • All five typechecks (t3, @t3tools/web, @t3tools/client-runtime, @t3tools/contracts, @t3tools/mobile) clean; lint and format clean on all 32 files.
  • Independent review hash-compared every file in the pick against upstream (all identical except the glossary renumbering), traced the watcher's O_NOFOLLOW/fstat-before-read/size-cap ordering, the id pattern that excludes path separators, the ws.ts merge (24/2, no Pylon branch lost), the reconnect strip, and the persisted adoption guard.
  • Not done: any runtime pass — live retint when a file is rewritten, the Appearance card and Duplicate flow, t3 theme set against a running server, and reconnect to an older server.

Claude Fable 5 in Pylon.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

ryanrhughes and others added 3 commits August 29, 2026 20:51
(cherry picked from commit c1c2d5401de5a352cf6722959de076c3da63d233)
Pylon's theme exporter has written `version: 2` since PR #172 retired the
`statusActive` and `statusInfo` roles, but the adopted `EnvironmentThemeFile`
schema only accepted `version: 1`. A theme downloaded from Pylon and dropped
into `themes/` was rejected by the watcher and by `t3 theme set <file>`, so
the feature could not round-trip Pylon's own output. The schema now accepts
`1` and `2`; unknown roles are already dropped client-side by
`lenientThemeColorOverrides`, so a v1 file carrying the retired roles still
loads.

Also rewrites the adopted product copy for Pylon: the CLI's invalid-file hint,
the environment-theme user doc (including the runtime home, now
`~/.pylon-code/userdata/themes/`, and Pylon Mobile), the glossary Appearance
entry, and the contracts JSDoc. `t3 theme`, `T3CODE_HOME`, and `.t3` stay as
compatibility identifiers. The glossary's new link references are renumbered
to 27 and 28 so they do not collide with Pylon's existing 25 and 26.
The publishing section said the themes directory is
`~/.pylon-code/userdata/themes/` "by default", which only holds for a server
the Pylon app started. A server started from the command line without
`--base-dir` or `T3CODE_HOME` resolves a different home, so the stated path
would send someone to a directory their server never reads.

Scopes the path to the app-started case, says plainly that a command-line
server uses the base directory it was given, and points at `t3 theme show`
as the way to have the server name that directory itself. Changing the
command-line default is a separate maintainer decision and is not touched
here.
@github-actions github-actions Bot added size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Aug 30, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.3 KiB 13.3 KiB −4 B (−0.0%) 15.1 KiB
Codex Thread snapshot wire 6.9 KiB 6.9 KiB −2 B (−0.0%) 7.3 KiB
Codex Live turn WebSocket wire 6.4 KiB 6.4 KiB −2 B (−0.0%) 7.8 KiB
Codex Live turn WebSocket decoded 55.5 KiB 55.5 KiB 0 B (0.0%) 66.4 KiB
Codex Live turn messages 9 9 0 (0.0%) 21
Claude Total thread wire 13.3 KiB 13.3 KiB −66 B (−0.5%) 15.1 KiB
Claude Thread snapshot wire 6.9 KiB 6.9 KiB −11 B (−0.2%) 7.3 KiB
Claude Live turn WebSocket wire 6.4 KiB 6.4 KiB −55 B (−0.8%) 7.8 KiB
Claude Live turn WebSocket decoded 56.4 KiB 56.3 KiB −44 B (−0.1%) 66.4 KiB
Claude Live turn messages 10 9 −1 (−10.0%) 21

Baseline: abe70a7 · PR result: 77a6cdc · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@rynfar
rynfar merged commit cfe055c into pylon Aug 30, 2026
14 checks passed
@rynfar
rynfar deleted the upstream/2026-08-29-environment-themes branch August 30, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants