Skip to content
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
69dfb7f
fix(desktop): claim the t3code:// scheme default on Linux at startup …
juliusmarminge Aug 2, 2026
30c9622
fix(web): polish interface spacing (#5252)
maria-rcks Aug 3, 2026
6f04a5c
fix(desktop): Niri/Hyprland - Linux secret storage backend (#2916)
mwolson Aug 3, 2026
a261a64
fix(web): match loading screen to dark theme (#5303)
t3-code-bot[bot] Aug 3, 2026
11639bf
fix(web): blink the terminal cursor again (#5314)
StiensWout Aug 4, 2026
5538672
fix(terminal): protect held Ctrl/Cmd+W close shortcut (#5322)
StiensWout Aug 4, 2026
1cbd88a
fix(server): strip replayable terminal queries from history (#5319)
StiensWout Aug 4, 2026
966cc05
fix(contracts): decode ServerProviders forward-compatibly (#5327)
Brechard Aug 4, 2026
25ec0b9
fix(web): simplify chat code blocks (#5301)
t3-code-bot[bot] Aug 4, 2026
cec1bb9
fix(web): align multiline error alert controls (#5304)
t3-code-bot[bot] Aug 4, 2026
2b1d4fe
Upgrade Effect to beta.103 (#5331)
juliusmarminge Aug 4, 2026
8a16cad
fix(web): increase tooltip z-index to overlay popovers and menus (#5326)
naMqe-h Aug 4, 2026
82b76e2
fix(server): use a Cursor todo's title when its content is blank (#5073)
arhxam Aug 4, 2026
37ae1ab
fix(ssh): isolate managed tunnel processes (#4347)
nateEc Aug 4, 2026
c30a6d9
fix(server): scrub AppImage XDG_DATA_DIRS and GSETTINGS_SCHEMA_DIR fr…
arhxam Aug 4, 2026
94331c5
fix(mobile): show correct provider icons for Grok, Cursor, and OpenCo…
Wraient Aug 4, 2026
90e3778
fix(web): stop the chat timeline reading through the provider status …
Bil0000 Aug 4, 2026
36caf34
fix(desktop,web): contain renderer memory growth and recover from ren…
t3dotgg Aug 4, 2026
d7950ac
fix(server): generate durable thread titles (#5357)
t3dotgg Aug 4, 2026
4fb03af
fix(web): better right panel (new diffs styling) (#5260)
maria-rcks Aug 4, 2026
9bd2a4c
fix(server): keep regenerated titles on topic (#5365)
t3dotgg Aug 4, 2026
2fa1fec
refactor(server): make title prompts plaintext (#5368)
t3dotgg Aug 4, 2026
8eca200
feat(web): configurable fonts and sizes under Settings → Appearance (…
StiensWout Aug 4, 2026
da6e1a9
feat(sidebar-v2): thread pinning for sidebar v2 (#5312)
t3dotgg Aug 4, 2026
fff6a5b
feat(web): make pairing QR codes actually scannable, with endpoint ch…
t3dotgg Aug 4, 2026
94696b0
fix(desktop,web): improve in-app browser shortcuts and URL behavior (…
t3dotgg Aug 5, 2026
7537adc
fix(web): prevent legacy model picker layout shift (#5349)
FllipEis Aug 5, 2026
7b38fb5
fix(server): allow remote updates with database migrations (#5374)
t3dotgg Aug 5, 2026
3d42966
fix(mcp): unblock Kimi models in OpenCode with preview tools (#5128)
hwanseoc Aug 5, 2026
41ebf22
fix(web): clear main branch lint warnings (#5384)
t3dotgg Aug 5, 2026
47dfc65
fix(mobile): preserve grouped project workspaces (#4642)
shivamhwp Aug 5, 2026
70de6e1
fix(mobile): prevent Android thread search crash (#5386)
shivamhwp Aug 5, 2026
3c5bdb8
fix(web): truncate long project switcher names (#5348)
FllipEis Aug 5, 2026
e0c85a2
fix(mobile): avoid double dividers between thread sections (#5391)
shivamhwp Aug 5, 2026
9235c83
fix(web): keep the composer command menu anchored to the composer (#5…
StiensWout Aug 5, 2026
9d9a872
fix(web): restore terminal link hover styles (#5382)
StiensWout Aug 5, 2026
9697b76
fix(ci): isolate releases from shared API rate limits (#5394)
t3dotgg Aug 5, 2026
37d3667
fix(web): keep model picker shortcuts in sync (#5400)
t3dotgg Aug 5, 2026
2a04db1
fix(web): keep terminal font settings reliable (#5397)
StiensWout Aug 5, 2026
de592a0
Enrich terminal font previews (#5428)
juliusmarminge Aug 5, 2026
30e4715
fix(web): preserve terminal font size when splitting (#5444)
t3-code-bot[bot] Aug 5, 2026
7251f1a
Prevent terminal loading flash (#5432)
juliusmarminge Aug 5, 2026
990bb0b
fix: reconnect faster after remote server updates (#5404)
t3dotgg Aug 6, 2026
e429353
Merge remote-tracking branch 'upstream/main' into claude/sync-2026-08-05
NoahHendrickson Aug 6, 2026
cbee1fd
Merge remote-tracking branch 'origin/custom' into claude/sync-2026-08-05
NoahHendrickson Aug 6, 2026
83317d4
fix(fork): re-port early-startup identity and the dark pre-paint lost…
NoahHendrickson Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 52 additions & 39 deletions .fork/customizations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,15 @@
only the five --color-sidebar-v2-status-* utility registrations sit inline
in index.css, because Tailwind reads utility names from the @theme block
alone and will not see them anywhere else.

Upstream's v0.0.32 thread pinning renders on the fork card as a 12px
PinIcon after the title text (upstream draws it on its project line,
which the fork card does not have). Pinned cards paint flat above the
grouped/flat active sections, closed by upstream's thin divider — a pin
freezes prominence across projects, so it deliberately escapes project
grouping — and they lead orderedThreads so keyboard order matches paint
order. Pin/unpin themselves (context menu, decider rules, snooze-beats-
pin classification) are upstream's; the fork only re-seats the rendering.
tier: 4
files:
- apps/web/src/custom/SidebarV2StatusIndicator.tsx
Expand Down Expand Up @@ -871,9 +880,11 @@
(#c0c0c0), still below chrome and foreground (#f1f3f7); lifting titles
to #e0e0e0 collapsed the receded/forward distinction to ~1.19:1.

The stage stays near upstream's pre-paint / overscroll colour (#161616 in
apps/web/index.html) so the load flash and rubber-band band do not seam
against the hydrated stage.
The dark pre-paint / overscroll colour in apps/web/index.html is
fork-owned at #161616 so the load flash and rubber-band band do not seam
against the hydrated #191919 stage. It was upstream's own value until the
v0.0.32 cycle moved upstream to #0a0a0a; a sync taking upstream's
constant flashes near-black against the fork stage on every load.

Every value is flat opaque sRGB, not an alpha over the panel. Alpha
compounds — a fill inside a hovered row inside a selected row lands on a
Expand Down Expand Up @@ -1203,45 +1214,42 @@

- id: geist-typography
intent: >
The app's typefaces are Geist (UI) and Geist Mono (code), bundled as
Fontsource variable faces and imported from the fork's own theme layer —
web and Electron renderer only; mobile and marketing still ship DM Sans.
The app's DEFAULT typefaces are Geist (UI) and Geist Mono (code), bundled
as Fontsource variable faces and imported from the fork's own theme layer —
web and Electron renderer only; mobile and marketing keep upstream's fonts.
Since upstream's v0.0.32 configurable-fonts work (Settings → Appearance:
family, size, smoothing — applied as inline styles on the root by
appearanceFonts.ts), the fork supplies the DEFAULT only: a user's explicit
font preference outranks the fork stacks by design, and clearing the
preference falls back to Geist, not to upstream's system stacks.
The stacks live in theme.custom.css as --fork-font-sans / --fork-font-mono
and index.css's @theme block reads them through
and index.css's non-inline @theme block reads them through
var(--fork-font-sans, <upstream stack>); that indirection is load-bearing,
not ceremony. The block is @theme inline, so Tailwind bakes font values
literally into .font-sans / .font-mono and their variants, and a compiled
utility cannot see a variable overridden in a later stylesheet — the same
constraint that forces the sidebar-v2 palette registrations to live in
index.css. Routing through --fork-font-* keeps the values scoped under the
fork marker while the fallbacks leave an unmarked build on DM Sans.
The terminal's font logic lives in custom/terminalFont.ts rather than in
the drawer: the ghostty surface takes its font as a creation option
instead of from the cascade, so --font-mono has to be resolved by hand at
the mount site. The drawer keeps one one-line call site passing
font.family. Unlike the xterm era there is no fork refit shim: the
surface itself waits on document.fonts before measuring the cell grid and
re-measures on loadingdone, so the webfont-landing-late case is upstream's
to handle now. In an unmarked build the cascade read returns upstream's
stack and no Geist byte is fetched; the module's fallback only engages
when the read comes back empty.
not ceremony — it keeps the values scoped under the fork marker while the
fallbacks leave an unmarked build on upstream's system stacks. Upstream
keeps the font tokens OUT of @theme inline precisely so runtime overrides
can reach them; a sync that moves them back into the inline block bakes
the values into every utility and kills both the fork default and
upstream's own settings feature.
The terminal is the one surface that does not read the cascade: the
ghostty surface takes its font as a creation option, and upstream feeds it
the Settings preference. The fork's default rides the empty-preference
branch of the drawer's terminalFontOptions, returning
FORK_TERMINAL_FONT_FALLBACK (custom/terminalFont.ts) — a constant rather
than a live cascade read, because Advanced typography isolates the
terminal from code-font changes and a computed --font-mono would carry
them in. Unlike the xterm era there is no fork refit shim: the surface
waits on document.fonts before measuring the cell grid and re-measures on
loadingdone, so the webfont-landing-late case is upstream's to handle.
Geist Mono is listed AHEAD of SF Mono: upstream puts SF Mono first, which
means on macOS its bundled mono webfont never renders at all. body / pre,
code are re-declared under
the fork marker because upstream hardcodes the literal stacks on those
selectors (index.css:964, index.css:1022) instead of reading its own
@theme tokens, so overriding --font-sans / --font-mono alone does not
reach them.
DM Sans / JetBrains Mono deps and their main.tsx imports stay, and the two
are not the same case. JetBrains Mono is load-bearing: the fork's own mono
stack still names it as the fallback for a Linux user with no SF Mono and
no Consolas when the Geist Mono fetch fails, and no guard would catch
dropping the face out from under a stack that still names it — the guards
assert the family is listed, not that a bundled face backs it. DM Sans is
inert in a marked build and costs only installer size (~60KB, plus 96KB
JetBrains Mono, emitted because main.tsx still imports the Fontsource
CSS); it is left as-is to keep main.tsx conflict-free and is the one to
revisit if package size matters.
on macOS means a bundled mono webfont never renders at all.
Upstream dropped its DM Sans / JetBrains Mono webfonts in the same cycle
(system stacks now); the fork followed, so the two Geist faces are the
app's only bundled fonts. "JetBrains Mono" stays NAMED in the fork's mono
stacks for a Linux user who has it installed, but no bundled face backs it
any more. Known cosmetic divergence: the Appearance font picker's
"Default" label names the face upstream's DEFAULT_*_FONT_STACK resolves to
(a system face), not Geist — appearanceFonts.ts is deliberately unfenced.
tier: 4
files:
- apps/web/src/theme.custom.css
Expand All @@ -1257,6 +1265,11 @@
# No fence, but load-bearing: ships the resolved --font-sans / --font-mono
# into the previewed page — see FORK-CUSTOMIZATION-DECISIONS.md.
- apps/web/src/browser/annotationTheme.ts
# Unfenced: the Settings → Appearance runtime override machinery whose
# unset-preference path the fork default relies on (removeProperty falls
# back to the stylesheet, i.e. the fork stacks), and whose DEFAULT_*
# stacks label the picker's "Default" entry.
- apps/web/src/appearanceFonts.ts
# Carries the Fontsource deps, which fences can't drift-watch — see
# FORK-CUSTOMIZATION-DECISIONS.md.
- apps/web/package.json
Expand Down
18 changes: 6 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -752,15 +752,9 @@ jobs:
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.build.result == 'success' && needs.publish_cli.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
permissions:
contents: write
steps:
- id: app_token
name: Mint release app token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}

- name: Checkout
uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -828,7 +822,7 @@ jobs:

- name: Publish release
if: needs.preflight.outputs.previous_tag != ''
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ needs.preflight.outputs.tag }}
target_commitish: ${{ needs.preflight.outputs.ref }}
Expand All @@ -845,11 +839,11 @@ jobs:
release-assets/*.blockmap
release-assets/*.yml
fail_on_unmatched_files: true
token: ${{ steps.app_token.outputs.token }}
token: ${{ github.token }}

- name: Publish first release
if: needs.preflight.outputs.previous_tag == ''
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ needs.preflight.outputs.tag }}
target_commitish: ${{ needs.preflight.outputs.ref }}
Expand All @@ -865,7 +859,7 @@ jobs:
release-assets/*.blockmap
release-assets/*.yml
fail_on_unmatched_files: true
token: ${{ steps.app_token.outputs.token }}
token: ${{ github.token }}

deploy_web:
name: Deploy hosted web app
Expand Down
43 changes: 41 additions & 2 deletions apps/desktop/src/app/DesktopApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as Crypto from "effect/Crypto";
import * as ElectronApp from "../electron/ElectronApp.ts";
import * as ElectronDialog from "../electron/ElectronDialog.ts";
import * as ElectronProtocol from "../electron/ElectronProtocol.ts";
import * as ElectronSafeStorage from "../electron/ElectronSafeStorage.ts";
import { installDesktopIpcHandlers } from "../ipc/DesktopIpcHandlers.ts";
import * as DesktopAppIdentity from "./DesktopAppIdentity.ts";
import * as DesktopClerk from "./DesktopClerk.ts";
Expand All @@ -17,7 +18,9 @@ import * as DesktopWindow from "../window/DesktopWindow.ts";
import * as DesktopBackendPool from "../backend/DesktopBackendPool.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";
import * as DesktopLifecycle from "./DesktopLifecycle.ts";
import * as DesktopLinuxUrlHandler from "./DesktopLinuxUrlHandler.ts";
import * as DesktopObservability from "./DesktopObservability.ts";
import * as DesktopPreReadyPlatform from "./DesktopPreReadyPlatform.ts";
import * as DesktopShutdown from "./DesktopShutdown.ts";
import * as DesktopServerExposure from "../backend/DesktopServerExposure.ts";
import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts";
Expand Down Expand Up @@ -220,9 +223,12 @@ const startup = Effect.gen(function* () {
const applicationMenu = yield* DesktopApplicationMenu.DesktopApplicationMenu;
const electronApp = yield* ElectronApp.ElectronApp;
const lifecycle = yield* DesktopLifecycle.DesktopLifecycle;
const linuxUrlHandler = yield* DesktopLinuxUrlHandler.DesktopLinuxUrlHandler;
const clerk = yield* DesktopClerk.DesktopClerk;
const shellEnvironment = yield* DesktopShellEnvironment.DesktopShellEnvironment;
const desktopSettings = yield* DesktopAppSettings.DesktopAppSettings;
const preReadyElectronOptions = yield* DesktopPreReadyPlatform.DesktopPreReadyElectronOptions;
const safeStorage = yield* ElectronSafeStorage.ElectronSafeStorage;
const updates = yield* DesktopUpdates.DesktopUpdates;
const environment = yield* DesktopEnvironment.DesktopEnvironment;

Expand All @@ -241,11 +247,37 @@ const startup = Effect.gen(function* () {
yield* electronApp.setPath("userData", userDataPath);
// fork:end fork-app-identity
yield* shellEnvironment.installIntoProcess;
const hasCommandLinePasswordStore =
preReadyElectronOptions.linuxPasswordStoreCommandLine !== null;
const linuxElectronOptions =
environment.platform === "linux" && !hasCommandLinePasswordStore
? DesktopPreReadyPlatform.resolveEarlyLinuxElectronOptionsFromProcess()
: preReadyElectronOptions.linux;
if (linuxElectronOptions !== null && !hasCommandLinePasswordStore) {
if (
linuxElectronOptions.passwordStore !== null ||
preReadyElectronOptions.linux?.passwordStore !== null
) {
yield* electronApp.removeCommandLineSwitch("password-store");
}
if (linuxElectronOptions.passwordStore !== null) {
yield* electronApp.appendCommandLineSwitch(
"password-store",
linuxElectronOptions.passwordStore,
);
}
}
yield* logStartupInfo("runtime logging configured", { logDir: environment.logDir });
yield* desktopSettings.load;

if (environment.platform === "linux") {
yield* electronApp.appendCommandLineSwitch("class", environment.linuxWmClass);
if (linuxElectronOptions !== null) {
yield* logStartupInfo("linux password store configured", {
passwordStore: hasCommandLinePasswordStore
? "command-line"
: (linuxElectronOptions.passwordStore ?? "electron-default"),
xdgCurrentDesktop: process.env.XDG_CURRENT_DESKTOP ?? null,
xdgSessionDesktop: process.env.XDG_SESSION_DESKTOP ?? null,
});
}

yield* appIdentity.configure;
Expand All @@ -257,9 +289,16 @@ const startup = Effect.gen(function* () {
Effect.catchCause((cause) => fatalStartupCause("whenReady", cause)),
);
yield* logStartupInfo("app ready");
if (environment.platform === "linux") {
const selectedBackend = yield* safeStorage.selectedStorageBackend;
yield* logStartupInfo("safe storage ready", {
backend: Option.getOrElse(selectedBackend, () => "unknown"),
});
}
yield* appIdentity.configure;
yield* applicationMenu.configure;
yield* updates.configure;
yield* linuxUrlHandler.register;
yield* bootstrap.pipe(Effect.catchCause((cause) => fatalStartupCause("bootstrap", cause)));
}).pipe(Effect.withSpan("desktop.startup"));

Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/app/DesktopAppIdentity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const makeElectronAppLayer = (calls: ElectronAppCalls) =>
}),
appendCommandLineSwitch: () => Effect.void,
onBeforeQuitForUpdate: () => Effect.void,
removeCommandLineSwitch: () => Effect.void,
on: () => Effect.void,
} satisfies ElectronApp.ElectronApp["Service"]);

Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/app/DesktopConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const compactEnv = (env: Readonly<Record<string, string | undefined>>): Record<s
export const DesktopConfig = Config.all({
appDataDirectory: trimmedString("APPDATA"),
xdgConfigHome: trimmedString("XDG_CONFIG_HOME"),
xdgDataHome: trimmedString("XDG_DATA_HOME"),
t3Home: trimmedString("T3CODE_HOME"),
devServerUrl: Config.url("VITE_DEV_SERVER_URL").pipe(Config.option),
appUserModelIdOverride: trimmedString("T3CODE_DESKTOP_APP_USER_MODEL_ID"),
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ function makeSafeStorageLayer(available: boolean, failDecrypt: Ref.Ref<boolean>
return decoded.slice("encrypted:".length);
});
},
selectedStorageBackend: Effect.succeed(Option.none()),
} satisfies ElectronSafeStorage.ElectronSafeStorage["Service"]);
}

Expand Down
Loading
Loading