Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
2d8f4c8
feat(web): add modular theme library
StiensWout Aug 2, 2026
53b10c9
fix(web): smooth theme preview gradients
StiensWout Aug 2, 2026
049d508
fix(web): finish theme review fixes
StiensWout Aug 2, 2026
ddab3f4
feat(web): theme status and update colors
StiensWout Aug 2, 2026
8596d82
fix(web): address theme review feedback
StiensWout Aug 2, 2026
d1179d6
test(web): reset theme cache before storage test
StiensWout Aug 2, 2026
45f521d
feat(web): simplify theme editor defaults
StiensWout Aug 2, 2026
c61c2c7
fix(web): unify theme card selection
StiensWout Aug 2, 2026
df1df49
fix(web): refine theme color editor
StiensWout Aug 2, 2026
95066e4
fix(web): use themed dialog for theme deletion
StiensWout Aug 2, 2026
ce74eae
feat(web): edit personal themes
StiensWout Aug 2, 2026
f15bea2
feat(web): add theme color picker
StiensWout Aug 2, 2026
f16de6a
fix(web): guide simple theme palettes
StiensWout Aug 3, 2026
f6ba01c
feat(web): add T3 Grove theme
StiensWout Aug 3, 2026
1f398b7
fix(web): simplify guided theme copy
StiensWout Aug 3, 2026
afb2a2b
fix(web): theme theme editor actions
StiensWout Aug 3, 2026
297cd63
fix(web): theme theme removal action
StiensWout Aug 3, 2026
c92c3ff
fix(web): align theme mode boot behavior
StiensWout Aug 3, 2026
f0b00fb
fix(web): use theme tokens for status accents
StiensWout Aug 3, 2026
d51de87
fix(web): harden theme boot parity, editor state, and token regressions
StiensWout Aug 3, 2026
9dae242
test(web): load the boot script via a raw import
StiensWout Aug 3, 2026
3832b30
fix(web): address Bugbot review on theme storage failure paths
StiensWout Aug 3, 2026
9e32464
fix(web): update every theme-color meta with the resolved chrome color
StiensWout Aug 3, 2026
4252052
chore: retrigger CI after GitHub 503 during checkout
StiensWout Aug 3, 2026
f0da686
feat(web): anchor the T3 Chat theme to the real t3.chat palette
StiensWout Aug 3, 2026
2cf3d56
feat(web): add maintainer theme palettes
StiensWout Aug 4, 2026
f36a424
fix(web): keep theme preferences in sync
StiensWout Aug 4, 2026
04e01c0
fix(web): apply follow-system mode transactionally
StiensWout Aug 4, 2026
cad65da
fix(web): handle theme removal storage failures
StiensWout Aug 4, 2026
a01ef14
fix(web): show removal error for theme delete failures
StiensWout Aug 4, 2026
5096359
feat(web): refine theme mode previews
StiensWout Aug 4, 2026
415a646
refine(web): clarify theme mode previews
StiensWout Aug 4, 2026
9e64055
fix(web): theme workspace action controls
StiensWout Aug 4, 2026
65371a5
fix(web): restore T3 Chat muted text contrast
StiensWout Aug 5, 2026
fca563c
Merge remote-tracking branch 'upstream/main' into t3code/modular-them…
StiensWout Aug 5, 2026
1ca27aa
refine(web): smooth theme preview balls and color picker
StiensWout Aug 5, 2026
b7506a0
fix(web): keep typography previews and managed surfaces on theme
StiensWout Aug 5, 2026
50ea1f0
refactor(web): drop the unused theme mode-suffix grammar
StiensWout Aug 5, 2026
b2d6150
refactor(web): single-source appearance persistence and tagged storag…
StiensWout Aug 5, 2026
6dbd4f5
refine(web): promote the Themes header and tidy theme library internals
StiensWout Aug 5, 2026
2fb2144
test(web): enforce boot splash parity against the real palettes
StiensWout Aug 5, 2026
85b9a07
refactor(web): persist the guided-editor flag on themes
StiensWout Aug 5, 2026
612afc2
fix(web): keep a bare dark preference dark during boot
StiensWout Aug 5, 2026
9b12a5a
refactor(web): split ThemeSettings into focused modules
StiensWout Aug 5, 2026
3f76f9a
fix(web): keep settings restore working when theme storage fails
StiensWout Aug 5, 2026
df13b50
feat(web): give each maintainer theme a companion action color
StiensWout Aug 5, 2026
39b6190
fix(web): lighten dark code surfaces and unify action theming
StiensWout Aug 5, 2026
92988b0
refine(web): tighten theme copy to the T3 voice
StiensWout Aug 5, 2026
073d0e4
fix(web): address theme review feedback
StiensWout Aug 5, 2026
4963d9c
Merge branch 'main' into t3code/modular-theme-system
StiensWout Aug 5, 2026
6d934d3
fix(web): put themed chat code blocks on the code surface
StiensWout Aug 5, 2026
9f7407f
Merge remote-tracking branch 'fork/t3code/modular-theme-system' into …
StiensWout Aug 5, 2026
e63b80d
fix(web): carry the theme hue through generated borders and inputs
StiensWout Aug 5, 2026
fce58e5
fix(web): stop backdrop bleed cutting the search outline; theme the d…
StiensWout Aug 5, 2026
fb8cee5
fix(web): move the remaining diff panel chrome onto the code tokens
StiensWout Aug 5, 2026
d105928
fix(web): theme the tooltip-wrapped toolbar action buttons
StiensWout Aug 5, 2026
d74fa7a
fix(web): keep the Appearance label stable across editor modes
StiensWout Aug 5, 2026
9023378
feat(web): mix light and dark halves from different themes
StiensWout Aug 5, 2026
73a86b4
feat(web): redesign Appearance around a color scheme picker
StiensWout Aug 5, 2026
c0d369d
Merge remote-tracking branch 'upstream/main' into t3code/modular-them…
StiensWout Aug 5, 2026
8799f45
fix(web): harden the theme mix against review findings
StiensWout Aug 5, 2026
71fc9eb
feat(web): surface the theme features in settings search
StiensWout Aug 5, 2026
dbd84be
fix(web): drop the theme-mix settings search entry
StiensWout Aug 5, 2026
4aaa93b
refine(web): deepen the T3 Chat light main panel
StiensWout Aug 5, 2026
fefc555
refine(web): soften the T3 Chat light toolbar seam
StiensWout Aug 5, 2026
3b34f6b
fix(web): restore the mix on failed theme writes; terminal follows ca…
StiensWout Aug 5, 2026
e133c15
fix(web): overhaul the color picker and harden the mix further
StiensWout Aug 5, 2026
d3b3687
fix(web): generated themes carry their own update colors
StiensWout Aug 5, 2026
a9b22d1
feat(web): OKLCH vivid palette engine for user-created themes
StiensWout Aug 5, 2026
afd01e5
feat(web): advanced-drawer theme creator sharing the settings wirefra…
StiensWout Aug 5, 2026
1f0e88b
fix(web): flush pending picker commits and roll back the live theme mix
StiensWout Aug 5, 2026
4622976
fix(web): follow the picked canvas in the vivid engine; hide guided f…
StiensWout Aug 5, 2026
ea10698
refactor(web): drop the t3- prefix from non-T3 maintainer theme ids
StiensWout Aug 5, 2026
4d6c94f
fix(web): ring the default card for unpicked halves and let it take o…
StiensWout Aug 5, 2026
0be8e60
fix(web): roll back the base theme when the default takeover cannot s…
StiensWout Aug 5, 2026
c9fed99
fix(web): unify the top bar with the main panel surface
StiensWout Aug 5, 2026
26b43ed
fix(web): keep the panel layout toggles ghost in themed palettes
StiensWout Aug 5, 2026
d11e5d3
feat(web): seed the theme creator from the active theme and add dupli…
StiensWout Aug 5, 2026
55c1b70
fix(web): keep surviving mix halves when removing the base theme
StiensWout Aug 5, 2026
8f8f6c4
fix(web): guard theme imports by size and accept dropped files
StiensWout Aug 5, 2026
7d424e2
feat(web): import VS Code color themes in the theme drop box
StiensWout Aug 5, 2026
531b08d
fix(web): check the derived foreground against the surface the file r…
StiensWout Aug 5, 2026
12bc98e
feat(web): preview the theme draft on the live app while editing
StiensWout Aug 5, 2026
df9f44e
fix(web): hold the live preview until the editor draft is seeded
StiensWout Aug 5, 2026
17145be
fix(web): release the editor peek when the dialog closes mid-hold
StiensWout Aug 5, 2026
4b59b97
feat(web): edit themes from a floating panel that survives navigation
StiensWout Aug 5, 2026
a4ad1aa
fix(web): save by the path the editor took and reseed on every session
StiensWout Aug 5, 2026
356379b
wip(web): compare theme editor color layouts and steady the panel tog…
StiensWout Aug 6, 2026
85afdad
feat(web): filterable color list in the theme editor
StiensWout Aug 6, 2026
ebe224d
fix(web): stop the panel toggles shifting when the right panel opens
StiensWout Aug 6, 2026
676bcfa
feat(web): drag the theme editor anywhere and mark imported VS Code t…
StiensWout Aug 6, 2026
3b7180f
fix(web): keep single-appearance themes on their own side of the mix
StiensWout Aug 6, 2026
35b80b9
feat(web): tabbed theme import with multi-file upload and family pairing
StiensWout Aug 6, 2026
da7a1f1
feat(web): re-import prompt and file-name disambiguation for theme im…
StiensWout Aug 6, 2026
0473775
feat(desktop): theme file picker opens in the VS Code extensions folder
StiensWout Aug 6, 2026
a2a0596
fix(web): theme the file preview surfaces and stop imports washing in…
StiensWout Aug 6, 2026
0253898
Merge remote-tracking branch 'upstream/main' into t3code/modular-them…
StiensWout Aug 6, 2026
f5e013f
fix(web,desktop): five review fixes across boot, import, and the file…
StiensWout Aug 6, 2026
c9cc740
fix(web): drop the import tabs for the single-view dialog
StiensWout Aug 6, 2026
df1a406
fix(web): derive status colors per palette instead of the default pink
StiensWout Aug 6, 2026
ec21653
feat(web): cast status colors with the palette accent
StiensWout Aug 6, 2026
3d24042
fix(web): fall back to T3 Code's standard status colors
StiensWout Aug 6, 2026
f7d67b9
fix(web): key status colors off the canvas, not the appearance slot
StiensWout Aug 6, 2026
4f7310b
feat(web): merge theme palettes by name
juliusmarminge Aug 6, 2026
89c75e1
feat(web): add theme editor panel resizing
juliusmarminge Aug 6, 2026
551ec5c
fix(web): drop the gradient from the loading screen
StiensWout Aug 6, 2026
6f360b2
fix(web): clamp panel resizing to the space below its own corner
StiensWout Aug 6, 2026
2033017
Merge remote-tracking branch 'upstream/main' into t3code/modular-them…
StiensWout Aug 6, 2026
a038ba1
fix(web): polish modular theme system
maria-rcks Aug 7, 2026
2c8b409
feat(web): add theme token inspection engine
maria-rcks Aug 7, 2026
5811120
feat(web): add interactive theme color inspector
maria-rcks Aug 7, 2026
001e397
fix(web): keep an oversized editor panel reachable and throttle the i…
StiensWout Aug 7, 2026
56f7a61
fix(web): stop a minimized panel jumping on window resize
StiensWout Aug 7, 2026
03a555a
fix(web): roll the merge back when retiring the edited theme fails
StiensWout Aug 7, 2026
1357ef3
Merge remote-tracking branch 'upstream/main' into t3code/modular-them…
StiensWout Aug 7, 2026
ba48826
fix(web): read the live appearance mode when restoring defaults
StiensWout Aug 7, 2026
c580679
Merge remote-tracking branch 'upstream/main' into t3code/modular-them…
StiensWout Aug 7, 2026
71db00e
Delete apps/server/.local/state/gh/device-id
juliusmarminge Aug 7, 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
51 changes: 51 additions & 0 deletions apps/desktop/src/electron/ElectronDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ export class ElectronDialogPickFolderError extends Schema.TaggedErrorClass<Elect
}
}

export class ElectronDialogPickFilesError extends Schema.TaggedErrorClass<ElectronDialogPickFilesError>()(
"ElectronDialogPickFilesError",
{
ownerWindowId: Schema.NullOr(Schema.Number),
defaultPath: Schema.NullOr(Schema.String),
cause: Schema.Defect(),
},
) {
override get message(): string {
const owner = this.ownerWindowId === null ? "the application" : `window ${this.ownerWindowId}`;
const defaultPath = this.defaultPath === null ? "no default path" : this.defaultPath;
return `Failed to open the Electron file picker for ${owner} with ${defaultPath}.`;
}
}

export class ElectronDialogConfirmError extends Schema.TaggedErrorClass<ElectronDialogConfirmError>()(
"ElectronDialogConfirmError",
{
Expand Down Expand Up @@ -69,6 +84,7 @@ export class ElectronDialogShowErrorBoxError extends Schema.TaggedErrorClass<Ele

export const ElectronDialogError = Schema.Union([
ElectronDialogPickFolderError,
ElectronDialogPickFilesError,
ElectronDialogConfirmError,
ElectronDialogShowMessageBoxError,
ElectronDialogShowErrorBoxError,
Expand All @@ -81,6 +97,12 @@ export interface ElectronDialogPickFolderInput {
readonly defaultPath: Option.Option<string>;
}

export interface ElectronDialogPickFilesInput {
readonly owner: Option.Option<Electron.BrowserWindow>;
readonly defaultPath: Option.Option<string>;
readonly filters: readonly Electron.FileFilter[];
}

export interface ElectronDialogConfirmInput {
readonly owner: Option.Option<Electron.BrowserWindow>;
readonly message: string;
Expand All @@ -92,6 +114,9 @@ export class ElectronDialog extends Context.Service<
readonly pickFolder: (
input: ElectronDialogPickFolderInput,
) => Effect.Effect<Option.Option<string>, ElectronDialogPickFolderError>;
readonly pickFiles: (
input: ElectronDialogPickFilesInput,
) => Effect.Effect<readonly string[], ElectronDialogPickFilesError>;
readonly confirm: (
input: ElectronDialogConfirmInput,
) => Effect.Effect<boolean, ElectronDialogConfirmError>;
Expand Down Expand Up @@ -137,6 +162,32 @@ export const make = ElectronDialog.of({
}
return Option.fromNullishOr(result.filePaths[0]);
}),
pickFiles: Effect.fn("desktop.electron.dialog.pickFiles")(function* (input) {
const ownerWindowId = Option.match(input.owner, {
onNone: () => null,
onSome: (owner) => owner.id,
});
const defaultPath = Option.getOrNull(input.defaultPath);
const openDialogOptions: Electron.OpenDialogOptions = {
properties: ["openFile", "multiSelections"],
filters: [...input.filters],
...(defaultPath === null ? {} : { defaultPath }),
};
const result = yield* Effect.tryPromise({
try: () =>
Option.match(input.owner, {
onNone: () => Electron.dialog.showOpenDialog(openDialogOptions),
onSome: (owner) => Electron.dialog.showOpenDialog(owner, openDialogOptions),
}),
catch: (cause) =>
new ElectronDialogPickFilesError({
ownerWindowId,
defaultPath,
cause,
}),
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
});
return result.canceled ? [] : result.filePaths;
}),
confirm: Effect.fn("desktop.electron.dialog.confirm")(function* (input) {
const normalizedMessage = input.message.trim();
if (normalizedMessage.length === 0) {
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/ipc/DesktopIpcHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
getWindowFullscreenState,
openExternal,
pickFolder,
pickThemeFiles,
setTheme,
showContextMenu,
} from "./methods/window.ts";
Expand Down Expand Up @@ -79,6 +80,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
yield* ipc.handle(setWslOnly);

yield* ipc.handle(pickFolder);
yield* ipc.handle(pickThemeFiles);
yield* ipc.handle(confirm);
yield* ipc.handle(setTheme);
yield* ipc.handle(showContextMenu);
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/ipc/channels.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
export const PICK_THEME_FILES_CHANNEL = "desktop:pick-theme-files";
export const CONFIRM_CHANNEL = "desktop:confirm";
export const SET_THEME_CHANNEL = "desktop:set-theme";
export const CONTEXT_MENU_CHANNEL = "desktop:context-menu";
Expand Down
51 changes: 51 additions & 0 deletions apps/desktop/src/ipc/methods/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ import {
DesktopAppBrandingSchema,
DesktopEnvironmentBootstrapSchema,
DesktopThemeSchema,
PickedThemeFileSchema,
PickFolderOptionsSchema,
PRIMARY_LOCAL_ENVIRONMENT_ID,
type DesktopEnvironmentBootstrap,
type PickedThemeFile,
} from "@t3tools/contracts";
import * as NodeOS from "node:os";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
Expand Down Expand Up @@ -268,3 +273,49 @@ export const openExternal = DesktopIpc.makeIpcMethod({
return yield* shell.openExternal(url);
}),
});

/** Theme files are a few KB; anything larger returns empty text and lets the
* renderer reject it by size without the contents ever crossing the bridge. */
const PICKED_THEME_FILE_MAX_BYTES = 256 * 1024;

export const pickThemeFiles = DesktopIpc.makeIpcMethod({
channel: IpcChannels.PICK_THEME_FILES_CHANNEL,
payload: Schema.Undefined,
result: Schema.NullOr(Schema.Array(PickedThemeFileSchema)),
handler: Effect.fn("desktop.ipc.window.pickThemeFiles")(function* () {
const dialog = yield* ElectronDialog.ElectronDialog;
const electronWindow = yield* ElectronWindow.ElectronWindow;
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
// The VS Code extensions directory is the same dotfolder on Windows,
// macOS, and Linux; when it is missing the picker opens wherever the
// platform would by default.
const extensionsDir = path.join(NodeOS.homedir(), ".vscode", "extensions");
const defaultPath = yield* fileSystem
.exists(extensionsDir)
.pipe(Effect.orElseSucceed(() => false));
const paths = yield* dialog.pickFiles({
owner: yield* electronWindow.focusedMainOrFirst,
defaultPath: defaultPath ? Option.some(extensionsDir) : Option.none(),
filters: [{ name: "JSON", extensions: ["json"] }],
});
if (paths.length === 0) {
return null;
}
return yield* Effect.forEach(paths, (filePath) => {
const name = path.basename(filePath);
return Effect.gen(function* () {
const info = yield* fileSystem.stat(filePath);
const size = Number(info.size);
if (size > PICKED_THEME_FILE_MAX_BYTES) {
return { name, size, text: "" } satisfies PickedThemeFile;
}
const text = yield* fileSystem.readFileString(filePath);
return { name, size, text } satisfies PickedThemeFile;
}).pipe(
// An unreadable file degrades to an entry the renderer reports.
Effect.orElseSucceed((): PickedThemeFile => ({ name, size: 0, text: "" })),
);
Comment thread
cursor[bot] marked this conversation as resolved.
});
}),
});
1 change: 1 addition & 0 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ contextBridge.exposeInMainWorld("desktopBridge", {
setWslDistro: (distro) => ipcRenderer.invoke(IpcChannels.SET_WSL_DISTRO_CHANNEL, distro),
setWslOnly: (enabled) => ipcRenderer.invoke(IpcChannels.SET_WSL_ONLY_CHANNEL, enabled),
pickFolder: (options) => ipcRenderer.invoke(IpcChannels.PICK_FOLDER_CHANNEL, options),
pickThemeFiles: () => ipcRenderer.invoke(IpcChannels.PICK_THEME_FILES_CHANNEL, undefined),
confirm: (message) => ipcRenderer.invoke(IpcChannels.CONFIRM_CHANNEL, message),
setTheme: (theme) => ipcRenderer.invoke(IpcChannels.SET_THEME_CHANNEL, theme),
showContextMenu: (items, position) =>
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/window/DesktopApplicationMenu.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const electronAppLayer = Layer.succeed(ElectronApp.ElectronApp, {

const electronDialogLayer = Layer.succeed(ElectronDialog.ElectronDialog, {
pickFolder: () => Effect.succeed(Option.none()),
pickFiles: () => Effect.succeed([]),
confirm: () => Effect.succeed(false),
showMessageBox: () => Effect.succeed({ response: 0, checkboxChecked: false }),
showErrorBox: () => Effect.void,
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/keybindings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ it.layer(NodeServices.layer)("keybindings", (it) => {
assert.equal(defaultsByCommand.get("thread.jump.1"), "mod+1");
assert.equal(defaultsByCommand.get("thread.jump.9"), "mod+9");
assert.equal(defaultsByCommand.get("modelPicker.toggle"), "mod+shift+m");
assert.equal(defaultsByCommand.get("themeEditor.toggle"), "mod+alt+shift+t");
assert.equal(defaultsByCommand.get("filePicker.toggle"), "mod+p");
assert.equal(defaultsByCommand.get("projectSearch.toggle"), "mod+shift+f");
assert.equal(defaultsByCommand.get("sidebar.toggle"), "mod+b");
Expand Down
Loading
Loading