diff --git a/apps/server/integration/OrchestrationEngineHarness.integration.ts b/apps/server/integration/OrchestrationEngineHarness.integration.ts index 4129739503982c2bccb197e9e4b7bb00d8336c5f..b6ae7ee98255c13c0f830726cf12a451fd435216 100644 --- a/apps/server/integration/OrchestrationEngineHarness.integration.ts +++ b/apps/server/integration/OrchestrationEngineHarness.integration.ts @@ -406,19 +406,14 @@ export const makeOrchestrationIntegrationHarness = ( disposed = true; const shutdown = Effect.gen(function* () { - const stopAllExit = yield* Effect.exit( - Effect.promise(() => runtime.runPromise(providerService.stopAll())), - ); const closeScopeExit = yield* Effect.exit(Scope.close(scope, Exit.void)); const disposeRuntimeExit = yield* Effect.exit(Effect.promise(() => runtime.dispose())); - const failureCause = Exit.isFailure(stopAllExit) - ? stopAllExit.cause - : Exit.isFailure(closeScopeExit) - ? closeScopeExit.cause - : Exit.isFailure(disposeRuntimeExit) - ? disposeRuntimeExit.cause - : null; + const failureCause = Exit.isFailure(closeScopeExit) + ? closeScopeExit.cause + : Exit.isFailure(disposeRuntimeExit) + ? disposeRuntimeExit.cause + : null; if (failureCause) { return yield* Effect.failCause(failureCause); diff --git a/apps/server/scripts/cli.ts b/apps/server/scripts/cli.ts index 7470a0dc638ac074ca8991e1c625e7024babf88c..ccf1f5a91afe3e1790ae4f292710ecb804a28fa9 100644 --- a/apps/server/scripts/cli.ts +++ b/apps/server/scripts/cli.ts @@ -6,6 +6,7 @@ import { Data, Effect, FileSystem, Logger, Option, Path } from "effect"; import { Command, Flag } from "effect/unstable/cli"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; +import { DEVELOPMENT_ICON_OVERRIDES, PUBLISH_ICON_OVERRIDES } from "../../../scripts/lib/brand-assets.ts"; import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog.ts"; import rootPackageJson from "../../../package.json" with { type: "json" }; import serverPackageJson from "../package.json" with { type: "json" }; @@ -31,6 +32,84 @@ const runCommand = Effect.fn("runCommand")(function* (command: ChildProcess.Comm } }); +interface PublishIconBackup { + readonly targetPath: string; + readonly backupPath: string; +} + +const applyPublishIconOverrides = Effect.fn("applyPublishIconOverrides")(function* ( + repoRoot: string, + serverDir: string, +) { + const path = yield* Path.Path; + const fs = yield* FileSystem.FileSystem; + const backups: PublishIconBackup[] = []; + + for (const override of PUBLISH_ICON_OVERRIDES) { + const sourcePath = path.join(repoRoot, override.sourceRelativePath); + const targetPath = path.join(serverDir, override.targetRelativePath); + const backupPath = `${targetPath}.publish-bak`; + + if (!(yield* fs.exists(sourcePath))) { + return yield* new CliError({ + message: `Missing publish icon source: ${sourcePath}`, + }); + } + if (!(yield* fs.exists(targetPath))) { + return yield* new CliError({ + message: `Missing publish icon target: ${targetPath}. Run the build subcommand first.`, + }); + } + + yield* fs.copyFile(targetPath, backupPath); + yield* fs.copyFile(sourcePath, targetPath); + backups.push({ targetPath, backupPath }); + } + + yield* Effect.log("[cli] Applied publish icon overrides to dist/client"); + return backups as ReadonlyArray; +}); + +const restorePublishIconOverrides = Effect.fn("restorePublishIconOverrides")(function* ( + backups: ReadonlyArray, +) { + const fs = yield* FileSystem.FileSystem; + for (const backup of backups) { + if (!(yield* fs.exists(backup.backupPath))) { + continue; + } + yield* fs.rename(backup.backupPath, backup.targetPath); + } +}); + +const applyDevelopmentIconOverrides = Effect.fn("applyDevelopmentIconOverrides")(function* ( + repoRoot: string, + serverDir: string, +) { + const path = yield* Path.Path; + const fs = yield* FileSystem.FileSystem; + + for (const override of DEVELOPMENT_ICON_OVERRIDES) { + const sourcePath = path.join(repoRoot, override.sourceRelativePath); + const targetPath = path.join(serverDir, override.targetRelativePath); + + if (!(yield* fs.exists(sourcePath))) { + return yield* new CliError({ + message: `Missing development icon source: ${sourcePath}`, + }); + } + if (!(yield* fs.exists(targetPath))) { + return yield* new CliError({ + message: `Missing development icon target: ${targetPath}. Build web first.`, + }); + } + + yield* fs.copyFile(sourcePath, targetPath); + } + + yield* Effect.log("[cli] Applied development icon overrides to dist/client"); +}); + // --------------------------------------------------------------------------- // build subcommand // --------------------------------------------------------------------------- @@ -61,6 +140,7 @@ const buildCmd = Command.make( if (yield* fs.exists(webDist)) { yield* fs.copy(webDist, clientTarget); + yield* applyDevelopmentIconOverrides(repoRoot, serverDir); yield* Effect.log("[cli] Bundled web app into dist/client"); } else { yield* Effect.logWarning("[cli] Web dist not found — skipping client bundle."); @@ -104,10 +184,8 @@ const publishCmd = Command.make( yield* Effect.acquireUseRelease( // Acquire: backup package.json, resolve catalog: deps, strip devDependencies/scripts Effect.gen(function* () { - const original = yield* fs.readFileString(packageJsonPath); - yield* fs.writeFileString(backupPath, original); - - // Build package.json for publish + // Resolve catalog dependencies before any file mutations. If this throws, + // acquire fails and no release hook runs, so filesystem must still be untouched. const version = Option.getOrElse(config.appVersion, () => serverPackageJson.version); const pkg = { name: serverPackageJson.name, @@ -120,15 +198,19 @@ const publishCmd = Command.make( dependencies: serverPackageJson.dependencies as Record, }; - // Resolve catalog: entries in production dependencies pkg.dependencies = resolveCatalogDependencies( pkg.dependencies, rootPackageJson.workspaces.catalog, "apps/server dependencies", ); + const original = yield* fs.readFileString(packageJsonPath); + yield* fs.writeFileString(backupPath, original); yield* fs.writeFileString(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`); yield* Effect.log("[cli] Resolved package.json for publish"); + + const iconBackups = yield* applyPublishIconOverrides(repoRoot, serverDir); + return { iconBackups }; }), // Use: npm publish () => @@ -147,8 +229,15 @@ const publishCmd = Command.make( ); }), // Release: restore - () => + (resource: { readonly iconBackups: ReadonlyArray }) => Effect.gen(function* () { + yield* restorePublishIconOverrides(resource.iconBackups).pipe( + Effect.catch((error) => + Effect.logError( + `[cli] Failed to restore publish icon overrides: ${String(error)}`, + ), + ), + ); yield* fs.rename(backupPath, packageJsonPath); if (config.verbose) yield* Effect.log("[cli] Restored original package.json"); }), diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts index e1e75682bbdfef95905f18b77990ad229bc31ed7..d675c85ff539fc3ce3287750c4f8958244a96fbe 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts @@ -91,7 +91,6 @@ function createProviderServiceHarness( listSessions, getCapabilities: () => Effect.succeed({ sessionModelSwitch: "in-session" }), rollbackConversation, - stopAll: () => Effect.void, streamEvents: Stream.fromPubSub(runtimeEventPubSub), }; diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts index e29adb2b43fa5034ac7aa7f77939866731751b84..4f352435fe50134f0b864532d878b50248d80c8d 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts @@ -188,7 +188,6 @@ describe("ProviderCommandReactor", () => { sessionModelSwitch: provider === "codex" ? "in-session" : "in-session", }), rollbackConversation: () => unsupported(), - stopAll: () => Effect.void, streamEvents: Stream.fromPubSub(runtimeEventPubSub), }; diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts index 09fae007ebe9f8f6c93c3d9d8c97e1868f0d4595..eb230e5202dec6d1f95227298246f342fcb5195e 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts @@ -65,7 +65,6 @@ function createProviderServiceHarness() { listSessions: () => Effect.succeed([]), getCapabilities: () => Effect.succeed({ sessionModelSwitch: "in-session" }), rollbackConversation: () => unsupported(), - stopAll: () => Effect.void, streamEvents: Stream.fromPubSub(runtimeEventPubSub), }; diff --git a/apps/server/src/provider/Layers/ProviderService.test.ts b/apps/server/src/provider/Layers/ProviderService.test.ts index 255c6fca05a392ff1673c324c2ac1f50c91970df..63b41d6b065c20109a5d6293238197604d8eba6d 100644 --- a/apps/server/src/provider/Layers/ProviderService.test.ts +++ b/apps/server/src/provider/Layers/ProviderService.test.ts @@ -356,11 +356,6 @@ it.effect( }); }).pipe(Effect.provide(firstProviderLayer)); - yield* Effect.gen(function* () { - const provider = yield* ProviderService; - yield* provider.stopAll(); - }).pipe(Effect.provide(firstProviderLayer)); - const persistedAfterStopAll = yield* Effect.gen(function* () { const repository = yield* ProviderSessionRuntimeRepository; return yield* repository.getByThreadId({ threadId: startedSession.threadId }); @@ -549,7 +544,7 @@ routing.layer("ProviderServiceLive routing", (it) => { runtimeMode: "full-access", }); - yield* provider.stopAll(); + yield* routing.codex.stopAll(); routing.codex.startSession.mockClear(); routing.codex.sendTurn.mockClear(); @@ -578,7 +573,7 @@ routing.layer("ProviderServiceLive routing", (it) => { }), ); - it.effect("stops all sessions and clears adapter runtime", () => + it.effect("lists no sessions after adapter runtime clears", () => Effect.gen(function* () { const provider = yield* ProviderService; @@ -593,7 +588,7 @@ routing.layer("ProviderServiceLive routing", (it) => { runtimeMode: "full-access", }); - yield* provider.stopAll(); + yield* routing.codex.stopAll(); const remaining = yield* provider.listSessions(); assert.equal(remaining.length, 0); @@ -641,14 +636,6 @@ routing.layer("ProviderServiceLive routing", (it) => { } } - yield* provider.stopAll(); - const stoppedRuntime = yield* runtimeRepository.getByThreadId({ - threadId: session.threadId, - }); - assert.equal(Option.isSome(stoppedRuntime), true); - if (Option.isSome(stoppedRuntime)) { - assert.equal(stoppedRuntime.value.status, "stopped"); - } }), ); }); diff --git a/apps/server/src/provider/Layers/ProviderService.ts b/apps/server/src/provider/Layers/ProviderService.ts index 1a56eb9462f0aa163372f7e17ca8545eaeea9280..285e0a870963f5ab7ee6a423926cddd6f71364fc 100644 --- a/apps/server/src/provider/Layers/ProviderService.ts +++ b/apps/server/src/provider/Layers/ProviderService.ts @@ -471,7 +471,7 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => }); }); - const stopAll: ProviderServiceShape["stopAll"] = () => + const runStopAll = () => Effect.gen(function* () { const threadIds = yield* directory.listThreadIds(); yield* Effect.forEach(adapters, (adapter) => adapter.stopAll()).pipe(Effect.asVoid); @@ -497,6 +497,12 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => yield* analytics.flush; }); + yield* Effect.addFinalizer(() => + Effect.catch(runStopAll(), (cause) => + Effect.logWarning("failed to stop provider service", { cause }), + ), + ); + return { startSession, sendTurn, @@ -507,7 +513,6 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => listSessions, getCapabilities, rollbackConversation, - stopAll, streamEvents: Stream.fromPubSub(runtimeEventPubSub), } satisfies ProviderServiceShape; }); diff --git a/apps/server/src/provider/Services/ProviderService.ts b/apps/server/src/provider/Services/ProviderService.ts index 600541e895a5bd34efceb02b9a553207ed9e6f7d..ebfe8c8ab1ac22a8ef0652a9e4770ed696c7f695 100644 --- a/apps/server/src/provider/Services/ProviderService.ts +++ b/apps/server/src/provider/Services/ProviderService.ts @@ -99,16 +99,6 @@ export interface ProviderServiceShape { readonly numTurns: number; }) => Effect.Effect; - /** - * Stop all active provider sessions. - * - * Persisted session bindings remain so stale sessions can be resumed after - * process restart. - * - * Runtime alias mappings are cleared as part of shutdown. - */ - readonly stopAll: () => Effect.Effect; - /** * Canonical provider runtime event stream. * diff --git a/apps/server/src/serverLogger.ts b/apps/server/src/serverLogger.ts index 0abf9f382687e209854da3fbcc8688cdd3fbcfe5..de6b27f4294c8ec8a0392b34bf205f2525976398 100644 --- a/apps/server/src/serverLogger.ts +++ b/apps/server/src/serverLogger.ts @@ -1,18 +1,15 @@ import fs from "node:fs"; import path from "node:path"; -import { Effect, Logger, Option } from "effect"; +import { Effect, Logger } from "effect"; import * as Layer from "effect/Layer"; import { ServerConfig } from "./config"; export const ServerLoggerLive = Effect.gen(function* () { - const config = yield* Effect.serviceOption(ServerConfig); - if (Option.isNone(config)) { - return Logger.layer([Logger.defaultLogger]); - } + const config = yield* ServerConfig; - const logDir = path.join(config.value.stateDir, "logs"); + const logDir = path.join(config.stateDir, "logs"); const logPath = path.join(logDir, "server.log"); yield* Effect.sync(() => { diff --git a/apps/server/src/wsServer.test.ts b/apps/server/src/wsServer.test.ts index adb4b3cfba3c8b457dd8b3c69e48bc7f059ed7d9..2c30b7cc544dfadac8c78efd5b6669a972ae0a27 100644 --- a/apps/server/src/wsServer.test.ts +++ b/apps/server/src/wsServer.test.ts @@ -1171,7 +1171,6 @@ describe("WebSocket Server", () => { listSessions: () => Effect.succeed([]), getCapabilities: () => Effect.succeed({ sessionModelSwitch: "in-session" }), rollbackConversation: () => unsupported(), - stopAll: () => Effect.void, streamEvents: Stream.fromPubSub(runtimeEventPubSub), }; const providerLayer = Layer.succeed(ProviderService, providerService); diff --git a/apps/server/src/wsServer.ts b/apps/server/src/wsServer.ts index 0cb29cb9b8b1b51b1aa6bd6fd46a8f3e4d3269ec..934ff92a522649e4ae49a665c491ab8c2c0f0f22 100644 --- a/apps/server/src/wsServer.ts +++ b/apps/server/src/wsServer.ts @@ -568,7 +568,6 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< const orchestrationEngine = yield* OrchestrationEngineService; const projectionReadModelQuery = yield* ProjectionSnapshotQuery; const checkpointDiffQuery = yield* CheckpointDiffQuery; - const liveProviderService = yield* ProviderService; const orchestrationReactor = yield* OrchestrationReactor; const { openInEditor } = yield* Open; @@ -660,11 +659,6 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< } const runPromise = yield* Effect.map(Effect.services(), Effect.runPromiseWith); - yield* Effect.addFinalizer(() => - Effect.catch(liveProviderService.stopAll(), (cause) => - Effect.logWarning("failed to stop provider service", { cause }), - ), - ); const unsubscribeTerminalEvents = yield* terminalManager.subscribe( (event) => void Effect.runPromise(onTerminalEvent(event)), diff --git a/apps/web/public/apple-touch-icon.png b/apps/web/public/apple-touch-icon.png index 43dc5d21602cc217500121410827b603a6a6fe5e..e0e1b9659b83e527b991094b50bb8d1fd8f87af3 100644 Binary files a/apps/web/public/apple-touch-icon.png and b/apps/web/public/apple-touch-icon.png differ diff --git a/apps/web/public/favicon-16x16.png b/apps/web/public/favicon-16x16.png index 1636b52162945be4ea63d866a9cb22ae68bb4ed0..673d84599981c10c9a819c4e17e817b978ef57b7 100644 Binary files a/apps/web/public/favicon-16x16.png and b/apps/web/public/favicon-16x16.png differ diff --git a/apps/web/public/favicon-32x32.png b/apps/web/public/favicon-32x32.png index b84a6ff27a093136a3511018cd15b6bb6bb9fff5..25bcc95d4aaa0639e93247795ebd57a9b947434a 100644 Binary files a/apps/web/public/favicon-32x32.png and b/apps/web/public/favicon-32x32.png differ diff --git a/apps/web/public/favicon.ico b/apps/web/public/favicon.ico index 8298f70d8b3690d1103d712c37d13a1d17fcec8d..36975b99782755a6bc000c800cbeb54aa258a063 100644 Binary files a/apps/web/public/favicon.ico and b/apps/web/public/favicon.ico differ diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg deleted file mode 100644 index 61b48db5af1a537cabab28a692c702b39a046e79..0000000000000000000000000000000000000000 --- a/apps/web/public/favicon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/T3 Code blueprint-macOS-Default-1024x1024@1x.png b/assets/T3 Code blueprint-macOS-Default-1024x1024@1x.png deleted file mode 100644 index f01a7b8813dfa98e6c7eed39fe8e5d0eb923b97e..0000000000000000000000000000000000000000 Binary files a/assets/T3 Code blueprint-macOS-Default-1024x1024@1x.png and /dev/null differ diff --git a/assets/T3 Code blueprint-macOS-Default-1024x1024@2x.png b/assets/T3 Code blueprint-macOS-Default-1024x1024@2x.png deleted file mode 100644 index 8dba03e01fe1bb2c33d82836236899cd47016ca4..0000000000000000000000000000000000000000 Binary files a/assets/T3 Code blueprint-macOS-Default-1024x1024@2x.png and /dev/null differ diff --git a/assets/T3 Code logo.svg b/assets/T3 Code logo.svg deleted file mode 100644 index 61b48db5af1a537cabab28a692c702b39a046e79..0000000000000000000000000000000000000000 --- a/assets/T3 Code logo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/T3 Code logo@16x.png b/assets/T3 Code logo@16x.png deleted file mode 100644 index 87d9f008c687041b72ccaab2833e0d17915b828c..0000000000000000000000000000000000000000 Binary files a/assets/T3 Code logo@16x.png and /dev/null differ diff --git a/assets/T3 Code logo@32x.png b/assets/T3 Code logo@32x.png deleted file mode 100644 index d75373862e46463a3990e86ff110e8b65dc1fa7d..0000000000000000000000000000000000000000 Binary files a/assets/T3 Code logo@32x.png and /dev/null differ diff --git a/assets/T3 Code logo@8x.png b/assets/T3 Code logo@8x.png deleted file mode 100644 index 88317dd41fbabe19ea69d8ba0308ed08bca12887..0000000000000000000000000000000000000000 Binary files a/assets/T3 Code logo@8x.png and /dev/null differ diff --git a/assets/T3 Code-macOS-Default-1024x1024@1x.png b/assets/T3 Code-macOS-Default-1024x1024@1x.png deleted file mode 100644 index 0a6e1cbfcff3433a3a96d97f3b7431292e1a96c1..0000000000000000000000000000000000000000 Binary files a/assets/T3 Code-macOS-Default-1024x1024@1x.png and /dev/null differ diff --git a/assets/T3 Code-macOS-Default-1024x1024@4x.png b/assets/T3 Code-macOS-Default-1024x1024@4x.png deleted file mode 100644 index d426376b135c9ffaf9c3042d7b7392c8408438a0..0000000000000000000000000000000000000000 Binary files a/assets/T3 Code-macOS-Default-1024x1024@4x.png and /dev/null differ diff --git a/assets/dev/blueprint-ios-1024.png b/assets/dev/blueprint-ios-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..b33d6f337b0b6dff201c4b8ed364e9b3a3d015f8 Binary files /dev/null and b/assets/dev/blueprint-ios-1024.png differ diff --git a/assets/dev/blueprint-macos-1024.png b/assets/dev/blueprint-macos-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..8dba03e01fe1bb2c33d82836236899cd47016ca4 Binary files /dev/null and b/assets/dev/blueprint-macos-1024.png differ diff --git a/assets/dev/blueprint-universal-1024.png b/assets/dev/blueprint-universal-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..b33d6f337b0b6dff201c4b8ed364e9b3a3d015f8 Binary files /dev/null and b/assets/dev/blueprint-universal-1024.png differ diff --git a/assets/dev/blueprint-web-apple-touch-180.png b/assets/dev/blueprint-web-apple-touch-180.png new file mode 100644 index 0000000000000000000000000000000000000000..e0e1b9659b83e527b991094b50bb8d1fd8f87af3 Binary files /dev/null and b/assets/dev/blueprint-web-apple-touch-180.png differ diff --git a/assets/dev/blueprint-web-favicon-16x16.png b/assets/dev/blueprint-web-favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..673d84599981c10c9a819c4e17e817b978ef57b7 Binary files /dev/null and b/assets/dev/blueprint-web-favicon-16x16.png differ diff --git a/assets/dev/blueprint-web-favicon-32x32.png b/assets/dev/blueprint-web-favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..25bcc95d4aaa0639e93247795ebd57a9b947434a Binary files /dev/null and b/assets/dev/blueprint-web-favicon-32x32.png differ diff --git a/assets/dev/blueprint-web-favicon.ico b/assets/dev/blueprint-web-favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..36975b99782755a6bc000c800cbeb54aa258a063 Binary files /dev/null and b/assets/dev/blueprint-web-favicon.ico differ diff --git a/assets/dev/blueprint-windows.ico b/assets/dev/blueprint-windows.ico new file mode 100644 index 0000000000000000000000000000000000000000..36975b99782755a6bc000c800cbeb54aa258a063 Binary files /dev/null and b/assets/dev/blueprint-windows.ico differ diff --git a/assets/logo-1024.png b/assets/logo-1024.png deleted file mode 100644 index 88317dd41fbabe19ea69d8ba0308ed08bca12887..0000000000000000000000000000000000000000 Binary files a/assets/logo-1024.png and /dev/null differ diff --git a/assets/logo.svg b/assets/logo.svg deleted file mode 100644 index 61b48db5af1a537cabab28a692c702b39a046e79..0000000000000000000000000000000000000000 --- a/assets/logo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/macos-icon-1024.png b/assets/macos-icon-1024.png deleted file mode 100644 index 0a6e1cbfcff3433a3a96d97f3b7431292e1a96c1..0000000000000000000000000000000000000000 Binary files a/assets/macos-icon-1024.png and /dev/null differ diff --git a/assets/prod/black-ios-1024.png b/assets/prod/black-ios-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..2ff311ce786f08a59e089364325c514920c4a022 Binary files /dev/null and b/assets/prod/black-ios-1024.png differ diff --git a/assets/prod/black-macos-1024.png b/assets/prod/black-macos-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..0a6e1cbfcff3433a3a96d97f3b7431292e1a96c1 Binary files /dev/null and b/assets/prod/black-macos-1024.png differ diff --git a/assets/prod/black-universal-1024.png b/assets/prod/black-universal-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..88317dd41fbabe19ea69d8ba0308ed08bca12887 Binary files /dev/null and b/assets/prod/black-universal-1024.png differ diff --git a/assets/prod/logo.svg b/assets/prod/logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..61b48db5af1a537cabab28a692c702b39a046e79 --- /dev/null +++ b/assets/prod/logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/prod/t3-black-web-apple-touch-180.png b/assets/prod/t3-black-web-apple-touch-180.png new file mode 100644 index 0000000000000000000000000000000000000000..9c593ce352ce0a7500804835e209d1e51c017b83 Binary files /dev/null and b/assets/prod/t3-black-web-apple-touch-180.png differ diff --git a/assets/prod/t3-black-web-favicon-16x16.png b/assets/prod/t3-black-web-favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..f85017caf75f8b4a8e75f5e59fc2d54606cf242a Binary files /dev/null and b/assets/prod/t3-black-web-favicon-16x16.png differ diff --git a/assets/prod/t3-black-web-favicon-32x32.png b/assets/prod/t3-black-web-favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..fae2d285b7a35f80184e2aa7a3758c33df21f287 Binary files /dev/null and b/assets/prod/t3-black-web-favicon-32x32.png differ diff --git a/assets/prod/t3-black-web-favicon.ico b/assets/prod/t3-black-web-favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e3ab4ae5e024825467b5191c903cd7bb0db927c1 Binary files /dev/null and b/assets/prod/t3-black-web-favicon.ico differ diff --git a/assets/prod/t3-black-windows.ico b/assets/prod/t3-black-windows.ico new file mode 100644 index 0000000000000000000000000000000000000000..e3ab4ae5e024825467b5191c903cd7bb0db927c1 Binary files /dev/null and b/assets/prod/t3-black-windows.ico differ diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index d8c4d08b150dba7c3a4f688779c4bb55dfda169d..8823b88513446fcf22c28036caf249a371a3982c 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -1,11 +1,14 @@ #!/usr/bin/env node import { spawnSync } from "node:child_process"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; import rootPackageJson from "../package.json" with { type: "json" }; import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" }; import serverPackageJson from "../apps/server/package.json" with { type: "json" }; +import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts"; import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; @@ -20,8 +23,18 @@ const BuildArch = Schema.Literals(["arm64", "x64", "universal"]); const RepoRoot = Effect.service(Path.Path).pipe( Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))), ); -const IconSource = Effect.zipWith(RepoRoot, Effect.service(Path.Path), (repoRoot, path) => - path.join(repoRoot, "assets/macos-icon-1024.png"), +const ProductionMacIconSource = Effect.zipWith(RepoRoot, Effect.service(Path.Path), (repoRoot, path) => + path.join(repoRoot, BRAND_ASSET_PATHS.productionMacIconPng), +); +const ProductionLinuxIconSource = Effect.zipWith( + RepoRoot, + Effect.service(Path.Path), + (repoRoot, path) => path.join(repoRoot, BRAND_ASSET_PATHS.productionLinuxIconPng), +); +const ProductionWindowsIconSource = Effect.zipWith( + RepoRoot, + Effect.service(Path.Path), + (repoRoot, path) => path.join(repoRoot, BRAND_ASSET_PATHS.productionWindowsIconIco), ); const encodeJsonString = Schema.encodeEffect(Schema.UnknownFromJsonString); @@ -104,6 +117,39 @@ function resolveGitCommitHash(repoRoot: string): string { return hash.toLowerCase(); } +function resolvePythonForNodeGyp(): string | undefined { + const configured = process.env.npm_config_python ?? process.env.PYTHON; + if (configured && existsSync(configured)) { + return configured; + } + + if (process.platform === "win32") { + const localAppData = process.env.LOCALAPPDATA; + if (localAppData) { + for (const version of ["Python313", "Python312", "Python311", "Python310"]) { + const candidate = join(localAppData, "Programs", "Python", version, "python.exe"); + if (existsSync(candidate)) { + return candidate; + } + } + } + } + + const probe = spawnSync("python", ["-c", "import sys;print(sys.executable)"], { + encoding: "utf8", + }); + if (probe.status !== 0) { + return undefined; + } + + const executable = probe.stdout.trim(); + if (!executable || !existsSync(executable)) { + return undefined; + } + + return executable; +} + interface ResolvedBuildOptions { readonly platform: typeof BuildPlatform.Type; readonly target: string; @@ -261,7 +307,7 @@ function stageMacIcons(stageResourcesDir: string, verbose: boolean) { return Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; - const iconSource = yield* IconSource; + const iconSource = yield* ProductionMacIconSource; if (!(yield* fs.exists(iconSource))) { return yield* new BuildScriptError({ message: `Production icon source is missing at ${iconSource}`, @@ -285,6 +331,38 @@ function stageMacIcons(stageResourcesDir: string, verbose: boolean) { }); } +function stageLinuxIcons(stageResourcesDir: string) { + return Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const iconSource = yield* ProductionLinuxIconSource; + if (!(yield* fs.exists(iconSource))) { + return yield* new BuildScriptError({ + message: `Production icon source is missing at ${iconSource}`, + }); + } + + const iconPath = path.join(stageResourcesDir, "icon.png"); + yield* fs.copyFile(iconSource, iconPath); + }); +} + +function stageWindowsIcons(stageResourcesDir: string) { + return Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const iconSource = yield* ProductionWindowsIconSource; + if (!(yield* fs.exists(iconSource))) { + return yield* new BuildScriptError({ + message: `Production Windows icon source is missing at ${iconSource}`, + }); + } + + const iconPath = path.join(stageResourcesDir, "icon.ico"); + yield* fs.copyFile(iconSource, iconPath); + }); +} + function validateBundledClientAssets(clientDir: string) { return Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; @@ -416,31 +494,18 @@ const assertPlatformBuildResources = Effect.fn("assertPlatformBuildResources")(f stageResourcesDir: string, verbose: boolean, ) { - const fs = yield* FileSystem.FileSystem; - const path = yield* Path.Path; - if (platform === "mac") { yield* stageMacIcons(stageResourcesDir, verbose); return; } if (platform === "linux") { - const iconPath = path.join(stageResourcesDir, "icon.png"); - if (!(yield* fs.exists(iconPath))) { - return yield* new BuildScriptError({ - message: `Missing Linux icon at ${iconPath}`, - }); - } + yield* stageLinuxIcons(stageResourcesDir); return; } if (platform === "win") { - const iconPath = path.join(stageResourcesDir, "icon.ico"); - if (!(yield* fs.exists(iconPath))) { - return yield* new BuildScriptError({ - message: `Missing Windows icon at ${iconPath}`, - }); - } + yield* stageWindowsIcons(stageResourcesDir); } }); @@ -597,6 +662,16 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( delete buildEnv.APPLE_API_ISSUER; } + if (process.platform === "win32") { + const python = resolvePythonForNodeGyp(); + if (python) { + buildEnv.PYTHON = python; + buildEnv.npm_config_python = python; + } + buildEnv.npm_config_msvs_version = buildEnv.npm_config_msvs_version ?? "2022"; + buildEnv.GYP_MSVS_VERSION = buildEnv.GYP_MSVS_VERSION ?? "2022"; + } + yield* Effect.log( `[desktop-artifact] Building ${options.platform}/${options.target} (arch=${options.arch}, version=${appVersion})...`, ); diff --git a/scripts/lib/brand-assets.ts b/scripts/lib/brand-assets.ts new file mode 100644 index 0000000000000000000000000000000000000000..990d05c321f914ad2ed79eb105cfa2aadb04e390 --- /dev/null +++ b/scripts/lib/brand-assets.ts @@ -0,0 +1,57 @@ +export const BRAND_ASSET_PATHS = { + productionMacIconPng: "assets/prod/black-macos-1024.png", + productionLinuxIconPng: "assets/prod/black-universal-1024.png", + productionWindowsIconIco: "assets/prod/t3-black-windows.ico", + productionWebFaviconIco: "assets/prod/t3-black-web-favicon.ico", + productionWebFavicon16Png: "assets/prod/t3-black-web-favicon-16x16.png", + productionWebFavicon32Png: "assets/prod/t3-black-web-favicon-32x32.png", + productionWebAppleTouchIconPng: "assets/prod/t3-black-web-apple-touch-180.png", + developmentWindowsIconIco: "assets/dev/blueprint-windows.ico", + developmentWebFaviconIco: "assets/dev/blueprint-web-favicon.ico", + developmentWebFavicon16Png: "assets/dev/blueprint-web-favicon-16x16.png", + developmentWebFavicon32Png: "assets/dev/blueprint-web-favicon-32x32.png", + developmentWebAppleTouchIconPng: "assets/dev/blueprint-web-apple-touch-180.png", +} as const; + +export interface IconOverride { + readonly sourceRelativePath: string; + readonly targetRelativePath: string; +} + +export const DEVELOPMENT_ICON_OVERRIDES: ReadonlyArray = [ + { + sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFaviconIco, + targetRelativePath: "dist/client/favicon.ico", + }, + { + sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFavicon16Png, + targetRelativePath: "dist/client/favicon-16x16.png", + }, + { + sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFavicon32Png, + targetRelativePath: "dist/client/favicon-32x32.png", + }, + { + sourceRelativePath: BRAND_ASSET_PATHS.developmentWebAppleTouchIconPng, + targetRelativePath: "dist/client/apple-touch-icon.png", + }, +]; + +export const PUBLISH_ICON_OVERRIDES: ReadonlyArray = [ + { + sourceRelativePath: BRAND_ASSET_PATHS.productionWebFaviconIco, + targetRelativePath: "dist/client/favicon.ico", + }, + { + sourceRelativePath: BRAND_ASSET_PATHS.productionWebFavicon16Png, + targetRelativePath: "dist/client/favicon-16x16.png", + }, + { + sourceRelativePath: BRAND_ASSET_PATHS.productionWebFavicon32Png, + targetRelativePath: "dist/client/favicon-32x32.png", + }, + { + sourceRelativePath: BRAND_ASSET_PATHS.productionWebAppleTouchIconPng, + targetRelativePath: "dist/client/apple-touch-icon.png", + }, +];