Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/desktop",
"version": "0.0.32",
"version": "0.0.33",
"private": true,
"type": "module",
"main": "dist-electron/main.cjs",
Expand Down
10 changes: 10 additions & 0 deletions apps/desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,23 @@ const resolveDesktopSshCliRunner = (
nodeEngineRange: serverPackageJson.engines.node,
};
}
const remoteDesktopAppContents = `/Applications/${environment.displayName}.app/Contents`;
return {
packageSpec: resolveRemoteT3CliPackageSpec({
appVersion: environment.appVersion,
updateChannel: settings.updateChannel,
isDevelopment: environment.isDevelopment,
}),
nodeEngineRange: serverPackageJson.engines.node,
...(environment.isPackaged && environment.platform === "darwin"
? {
desktopCli: {
executablePath: `${remoteDesktopAppContents}/MacOS/${environment.displayName}`,
entryPath: `${remoteDesktopAppContents}/Resources/app.asar/apps/server/dist/bin.mjs`,
version: environment.appVersion,
},
}
: {}),
};
};

Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "t3",
"version": "0.0.32",
"version": "0.0.33",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/web",
"version": "0.0.32",
"version": "0.0.33",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/settings/SettingsPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1649,7 +1649,7 @@ function LegacyFeaturesSection() {
/>
<SettingsRow
{...searchableSetting("legacy-sidebar")}
description="Brings back the original sidebar with per-project thread trees. The default sidebar shows one flat list: active work as rich cards, settled threads as compact rows."
description="Groups threads into expandable project trees. Turn this off to use one flat list with active work as rich cards and settled threads as compact rows."
control={
<Switch
checked={settings.legacySidebarEnabled}
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@t3tools/contracts",
"version": "0.0.32",
"version": "0.0.33",
"private": true,
"files": [
"dist"
Expand Down
6 changes: 3 additions & 3 deletions packages/contracts/src/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ describe("ClientSettings environment identification", () => {
});

describe("ClientSettings sidebar", () => {
it("defaults to the current sidebar with a three-day auto-settle threshold", () => {
it("defaults to the expandable project sidebar with a three-day auto-settle threshold", () => {
const settings = decodeClientSettings({});
expect(settings.legacySidebarEnabled).toBe(false);
expect(settings.legacySidebarEnabled).toBe(true);
expect(settings.sidebarAutoSettleAfterDays).toBe(3);
});

Expand All @@ -79,7 +79,7 @@ describe("ClientSettings sidebar", () => {
sidebarV2Enabled: false,
sidebarV2ConfiguredByUser: true,
});
expect(decoded.legacySidebarEnabled).toBe(false);
expect(decoded.legacySidebarEnabled).toBe(true);
expect(decoded).not.toHaveProperty("sidebarV2Enabled");
expect(decoded).not.toHaveProperty("sidebarV2ConfiguredByUser");
});
Expand Down
9 changes: 4 additions & 5 deletions packages/contracts/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,10 @@ export const ClientSettingsSchema = Schema.Struct({
// default UI; this beta flag restores it (plus the /plan and /default slash
// commands) for users who still rely on the old workflow.
planModeEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
// Legacy sidebar (the original per-project tree). Deliberately a fresh key
// (was `sidebarV2Enabled` + `sidebarV2ConfiguredByUser`): decoding drops the
// old keys, so everyone, including prior beta opt-outs, resets to the new
// default sidebar.
legacySidebarEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
// The original per-project tree remains the default because it preserves
// project context and lets users expand each project into its threads. The
// flat sidebar stays available as an explicit opt-out through this setting.
legacySidebarEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),

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.

Sidebar flashes wrong default

Medium Severity

Flipping legacySidebarEnabled to default true leaves useLegacySidebarEnabled gating with settingsHydrated && legacySidebarEnabled, which still forces false until hydration. Most users briefly mount the flat sidebar, then remount the project-tree sidebar once settings load.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 66a0df1. Configure here.

sidebarAutoSettleAfterDays: Schema.NullOr(SidebarAutoSettleAfterDays).pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_AUTO_SETTLE_AFTER_DAYS)),
),
Expand Down
28 changes: 28 additions & 0 deletions packages/ssh/src/tunnel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ describe("ssh tunnel scripts", () => {
const script = buildRemoteT3RunnerScript({ nodeEngineRange: TEST_NODE_ENGINE_RANGE });

assert.include(script, "T3_NODE_SCRIPT_PATH=''");
assert.include(script, "T3_DESKTOP_CLI_EXECUTABLE=''");
assert.include(script, 'exec t3 "$@"');
assert.include(script, "exec npx --yes 't3@latest' \"$@\"");
assert.include(script, "exec npm exec --yes 't3@latest' -- \"$@\"");
Expand All @@ -115,6 +116,33 @@ describe("ssh tunnel scripts", () => {
assert.notInclude(script, "ensure $NVM_DIR/nvm.sh is available");
});

it("prefers a matching installed desktop CLI before the package fallback", () => {
const script = buildRemoteT3RunnerScript({
packageSpec: "t3@0.0.33",
desktopCli: {
executablePath: "/Applications/T3 Code (Alpha).app/Contents/MacOS/T3 Code (Alpha)",
entryPath:
"/Applications/T3 Code (Alpha).app/Contents/Resources/app.asar/apps/server/dist/bin.mjs",
version: "0.0.33",
},
});

assert.include(
script,
"T3_DESKTOP_CLI_EXECUTABLE='/Applications/T3 Code (Alpha).app/Contents/MacOS/T3 Code (Alpha)'",
);
assert.include(script, 'T3_INSTALLED_DESKTOP_CLI_VERSION="$(env ELECTRON_RUN_AS_NODE=1');
assert.include(
script,
'if [ "$T3_INSTALLED_DESKTOP_CLI_VERSION" = "t3 v$T3_DESKTOP_CLI_VERSION" ]; then',
);
assert.include(
script,
'exec env ELECTRON_RUN_AS_NODE=1 "$T3_DESKTOP_CLI_EXECUTABLE" "$T3_DESKTOP_CLI_ENTRY" "$@"',
);
assert.include(script, "exec npx --yes 't3@0.0.33' \"$@\"");
});

it("does not hard-code a remote node engine range", () => {
const script = buildRemoteT3RunnerScript();

Expand Down
18 changes: 18 additions & 0 deletions packages/ssh/src/tunnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export interface RemoteT3RunnerOptions {
readonly packageSpec?: string;
readonly nodeScriptPath?: string | null;
readonly nodeEngineRange?: string | null;
readonly desktopCli?: {
readonly executablePath: string;
readonly entryPath: string;
readonly version: string;
} | null;
}

export interface SshEnvironmentManagerOptions {
Expand Down Expand Up @@ -422,6 +427,15 @@ if [ -n "$T3_NODE_SCRIPT_PATH" ]; then
fi
exec node "$T3_NODE_SCRIPT_PATH" "$@"
fi
T3_DESKTOP_CLI_EXECUTABLE=@@T3_DESKTOP_CLI_EXECUTABLE@@
T3_DESKTOP_CLI_ENTRY=@@T3_DESKTOP_CLI_ENTRY@@
T3_DESKTOP_CLI_VERSION=@@T3_DESKTOP_CLI_VERSION@@
if [ -n "$T3_DESKTOP_CLI_EXECUTABLE" ] && [ -x "$T3_DESKTOP_CLI_EXECUTABLE" ]; then
T3_INSTALLED_DESKTOP_CLI_VERSION="$(env ELECTRON_RUN_AS_NODE=1 "$T3_DESKTOP_CLI_EXECUTABLE" "$T3_DESKTOP_CLI_ENTRY" --version 2>/dev/null || true)"
if [ "$T3_INSTALLED_DESKTOP_CLI_VERSION" = "t3 v$T3_DESKTOP_CLI_VERSION" ]; then
exec env ELECTRON_RUN_AS_NODE=1 "$T3_DESKTOP_CLI_EXECUTABLE" "$T3_DESKTOP_CLI_ENTRY" "$@"
fi
fi
if command -v t3 >/dev/null 2>&1; then
exec t3 "$@"
fi
Expand Down Expand Up @@ -633,10 +647,14 @@ fi
export function buildRemoteT3RunnerScript(input?: RemoteT3RunnerOptions): string {
const packageSpec = shellSingleQuote(input?.packageSpec?.trim() || "t3@latest");
const nodeScriptPath = input?.nodeScriptPath?.trim() || "";
const desktopCli = input?.desktopCli;
return stripTrailingNewlines(
applyScriptPlaceholders(REMOTE_RUNNER_SCRIPT, {
T3_PACKAGE_SPEC: packageSpec,
T3_NODE_SCRIPT_PATH: shellSingleQuote(nodeScriptPath),
T3_DESKTOP_CLI_EXECUTABLE: shellSingleQuote(desktopCli?.executablePath.trim() || ""),
T3_DESKTOP_CLI_ENTRY: shellSingleQuote(desktopCli?.entryPath.trim() || ""),
T3_DESKTOP_CLI_VERSION: shellSingleQuote(desktopCli?.version.trim() || ""),
T3_NODE_ENV_SCRIPT: buildRemoteNodeEnvScript(input),
}),
);
Expand Down
16 changes: 16 additions & 0 deletions scripts/build-desktop-artifact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
it("switches desktop packaging icons to the nightly artwork for nightly versions", () => {
assert.deepStrictEqual(resolveDesktopBuildIconAssets("0.0.17"), {
macIconPng: BRAND_ASSET_PATHS.productionMacIconPng,
macIconIcns: "apps/desktop/resources/icon.icns",
linuxIconPng: BRAND_ASSET_PATHS.productionLinuxIconPng,
windowsIconIco: BRAND_ASSET_PATHS.productionWindowsIconIco,
});
Expand Down Expand Up @@ -155,6 +156,21 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
}),
);

it.effect("uses the canonical update feed for local desktop builds", () =>
Effect.gen(function* () {
const config = yield* resolveGitHubPublishConfig("latest").pipe(
Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} }))),
);

assert.deepStrictEqual(config, {
provider: "github",
owner: "pingdotgg",
repo: "t3code",
releaseType: "release",
});
}),
);

it("omits bundled workspace packages from staged desktop dependencies", () => {
assert.deepStrictEqual(
resolveDesktopRuntimeDependencies(
Expand Down
22 changes: 19 additions & 3 deletions scripts/build-desktop-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";

const LINUX_ICON_SIZES = [16, 22, 24, 32, 48, 64, 128, 256, 512] as const;
const DESKTOP_APP_ID = "com.t3tools.t3code";
const DEFAULT_DESKTOP_UPDATE_REPOSITORY = "pingdotgg/t3code";
const APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/u;

const BuildPlatform = Schema.Literals(["mac", "linux", "win"]);
Expand Down Expand Up @@ -84,6 +85,7 @@ const readWorkspaceConfig = Effect.fn("readWorkspaceConfig")(function* () {

interface DesktopBuildIconAssets {
readonly macIconPng: string;
readonly macIconIcns?: string;
readonly linuxIconPng: string;
readonly windowsIconIco: string;
}
Expand Down Expand Up @@ -1290,7 +1292,12 @@ function generateMacIconSet(
});
}

function stageMacIcons(stageResourcesDir: string, sourcePng: string, verbose: boolean) {
function stageMacIcons(
stageResourcesDir: string,
sourcePng: string,
sourceIcns: string | undefined,
verbose: boolean,
) {
return Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
Expand All @@ -1313,6 +1320,11 @@ function stageMacIcons(stageResourcesDir: string, sourcePng: string, verbose: bo
verbose,
});

if (sourceIcns && (yield* fs.exists(sourceIcns))) {
yield* fs.copyFile(sourceIcns, iconIcnsPath);
return;
}

yield* generateMacIconSet(sourcePng, iconIcnsPath, tmpRoot, path, verbose);
});
}
Expand Down Expand Up @@ -1458,7 +1470,7 @@ export const resolveGitHubPublishConfig = Effect.fn("resolveGitHubPublishConfig"
const rawRepo = (
Option.getOrUndefined(env.updateRepository)?.trim() ||
Option.getOrUndefined(env.githubRepository)?.trim() ||
""
DEFAULT_DESKTOP_UPDATE_REPOSITORY
).trim();
if (!rawRepo) return undefined;

Expand Down Expand Up @@ -1493,6 +1505,7 @@ export function resolveDesktopBuildIconAssets(version: string): DesktopBuildIcon

return {
macIconPng: BRAND_ASSET_PATHS.productionMacIconPng,
macIconIcns: "apps/desktop/resources/icon.icns",
linuxIconPng: BRAND_ASSET_PATHS.productionLinuxIconPng,
windowsIconIco: BRAND_ASSET_PATHS.productionWindowsIconIco,
};
Expand Down Expand Up @@ -1632,7 +1645,7 @@ const assertPlatformBuildResources = Effect.fn("assertPlatformBuildResources")(f
verbose: boolean,
) {
if (platform === "mac") {
yield* stageMacIcons(stageResourcesDir, iconAssets.macIconPng, verbose);
yield* stageMacIcons(stageResourcesDir, iconAssets.macIconPng, iconAssets.macIconIcns, verbose);
return;
}

Expand Down Expand Up @@ -1850,6 +1863,9 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
stageResourcesDir,
{
macIconPng: path.join(repoRoot, iconAssets.macIconPng),
...(iconAssets.macIconIcns
? { macIconIcns: path.join(repoRoot, iconAssets.macIconIcns) }
: {}),
linuxIconPng: path.join(repoRoot, iconAssets.linuxIconPng),
windowsIconIco: path.join(repoRoot, iconAssets.windowsIconIco),
},
Expand Down
Loading