feat(environments): draw each environment as the machine it runs on - #9299

Merged
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons
Sep 3, 2026
Merged

feat(environments): draw each environment as the machine it runs on#9299
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Problem

Every remote environment wore the same glyph (a cloud in the sidebar, a server elsewhere), so a Mac mini, a MacBook, and a Hetzner VM were indistinguishable in the thread list, the "Run on" picker, the pull request server filter, and the mobile lists. In the hosted web app, where there is no local environment, thread rows carried no marker at all.

Fix

Each environment is now drawn as the machine it runs on, from a fixed set: server, cloud VM, desktop, laptop, Mac mini, Mac Studio.

  • Detection. At startup the server reads the IOKit product name (falling back to hw.model on Intel Macs) or, on Linux, the DMI chassis type plus vendor/product strings, with hypervisor and cloud markers mapping to "cloud". The result ships as platform.machine on the environment descriptor; absent when there is no signal (Windows, containers, unknown DMI).
  • Override. A nullable environmentIcon server setting, picked in Settings → Connections. It lives on the server rather than the client so every device connecting to that environment sees the same icon. The picker reads "Automatic (Mac mini)" so you can tell whether detection got it right before overriding.
  • One resolver.resolveEnvironmentMachineKind in contracts holds the precedence (override → detected → generic server) so web and mobile cannot drift. platform.machine decodes forward-compatibly: a kind from a newer server falls back to the generic glyph instead of failing the descriptor.
  • Surfaces. Web sidebar (new and legacy) rows and tooltip, composer "Run on" picker, pull request server filter and act-on picker, Connections settings; mobile thread rows (v1 and v2), Environments and T3 Connect rows. The sidebar marker now shows whenever a thread's environment differs from the primary, which in the hosted app means every row, since the glyph is what tells them apart there.

Lucide has no Apple desktops, so Mac mini and Mac Studio are drawn in its grammar. On iOS the SF Symbols macmini and macstudio are used; Android falls back to the closest Tabler silhouettes.

Icons

Server · Mac mini · Mac Studio · Desktop · Laptop (cloud is lucide's stock icon)

environment machine icons

Verification

  • 12 new server tests cover the Apple and DMI mappers and the mocked darwin / linux / win32 probe paths; 4 new contracts tests cover the resolver precedence and the forward-compat decode.
  • Verified detection on real hardware: a 2024 Mac mini resolves mac-mini, a GMKtec NucBox on Linux resolves desktop.
  • Typecheck passes for contracts, server, client-runtime, desktop, web, and mobile; existing sidebar, BranchToolbar, pull request assignment, and settings search suites pass.

Built with Claude Fable 5 in Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
Presentation and optional server settings with best-effort hardware probes that fail safely to null; forward-compatible schema handling limits version-skew breakage.

Overview
Environments are no longer shown as a generic cloud/server everywhere. Each one can display a machine glyph (server, cloud VM, desktop, laptop, Mac mini, Mac Studio) derived from hardware detection, an optional user override, or a generic server fallback.

Server & contracts: Startup detection fills environment.platform.machine (macOS IOKit/hw.model, Linux DMI with VM→cloud heuristics) and advertises an environmentIcon capability. A nullable settings.environmentIcon override is stored on the server so all clients agree. resolveEnvironmentMachineKind centralizes precedence (override → detected → "server"), with forward-compatible decoding for unknown kinds.

Clients: Web and mobile add shared icon components (EnvironmentMachineIcon / EnvironmentMachineSymbol) plus an Environment icon picker in Connections (gated on connect, server version, and operate access). Thread sidebars, connection rows, composer “Run on”, and pull-request server pickers/filters now use these glyphs instead of one-size-fits-all icons; remote-thread markers also apply when the primary environment differs (including hosted/mobile where every row is remote).

Reviewed by Cursor Bugbot for commit ca9f250. Bugbot is set up for automated code reviews on this repo. Configure here.

Every remote environment used to wear the same cloud or server glyph, so a
Mac mini, a laptop, and a Hetzner VM were indistinguishable in the sidebar,
the Run on picker, the pull request filters, and the mobile lists. In the
hosted app, where every environment is remote, rows carried no marker at all.
Servers now detect their hardware at startup (IOKit product name or hw.model
on macOS, DMI chassis type and virtualization markers on Linux) and publish
it as platform.machine. A nullable environmentIcon server setting lets the
user override it from Settings -> Connections, stored on that server so every
device agrees. resolveEnvironmentMachineKind in contracts holds the
precedence so web and mobile cannot drift, and platform.machine decodes
forward-compatibly so a kind from a newer server falls back to a server
glyph instead of failing the descriptor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.5 KiB+177 B (+1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.6 KiB+177 B (+2.7%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB57.0 KiB+1.4 KiB (+2.6%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.3 KiB−154 B (−1.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.4 KiB−151 B (−2.2%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB56.4 KiB−1.4 KiB (−2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ef6cc0b · PR result: ca9f250 · 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.

@juliusmarmingejuliusmarminge added the preview:web Deploy a hosted-web preview to Vercel for this PR on every push. label Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Web preview

https://t3code-673kpkord-pinglabs.vercel.app (for ca9f250)

Open this exact URL — the hosted-app origin is baked in at build time.
Pair a server into it with t3 pair --tailscale, or paste a host + pairing
code under Settings → Connections.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit b74b5f6. Configure here.

Comment threadpackages/contracts/src/settings.ts
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new persisted environment-icon capability and server-side hardware detection, then changes default icon behavior across web and mobile thread, connection, run-on, and pull-request surfaces. Because existing users receive changed defaults and the implementation spans multiple runtime layers, human review is warranted.

Not approved because:

  • Monthly spending limit reached (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

Julius Marmingeand others added 2 commits September 2, 2026 18:33
The environmentIcon setting was a closed NullOr(EnvironmentMachineKind), so
an override picked on a newer server would fail the whole ServerSettings
decode for an older client, while platform.machine already tolerated
unknown kinds. Both now degrade to the generic server glyph.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The picker was live on every connected environment, including servers that
predate the setting (which drop the key on write) and sessions without the
operate scope. It now stays visible so the current icon still reads, but
goes inert with a tooltip in those cases, gated the same way the provider
settings are: a new environmentIcon capability on the descriptor, plus the
session's scopes for that environment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit 0fbb942 into mainSep 3, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/environment-specific-icons branch September 3, 2026 02:06
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview:webDeploy a hosted-web preview to Vercel for this PR on every push.size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(environments): draw each environment as the machine it runs on - #9299

Merged
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons
Sep 3, 2026
Merged

feat(environments): draw each environment as the machine it runs on#9299
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Problem

Every remote environment wore the same glyph (a cloud in the sidebar, a server elsewhere), so a Mac mini, a MacBook, and a Hetzner VM were indistinguishable in the thread list, the "Run on" picker, the pull request server filter, and the mobile lists. In the hosted web app, where there is no local environment, thread rows carried no marker at all.

Fix

Each environment is now drawn as the machine it runs on, from a fixed set: server, cloud VM, desktop, laptop, Mac mini, Mac Studio.

  • Detection. At startup the server reads the IOKit product name (falling back to hw.model on Intel Macs) or, on Linux, the DMI chassis type plus vendor/product strings, with hypervisor and cloud markers mapping to "cloud". The result ships as platform.machine on the environment descriptor; absent when there is no signal (Windows, containers, unknown DMI).
  • Override. A nullable environmentIcon server setting, picked in Settings → Connections. It lives on the server rather than the client so every device connecting to that environment sees the same icon. The picker reads "Automatic (Mac mini)" so you can tell whether detection got it right before overriding.
  • One resolver.resolveEnvironmentMachineKind in contracts holds the precedence (override → detected → generic server) so web and mobile cannot drift. platform.machine decodes forward-compatibly: a kind from a newer server falls back to the generic glyph instead of failing the descriptor.
  • Surfaces. Web sidebar (new and legacy) rows and tooltip, composer "Run on" picker, pull request server filter and act-on picker, Connections settings; mobile thread rows (v1 and v2), Environments and T3 Connect rows. The sidebar marker now shows whenever a thread's environment differs from the primary, which in the hosted app means every row, since the glyph is what tells them apart there.

Lucide has no Apple desktops, so Mac mini and Mac Studio are drawn in its grammar. On iOS the SF Symbols macmini and macstudio are used; Android falls back to the closest Tabler silhouettes.

Icons

Server · Mac mini · Mac Studio · Desktop · Laptop (cloud is lucide's stock icon)

environment machine icons

Verification

  • 12 new server tests cover the Apple and DMI mappers and the mocked darwin / linux / win32 probe paths; 4 new contracts tests cover the resolver precedence and the forward-compat decode.
  • Verified detection on real hardware: a 2024 Mac mini resolves mac-mini, a GMKtec NucBox on Linux resolves desktop.
  • Typecheck passes for contracts, server, client-runtime, desktop, web, and mobile; existing sidebar, BranchToolbar, pull request assignment, and settings search suites pass.

Built with Claude Fable 5 in Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
Presentation and optional server settings with best-effort hardware probes that fail safely to null; forward-compatible schema handling limits version-skew breakage.

Overview
Environments are no longer shown as a generic cloud/server everywhere. Each one can display a machine glyph (server, cloud VM, desktop, laptop, Mac mini, Mac Studio) derived from hardware detection, an optional user override, or a generic server fallback.

Server & contracts: Startup detection fills environment.platform.machine (macOS IOKit/hw.model, Linux DMI with VM→cloud heuristics) and advertises an environmentIcon capability. A nullable settings.environmentIcon override is stored on the server so all clients agree. resolveEnvironmentMachineKind centralizes precedence (override → detected → "server"), with forward-compatible decoding for unknown kinds.

Clients: Web and mobile add shared icon components (EnvironmentMachineIcon / EnvironmentMachineSymbol) plus an Environment icon picker in Connections (gated on connect, server version, and operate access). Thread sidebars, connection rows, composer “Run on”, and pull-request server pickers/filters now use these glyphs instead of one-size-fits-all icons; remote-thread markers also apply when the primary environment differs (including hosted/mobile where every row is remote).

Reviewed by Cursor Bugbot for commit ca9f250. Bugbot is set up for automated code reviews on this repo. Configure here.

Every remote environment used to wear the same cloud or server glyph, so a
Mac mini, a laptop, and a Hetzner VM were indistinguishable in the sidebar,
the Run on picker, the pull request filters, and the mobile lists. In the
hosted app, where every environment is remote, rows carried no marker at all.
Servers now detect their hardware at startup (IOKit product name or hw.model
on macOS, DMI chassis type and virtualization markers on Linux) and publish
it as platform.machine. A nullable environmentIcon server setting lets the
user override it from Settings -> Connections, stored on that server so every
device agrees. resolveEnvironmentMachineKind in contracts holds the
precedence so web and mobile cannot drift, and platform.machine decodes
forward-compatibly so a kind from a newer server falls back to a server
glyph instead of failing the descriptor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.5 KiB+177 B (+1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.6 KiB+177 B (+2.7%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB57.0 KiB+1.4 KiB (+2.6%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.3 KiB−154 B (−1.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.4 KiB−151 B (−2.2%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB56.4 KiB−1.4 KiB (−2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ef6cc0b · PR result: ca9f250 · 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.

@juliusmarmingejuliusmarminge added the preview:web Deploy a hosted-web preview to Vercel for this PR on every push. label Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Web preview

https://t3code-673kpkord-pinglabs.vercel.app (for ca9f250)

Open this exact URL — the hosted-app origin is baked in at build time.
Pair a server into it with t3 pair --tailscale, or paste a host + pairing
code under Settings → Connections.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit b74b5f6. Configure here.

Comment threadpackages/contracts/src/settings.ts
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new persisted environment-icon capability and server-side hardware detection, then changes default icon behavior across web and mobile thread, connection, run-on, and pull-request surfaces. Because existing users receive changed defaults and the implementation spans multiple runtime layers, human review is warranted.

Not approved because:

  • Monthly spending limit reached (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

Julius Marmingeand others added 2 commits September 2, 2026 18:33
The environmentIcon setting was a closed NullOr(EnvironmentMachineKind), so
an override picked on a newer server would fail the whole ServerSettings
decode for an older client, while platform.machine already tolerated
unknown kinds. Both now degrade to the generic server glyph.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The picker was live on every connected environment, including servers that
predate the setting (which drop the key on write) and sessions without the
operate scope. It now stays visible so the current icon still reads, but
goes inert with a tooltip in those cases, gated the same way the provider
settings are: a new environmentIcon capability on the descriptor, plus the
session's scopes for that environment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit 0fbb942 into mainSep 3, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/environment-specific-icons branch September 3, 2026 02:06
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview:webDeploy a hosted-web preview to Vercel for this PR on every push.size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(environments): draw each environment as the machine it runs on - #9299

Merged
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons
Sep 3, 2026
Merged

feat(environments): draw each environment as the machine it runs on#9299
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Problem

Every remote environment wore the same glyph (a cloud in the sidebar, a server elsewhere), so a Mac mini, a MacBook, and a Hetzner VM were indistinguishable in the thread list, the "Run on" picker, the pull request server filter, and the mobile lists. In the hosted web app, where there is no local environment, thread rows carried no marker at all.

Fix

Each environment is now drawn as the machine it runs on, from a fixed set: server, cloud VM, desktop, laptop, Mac mini, Mac Studio.

  • Detection. At startup the server reads the IOKit product name (falling back to hw.model on Intel Macs) or, on Linux, the DMI chassis type plus vendor/product strings, with hypervisor and cloud markers mapping to "cloud". The result ships as platform.machine on the environment descriptor; absent when there is no signal (Windows, containers, unknown DMI).
  • Override. A nullable environmentIcon server setting, picked in Settings → Connections. It lives on the server rather than the client so every device connecting to that environment sees the same icon. The picker reads "Automatic (Mac mini)" so you can tell whether detection got it right before overriding.
  • One resolver.resolveEnvironmentMachineKind in contracts holds the precedence (override → detected → generic server) so web and mobile cannot drift. platform.machine decodes forward-compatibly: a kind from a newer server falls back to the generic glyph instead of failing the descriptor.
  • Surfaces. Web sidebar (new and legacy) rows and tooltip, composer "Run on" picker, pull request server filter and act-on picker, Connections settings; mobile thread rows (v1 and v2), Environments and T3 Connect rows. The sidebar marker now shows whenever a thread's environment differs from the primary, which in the hosted app means every row, since the glyph is what tells them apart there.

Lucide has no Apple desktops, so Mac mini and Mac Studio are drawn in its grammar. On iOS the SF Symbols macmini and macstudio are used; Android falls back to the closest Tabler silhouettes.

Icons

Server · Mac mini · Mac Studio · Desktop · Laptop (cloud is lucide's stock icon)

environment machine icons

Verification

  • 12 new server tests cover the Apple and DMI mappers and the mocked darwin / linux / win32 probe paths; 4 new contracts tests cover the resolver precedence and the forward-compat decode.
  • Verified detection on real hardware: a 2024 Mac mini resolves mac-mini, a GMKtec NucBox on Linux resolves desktop.
  • Typecheck passes for contracts, server, client-runtime, desktop, web, and mobile; existing sidebar, BranchToolbar, pull request assignment, and settings search suites pass.

Built with Claude Fable 5 in Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
Presentation and optional server settings with best-effort hardware probes that fail safely to null; forward-compatible schema handling limits version-skew breakage.

Overview
Environments are no longer shown as a generic cloud/server everywhere. Each one can display a machine glyph (server, cloud VM, desktop, laptop, Mac mini, Mac Studio) derived from hardware detection, an optional user override, or a generic server fallback.

Server & contracts: Startup detection fills environment.platform.machine (macOS IOKit/hw.model, Linux DMI with VM→cloud heuristics) and advertises an environmentIcon capability. A nullable settings.environmentIcon override is stored on the server so all clients agree. resolveEnvironmentMachineKind centralizes precedence (override → detected → "server"), with forward-compatible decoding for unknown kinds.

Clients: Web and mobile add shared icon components (EnvironmentMachineIcon / EnvironmentMachineSymbol) plus an Environment icon picker in Connections (gated on connect, server version, and operate access). Thread sidebars, connection rows, composer “Run on”, and pull-request server pickers/filters now use these glyphs instead of one-size-fits-all icons; remote-thread markers also apply when the primary environment differs (including hosted/mobile where every row is remote).

Reviewed by Cursor Bugbot for commit ca9f250. Bugbot is set up for automated code reviews on this repo. Configure here.

Every remote environment used to wear the same cloud or server glyph, so a
Mac mini, a laptop, and a Hetzner VM were indistinguishable in the sidebar,
the Run on picker, the pull request filters, and the mobile lists. In the
hosted app, where every environment is remote, rows carried no marker at all.
Servers now detect their hardware at startup (IOKit product name or hw.model
on macOS, DMI chassis type and virtualization markers on Linux) and publish
it as platform.machine. A nullable environmentIcon server setting lets the
user override it from Settings -> Connections, stored on that server so every
device agrees. resolveEnvironmentMachineKind in contracts holds the
precedence so web and mobile cannot drift, and platform.machine decodes
forward-compatibly so a kind from a newer server falls back to a server
glyph instead of failing the descriptor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.5 KiB+177 B (+1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.6 KiB+177 B (+2.7%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB57.0 KiB+1.4 KiB (+2.6%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.3 KiB−154 B (−1.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.4 KiB−151 B (−2.2%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB56.4 KiB−1.4 KiB (−2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ef6cc0b · PR result: ca9f250 · 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.

@juliusmarmingejuliusmarminge added the preview:web Deploy a hosted-web preview to Vercel for this PR on every push. label Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Web preview

https://t3code-673kpkord-pinglabs.vercel.app (for ca9f250)

Open this exact URL — the hosted-app origin is baked in at build time.
Pair a server into it with t3 pair --tailscale, or paste a host + pairing
code under Settings → Connections.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit b74b5f6. Configure here.

Comment threadpackages/contracts/src/settings.ts
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new persisted environment-icon capability and server-side hardware detection, then changes default icon behavior across web and mobile thread, connection, run-on, and pull-request surfaces. Because existing users receive changed defaults and the implementation spans multiple runtime layers, human review is warranted.

Not approved because:

  • Monthly spending limit reached (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

Julius Marmingeand others added 2 commits September 2, 2026 18:33
The environmentIcon setting was a closed NullOr(EnvironmentMachineKind), so
an override picked on a newer server would fail the whole ServerSettings
decode for an older client, while platform.machine already tolerated
unknown kinds. Both now degrade to the generic server glyph.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The picker was live on every connected environment, including servers that
predate the setting (which drop the key on write) and sessions without the
operate scope. It now stays visible so the current icon still reads, but
goes inert with a tooltip in those cases, gated the same way the provider
settings are: a new environmentIcon capability on the descriptor, plus the
session's scopes for that environment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit 0fbb942 into mainSep 3, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/environment-specific-icons branch September 3, 2026 02:06
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview:webDeploy a hosted-web preview to Vercel for this PR on every push.size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(environments): draw each environment as the machine it runs on - #9299

Merged
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons
Sep 3, 2026
Merged

feat(environments): draw each environment as the machine it runs on#9299
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Problem

Every remote environment wore the same glyph (a cloud in the sidebar, a server elsewhere), so a Mac mini, a MacBook, and a Hetzner VM were indistinguishable in the thread list, the "Run on" picker, the pull request server filter, and the mobile lists. In the hosted web app, where there is no local environment, thread rows carried no marker at all.

Fix

Each environment is now drawn as the machine it runs on, from a fixed set: server, cloud VM, desktop, laptop, Mac mini, Mac Studio.

  • Detection. At startup the server reads the IOKit product name (falling back to hw.model on Intel Macs) or, on Linux, the DMI chassis type plus vendor/product strings, with hypervisor and cloud markers mapping to "cloud". The result ships as platform.machine on the environment descriptor; absent when there is no signal (Windows, containers, unknown DMI).
  • Override. A nullable environmentIcon server setting, picked in Settings → Connections. It lives on the server rather than the client so every device connecting to that environment sees the same icon. The picker reads "Automatic (Mac mini)" so you can tell whether detection got it right before overriding.
  • One resolver.resolveEnvironmentMachineKind in contracts holds the precedence (override → detected → generic server) so web and mobile cannot drift. platform.machine decodes forward-compatibly: a kind from a newer server falls back to the generic glyph instead of failing the descriptor.
  • Surfaces. Web sidebar (new and legacy) rows and tooltip, composer "Run on" picker, pull request server filter and act-on picker, Connections settings; mobile thread rows (v1 and v2), Environments and T3 Connect rows. The sidebar marker now shows whenever a thread's environment differs from the primary, which in the hosted app means every row, since the glyph is what tells them apart there.

Lucide has no Apple desktops, so Mac mini and Mac Studio are drawn in its grammar. On iOS the SF Symbols macmini and macstudio are used; Android falls back to the closest Tabler silhouettes.

Icons

Server · Mac mini · Mac Studio · Desktop · Laptop (cloud is lucide's stock icon)

environment machine icons

Verification

  • 12 new server tests cover the Apple and DMI mappers and the mocked darwin / linux / win32 probe paths; 4 new contracts tests cover the resolver precedence and the forward-compat decode.
  • Verified detection on real hardware: a 2024 Mac mini resolves mac-mini, a GMKtec NucBox on Linux resolves desktop.
  • Typecheck passes for contracts, server, client-runtime, desktop, web, and mobile; existing sidebar, BranchToolbar, pull request assignment, and settings search suites pass.

Built with Claude Fable 5 in Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
Presentation and optional server settings with best-effort hardware probes that fail safely to null; forward-compatible schema handling limits version-skew breakage.

Overview
Environments are no longer shown as a generic cloud/server everywhere. Each one can display a machine glyph (server, cloud VM, desktop, laptop, Mac mini, Mac Studio) derived from hardware detection, an optional user override, or a generic server fallback.

Server & contracts: Startup detection fills environment.platform.machine (macOS IOKit/hw.model, Linux DMI with VM→cloud heuristics) and advertises an environmentIcon capability. A nullable settings.environmentIcon override is stored on the server so all clients agree. resolveEnvironmentMachineKind centralizes precedence (override → detected → "server"), with forward-compatible decoding for unknown kinds.

Clients: Web and mobile add shared icon components (EnvironmentMachineIcon / EnvironmentMachineSymbol) plus an Environment icon picker in Connections (gated on connect, server version, and operate access). Thread sidebars, connection rows, composer “Run on”, and pull-request server pickers/filters now use these glyphs instead of one-size-fits-all icons; remote-thread markers also apply when the primary environment differs (including hosted/mobile where every row is remote).

Reviewed by Cursor Bugbot for commit ca9f250. Bugbot is set up for automated code reviews on this repo. Configure here.

Every remote environment used to wear the same cloud or server glyph, so a
Mac mini, a laptop, and a Hetzner VM were indistinguishable in the sidebar,
the Run on picker, the pull request filters, and the mobile lists. In the
hosted app, where every environment is remote, rows carried no marker at all.
Servers now detect their hardware at startup (IOKit product name or hw.model
on macOS, DMI chassis type and virtualization markers on Linux) and publish
it as platform.machine. A nullable environmentIcon server setting lets the
user override it from Settings -> Connections, stored on that server so every
device agrees. resolveEnvironmentMachineKind in contracts holds the
precedence so web and mobile cannot drift, and platform.machine decodes
forward-compatibly so a kind from a newer server falls back to a server
glyph instead of failing the descriptor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.5 KiB+177 B (+1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.6 KiB+177 B (+2.7%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB57.0 KiB+1.4 KiB (+2.6%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.3 KiB−154 B (−1.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.4 KiB−151 B (−2.2%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB56.4 KiB−1.4 KiB (−2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ef6cc0b · PR result: ca9f250 · 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.

@juliusmarmingejuliusmarminge added the preview:web Deploy a hosted-web preview to Vercel for this PR on every push. label Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Web preview

https://t3code-673kpkord-pinglabs.vercel.app (for ca9f250)

Open this exact URL — the hosted-app origin is baked in at build time.
Pair a server into it with t3 pair --tailscale, or paste a host + pairing
code under Settings → Connections.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit b74b5f6. Configure here.

Comment threadpackages/contracts/src/settings.ts
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new persisted environment-icon capability and server-side hardware detection, then changes default icon behavior across web and mobile thread, connection, run-on, and pull-request surfaces. Because existing users receive changed defaults and the implementation spans multiple runtime layers, human review is warranted.

Not approved because:

  • Monthly spending limit reached (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

Julius Marmingeand others added 2 commits September 2, 2026 18:33
The environmentIcon setting was a closed NullOr(EnvironmentMachineKind), so
an override picked on a newer server would fail the whole ServerSettings
decode for an older client, while platform.machine already tolerated
unknown kinds. Both now degrade to the generic server glyph.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The picker was live on every connected environment, including servers that
predate the setting (which drop the key on write) and sessions without the
operate scope. It now stays visible so the current icon still reads, but
goes inert with a tooltip in those cases, gated the same way the provider
settings are: a new environmentIcon capability on the descriptor, plus the
session's scopes for that environment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit 0fbb942 into mainSep 3, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/environment-specific-icons branch September 3, 2026 02:06
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview:webDeploy a hosted-web preview to Vercel for this PR on every push.size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(environments): draw each environment as the machine it runs on - #9299

Merged
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons
Sep 3, 2026
Merged

feat(environments): draw each environment as the machine it runs on#9299
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Problem

Every remote environment wore the same glyph (a cloud in the sidebar, a server elsewhere), so a Mac mini, a MacBook, and a Hetzner VM were indistinguishable in the thread list, the "Run on" picker, the pull request server filter, and the mobile lists. In the hosted web app, where there is no local environment, thread rows carried no marker at all.

Fix

Each environment is now drawn as the machine it runs on, from a fixed set: server, cloud VM, desktop, laptop, Mac mini, Mac Studio.

  • Detection. At startup the server reads the IOKit product name (falling back to hw.model on Intel Macs) or, on Linux, the DMI chassis type plus vendor/product strings, with hypervisor and cloud markers mapping to "cloud". The result ships as platform.machine on the environment descriptor; absent when there is no signal (Windows, containers, unknown DMI).
  • Override. A nullable environmentIcon server setting, picked in Settings → Connections. It lives on the server rather than the client so every device connecting to that environment sees the same icon. The picker reads "Automatic (Mac mini)" so you can tell whether detection got it right before overriding.
  • One resolver.resolveEnvironmentMachineKind in contracts holds the precedence (override → detected → generic server) so web and mobile cannot drift. platform.machine decodes forward-compatibly: a kind from a newer server falls back to the generic glyph instead of failing the descriptor.
  • Surfaces. Web sidebar (new and legacy) rows and tooltip, composer "Run on" picker, pull request server filter and act-on picker, Connections settings; mobile thread rows (v1 and v2), Environments and T3 Connect rows. The sidebar marker now shows whenever a thread's environment differs from the primary, which in the hosted app means every row, since the glyph is what tells them apart there.

Lucide has no Apple desktops, so Mac mini and Mac Studio are drawn in its grammar. On iOS the SF Symbols macmini and macstudio are used; Android falls back to the closest Tabler silhouettes.

Icons

Server · Mac mini · Mac Studio · Desktop · Laptop (cloud is lucide's stock icon)

environment machine icons

Verification

  • 12 new server tests cover the Apple and DMI mappers and the mocked darwin / linux / win32 probe paths; 4 new contracts tests cover the resolver precedence and the forward-compat decode.
  • Verified detection on real hardware: a 2024 Mac mini resolves mac-mini, a GMKtec NucBox on Linux resolves desktop.
  • Typecheck passes for contracts, server, client-runtime, desktop, web, and mobile; existing sidebar, BranchToolbar, pull request assignment, and settings search suites pass.

Built with Claude Fable 5 in Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
Presentation and optional server settings with best-effort hardware probes that fail safely to null; forward-compatible schema handling limits version-skew breakage.

Overview
Environments are no longer shown as a generic cloud/server everywhere. Each one can display a machine glyph (server, cloud VM, desktop, laptop, Mac mini, Mac Studio) derived from hardware detection, an optional user override, or a generic server fallback.

Server & contracts: Startup detection fills environment.platform.machine (macOS IOKit/hw.model, Linux DMI with VM→cloud heuristics) and advertises an environmentIcon capability. A nullable settings.environmentIcon override is stored on the server so all clients agree. resolveEnvironmentMachineKind centralizes precedence (override → detected → "server"), with forward-compatible decoding for unknown kinds.

Clients: Web and mobile add shared icon components (EnvironmentMachineIcon / EnvironmentMachineSymbol) plus an Environment icon picker in Connections (gated on connect, server version, and operate access). Thread sidebars, connection rows, composer “Run on”, and pull-request server pickers/filters now use these glyphs instead of one-size-fits-all icons; remote-thread markers also apply when the primary environment differs (including hosted/mobile where every row is remote).

Reviewed by Cursor Bugbot for commit ca9f250. Bugbot is set up for automated code reviews on this repo. Configure here.

Every remote environment used to wear the same cloud or server glyph, so a
Mac mini, a laptop, and a Hetzner VM were indistinguishable in the sidebar,
the Run on picker, the pull request filters, and the mobile lists. In the
hosted app, where every environment is remote, rows carried no marker at all.
Servers now detect their hardware at startup (IOKit product name or hw.model
on macOS, DMI chassis type and virtualization markers on Linux) and publish
it as platform.machine. A nullable environmentIcon server setting lets the
user override it from Settings -> Connections, stored on that server so every
device agrees. resolveEnvironmentMachineKind in contracts holds the
precedence so web and mobile cannot drift, and platform.machine decodes
forward-compatibly so a kind from a newer server falls back to a server
glyph instead of failing the descriptor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.5 KiB+177 B (+1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.6 KiB+177 B (+2.7%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB57.0 KiB+1.4 KiB (+2.6%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.3 KiB−154 B (−1.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.4 KiB−151 B (−2.2%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB56.4 KiB−1.4 KiB (−2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ef6cc0b · PR result: ca9f250 · 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.

@juliusmarmingejuliusmarminge added the preview:web Deploy a hosted-web preview to Vercel for this PR on every push. label Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Web preview

https://t3code-673kpkord-pinglabs.vercel.app (for ca9f250)

Open this exact URL — the hosted-app origin is baked in at build time.
Pair a server into it with t3 pair --tailscale, or paste a host + pairing
code under Settings → Connections.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit b74b5f6. Configure here.

Comment threadpackages/contracts/src/settings.ts
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new persisted environment-icon capability and server-side hardware detection, then changes default icon behavior across web and mobile thread, connection, run-on, and pull-request surfaces. Because existing users receive changed defaults and the implementation spans multiple runtime layers, human review is warranted.

Not approved because:

  • Monthly spending limit reached (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

Julius Marmingeand others added 2 commits September 2, 2026 18:33
The environmentIcon setting was a closed NullOr(EnvironmentMachineKind), so
an override picked on a newer server would fail the whole ServerSettings
decode for an older client, while platform.machine already tolerated
unknown kinds. Both now degrade to the generic server glyph.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The picker was live on every connected environment, including servers that
predate the setting (which drop the key on write) and sessions without the
operate scope. It now stays visible so the current icon still reads, but
goes inert with a tooltip in those cases, gated the same way the provider
settings are: a new environmentIcon capability on the descriptor, plus the
session's scopes for that environment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit 0fbb942 into mainSep 3, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/environment-specific-icons branch September 3, 2026 02:06
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview:webDeploy a hosted-web preview to Vercel for this PR on every push.size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(environments): draw each environment as the machine it runs on - #9299

Merged
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons
Sep 3, 2026
Merged

feat(environments): draw each environment as the machine it runs on#9299
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Problem

Every remote environment wore the same glyph (a cloud in the sidebar, a server elsewhere), so a Mac mini, a MacBook, and a Hetzner VM were indistinguishable in the thread list, the "Run on" picker, the pull request server filter, and the mobile lists. In the hosted web app, where there is no local environment, thread rows carried no marker at all.

Fix

Each environment is now drawn as the machine it runs on, from a fixed set: server, cloud VM, desktop, laptop, Mac mini, Mac Studio.

  • Detection. At startup the server reads the IOKit product name (falling back to hw.model on Intel Macs) or, on Linux, the DMI chassis type plus vendor/product strings, with hypervisor and cloud markers mapping to "cloud". The result ships as platform.machine on the environment descriptor; absent when there is no signal (Windows, containers, unknown DMI).
  • Override. A nullable environmentIcon server setting, picked in Settings → Connections. It lives on the server rather than the client so every device connecting to that environment sees the same icon. The picker reads "Automatic (Mac mini)" so you can tell whether detection got it right before overriding.
  • One resolver.resolveEnvironmentMachineKind in contracts holds the precedence (override → detected → generic server) so web and mobile cannot drift. platform.machine decodes forward-compatibly: a kind from a newer server falls back to the generic glyph instead of failing the descriptor.
  • Surfaces. Web sidebar (new and legacy) rows and tooltip, composer "Run on" picker, pull request server filter and act-on picker, Connections settings; mobile thread rows (v1 and v2), Environments and T3 Connect rows. The sidebar marker now shows whenever a thread's environment differs from the primary, which in the hosted app means every row, since the glyph is what tells them apart there.

Lucide has no Apple desktops, so Mac mini and Mac Studio are drawn in its grammar. On iOS the SF Symbols macmini and macstudio are used; Android falls back to the closest Tabler silhouettes.

Icons

Server · Mac mini · Mac Studio · Desktop · Laptop (cloud is lucide's stock icon)

environment machine icons

Verification

  • 12 new server tests cover the Apple and DMI mappers and the mocked darwin / linux / win32 probe paths; 4 new contracts tests cover the resolver precedence and the forward-compat decode.
  • Verified detection on real hardware: a 2024 Mac mini resolves mac-mini, a GMKtec NucBox on Linux resolves desktop.
  • Typecheck passes for contracts, server, client-runtime, desktop, web, and mobile; existing sidebar, BranchToolbar, pull request assignment, and settings search suites pass.

Built with Claude Fable 5 in Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
Presentation and optional server settings with best-effort hardware probes that fail safely to null; forward-compatible schema handling limits version-skew breakage.

Overview
Environments are no longer shown as a generic cloud/server everywhere. Each one can display a machine glyph (server, cloud VM, desktop, laptop, Mac mini, Mac Studio) derived from hardware detection, an optional user override, or a generic server fallback.

Server & contracts: Startup detection fills environment.platform.machine (macOS IOKit/hw.model, Linux DMI with VM→cloud heuristics) and advertises an environmentIcon capability. A nullable settings.environmentIcon override is stored on the server so all clients agree. resolveEnvironmentMachineKind centralizes precedence (override → detected → "server"), with forward-compatible decoding for unknown kinds.

Clients: Web and mobile add shared icon components (EnvironmentMachineIcon / EnvironmentMachineSymbol) plus an Environment icon picker in Connections (gated on connect, server version, and operate access). Thread sidebars, connection rows, composer “Run on”, and pull-request server pickers/filters now use these glyphs instead of one-size-fits-all icons; remote-thread markers also apply when the primary environment differs (including hosted/mobile where every row is remote).

Reviewed by Cursor Bugbot for commit ca9f250. Bugbot is set up for automated code reviews on this repo. Configure here.

Every remote environment used to wear the same cloud or server glyph, so a
Mac mini, a laptop, and a Hetzner VM were indistinguishable in the sidebar,
the Run on picker, the pull request filters, and the mobile lists. In the
hosted app, where every environment is remote, rows carried no marker at all.
Servers now detect their hardware at startup (IOKit product name or hw.model
on macOS, DMI chassis type and virtualization markers on Linux) and publish
it as platform.machine. A nullable environmentIcon server setting lets the
user override it from Settings -> Connections, stored on that server so every
device agrees. resolveEnvironmentMachineKind in contracts holds the
precedence so web and mobile cannot drift, and platform.machine decodes
forward-compatibly so a kind from a newer server falls back to a server
glyph instead of failing the descriptor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.5 KiB+177 B (+1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.6 KiB+177 B (+2.7%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB57.0 KiB+1.4 KiB (+2.6%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.3 KiB−154 B (−1.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.4 KiB−151 B (−2.2%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB56.4 KiB−1.4 KiB (−2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ef6cc0b · PR result: ca9f250 · 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.

@juliusmarmingejuliusmarminge added the preview:web Deploy a hosted-web preview to Vercel for this PR on every push. label Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Web preview

https://t3code-673kpkord-pinglabs.vercel.app (for ca9f250)

Open this exact URL — the hosted-app origin is baked in at build time.
Pair a server into it with t3 pair --tailscale, or paste a host + pairing
code under Settings → Connections.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit b74b5f6. Configure here.

Comment threadpackages/contracts/src/settings.ts
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new persisted environment-icon capability and server-side hardware detection, then changes default icon behavior across web and mobile thread, connection, run-on, and pull-request surfaces. Because existing users receive changed defaults and the implementation spans multiple runtime layers, human review is warranted.

Not approved because:

  • Monthly spending limit reached (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

Julius Marmingeand others added 2 commits September 2, 2026 18:33
The environmentIcon setting was a closed NullOr(EnvironmentMachineKind), so
an override picked on a newer server would fail the whole ServerSettings
decode for an older client, while platform.machine already tolerated
unknown kinds. Both now degrade to the generic server glyph.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The picker was live on every connected environment, including servers that
predate the setting (which drop the key on write) and sessions without the
operate scope. It now stays visible so the current icon still reads, but
goes inert with a tooltip in those cases, gated the same way the provider
settings are: a new environmentIcon capability on the descriptor, plus the
session's scopes for that environment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit 0fbb942 into mainSep 3, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/environment-specific-icons branch September 3, 2026 02:06
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview:webDeploy a hosted-web preview to Vercel for this PR on every push.size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(environments): draw each environment as the machine it runs on - #9299

Merged
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons
Sep 3, 2026
Merged

feat(environments): draw each environment as the machine it runs on#9299
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Problem

Every remote environment wore the same glyph (a cloud in the sidebar, a server elsewhere), so a Mac mini, a MacBook, and a Hetzner VM were indistinguishable in the thread list, the "Run on" picker, the pull request server filter, and the mobile lists. In the hosted web app, where there is no local environment, thread rows carried no marker at all.

Fix

Each environment is now drawn as the machine it runs on, from a fixed set: server, cloud VM, desktop, laptop, Mac mini, Mac Studio.

  • Detection. At startup the server reads the IOKit product name (falling back to hw.model on Intel Macs) or, on Linux, the DMI chassis type plus vendor/product strings, with hypervisor and cloud markers mapping to "cloud". The result ships as platform.machine on the environment descriptor; absent when there is no signal (Windows, containers, unknown DMI).
  • Override. A nullable environmentIcon server setting, picked in Settings → Connections. It lives on the server rather than the client so every device connecting to that environment sees the same icon. The picker reads "Automatic (Mac mini)" so you can tell whether detection got it right before overriding.
  • One resolver.resolveEnvironmentMachineKind in contracts holds the precedence (override → detected → generic server) so web and mobile cannot drift. platform.machine decodes forward-compatibly: a kind from a newer server falls back to the generic glyph instead of failing the descriptor.
  • Surfaces. Web sidebar (new and legacy) rows and tooltip, composer "Run on" picker, pull request server filter and act-on picker, Connections settings; mobile thread rows (v1 and v2), Environments and T3 Connect rows. The sidebar marker now shows whenever a thread's environment differs from the primary, which in the hosted app means every row, since the glyph is what tells them apart there.

Lucide has no Apple desktops, so Mac mini and Mac Studio are drawn in its grammar. On iOS the SF Symbols macmini and macstudio are used; Android falls back to the closest Tabler silhouettes.

Icons

Server · Mac mini · Mac Studio · Desktop · Laptop (cloud is lucide's stock icon)

environment machine icons

Verification

  • 12 new server tests cover the Apple and DMI mappers and the mocked darwin / linux / win32 probe paths; 4 new contracts tests cover the resolver precedence and the forward-compat decode.
  • Verified detection on real hardware: a 2024 Mac mini resolves mac-mini, a GMKtec NucBox on Linux resolves desktop.
  • Typecheck passes for contracts, server, client-runtime, desktop, web, and mobile; existing sidebar, BranchToolbar, pull request assignment, and settings search suites pass.

Built with Claude Fable 5 in Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
Presentation and optional server settings with best-effort hardware probes that fail safely to null; forward-compatible schema handling limits version-skew breakage.

Overview
Environments are no longer shown as a generic cloud/server everywhere. Each one can display a machine glyph (server, cloud VM, desktop, laptop, Mac mini, Mac Studio) derived from hardware detection, an optional user override, or a generic server fallback.

Server & contracts: Startup detection fills environment.platform.machine (macOS IOKit/hw.model, Linux DMI with VM→cloud heuristics) and advertises an environmentIcon capability. A nullable settings.environmentIcon override is stored on the server so all clients agree. resolveEnvironmentMachineKind centralizes precedence (override → detected → "server"), with forward-compatible decoding for unknown kinds.

Clients: Web and mobile add shared icon components (EnvironmentMachineIcon / EnvironmentMachineSymbol) plus an Environment icon picker in Connections (gated on connect, server version, and operate access). Thread sidebars, connection rows, composer “Run on”, and pull-request server pickers/filters now use these glyphs instead of one-size-fits-all icons; remote-thread markers also apply when the primary environment differs (including hosted/mobile where every row is remote).

Reviewed by Cursor Bugbot for commit ca9f250. Bugbot is set up for automated code reviews on this repo. Configure here.

Every remote environment used to wear the same cloud or server glyph, so a
Mac mini, a laptop, and a Hetzner VM were indistinguishable in the sidebar,
the Run on picker, the pull request filters, and the mobile lists. In the
hosted app, where every environment is remote, rows carried no marker at all.
Servers now detect their hardware at startup (IOKit product name or hw.model
on macOS, DMI chassis type and virtualization markers on Linux) and publish
it as platform.machine. A nullable environmentIcon server setting lets the
user override it from Settings -> Connections, stored on that server so every
device agrees. resolveEnvironmentMachineKind in contracts holds the
precedence so web and mobile cannot drift, and platform.machine decodes
forward-compatibly so a kind from a newer server falls back to a server
glyph instead of failing the descriptor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.5 KiB+177 B (+1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.6 KiB+177 B (+2.7%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB57.0 KiB+1.4 KiB (+2.6%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.3 KiB−154 B (−1.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.4 KiB−151 B (−2.2%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB56.4 KiB−1.4 KiB (−2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ef6cc0b · PR result: ca9f250 · 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.

@juliusmarmingejuliusmarminge added the preview:web Deploy a hosted-web preview to Vercel for this PR on every push. label Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Web preview

https://t3code-673kpkord-pinglabs.vercel.app (for ca9f250)

Open this exact URL — the hosted-app origin is baked in at build time.
Pair a server into it with t3 pair --tailscale, or paste a host + pairing
code under Settings → Connections.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit b74b5f6. Configure here.

Comment threadpackages/contracts/src/settings.ts
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new persisted environment-icon capability and server-side hardware detection, then changes default icon behavior across web and mobile thread, connection, run-on, and pull-request surfaces. Because existing users receive changed defaults and the implementation spans multiple runtime layers, human review is warranted.

Not approved because:

  • Monthly spending limit reached (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

Julius Marmingeand others added 2 commits September 2, 2026 18:33
The environmentIcon setting was a closed NullOr(EnvironmentMachineKind), so
an override picked on a newer server would fail the whole ServerSettings
decode for an older client, while platform.machine already tolerated
unknown kinds. Both now degrade to the generic server glyph.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The picker was live on every connected environment, including servers that
predate the setting (which drop the key on write) and sessions without the
operate scope. It now stays visible so the current icon still reads, but
goes inert with a tooltip in those cases, gated the same way the provider
settings are: a new environmentIcon capability on the descriptor, plus the
session's scopes for that environment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit 0fbb942 into mainSep 3, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/environment-specific-icons branch September 3, 2026 02:06
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview:webDeploy a hosted-web preview to Vercel for this PR on every push.size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(environments): draw each environment as the machine it runs on - #9299

Merged
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons
Sep 3, 2026
Merged

feat(environments): draw each environment as the machine it runs on#9299
juliusmarminge merged 3 commits into
mainfrom
t3code/environment-specific-icons

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Sep 3, 2026

Copy link
Copy Markdown
Member

Problem

Every remote environment wore the same glyph (a cloud in the sidebar, a server elsewhere), so a Mac mini, a MacBook, and a Hetzner VM were indistinguishable in the thread list, the "Run on" picker, the pull request server filter, and the mobile lists. In the hosted web app, where there is no local environment, thread rows carried no marker at all.

Fix

Each environment is now drawn as the machine it runs on, from a fixed set: server, cloud VM, desktop, laptop, Mac mini, Mac Studio.

  • Detection. At startup the server reads the IOKit product name (falling back to hw.model on Intel Macs) or, on Linux, the DMI chassis type plus vendor/product strings, with hypervisor and cloud markers mapping to "cloud". The result ships as platform.machine on the environment descriptor; absent when there is no signal (Windows, containers, unknown DMI).
  • Override. A nullable environmentIcon server setting, picked in Settings → Connections. It lives on the server rather than the client so every device connecting to that environment sees the same icon. The picker reads "Automatic (Mac mini)" so you can tell whether detection got it right before overriding.
  • One resolver.resolveEnvironmentMachineKind in contracts holds the precedence (override → detected → generic server) so web and mobile cannot drift. platform.machine decodes forward-compatibly: a kind from a newer server falls back to the generic glyph instead of failing the descriptor.
  • Surfaces. Web sidebar (new and legacy) rows and tooltip, composer "Run on" picker, pull request server filter and act-on picker, Connections settings; mobile thread rows (v1 and v2), Environments and T3 Connect rows. The sidebar marker now shows whenever a thread's environment differs from the primary, which in the hosted app means every row, since the glyph is what tells them apart there.

Lucide has no Apple desktops, so Mac mini and Mac Studio are drawn in its grammar. On iOS the SF Symbols macmini and macstudio are used; Android falls back to the closest Tabler silhouettes.

Icons

Server · Mac mini · Mac Studio · Desktop · Laptop (cloud is lucide's stock icon)

environment machine icons

Verification

  • 12 new server tests cover the Apple and DMI mappers and the mocked darwin / linux / win32 probe paths; 4 new contracts tests cover the resolver precedence and the forward-compat decode.
  • Verified detection on real hardware: a 2024 Mac mini resolves mac-mini, a GMKtec NucBox on Linux resolves desktop.
  • Typecheck passes for contracts, server, client-runtime, desktop, web, and mobile; existing sidebar, BranchToolbar, pull request assignment, and settings search suites pass.

Built with Claude Fable 5 in Claude Code.

🤖 Generated with Claude Code


Note

Low Risk
Presentation and optional server settings with best-effort hardware probes that fail safely to null; forward-compatible schema handling limits version-skew breakage.

Overview
Environments are no longer shown as a generic cloud/server everywhere. Each one can display a machine glyph (server, cloud VM, desktop, laptop, Mac mini, Mac Studio) derived from hardware detection, an optional user override, or a generic server fallback.

Server & contracts: Startup detection fills environment.platform.machine (macOS IOKit/hw.model, Linux DMI with VM→cloud heuristics) and advertises an environmentIcon capability. A nullable settings.environmentIcon override is stored on the server so all clients agree. resolveEnvironmentMachineKind centralizes precedence (override → detected → "server"), with forward-compatible decoding for unknown kinds.

Clients: Web and mobile add shared icon components (EnvironmentMachineIcon / EnvironmentMachineSymbol) plus an Environment icon picker in Connections (gated on connect, server version, and operate access). Thread sidebars, connection rows, composer “Run on”, and pull-request server pickers/filters now use these glyphs instead of one-size-fits-all icons; remote-thread markers also apply when the primary environment differs (including hosted/mobile where every row is remote).

Reviewed by Cursor Bugbot for commit ca9f250. Bugbot is set up for automated code reviews on this repo. Configure here.

Every remote environment used to wear the same cloud or server glyph, so a
Mac mini, a laptop, and a Hetzner VM were indistinguishable in the sidebar,
the Run on picker, the pull request filters, and the mobile lists. In the
hosted app, where every environment is remote, rows carried no marker at all.
Servers now detect their hardware at startup (IOKit product name or hw.model
on macOS, DMI chassis type and virtualization markers on Linux) and publish
it as platform.machine. A nullable environmentIcon server setting lets the
user override it from Settings -> Connections, stored on that server so every
device agrees. resolveEnvironmentMachineKind in contracts holds the
precedence so web and mobile cannot drift, and platform.machine decodes
forward-compatibly so a kind from a newer server falls back to a server
glyph instead of failing the descriptor.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.5 KiB+177 B (+1.3%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB0 B (0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.6 KiB+177 B (+2.7%)7.8 KiB
CodexLive turn WebSocket decoded55.6 KiB57.0 KiB+1.4 KiB (+2.6%)66.4 KiB
CodexLive turn messages10100 (0.0%)21
ClaudeTotal thread wire13.5 KiB13.3 KiB−154 B (−1.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.6 KiB6.4 KiB−151 B (−2.2%)7.8 KiB
ClaudeLive turn WebSocket decoded57.8 KiB56.4 KiB−1.4 KiB (−2.5%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: ef6cc0b · PR result: ca9f250 · 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.

@juliusmarmingejuliusmarminge added the preview:web Deploy a hosted-web preview to Vercel for this PR on every push. label Sep 3, 2026
@github-actions

github-actionsBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Web preview

https://t3code-673kpkord-pinglabs.vercel.app (for ca9f250)

Open this exact URL — the hosted-app origin is baked in at build time.
Pair a server into it with t3 pair --tailscale, or paste a host + pairing
code under Settings → Connections.

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit b74b5f6. Configure here.

Comment threadpackages/contracts/src/settings.ts
@macroscopeapp

macroscopeappBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new persisted environment-icon capability and server-side hardware detection, then changes default icon behavior across web and mobile thread, connection, run-on, and pull-request surfaces. Because existing users receive changed defaults and the implementation spans multiple runtime layers, human review is warranted.

Not approved because:

  • Monthly spending limit reached (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more.

Julius Marmingeand others added 2 commits September 2, 2026 18:33
The environmentIcon setting was a closed NullOr(EnvironmentMachineKind), so
an override picked on a newer server would fail the whole ServerSettings
decode for an older client, while platform.machine already tolerated
unknown kinds. Both now degrade to the generic server glyph.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The picker was live on every connected environment, including servers that
predate the setting (which drop the key on write) and sessions without the
operate scope. It now stays visible so the current icon still reads, but
goes inert with a tooltip in those cases, gated the same way the provider
settings are: a new environmentIcon capability on the descriptor, plus the
session's scopes for that environment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit 0fbb942 into mainSep 3, 2026
27 checks passed
@juliusmarminge
juliusmarminge deleted the t3code/environment-specific-icons branch September 3, 2026 02:06
github-actionsBot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 3, 2026
## What's Changed
* chore(ci): only run check-run agents on vouched contributors by @juliusmarminge in pingdotgg/t3code#9298
* fix(web): stop remounting markdown on every activity delta by @juliusmarminge in pingdotgg/t3code#9306
* fix(pull-requests): keep cached PR chrome on reopen by @maria-rcks in pingdotgg/t3code#9294
* feat(environments): draw each environment as the machine it runs on by @juliusmarminge in pingdotgg/t3code#9299
* feat(web): apply and remove labels from the pull request tab by @juliusmarminge in pingdotgg/t3code#9313
* fix(sidebar): collapse settled and snoozed shelves by default by @maria-rcks in pingdotgg/t3code#9314
* refactor(media): unify file and media previews across clients by @juliusmarminge in pingdotgg/t3code#9253
* fix(chat): keep live tool labels in present tense by @maria-rcks in pingdotgg/t3code#9316
* chore: audit lint directives and move plugin allowlists into config by @juliusmarminge in pingdotgg/t3code#9300
* chore(ci): narrow the Effect conventions check-run agent by @juliusmarminge in pingdotgg/t3code#9321
* refactor(mobile): style plain views with Uniwind classes instead of the theme bridge by @juliusmarminge in pingdotgg/t3code#9322
* fix(dev): share dev servers on the loopback Vite actually binds by @juliusmarminge in pingdotgg/t3code#9324
* fix(web): line up the titlebar wordmark label and version pill by @tristanmanchester in pingdotgg/t3code#9255
* fix(web): make the diff layout toggle a persisted setting by @juliusmarminge in pingdotgg/t3code#9326
* chore: dedupe lightningcss and tailwind node bindings by @juliusmarminge in pingdotgg/t3code#9331
* chore: upgrade vite-plus to 0.3.0 by @juliusmarminge in pingdotgg/t3code#9327
* feat(web): add a file tree to the diff panel and pull request code tab by @juliusmarminge in pingdotgg/t3code#9330
* feat(web): add PageUp/PageDown chat navigation by @Yash-Singh1 in pingdotgg/t3code#9315
* fix(web): collapse PR header actions to icons when narrow by @maria-rcks in pingdotgg/t3code#9334
* fix(web): resolve Vite sourcemap and supports warnings by @juliusmarminge in pingdotgg/t3code#9343
* feat(web): choose whether links open in the default browser or in T3 Code by @juliusmarminge in pingdotgg/t3code#9339
* fix(web): add press feedback to buttons by @maria-rcks in pingdotgg/t3code#9349
* feat(web): add customizable project icons by @saphid in pingdotgg/t3code#9137
* fix(mobile): stop indented code overflowing Android chat bubbles by @Adamulek123 in pingdotgg/t3code#9347
* fix(web): let the pull request list use wide screens by @juliusmarminge in pingdotgg/t3code#9351
* feat: display native app and browser icons in work logs by @Yash-Singh1 in pingdotgg/t3code#9093
## New Contributors
* @tristanmanchester made their first contribution in pingdotgg/t3code#9255
**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260903.1262...v0.0.39-nightly.20260903.1265
Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260903.1265
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview:webDeploy a hosted-web preview to Vercel for this PR on every push.size:XL500-999 changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@juliusmarminge