From 66a0df1a53e22dfa30ccd7ee87d31e01f205d919 Mon Sep 17 00:00:00 2001 From: ihadd Date: Wed, 12 Aug 2026 00:36:01 -0400 Subject: [PATCH] feat: restore project thread trees and align desktop updates --- apps/desktop/package.json | 2 +- apps/desktop/src/main.ts | 10 +++++++ apps/server/package.json | 2 +- apps/web/package.json | 2 +- .../components/settings/SettingsPanels.tsx | 2 +- packages/contracts/package.json | 2 +- packages/contracts/src/settings.test.ts | 6 ++-- packages/contracts/src/settings.ts | 9 +++--- packages/ssh/src/tunnel.test.ts | 28 +++++++++++++++++++ packages/ssh/src/tunnel.ts | 18 ++++++++++++ scripts/build-desktop-artifact.test.ts | 16 +++++++++++ scripts/build-desktop-artifact.ts | 22 +++++++++++++-- 12 files changed, 103 insertions(+), 16 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index a69cda53bf46..a34a55f16acf 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@t3tools/desktop", - "version": "0.0.32", + "version": "0.0.33", "private": true, "type": "module", "main": "dist-electron/main.cjs", diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 0616184ec74d..b45c47619644 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -91,6 +91,7 @@ const resolveDesktopSshCliRunner = ( nodeEngineRange: serverPackageJson.engines.node, }; } + const remoteDesktopAppContents = `/Applications/${environment.displayName}.app/Contents`; return { packageSpec: resolveRemoteT3CliPackageSpec({ appVersion: environment.appVersion, @@ -98,6 +99,15 @@ const resolveDesktopSshCliRunner = ( 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, + }, + } + : {}), }; }; diff --git a/apps/server/package.json b/apps/server/package.json index 360f00569b5d..7a508a38effb 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "t3", - "version": "0.0.32", + "version": "0.0.33", "license": "MIT", "repository": { "type": "git", diff --git a/apps/web/package.json b/apps/web/package.json index f396bff7a5eb..dfec330a1079 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@t3tools/web", - "version": "0.0.32", + "version": "0.0.33", "private": true, "type": "module", "scripts": { diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index c10dd5d2bf8a..5a447c739147 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -1649,7 +1649,7 @@ function LegacyFeaturesSection() { /> { }); 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); }); @@ -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"); }); diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index 388205649c85..99b920e74300 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -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))), sidebarAutoSettleAfterDays: Schema.NullOr(SidebarAutoSettleAfterDays).pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_AUTO_SETTLE_AFTER_DAYS)), ), diff --git a/packages/ssh/src/tunnel.test.ts b/packages/ssh/src/tunnel.test.ts index 4c2ecb331836..5f14fd165ecc 100644 --- a/packages/ssh/src/tunnel.test.ts +++ b/packages/ssh/src/tunnel.test.ts @@ -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' -- \"$@\""); @@ -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(); diff --git a/packages/ssh/src/tunnel.ts b/packages/ssh/src/tunnel.ts index 179d1fcb547d..7a48d557e55d 100644 --- a/packages/ssh/src/tunnel.ts +++ b/packages/ssh/src/tunnel.ts @@ -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 { @@ -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 @@ -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), }), ); diff --git a/scripts/build-desktop-artifact.test.ts b/scripts/build-desktop-artifact.test.ts index 7d2b7410a9e7..cec8a38d98fc 100644 --- a/scripts/build-desktop-artifact.test.ts +++ b/scripts/build-desktop-artifact.test.ts @@ -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, }); @@ -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( diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index a30b6d4a90a2..9f5713387ab5 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -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"]); @@ -84,6 +85,7 @@ const readWorkspaceConfig = Effect.fn("readWorkspaceConfig")(function* () { interface DesktopBuildIconAssets { readonly macIconPng: string; + readonly macIconIcns?: string; readonly linuxIconPng: string; readonly windowsIconIco: string; } @@ -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; @@ -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); }); } @@ -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; @@ -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, }; @@ -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; } @@ -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), },