Skip to content

Commit 188a409

Browse files
authored
Merge pull request #68 from pylon-code/upstream/2026-08-23-batch
chore(upstream): adopt the 2026-08-23 T3 Code batch
2 parents 35217db + b9728d7 commit 188a409

145 files changed

Lines changed: 4261 additions & 558 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/upstream-review.md

Lines changed: 126 additions & 4 deletions
Large diffs are not rendered by default.

.macroscope/check-run-agents/ui-consistency.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ The goal is not to minimize CSS or class counts at any cost. The goal is to put
4747
- light-only declarations use `@variant light`;
4848
- raw `.dark` should remain only in the `dark` and `light` custom-variant definitions.
4949
- Preserve custom themes and runtime token bridges. Removing a variable or selector is safe only when all runtime, inspector, generated, and theme-palette consumers are accounted for.
50+
- Contrast and accessibility settings that target app chrome must derive from semantic color tokens. Do not apply `filter` to `html`, `body`, or the app root: it also changes user media, previews, terminals, glass backdrop ownership, and view-transition snapshots.
51+
- Preserve alpha and surface ownership when deriving contrast tokens. Soften translucent borders and inputs toward transparent rather than an opaque canvas, use a modest semantic-foreground mix for stronger borders, and adjust card, popover, accent, secondary, and message foregrounds against their own surfaces when the base foreground changes.
52+
- Runtime-adjusted roles must be ordinary custom properties shared by the Tailwind bridge, global CSS, imperative style strings, and bridge snapshots sent to other renderers. Audit literal `var(--foreground)`, `var(--border)`, and related role reads so headings, markdown chrome, menus, previews, and utilities do not split into adjusted and unadjusted colors.
5053
- Inspect emitted production CSS after unusual variants, arbitrary selectors, nested pseudo-elements, or attribute matching. Source syntax that looks valid is insufficient.
5154
- Flag malformed or empty emitted selectors such as empty `:is()` or `:not(:is())`, selector branches that can never match their own class attribute, and transformations that silently drop the intended rule.
5255
- Prefer source-level logic over clever selectors when behavior depends on consumer-provided class strings. Preserve `MenuPopup`'s current defaulting contract: a string `className` containing a `w-*`, `min-w-*`, or `max-w-*` utility after variant prefixes are stripped suppresses `min-w-32`; a string without one and a functional/non-string `className` keep the default. Arbitrary width values count as width utilities, and the consumer class must be merged last so it retains control. Do not replace this with a raw class-attribute substring selector.

apps/desktop/src/electron/ElectronDialog.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,34 @@ describe("ElectronDialog", () => {
5353
}).pipe(Effect.provide(ElectronDialog.layer)),
5454
);
5555

56+
it.effect("opens a single-file picker when multiple selections are disabled", () =>
57+
Effect.gen(function* () {
58+
showOpenDialogMock.mockResolvedValue({
59+
canceled: false,
60+
filePaths: ["/pictures/icon.png"],
61+
});
62+
const dialog = yield* ElectronDialog.ElectronDialog;
63+
64+
const paths = yield* dialog.pickFiles({
65+
owner: Option.none(),
66+
defaultPath: Option.some("/project"),
67+
filters: [{ name: "Images", extensions: ["png"] }],
68+
multiple: false,
69+
});
70+
71+
assert.deepEqual(paths, ["/pictures/icon.png"]);
72+
assert.deepEqual(showOpenDialogMock.mock.calls, [
73+
[
74+
{
75+
defaultPath: "/project",
76+
filters: [{ name: "Images", extensions: ["png"] }],
77+
properties: ["openFile"],
78+
},
79+
],
80+
]);
81+
}).pipe(Effect.provide(ElectronDialog.layer)),
82+
);
83+
5684
it.effect("preserves message box request context and cause", () =>
5785
Effect.gen(function* () {
5886
const cause = new Error("message box failed");

apps/desktop/src/electron/ElectronDialog.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export interface ElectronDialogPickFilesInput {
8484
readonly owner: Option.Option<Electron.BrowserWindow>;
8585
readonly defaultPath: Option.Option<string>;
8686
readonly filters: readonly Electron.FileFilter[];
87+
readonly multiple: boolean;
8788
}
8889

8990
export class ElectronDialog extends Context.Service<
@@ -144,7 +145,7 @@ export const make = ElectronDialog.of({
144145
});
145146
const defaultPath = Option.getOrNull(input.defaultPath);
146147
const openDialogOptions: Electron.OpenDialogOptions = {
147-
properties: ["openFile", "multiSelections"],
148+
properties: input.multiple ? ["openFile", "multiSelections"] : ["openFile"],
148149
filters: [...input.filters],
149150
...(defaultPath === null ? {} : { defaultPath }),
150151
};

apps/desktop/src/ipc/DesktopIpcHandlers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
openExternal,
4040
probeRemoteEditors,
4141
pickFolder,
42+
pickProjectFavicon,
4243
pickThemeFiles,
4344
setTheme,
4445
showContextMenu,
@@ -82,6 +83,7 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
8283
yield* ipc.handle(setWslOnly);
8384

8485
yield* ipc.handle(pickFolder);
86+
yield* ipc.handle(pickProjectFavicon);
8587
yield* ipc.handle(pickThemeFiles);
8688
yield* ipc.handle(setTheme);
8789
yield* ipc.handle(showContextMenu);

apps/desktop/src/ipc/channels.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export const PICK_FOLDER_CHANNEL = "desktop:pick-folder";
2+
export const PICK_PROJECT_FAVICON_CHANNEL = "desktop:pick-project-favicon";
23
export const PICK_THEME_FILES_CHANNEL = "desktop:pick-theme-files";
34
export const SET_THEME_CHANNEL = "desktop:set-theme";
45
export const CONTEXT_MENU_CHANNEL = "desktop:context-menu";

apps/desktop/src/ipc/methods/window.test.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ import { assert, describe, it } from "@effect/vitest";
22
import * as Effect from "effect/Effect";
33
import * as Layer from "effect/Layer";
44
import * as Option from "effect/Option";
5+
import { vi } from "vite-plus/test";
56

67
import type * as Electron from "electron";
78

89
import * as DesktopBackendManager from "../../backend/DesktopBackendManager.ts";
910
import * as DesktopBackendPool from "../../backend/DesktopBackendPool.ts";
11+
import * as ElectronDialog from "../../electron/ElectronDialog.ts";
1012
import * as ElectronWindow from "../../electron/ElectronWindow.ts";
11-
import { getLocalEnvironmentBootstraps, getWindowFullscreenState } from "./window.ts";
13+
import {
14+
getLocalEnvironmentBootstraps,
15+
getWindowFullscreenState,
16+
pickProjectFavicon,
17+
} from "./window.ts";
1218

1319
const readyWslConfig: DesktopBackendManager.DesktopBackendStartConfig = {
1420
executablePath: "wsl.exe",
@@ -146,3 +152,38 @@ describe("getWindowFullscreenState", () => {
146152
);
147153
});
148154
});
155+
156+
describe("pickProjectFavicon", () => {
157+
it.effect("opens a single-image picker from the project directory", () =>
158+
Effect.gen(function* () {
159+
const pickFiles = vi.fn(() => Effect.succeed(["/pictures/icon.png"]));
160+
const result = yield* pickProjectFavicon.handler("/project").pipe(
161+
Effect.provide(
162+
Layer.mergeAll(
163+
Layer.mock(ElectronDialog.ElectronDialog)({ pickFiles }),
164+
Layer.mock(ElectronWindow.ElectronWindow)({
165+
focusedMainOrFirst: Effect.succeed(Option.none()),
166+
}),
167+
),
168+
),
169+
);
170+
171+
assert.strictEqual(result, "/pictures/icon.png");
172+
assert.deepEqual(pickFiles.mock.calls, [
173+
[
174+
{
175+
owner: Option.none(),
176+
defaultPath: Option.some("/project"),
177+
multiple: false,
178+
filters: [
179+
{
180+
name: "Images",
181+
extensions: ["avif", "gif", "ico", "jpeg", "jpg", "png", "svg", "webp"],
182+
},
183+
],
184+
},
185+
],
186+
]);
187+
}),
188+
);
189+
});

apps/desktop/src/ipc/methods/window.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type DesktopEnvironmentBootstrap,
1313
type PickedThemeFile,
1414
} from "@t3tools/contracts";
15+
import { WORKSPACE_IMAGE_PREVIEW_EXTENSIONS } from "@t3tools/shared/filePreview";
1516
import { isCommandAvailable } from "@t3tools/shared/shell";
1617
import * as NodeOS from "node:os";
1718
import * as FileSystem from "effect/FileSystem";
@@ -234,6 +235,28 @@ export const pickFolder = DesktopIpc.makeIpcMethod({
234235
}),
235236
});
236237

238+
export const pickProjectFavicon = DesktopIpc.makeIpcMethod({
239+
channel: IpcChannels.PICK_PROJECT_FAVICON_CHANNEL,
240+
payload: Schema.UndefinedOr(Schema.String),
241+
result: Schema.NullOr(Schema.String),
242+
handler: Effect.fn("desktop.ipc.window.pickProjectFavicon")(function* (initialPath) {
243+
const dialog = yield* ElectronDialog.ElectronDialog;
244+
const electronWindow = yield* ElectronWindow.ElectronWindow;
245+
const paths = yield* dialog.pickFiles({
246+
owner: yield* electronWindow.focusedMainOrFirst,
247+
defaultPath: Option.fromNullishOr(initialPath),
248+
multiple: false,
249+
filters: [
250+
{
251+
name: "Images",
252+
extensions: WORKSPACE_IMAGE_PREVIEW_EXTENSIONS.map((extension) => extension.slice(1)),
253+
},
254+
],
255+
});
256+
return paths[0] ?? null;
257+
}),
258+
});
259+
237260
export const setTheme = DesktopIpc.makeIpcMethod({
238261
channel: IpcChannels.SET_THEME_CHANNEL,
239262
payload: DesktopThemeSchema,
@@ -323,6 +346,7 @@ export const pickThemeFiles = DesktopIpc.makeIpcMethod({
323346
owner: yield* electronWindow.focusedMainOrFirst,
324347
defaultPath: defaultPath ? Option.some(extensionsDir) : Option.none(),
325348
filters: [{ name: "JSON", extensions: ["json"] }],
349+
multiple: true,
326350
});
327351
if (paths.length === 0) {
328352
return null;

apps/desktop/src/preload.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ contextBridge.exposeInMainWorld("desktopBridge", {
101101
setWslDistro: (distro) => ipcRenderer.invoke(IpcChannels.SET_WSL_DISTRO_CHANNEL, distro),
102102
setWslOnly: (enabled) => ipcRenderer.invoke(IpcChannels.SET_WSL_ONLY_CHANNEL, enabled),
103103
pickFolder: (options) => ipcRenderer.invoke(IpcChannels.PICK_FOLDER_CHANNEL, options),
104+
pickProjectFavicon: (initialPath) =>
105+
ipcRenderer.invoke(IpcChannels.PICK_PROJECT_FAVICON_CHANNEL, initialPath),
104106
pickThemeFiles: () => ipcRenderer.invoke(IpcChannels.PICK_THEME_FILES_CHANNEL, undefined),
105107
setTheme: (theme) => ipcRenderer.invoke(IpcChannels.SET_THEME_CHANNEL, theme),
106108
showContextMenu: (items, position) =>

apps/desktop/src/settings/DesktopClientSettings.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
1313
import * as DesktopClientSettings from "./DesktopClientSettings.ts";
1414

1515
const clientSettings: ClientSettings = {
16+
appearanceContrast: 100,
1617
browserDefaultViewport: { _tag: "preset", width: 1024, height: 600, presetId: "nest-hub" },
1718
browserDefaultZoomFactor: 1.25,
1819
browserDefaultAppearance: "dark",

0 commit comments

Comments
 (0)