Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c5ff51e
feat(web): refresh application surfaces
maria-rcks Jul 22, 2026
b6a2563
style(web): polish dark mode dialogs
maria-rcks Jul 22, 2026
14b6bfd
fix(web): unify and tune glass surfaces
juliusmarminge Jul 23, 2026
29b1abc
fix(web): apply glass opacity to thread tooltip
maria-rcks Jul 23, 2026
6e5df67
fix(web): override thread tooltip background
maria-rcks Jul 23, 2026
10da67b
fix(web): apply glass opacity to model picker
maria-rcks Jul 23, 2026
0b1ce58
style(web): polish glass opacity slider
maria-rcks Jul 23, 2026
5961d36
fix(web): apply glass opacity to command palette
maria-rcks Jul 23, 2026
d330759
fix(web): add glass composer alerts
maria-rcks Jul 23, 2026
160d97f
fix(web): honor composer glass opacity
maria-rcks Jul 23, 2026
39cd15b
test(web): stabilize timeline module setup
maria-rcks Jul 23, 2026
c38225e
style(web): fade settings content beneath navbar
maria-rcks Jul 23, 2026
936394b
test(desktop): include glass opacity in settings fixture
maria-rcks Jul 23, 2026
9c9916a
fix(web): address redesign review findings
maria-rcks Jul 23, 2026
b44ed83
fix(web): sync provider banner dismissal
maria-rcks Jul 23, 2026
16491a8
fix(web): new-thread defaults ignored for remote environments (#4276)
t3dotgg Jul 23, 2026
fbd7742
feat: add "Auto" runtime mode — AI-reviewed approvals for Codex and C…
t3dotgg Jul 23, 2026
1c9a6de
Add shared t3.json project configuration support (#4317)
juliusmarminge Jul 23, 2026
e51538b
Restore light-mode surfaces and refine dialog styling
juliusmarminge Jul 23, 2026
4d83436
Unify dialog glass and fix composer overlays (#4365)
juliusmarminge Jul 23, 2026
c6b76cd
Merge remote-tracking branch 'upstream/main' into t3code/evaluate-ups…
jetblk Jul 23, 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
1 change: 1 addition & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const clientSettings: ClientSettings = {
dismissedProviderUpdateNotificationKeys: [],
diffIgnoreWhitespace: true,
favorites: [],
glassOpacity: 80,
providerModelPreferences: {},
sidebarAutoSettleAfterDays: 3,
sidebarProjectGroupingMode: "repository_path",
Expand Down
1 change: 1 addition & 0 deletions apps/marketing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"typecheck": "astro check"
},
"dependencies": {
"@t3tools/shared": "workspace:*",
"astro": "^7.0.3"
},
"devDependencies": {
Expand Down
10 changes: 10 additions & 0 deletions apps/marketing/src/pages/schema/t3.json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { APIRoute } from "astro";

import { buildT3ProjectFileJsonSchema } from "@t3tools/shared/t3ProjectFile";

// Rendered at build time; published at https://t3.codes/schema/t3.json so
// t3.json files can reference it via "$schema" for editor/LSP support.
export const GET: APIRoute = () =>
new Response(`${JSON.stringify(buildT3ProjectFileJsonSchema(), null, 2)}\n`, {
headers: { "Content-Type": "application/json" },
});
2 changes: 1 addition & 1 deletion apps/marketing/vercel.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { VercelConfig } from "@vercel/config/v1";

export const config: VercelConfig = {
installCommand: "npm install -g vite-plus && vp install --filter '@t3tools/marketing'",
installCommand: "npm install -g vite-plus && vp install --filter '@t3tools/marketing...'",
buildCommand: "vp run --filter @t3tools/marketing build",
outputDirectory: "dist",
};
5 changes: 4 additions & 1 deletion apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -582,10 +582,13 @@ export function NewTaskDraftScreen(props: {
? "Approve actions"
: flow.runtimeMode === "auto-accept-edits"
? "Auto-accept edits"
: "Full access",
: flow.runtimeMode === "auto"
? "Auto"
: "Full access",
subactions: [
{ id: "options:runtime:approval-required", title: "Approve actions" },
{ id: "options:runtime:auto-accept-edits", title: "Auto-accept edits" },
{ id: "options:runtime:auto", title: "Auto" },
{ id: "options:runtime:full-access", title: "Full access" },
].map((option) => {
const value = option.id.replace("options:runtime:", "");
Expand Down
5 changes: 4 additions & 1 deletion apps/mobile/src/features/threads/ThreadComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -638,10 +638,13 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
? "Approve actions"
: currentRuntimeMode === "auto-accept-edits"
? "Auto-accept edits"
: "Full access",
: currentRuntimeMode === "auto"
? "Auto"
: "Full access",
subactions: [
{ id: "options:runtime:approval-required", title: "Approve actions" },
{ id: "options:runtime:auto-accept-edits", title: "Auto-accept edits" },
{ id: "options:runtime:auto", title: "Auto" },
{ id: "options:runtime:full-access", title: "Full access" },
].map((option) => {
const value = option.id.replace("options:runtime:", "");
Expand Down
6 changes: 5 additions & 1 deletion apps/server/src/assets/AssetAccess.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as PlatformError from "effect/PlatformError";
import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
import * as ServerConfig from "../config.ts";
import * as ProjectFaviconResolver from "../project/ProjectFaviconResolver.ts";
import * as T3ProjectFileLoader from "../project/T3ProjectFileLoader.ts";
import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
import { ASSET_ROUTE_PREFIX, issueAssetUrl, resolveAsset } from "./AssetAccess.ts";

Expand All @@ -20,7 +21,10 @@ const configLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
const testLayer = Layer.mergeAll(
configLayer,
WorkspacePaths.layer,
ProjectFaviconResolver.layer.pipe(Layer.provide(WorkspacePaths.layer)),
ProjectFaviconResolver.layer.pipe(
Layer.provide(WorkspacePaths.layer),
Layer.provide(T3ProjectFileLoader.layer),
),
ServerSecretStore.layer.pipe(Layer.provide(configLayer)),
).pipe(Layer.provideMerge(NodeServices.layer));

Expand Down
67 changes: 65 additions & 2 deletions apps/server/src/project/ProjectFaviconResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ import * as PlatformError from "effect/PlatformError";

import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
import * as ProjectFaviconResolver from "./ProjectFaviconResolver.ts";
import * as T3ProjectFileLoader from "./T3ProjectFileLoader.ts";

const TestLayer = Layer.empty.pipe(
Layer.provideMerge(ProjectFaviconResolver.layer.pipe(Layer.provide(WorkspacePaths.layer))),
Layer.provideMerge(
ProjectFaviconResolver.layer.pipe(
Layer.provide(WorkspacePaths.layer),
Layer.provide(T3ProjectFileLoader.layer),
),
),
Layer.provideMerge(NodeServices.layer),
);

Expand All @@ -37,7 +43,7 @@ const writeTextFile = Effect.fn("writeTextFile")(function* (

const makeResolverWithFileSystem = (fileSystem: FileSystem.FileSystem) =>
ProjectFaviconResolver.make.pipe(
Effect.provide(WorkspacePaths.layer),
Effect.provide([WorkspacePaths.layer, T3ProjectFileLoader.layer]),
Effect.provideService(FileSystem.FileSystem, fileSystem),
);

Expand All @@ -56,6 +62,63 @@ it.layer(TestLayer)("ProjectFaviconResolverLive", (it) => {
}),
);

it.effect("prefers a t3.json iconPath over well-known files", () =>
Effect.gen(function* () {
const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver;
const cwd = yield* makeTempDir;
yield* writeTextFile(cwd, "t3.json", '{ "iconPath": "brand/mark.svg" }');
yield* writeTextFile(cwd, "brand/mark.svg", "<svg>mark</svg>");
yield* writeTextFile(cwd, "favicon.svg", "<svg>favicon</svg>");

const resolved = yield* resolver.resolvePath(cwd);

expect(resolved).not.toBeNull();
expect(resolved).toContain("brand/mark.svg");
}),
);

it.effect("falls back to well-known files when the t3.json iconPath does not exist", () =>
Effect.gen(function* () {
const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver;
const cwd = yield* makeTempDir;
yield* writeTextFile(cwd, "t3.json", '{ "iconPath": "brand/missing.svg" }');
yield* writeTextFile(cwd, "favicon.svg", "<svg>favicon</svg>");

const resolved = yield* resolver.resolvePath(cwd);

expect(resolved).not.toBeNull();
expect(resolved).toContain("favicon.svg");
}),
);

it.effect("ignores invalid t3.json files", () =>
Effect.gen(function* () {
const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver;
const cwd = yield* makeTempDir;
yield* writeTextFile(cwd, "t3.json", "{ not json");
yield* writeTextFile(cwd, "favicon.svg", "<svg>favicon</svg>");

const resolved = yield* resolver.resolvePath(cwd);

expect(resolved).not.toBeNull();
expect(resolved).toContain("favicon.svg");
}),
);

it.effect("does not resolve a t3.json iconPath outside the workspace root", () =>
Effect.gen(function* () {
const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver;
const parent = yield* makeTempDir;
const cwd = `${parent}/app`;
yield* writeTextFile(parent, "secret.svg", "<svg>secret</svg>");
yield* writeTextFile(cwd, "t3.json", '{ "iconPath": "../secret.svg" }');

const resolved = yield* resolver.resolvePath(cwd);

expect(resolved).toBeNull();
}),
);

it.effect("resolves icon hrefs from project source files", () =>
Effect.gen(function* () {
const resolver = yield* ProjectFaviconResolver.ProjectFaviconResolver;
Expand Down
11 changes: 11 additions & 0 deletions apps/server/src/project/ProjectFaviconResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as PlatformError from "effect/PlatformError";
import * as Schema from "effect/Schema";

import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
import * as T3ProjectFileLoader from "./T3ProjectFileLoader.ts";

// Well-known favicon paths checked in order.
const FAVICON_CANDIDATES = [
Expand Down Expand Up @@ -117,6 +118,7 @@ export const make = Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const workspacePaths = yield* WorkspacePaths.WorkspacePaths;
const projectFileLoader = yield* T3ProjectFileLoader.T3ProjectFileLoader;

const resolveIconHref = (href: string): ReadonlyArray<string> => {
const clean = href.replace(/^\//, "");
Expand Down Expand Up @@ -177,6 +179,15 @@ export const make = Effect.gen(function* () {
}),
),
);
// A t3.json iconPath takes precedence over the well-known locations.
const projectFile = yield* projectFileLoader.load(projectCwd);
if (Option.isSome(projectFile) && projectFile.value.iconPath !== undefined) {
const existing = yield* findExistingFile(projectCwd, [projectFile.value.iconPath]);
if (existing) {
return existing;
}
}

for (const candidate of FAVICON_CANDIDATES) {
const existing = yield* findExistingFile(projectCwd, [candidate]);
if (existing) {
Expand Down
89 changes: 89 additions & 0 deletions apps/server/src/project/T3ProjectFileLoader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as NodeServices from "@effect/platform-node/NodeServices";
import { it, describe, expect } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Path from "effect/Path";

import * as T3ProjectFileLoader from "./T3ProjectFileLoader.ts";

const TestLayer = Layer.empty.pipe(
Layer.provideMerge(T3ProjectFileLoader.layer),
Layer.provideMerge(NodeServices.layer),
);

const makeTempDir = Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
return yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3code-project-file-",
});
});

const writeProjectFile = Effect.fn("writeProjectFile")(function* (cwd: string, contents: string) {
const fileSystem = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
yield* fileSystem.writeFileString(path.join(cwd, "t3.json"), contents).pipe(Effect.orDie);
});

it.layer(TestLayer)("T3ProjectFileLoader", (it) => {
describe("load", () => {
it.effect("loads and decodes a valid t3.json", () =>
Effect.gen(function* () {
const loader = yield* T3ProjectFileLoader.T3ProjectFileLoader;
const cwd = yield* makeTempDir;
yield* writeProjectFile(
cwd,
`{
// JSONC is tolerated
"iconPath": "assets/logo.svg",
"scripts": [{ "name": "Dev", "command": "pnpm dev" }],
}`,
);

const loaded = yield* loader.load(cwd);

expect(Option.isSome(loaded)).toBe(true);
if (Option.isSome(loaded)) {
expect(loaded.value.iconPath).toBe("assets/logo.svg");
expect(loaded.value.scripts).toEqual([{ name: "Dev", command: "pnpm dev" }]);
}
}),
);

it.effect("returns none when t3.json is missing", () =>
Effect.gen(function* () {
const loader = yield* T3ProjectFileLoader.T3ProjectFileLoader;
const cwd = yield* makeTempDir;

const loaded = yield* loader.load(cwd);

expect(Option.isNone(loaded)).toBe(true);
}),
);

it.effect("returns none for malformed JSON without failing", () =>
Effect.gen(function* () {
const loader = yield* T3ProjectFileLoader.T3ProjectFileLoader;
const cwd = yield* makeTempDir;
yield* writeProjectFile(cwd, "{ not json");

const loaded = yield* loader.load(cwd);

expect(Option.isNone(loaded)).toBe(true);
}),
);

it.effect("returns none for schema-invalid files without failing", () =>
Effect.gen(function* () {
const loader = yield* T3ProjectFileLoader.T3ProjectFileLoader;
const cwd = yield* makeTempDir;
yield* writeProjectFile(cwd, '{ "scripts": [{ "name": "Dev" }] }');

const loaded = yield* loader.load(cwd);

expect(Option.isNone(loaded)).toBe(true);
}),
);
});
});
Loading