diff --git a/.agents/skills/test-t3-mobile/SKILL.md b/.agents/skills/test-t3-mobile/SKILL.md index b2f5443e431a13c056c1d8305be2a149b462ded0..bea684115cd92c5529be346aad3d9201464d2faa 100644 --- a/.agents/skills/test-t3-mobile/SKILL.md +++ b/.agents/skills/test-t3-mobile/SKILL.md @@ -26,7 +26,7 @@ Test with meaningful project and thread data. Read the shared [SQLite fixture reference](../test-t3-app/references/sqlite-fixtures.md) only when inspecting or seeding SQLite. Stop the test server before fixture writes. -## Launch T3 Code Dev +## Launch The Knife Dev From the checkout being tested on the selected device host, run: @@ -57,7 +57,7 @@ arguments stored in `agent_device_command` and the Bash array "$agent_device_command" "${agent_device_target_args[@]}" ``` -It issues a fresh credential and opens T3 Code Dev's existing pairing route +It issues a fresh credential and opens The Knife Dev's existing pairing route through AgentDevice. For a backend on the device host, use `http://127.0.0.1:` on iOS or `http://10.0.2.2:` on Android. For a remote backend, use its reachable origin. diff --git a/.gitignore b/.gitignore index 38bac103f1f41851f2e2a2b403149330abed73ad..34acbc25b3f63a15a43364a15f4bcf395732a23d 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ node_modules/ # pnpm content-addressable store; never belongs in the repo. .pnpm-store/ +/apps/web/public/fonts/private/ diff --git a/AGENTS.md b/AGENTS.md index f0202081de835776440dfcdcab63c06c7a1b1e39..c95fd981ac9a3b29df48926f50cc3547b8b5ad49 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,6 @@ -# T3 Code +# The Knife + +The Knife is Clover’s fork of T3 Code, focused on precise engineering across the backend and UI. Fork behavior and identity belong to this patch stack; the upstream architecture and service names below describe the foundation. T3 Code is a minimal GUI for coding agents. A Node WebSocket server wraps provider CLIs and agents (Codex, Claude Code, Cursor, Grok, OpenCode, Antigravity) and serves web, desktop, and mobile clients. @@ -104,16 +106,6 @@ An empty database is a bad test. Seed your worktree's `.t3` with a copy of real For authorized mobile verification, a missing or outdated native client is a build step, not a blocker. Run `node scripts/mobile-native-client.ts ensure ` on the simulator host before starting Metro. It checks the local Expo fingerprint and builds/installs when needed. See `test-t3-mobile` for the full workflow. -## Pull requests - -- Never make a PR unless the developer explicitly asks you to do so. -- Conventional commit titles, plain language: `fix(web): new threads no longer spike CPU`. -- Body: the problem in a sentence or two, then how you fixed it. End with the model and harness that did the work. -- UI changes need before/after images. Motion or timing needs a short video. -- Upload PR evidence to GitHub. Never commit PR-only screenshots or assets such as `.github/pr-assets/`. -- One request is one PR. Split it only when the maintainer asks. Outside contributions follow the stricter [one problem per PR](CONTRIBUTING.md#one-problem) rule. -- When babysitting: poll checks and comments newer than the last push, verify each bot finding against the source, fix real ones, dismiss false positives with a written reason. Stay quiet when nothing is new. Stop when the bots are green on the latest commit. - ## Documentation Most code changes do not need an internal documentation change. Agents can read the code. @@ -126,6 +118,45 @@ Most code changes do not need an internal documentation change. Agents can read - Keep user docs in the shipped product's voice, without implementation details or contributor tooling. Update the relevant feature section when how to use it changes. A UI tweak does not need a documentation entry, and a new control does not need its own page. - `docs/operations/` holds maintainer setup, release, and debugging procedures. Keep instructions for operating an installed T3 Code server in the user guides. +## Fork patch workflow + +This checkout is a fork of T3 code for Clover. All changes should be +categorized into a set of well-maintained changes on top of the upstream. When +a change to something you already patched is requested, the changes are +squashed in. This allows rebasing without having hundreds of changes. The +patches can also be cherry-picked for upstreaming if Theo or someone elses fork +wants the change. Exact operating rules: + +- Fork-only work follows these rules instead of upstream contribution and PR + conventions. Apply the upstream process when Clover requests an upstream submission. +- Keep one Jujutsu change per complete behavior above `main@upstream`, not per + iteration or touched file. Fold revisions into their owning patch with + `jj squash --from @ --into --use-destination-message`. +- Order setup first, general UI patches next, and work integrations last. Within + each group, put dependencies before their consumers. +- Use unscoped titles such as `feat: ...` and `fix: ...`. The only scope is + `at-work`, for work-specific patches. +- Titles name the concrete change. For replacements, name the original T3 + component and replacement package; explain behavior and retained features in + the description. Rewrite descriptions when a patch's scope changes. +- Keep fork setup and agent guidance together in the setup patch. +- After testing a change yourself, publish it with `vp run fork:release` before + handing it back. Follow the + [private release procedure](docs/operations/development.md#private-fork-releases-for-apple-silicon). + Publish every supported desktop platform affected by the change, using the + native build and ingestion steps in that procedure. + Reuse unchanged UI, static assets, application code, and shell artifacts; all machines consume + the same public feed. Keep the upload API key and signing credentials in + `~/.config/t3-fork-release.json`, outside the repository. +- Downloads are automatic. Backend and shell restarts require confirmation. + Automatically refresh a pending UI after one minute unfocused or ten minutes + idle only when browser, terminal, draft, composer, and scroll state survive. + Defer the refresh when a live view or unsaved work cannot be restored. +- Keep employer names, logos, repository identifiers, and service origins in + private host configuration, outside tracked files and change descriptions. +- Describe the intended behavior and why Clover requested it in each change's description. +- Use managed Jujutsu workspaces when they help parallel subagents; integrate their changes into the same stack. + ## Plans and work artifacts - Do not commit implementation plans, research notes, or agent scratch files. Keep temporary working material outside the worktree. `.plans/` is gitignored only as a safety net for legacy tooling. diff --git a/README.md b/README.md index 8aa78f81b9ad72753cde7a8d3106ef8c7a8f0697..434570e52149a20df837421b6ff846f8f9e29422 100644 --- a/README.md +++ b/README.md @@ -1,141 +1,7 @@ -# T3 Code +# The Knife -T3 Code is an "agent harness control surface". It enables control of the agents on your machine with a best-in-class mobile app ([iOS](https://apps.apple.com/us/app/t3-code-remote-claude-more/id6787819824), [Android](https://play.google.com/store/apps/details?id=com.t3tools.t3code)), [web app](https://app.t3.codes) and [Electron-based desktop app](https://t3.codes). +A fork of [T3 Code](https://t3.codes) with more precise engineering from the +backend to the UI/UX. -Works with your subscriptions on Claude Code, Codex, Cursor, Grok Build, OpenCode, and Google Antigravity. If they're set up on your computer, T3 Code can control them. - -## "Wait, what are you selling me?" - -Nothing. We built T3 Code because we wanted the best possible development experience with agents. We were inspired by existing solutions like the Codex desktop app, Conductor, Claude Desktop and Cursor Glass, but none met our bar. - -We wanted something performant, remote-ready, and truly open. If we ever go the wrong direction, we want you to have everything you need to fork and build the editor that you want. - -## Installation - -> [!WARNING] -> T3 Code currently supports Codex, Claude, Cursor, Grok Build, OpenCode, and Antigravity. Install and authenticate at least one provider before use: -> -> - Codex: install [Codex CLI](https://developers.openai.com/codex/cli) and run `codex login` -> - Claude: install [Claude Code](https://claude.com/product/claude-code) and run `claude auth login` -> - Cursor: install [Cursor CLI](https://cursor.com/cli) and run `agent login` -> - Grok Build: install [Grok Build CLI](https://x.ai/cli) and run `grok login` -> - OpenCode: install [OpenCode](https://opencode.ai) and run `opencode auth login` -> - Antigravity: enable it in Settings, then use **Install Antigravity** and **Sign in with Google**. No CLI is required. - -### Command line - -```bash -curl -fsSL https://t3.codes/install.sh | sh -``` - -On Windows, in PowerShell: - -```powershell -irm https://t3.codes/install.ps1 | iex -``` - -Then run `t3` to start the server and open the local web app. `t3 service install` keeps it running in the background, `t3 update` moves to a newer release, and `t3 --help` has the full reference. - -To try it once without installing, run `npx t3@latest` instead. - -### Desktop app - -Install the latest version of the desktop app from [GitHub Releases](https://github.com/pingdotgg/t3code/releases), or from your favorite package registry: - -#### Windows (`winget`) - -```bash -winget install T3Tools.T3Code -``` - -#### Windows (`scoop`) - -```bash -scoop install extras/t3code -``` - -#### macOS (Homebrew) - -```bash -brew install --cask t3-code -``` - -#### Debian, Ubuntu (`.deb`) - -Download the `.deb` from [GitHub Releases](https://github.com/pingdotgg/t3code/releases), then: - -```bash -sudo apt install ./T3-Code-*.deb -``` - -#### Arch Linux (AUR) - -Stable: - -```bash -yay -S t3code-bin -``` - -Nightly: - -```bash -yay -S t3code-nightly-bin -``` - -The AUR packaging is maintained in this repository under [`packaging/aur`](./packaging/aur). - -## Some notes - -We are very very early in this project. Expect bugs. - -We are (mostly) not accepting contributions yet. Small fixes may be considered. Big features will not be. - -## Documentation - -Full docs live in [docs/](./docs). There's no docs site yet. - -- [Install and first run](./docs/user/install.md) -- [Permission modes](./docs/user/permission-modes.md) -- [Keyboard shortcuts](./docs/user/keybindings.md) -- [Project settings](./docs/user/project-settings.md) -- [Appearance preferences](./docs/user/appearance.md) -- [Remote access from a phone or another machine](./docs/user/remote-access.md) -- [Connect Claude Code, Codex, ChatGPT and other agents over MCP](./docs/user/outside-agents.md) -- [Keeping app and server in sync](./docs/user/updating.md) -- [Source control integrations](./docs/user/source-control.md) -- Multiple accounts: [Codex](./docs/user/providers-codex.md) · [Claude](./docs/user/providers-claude.md) -- [Run T3 Code as a background service](./docs/user/background-service.md) - -Building from source? Start at [docs/internals/overview.md](./docs/internals/overview.md). - -## If you REALLY want to contribute still.... read this first - -### Install `vp` - -T3 Code uses Vite+ so you'll need to install the global `vp` command-line tool. - -#### macOS / Linux - -```bash -curl -fsSL https://vite.plus | bash -``` - -#### Windows - -```bash -irm https://vite.plus/ps1 | iex -``` - -Checkout their getting started guide for more information: https://viteplus.dev/guide/ - -### Install dependencies - -```bash -vp i -``` - -Read [CONTRIBUTING.md](./CONTRIBUTING.md) before reporting a bug or opening a PR. - -Have a feature request? Start an [Ideas discussion](https://github.com/pingdotgg/t3code/discussions/categories/ideas). - -Need support? Join the [Discord](https://discord.gg/jn4EGJjrvv). +- [Read patches](https://shale.paperclover.net/the-knife/log). +- Download for [Apple Silicon Mac](https://file.paperclover.net/shr/the-knife/The-Knife-darwin-arm64.zip), [Windows](https://file.paperclover.net/shr/the-knife/The-Knife-win32-x64.exe), or [Linux x64](https://file.paperclover.net/shr/the-knife/The-Knife-linux-x64.AppImage). diff --git a/apps/desktop/gnome-extension/captureFeedback.js b/apps/desktop/gnome-extension/captureFeedback.js index 1278101b57ca6d58e07f62fd49860c8790dcc82a..3593338f74f605b9fc0050602b539c3689dbcfb8 100644 --- a/apps/desktop/gnome-extension/captureFeedback.js +++ b/apps/desktop/gnome-extension/captureFeedback.js @@ -110,7 +110,7 @@ export class CaptureFeedback { check(); })); if (this._owned(sender) !== session || !target) - throw new Error("T3 Code's window is not available for activation."); + throw new Error("The Knife's window is not available for activation."); session.target = target; Main.activateWindow(target, global.get_current_time()); } diff --git a/apps/desktop/gnome-extension/captureService.js b/apps/desktop/gnome-extension/captureService.js index 3b436afd5e22b68e64af43063226fc106be97dd3..79920f40333c557df62c02832ef52b49c47f7ef4 100644 --- a/apps/desktop/gnome-extension/captureService.js +++ b/apps/desktop/gnome-extension/captureService.js @@ -43,7 +43,7 @@ export class CaptureService { break; } } - if (!allowed) throw new Error("Only T3 Code may request a snapshot."); + if (!allowed) throw new Error("Only The Knife may request a snapshot."); const pid = options ? await this._getProcessId(sender) : undefined; this._checkSession(); const snapshot = await this._takeSnapshot(options?.animate ?? false); diff --git a/apps/desktop/gnome-extension/captureService.test.js b/apps/desktop/gnome-extension/captureService.test.js index b8313f920f72a68f3310307f5f841530385b9da5..c4f3ce9543148108db099a7e285bcc5fa02d8108 100644 --- a/apps/desktop/gnome-extension/captureService.test.js +++ b/apps/desktop/gnome-extension/captureService.test.js @@ -23,7 +23,7 @@ function fixture(overrides = {}) { it("rejects other clients before looking at the active window", async () => { const { service, snapshots } = fixture(); - await expect(service.capture(":1.99")).rejects.toThrow("Only T3 Code"); + await expect(service.capture(":1.99")).rejects.toThrow("Only The Knife"); expect(snapshots()).toBe(0); expect(await service.capture(":1.23")).toBe("pixels"); }); @@ -38,7 +38,7 @@ it("rechecks the name owner on each capture", async () => { const { service } = fixture({ getNameOwner: async () => owner }); expect(await service.capture(owner)).toBe("pixels"); owner = ":1.24"; - await expect(service.capture(":1.23")).rejects.toThrow("Only T3 Code"); + await expect(service.capture(":1.23")).rejects.toThrow("Only The Knife"); }); it("rejects locked and non-Wayland sessions without taking a screenshot", async () => { @@ -94,7 +94,7 @@ it("rejects concurrent requests and clears busy after a failed capture", async ( await expect(service.capture(":1.23")).rejects.toThrow("already in progress"); finish(new Error("gone")); await expect(first).rejects.toThrow("gone"); - await expect(service.capture(":1.99")).rejects.toThrow("Only T3 Code"); + await expect(service.capture(":1.99")).rejects.toThrow("Only The Knife"); }); it("prepares focus/effects only after pixels and identity have been captured", async () => { diff --git a/apps/desktop/gnome-extension/metadata.json b/apps/desktop/gnome-extension/metadata.json index ff51dbb9a4865b0cb49acd5db76f490b0f25c316..4504baf1343b7ad49c65c2db72dd4b83fb5be309 100644 --- a/apps/desktop/gnome-extension/metadata.json +++ b/apps/desktop/gnome-extension/metadata.json @@ -1,7 +1,7 @@ { "uuid": "snap-shot@t3.codes", - "name": "T3 Code SnapShots", - "description": "Allows T3 Code to capture the focused window, identify it for optional accessibility text, and animate it into T3 Code. Disable this extension to revoke access.", + "name": "The Knife SnapShots", + "description": "Allows The Knife to capture the focused window, identify it for optional accessibility text, and animate it into The Knife. Disable this extension to revoke access.", "shell-version": ["45", "46", "47", "48", "49", "50"], "version": 2, "url": "https://github.com/pingdotgg/t3code" diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 8e736a7d54105a46abdcbc6ffe6dafaee2e9a271..9f1ed12d9d4e602458e78932fc2c6f4366501696 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -42,5 +42,5 @@ "tailwindcss": "^4.0.0", "vite-plus": "catalog:" }, - "productName": "T3 Code (Alpha)" + "productName": "The Knife" } diff --git a/apps/desktop/resources/dmg/dmg-background-latest.svg b/apps/desktop/resources/dmg/dmg-background-latest.svg index 2c300aa5242377e7335be369db698f5879d054cc..2f7cb66ab31a73f198bf867b0a748461088d64dc 100644 --- a/apps/desktop/resources/dmg/dmg-background-latest.svg +++ b/apps/desktop/resources/dmg/dmg-background-latest.svg @@ -20,7 +20,7 @@ - Drag T3 Code into Applications. + Drag The Knife into Applications. Once copied, open it from Applications. diff --git a/apps/desktop/resources/dmg/dmg-background-nightly.svg b/apps/desktop/resources/dmg/dmg-background-nightly.svg index 6ae74b3b51ef951d4fbe13ececc4de899a8309a0..b72556cd175df422207065d76daa1e55dcc26633 100644 --- a/apps/desktop/resources/dmg/dmg-background-nightly.svg +++ b/apps/desktop/resources/dmg/dmg-background-nightly.svg @@ -27,7 +27,7 @@ NIGHTLY - Drag T3 Code into Applications. + Drag The Knife into Applications. Once copied, open it from Applications. diff --git a/apps/desktop/resources/linux/com.t3tools.t3code.metainfo.xml b/apps/desktop/resources/linux/com.t3tools.t3code.metainfo.xml index 0ae54a9849fcb6f99350d59fc590d075f92288a6..82fdc3e34bae6e3e3bf549e60a2dc97489f5f217 100644 --- a/apps/desktop/resources/linux/com.t3tools.t3code.metainfo.xml +++ b/apps/desktop/resources/linux/com.t3tools.t3code.metainfo.xml @@ -3,14 +3,14 @@ com.t3tools.t3code CC0-1.0 MIT - T3 Code + The Knife Desktop GUI for coding agents T3 Tools Inc.

- T3 Code is an open-source desktop app for coding agents. Work with your + The Knife is an open-source desktop app for coding agents. Work with your existing agent subscriptions, review code changes, and run commands in your projects.

diff --git a/apps/desktop/scripts/electron-launcher.mjs b/apps/desktop/scripts/electron-launcher.mjs index b355650f841fe956a78c6130b4d8f1463b334c98..5c6b139021f43a2de665d4f6935252f56feb75a4 100644 --- a/apps/desktop/scripts/electron-launcher.mjs +++ b/apps/desktop/scripts/electron-launcher.mjs @@ -1,4 +1,4 @@ -// This file mostly exists because we want dev mode to say "T3 Code (Dev)" instead of "electron" +// This file mostly exists because we want dev mode to say "The Knife (Dev)" instead of "electron" import * as NodeChildProcess from "node:child_process"; import * as NodeFS from "node:fs"; @@ -15,19 +15,13 @@ const repoRoot = NodePath.resolve(desktopDir, "..", ".."); const devBundleIdSuffix = NodePath.basename(repoRoot) .toLowerCase() .replaceAll(/[^a-z0-9]+/g, ""); -const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)"; +const APP_DISPLAY_NAME = isDevelopment ? "The Knife (Dev)" : "The Knife"; const APP_BUNDLE_ID = isDevelopment ? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}` : "com.t3tools.t3code"; const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"]; const LAUNCHER_VERSION = 20; -const developmentMacIconPngPath = NodePath.join( - repoRoot, - "assets", - "dev", - "blueprint-macos-1024.png", -); -const productionMacIconPngPath = NodePath.join(repoRoot, "assets", "prod", "black-macos-1024.png"); +const macIconPngPath = NodePath.join(repoRoot, "assets", "knife", "macos-1024.png"); // oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher script has no Effect runtime. const hostPlatform = NodeOS.platform(); @@ -203,13 +197,10 @@ function registerMacLauncherBundle(appBundlePath) { } // Bundle-internal paths are macOS paths whatever host builds them. -export function resolveMacLauncherIconPaths(runtimeDir, development = isDevelopment) { +export function resolveMacLauncherIconPaths(runtimeDir) { return { - sourceIconPath: development ? developmentMacIconPngPath : productionMacIconPngPath, - generatedIconPath: NodePath.posix.join( - runtimeDir, - development ? "icon-dev.icns" : "icon-prod.icns", - ), + sourceIconPath: macIconPngPath, + generatedIconPath: NodePath.posix.join(runtimeDir, "icon.icns"), }; } @@ -270,8 +261,8 @@ export function resolveMacBundleInfoPlistStrings(executableName) { CFBundleExecutable: executableName, CFBundleIconFile: "icon.icns", NSScreenCaptureUsageDescription: - "T3 Code captures the active window when you use the snapshot shortcut.", - NSDocumentsFolderUsageDescription: "T3 Code reads project files you open in the desktop app.", + "The Knife captures the active window when you use the snapshot shortcut.", + NSDocumentsFolderUsageDescription: "The Knife reads project files you open in the desktop app.", }; } @@ -404,7 +395,7 @@ function buildMacLauncher(electronBinaryPath) { if (isDevelopment) { // Keep Electron's native executable inside the branded bundle. Launching the // node_modules copy makes macOS associate the process (and Dock label) with - // Electron.app even though this bundle's Info.plist has the T3 Code name. + // Electron.app even though this bundle's Info.plist names The Knife. // Its conventional executable name also keeps Electron's default-app runtime // in development mode instead of making app.isPackaged report true. writeDevelopmentEnvironmentScript(); diff --git a/apps/desktop/scripts/electron-launcher.test.mjs b/apps/desktop/scripts/electron-launcher.test.mjs index 84d08cd9b6b898da2fc95edd0233bd6a2a7c9d98..7f65dca0e9d082aa5a86456594d68835fa67abc3 100644 --- a/apps/desktop/scripts/electron-launcher.test.mjs +++ b/apps/desktop/scripts/electron-launcher.test.mjs @@ -76,18 +76,18 @@ describe("electron development launcher", () => { it("keeps the native Electron executable name inside the branded macOS bundle", () => { const paths = resolveMacLauncherPaths( - "/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app", - "T3 Code (Dev)", + "/repo/apps/desktop/.electron-runtime/The Knife (Dev).app", + "The Knife (Dev)", ); - assert.equal(paths.launcherExecutableName, "T3 Code (Dev) Launcher"); + assert.equal(paths.launcherExecutableName, "The Knife (Dev) Launcher"); assert.equal( paths.launcherBinaryPath, - "/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/T3 Code (Dev) Launcher", + "/repo/apps/desktop/.electron-runtime/The Knife (Dev).app/Contents/MacOS/The Knife (Dev) Launcher", ); assert.equal( paths.runtimeElectronBinaryPath, - "/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/Electron", + "/repo/apps/desktop/.electron-runtime/The Knife (Dev).app/Contents/MacOS/Electron", ); const script = makeDevelopmentLauncherScript({ @@ -98,32 +98,32 @@ describe("electron development launcher", () => { }); assert.include( script, - "exec '/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/Electron'", + "exec '/repo/apps/desktop/.electron-runtime/The Knife (Dev).app/Contents/MacOS/Electron'", ); assert.notInclude(script, "node_modules/electron"); }); it("declares why the macOS app needs protected access", () => { - const values = resolveMacBundleInfoPlistStrings("T3 Code (Dev) Launcher"); + const values = resolveMacBundleInfoPlistStrings("The Knife (Dev) Launcher"); assert.equal( values.NSScreenCaptureUsageDescription, - "T3 Code captures the active window when you use the snapshot shortcut.", + "The Knife captures the active window when you use the snapshot shortcut.", ); assert.equal( values.NSDocumentsFolderUsageDescription, - "T3 Code reads project files you open in the desktop app.", + "The Knife reads project files you open in the desktop app.", ); }); it("ad-hoc signs the complete development app bundle", () => { - assert.deepEqual(resolveMacCodeSignArguments("/runtime/T3 Code (Dev).app"), [ + assert.deepEqual(resolveMacCodeSignArguments("/runtime/The Knife (Dev).app"), [ "--force", "--deep", "--sign", "-", "--timestamp=none", - "/runtime/T3 Code (Dev).app", + "/runtime/The Knife (Dev).app", ]); }); @@ -141,14 +141,9 @@ describe("electron development launcher", () => { } }); - it("derives launcher icons from canonical development and production assets", () => { - const development = resolveMacLauncherIconPaths("/runtime", true); - const production = resolveMacLauncherIconPaths("/runtime", false); - - // The source icons are real repo paths, joined for the host. - assert.match(development.sourceIconPath, /assets[\\/]dev[\\/]blueprint-macos-1024\.png$/); - assert.equal(development.generatedIconPath, "/runtime/icon-dev.icns"); - assert.match(production.sourceIconPath, /assets[\\/]prod[\\/]black-macos-1024\.png$/); - assert.equal(production.generatedIconPath, "/runtime/icon-prod.icns"); + it("derives launcher icons from the canonical Knife artwork", () => { + const icon = resolveMacLauncherIconPaths("/runtime"); + assert.match(icon.sourceIconPath, /assets[\\/]knife[\\/]macos-1024\.png$/); + assert.equal(icon.generatedIconPath, "/runtime/icon.icns"); }); }); diff --git a/apps/desktop/scripts/verify-preload-bundle.mjs b/apps/desktop/scripts/verify-preload-bundle.mjs index f6976220d07fed46457c445d96c49b8564b685c7..ef961eee8a9413f9f55f2ca968c1d6734754718f 100644 --- a/apps/desktop/scripts/verify-preload-bundle.mjs +++ b/apps/desktop/scripts/verify-preload-bundle.mjs @@ -108,6 +108,7 @@ const executeBundle = (source, sandboxModules) => { { process: sandboxProcess, require: requireSandboxModule, + window: { addEventListener: () => undefined }, }, { filename: "desktop-preload.cjs", diff --git a/apps/desktop/src/app/DesktopApp.ts b/apps/desktop/src/app/DesktopApp.ts index 24dbf2232f0050acc421b38b61af36cd622956df..1504e0ca63edaa55f71b8e636a30509ab7ea0e58 100644 --- a/apps/desktop/src/app/DesktopApp.ts +++ b/apps/desktop/src/app/DesktopApp.ts @@ -136,7 +136,7 @@ const handleFatalStartupError = Effect.fn("desktop.startup.handleFatalStartupErr const wasQuitting = yield* Ref.getAndSet(state.quitting, true); if (!wasQuitting) { yield* electronDialog.showErrorBox( - "T3 Code failed to start", + "The Knife failed to start", `Stage: ${stage}\n${message}${detail}`, ); } diff --git a/apps/desktop/src/app/DesktopAppActivation.ts b/apps/desktop/src/app/DesktopAppActivation.ts index 19f24460106e9aa54378deb69a68bb4583ce87ec..e598c70dc68425182b945ad17a37d15d9833d358 100644 --- a/apps/desktop/src/app/DesktopAppActivation.ts +++ b/apps/desktop/src/app/DesktopAppActivation.ts @@ -171,7 +171,7 @@ export async function startDesktopAppControlServer(input: { activeRequestId = parsed.requestId; void input.handle(parsed).then(finish, () => { finish( - invalidResponse(parsed.requestId, "T3 Code could not process the desktop app request."), + invalidResponse(parsed.requestId, "The Knife could not process the desktop app request."), ); }); }); diff --git a/apps/desktop/src/app/DesktopAppActivationBroker.ts b/apps/desktop/src/app/DesktopAppActivationBroker.ts index 221df9ca86ddb612ab04cd79295968f3cec73a39..7b9dab3c7d7330b080e82f1ec47da8f56f81d024 100644 --- a/apps/desktop/src/app/DesktopAppActivationBroker.ts +++ b/apps/desktop/src/app/DesktopAppActivationBroker.ts @@ -45,7 +45,7 @@ export class DesktopAppActivationBroker { request(request: DesktopAppActivationRequest): Promise { if (this.#closed) { return Promise.resolve( - failure(request.requestId, "renderer-unavailable", "T3 Code is shutting down."), + failure(request.requestId, "renderer-unavailable", "The Knife is shutting down."), ); } if (this.#pending.has(request.requestId)) { @@ -90,7 +90,7 @@ export class DesktopAppActivationBroker { failure( pending.request.requestId, "renderer-unavailable", - "The T3 Code window closed before it opened the project.", + "The Knife closed before opening the project.", ), ); } @@ -103,7 +103,7 @@ export class DesktopAppActivationBroker { cancel(requestId: string): void { this.#settle( - failure(requestId, "renderer-unavailable", "The command closed before T3 Code was ready."), + failure(requestId, "renderer-unavailable", "The command closed before The Knife was ready."), ); } @@ -112,7 +112,7 @@ export class DesktopAppActivationBroker { this.#renderer = null; for (const pending of this.#pending.values()) { this.#settle( - failure(pending.request.requestId, "renderer-unavailable", "T3 Code is shutting down."), + failure(pending.request.requestId, "renderer-unavailable", "The Knife is shutting down."), ); } } diff --git a/apps/desktop/src/app/DesktopAppIdentity.test.ts b/apps/desktop/src/app/DesktopAppIdentity.test.ts index cc0277d3ed104feb0b19a08d10056743ff94ebe6..5a4f52058effc07100c8537a4e889b6c8ae30ac9 100644 --- a/apps/desktop/src/app/DesktopAppIdentity.test.ts +++ b/apps/desktop/src/app/DesktopAppIdentity.test.ts @@ -218,7 +218,7 @@ describe("DesktopAppIdentity", () => { yield* identity.configure; assert.deepEqual(calls.setName, ["T3 Code Alpha"]); - assert.equal(calls.setAboutPanelOptions[0]?.applicationName, "T3 Code (Alpha)"); + assert.equal(calls.setAboutPanelOptions[0]?.applicationName, "The Knife"); assert.equal(calls.setAboutPanelOptions[0]?.applicationVersion, "1.2.3"); assert.equal(calls.setAboutPanelOptions[0]?.version, "0123456789ab"); // Packaged: the bundle's own icon stands, so a custom one the user @@ -247,28 +247,34 @@ describe("DesktopAppIdentity", () => { stage: "Dev", environment: { env: { VITE_DEV_SERVER_URL: "http://localhost:5173" } }, }, - ])("uses a valid native User-Agent product name for $stage", ({ stage, environment }) => { - const calls: ElectronAppCalls = { - setAboutPanelOptions: [], - setDockIcon: [], - setName: [], - }; + ])( + "preserves the credential identity and valid native User-Agent for $stage", + ({ stage, environment }) => { + const calls: ElectronAppCalls = { + setAboutPanelOptions: [], + setDockIcon: [], + setName: [], + }; - return withIdentity( - Effect.gen(function* () { - const identity = yield* DesktopAppIdentity.DesktopAppIdentity; - yield* identity.configure; + return withIdentity( + Effect.gen(function* () { + const identity = yield* DesktopAppIdentity.DesktopAppIdentity; + yield* identity.configure; - const runtimeName = calls.setName[0]; - assert.isDefined(runtimeName); - assert.equal(runtimeName, `T3 Code ${stage}`); - // RFC 9110's token grammar, after Electron removes ASCII spaces. - assert.match(runtimeName.replaceAll(" ", ""), /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/); - assert.equal(calls.setAboutPanelOptions[0]?.applicationName, `T3 Code (${stage})`); - }), - { calls, environment }, - ); - }); + const runtimeName = calls.setName[0]; + assert.isDefined(runtimeName); + assert.equal(runtimeName, `T3 Code ${stage}`); + // RFC 9110's token grammar, after Electron removes ASCII spaces. + assert.match(runtimeName.replaceAll(" ", ""), /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/); + assert.equal( + calls.setAboutPanelOptions[0]?.applicationName, + stage === "Alpha" ? "The Knife" : `The Knife (${stage})`, + ); + }), + { calls, environment }, + ); + }, + ); it.effect("sets the dock icon only when running unpackaged", () => { const calls: ElectronAppCalls = { diff --git a/apps/desktop/src/app/DesktopAppIdentity.ts b/apps/desktop/src/app/DesktopAppIdentity.ts index 395c06141d64c669435da431b8506695f669c021..0ef02a88b100e8ab1b34465aa10ba729248c1ce2 100644 --- a/apps/desktop/src/app/DesktopAppIdentity.ts +++ b/apps/desktop/src/app/DesktopAppIdentity.ts @@ -91,12 +91,8 @@ export const make = Effect.gen(function* () { const configure = Effect.gen(function* () { const commitHash = yield* resolveAboutCommitHash; - // Electron removes spaces from this name to build the native User-Agent - // product token, but leaves parentheses intact. Keep the runtime name valid - // without rewriting preview sessions (which breaks Turnstile, #7110). - yield* electronApp.setName( - `${environment.branding.baseName} ${environment.branding.stageLabel}`, - ); + // Electron's runtime name also identifies the shipped Safe Storage/keyring entry. + yield* electronApp.setName(`T3 Code ${environment.branding.stageLabel}`); yield* electronApp.setAboutPanelOptions({ applicationName: environment.displayName, applicationVersion: environment.appVersion, diff --git a/apps/desktop/src/app/DesktopAssets.test.ts b/apps/desktop/src/app/DesktopAssets.test.ts index 6d2179f0256ab0541809f849d62b3fdb613b85dc..33791c4cd27e5691becb31bc4814b43445d6149e 100644 --- a/apps/desktop/src/app/DesktopAssets.test.ts +++ b/apps/desktop/src/app/DesktopAssets.test.ts @@ -50,7 +50,7 @@ describe("DesktopAssets", () => { ), ); const layerFileSystem = FileSystem.layerNoop({ - exists: (path) => Effect.succeed(String(path).includes("/assets/dev/")), + exists: (path) => Effect.succeed(String(path).includes("/assets/knife/")), }); const assets = yield* DesktopAssets.DesktopAssets.pipe( Effect.provide( @@ -62,8 +62,8 @@ describe("DesktopAssets", () => { const icons = yield* assets.iconPaths; - assert.match(Option.getOrThrow(icons.ico), /assets\/dev\/blueprint-windows\.ico$/); - assert.match(Option.getOrThrow(icons.png), /assets\/dev\/blueprint-universal-1024\.png$/); + assert.match(Option.getOrThrow(icons.ico), /assets\/knife\/icon\.ico$/); + assert.match(Option.getOrThrow(icons.png), /assets\/knife\/icon-1024\.png$/); assert.isTrue(Option.isNone(icons.icns)); }), ); diff --git a/apps/desktop/src/app/DesktopAssets.ts b/apps/desktop/src/app/DesktopAssets.ts index 9c81b8ab1c047b134cdeafaab489becc8fd62cd8..48c6e7f68c51e85854b5c4c9f9e91892b49a4606 100644 --- a/apps/desktop/src/app/DesktopAssets.ts +++ b/apps/desktop/src/app/DesktopAssets.ts @@ -61,33 +61,18 @@ const resolveResourcePath = Effect.fn("desktop.assets.resolveResourcePath")(func return Option.none(); }); -const sourceTreeIconFileNames = { - dev: { - ico: "blueprint-windows.ico", - macPng: "blueprint-macos-1024.png", - universalPng: "blueprint-universal-1024.png", - }, - prod: { - ico: "t3-black-windows.ico", - macPng: "black-macos-1024.png", - universalPng: "black-universal-1024.png", - }, -} as const; - function resolveSourceTreeIconPath( environment: DesktopEnvironment.DesktopEnvironment["Service"], ext: keyof DesktopIconPaths, ): string | undefined { if (environment.isPackaged || ext === "icns") return undefined; - const brand = environment.isDevelopment ? "dev" : "prod"; - const fileNames = sourceTreeIconFileNames[brand]; const fileName = ext === "ico" - ? fileNames.ico + ? "icon.ico" : environment.platform === "darwin" - ? fileNames.macPng - : fileNames.universalPng; - return environment.path.join(environment.rootDir, "assets", brand, fileName); + ? "macos-1024.png" + : "icon-1024.png"; + return environment.path.join(environment.rootDir, "assets", "knife", fileName); } const resolveIconPath = Effect.fn("desktop.assets.resolveIconPath")(function* ( diff --git a/apps/desktop/src/app/DesktopCliShim.ts b/apps/desktop/src/app/DesktopCliShim.ts index 5cd942f66ced96db3b8f38fce2c72f43153ec9d1..870bc259f1e102f94b38a5d1765227fabb12e2db 100644 --- a/apps/desktop/src/app/DesktopCliShim.ts +++ b/apps/desktop/src/app/DesktopCliShim.ts @@ -24,7 +24,7 @@ const shellWord = (value: string) => `'${value.replaceAll("'", `'"'"'`)}'`; const cmdText = (value: string) => value.replaceAll("%", "%%"); const cmdWord = (value: string) => `"${cmdText(value)}"`; -const MOVED = "T3 Code has moved or been removed. Open the app once to update this command."; +const MOVED = "The Knife has moved or been removed. Open the app once to update this command."; export type CliShimTarget = | { readonly kind: "appimage"; readonly appImage: string; readonly executableName: string } diff --git a/apps/desktop/src/app/DesktopEnvironment.ts b/apps/desktop/src/app/DesktopEnvironment.ts index 6a8bc8e0ea4955a0313349f0ffbe1bc41e1b1abb..abc0f59da13e3e7046283cbd1ad1c2ec903070cf 100644 --- a/apps/desktop/src/app/DesktopEnvironment.ts +++ b/apps/desktop/src/app/DesktopEnvironment.ts @@ -93,7 +93,7 @@ export class DesktopEnvironment extends Context.Service< } >()("@t3tools/desktop/app/DesktopEnvironment") {} -const APP_BASE_NAME = "T3 Code"; +const APP_BASE_NAME = "The Knife"; function resolveDesktopAppStageLabel(input: { readonly isDevelopment: boolean; @@ -114,7 +114,7 @@ export function resolveDesktopAppBranding(input: { return { baseName: APP_BASE_NAME, stageLabel, - displayName: `${APP_BASE_NAME} (${stageLabel})`, + displayName: stageLabel === "Alpha" ? APP_BASE_NAME : `${APP_BASE_NAME} (${stageLabel})`, }; } diff --git a/apps/desktop/src/app/DesktopWebLinks.ts b/apps/desktop/src/app/DesktopWebLinks.ts index 2031ada45a12dbac028fe52800e44c6b52748141..14f388ecdc4c1302ed7027d0a79abcc4150ebfdd 100644 --- a/apps/desktop/src/app/DesktopWebLinks.ts +++ b/apps/desktop/src/app/DesktopWebLinks.ts @@ -6,14 +6,14 @@ import * as Option from "effect/Option"; import * as ElectronWindow from "../electron/ElectronWindow.ts"; import { WEB_LINK_OPEN_CHANNEL } from "../ipc/channels.ts"; -/** An http(s) link the operating system handed to T3 Code as the default browser. */ +/** An http(s) link the operating system handed to The Knife as the default browser. */ export const isWebLink = (value: string) => { if (!URL.canParse(value)) return false; const { protocol } = new URL(value); return protocol === "http:" || protocol === "https:"; }; -/** An HTML file the operating system asked T3 Code to open, as browsers are asked to. */ +/** An HTML file the operating system asked The Knife to open, as browsers are asked to. */ export const isWebPageFile = (path: string) => /\.x?html?$/i.test(path); /** diff --git a/apps/desktop/src/backend/DesktopBackendPool.ts b/apps/desktop/src/backend/DesktopBackendPool.ts index 21845205110c4f171bd627d57246fcf833d478a0..3a3ec70c57b804160a9d33eb9698afe53145d56f 100644 --- a/apps/desktop/src/backend/DesktopBackendPool.ts +++ b/apps/desktop/src/backend/DesktopBackendPool.ts @@ -247,7 +247,7 @@ export const layer = Layer.effect( ); yield* electronDialog.showErrorBox( "WSL backend is still unavailable", - `${reason}\n\nT3 Code will use the Windows backend for this launch and retry WSL the next time the app starts.`, + `${reason}\n\nThe Knife will use the Windows backend for this launch and retry WSL the next time the app starts.`, ); yield* appSettings.applyWslWindowsFallbackInMemory; return true; @@ -258,7 +258,7 @@ export const layer = Layer.effect( }); yield* electronDialog.showErrorBox( "WSL backend couldn't start", - `${reason}\n\nFalling back to the Windows backend so T3 Code can open. Re-enable the WSL backend from Settings > Connections once the WSL distro is fixed.`, + `${reason}\n\nFalling back to the Windows backend so The Knife can open. Re-enable the WSL backend from Settings > Connections once the WSL distro is fixed.`, ); // Fully disable the WSL backend — both flags, matching the "Switch to // Windows" recovery path — so the manager's next restart re-resolves the @@ -309,7 +309,7 @@ export const layer = Layer.effect( electronDialog .showErrorBox( "This T3 home is unavailable", - "Another T3 Code server, such as the background service, is using this data directory. Stop that server and reopen T3 Code, or set a separate T3CODE_HOME and pair with the running server.", + "Another The Knife server, such as the background service, is using this data directory. Stop that server and reopen The Knife, or set a separate T3CODE_HOME and pair with the running server.", ) .pipe(Effect.andThen(electronApp.quit)), }); diff --git a/apps/desktop/src/backend/DesktopLocalEnvironmentAuth.ts b/apps/desktop/src/backend/DesktopLocalEnvironmentAuth.ts index d137d502e6c5ede735a5e75bb9a6c47eccde399c..e1a0bb9123a6572d271237563ff8b6653f10517b 100644 --- a/apps/desktop/src/backend/DesktopLocalEnvironmentAuth.ts +++ b/apps/desktop/src/backend/DesktopLocalEnvironmentAuth.ts @@ -104,7 +104,7 @@ export const make = Effect.gen(function* () { httpBaseUrl: config.httpBaseUrl.href, credential, clientMetadata: { - label: "T3 Code Desktop", + label: "The Knife Desktop", deviceType: "desktop", }, }).pipe( diff --git a/apps/desktop/src/electron/WindowsForeground.ts b/apps/desktop/src/electron/WindowsForeground.ts index b90081d20cdeba5819c2090c5978a20e3f58af9e..29184b1fa2dc60cbadde2c5f13056e8b01accbc5 100644 --- a/apps/desktop/src/electron/WindowsForeground.ts +++ b/apps/desktop/src/electron/WindowsForeground.ts @@ -216,7 +216,7 @@ export function loadWindowsForegroundApi(): Promise { export async function activateWindowsForeground(handleBuffer: Buffer): Promise { const api = await loadWindowsForegroundApi(); if (activateWindowsForegroundWithApi(handleBuffer, api)) return; - throw new Error("Windows refused to activate the T3 Code window."); + throw new Error("Windows refused to activate The Knife."); } export async function isWindowsShellHostedForeground(): Promise { diff --git a/apps/desktop/src/permissions/MacPermissionHelper.test.ts b/apps/desktop/src/permissions/MacPermissionHelper.test.ts index e1160d9bbe3819185c3c557b16a3be14d53b1ade..4fafb2b9d7a1ac423338dba2ee478f265df5c69b 100644 --- a/apps/desktop/src/permissions/MacPermissionHelper.test.ts +++ b/apps/desktop/src/permissions/MacPermissionHelper.test.ts @@ -61,7 +61,7 @@ vi.mock("electron", async () => { } return { app: { - getPath: () => "/Applications/T3 Code (Nightly).app/Contents/MacOS/T3 Code", + getPath: () => "/Applications/The Knife (Nightly).app/Contents/MacOS/The Knife", }, nativeImage: { createFromPath: mocks.createFromPath }, BrowserWindow: class extends MockWindow {}, @@ -124,11 +124,11 @@ function send(action: string, trusted = true) { describe("macAppBundlePath", () => { it("resolves bundles with spaces and refuses non-bundle executables", () => { - expect(macAppBundlePath("/Applications/T3 Code.app/Contents/MacOS/T3 Code")).toBe( - "/Applications/T3 Code.app", + expect(macAppBundlePath("/Applications/The Knife.app/Contents/MacOS/The Knife")).toBe( + "/Applications/The Knife.app", ); expect(macAppBundlePath("/usr/local/bin/electron")).toBeUndefined(); - expect(macAppBundlePath("/Applications/T3 Code.app/other/MacOS/T3 Code")).toBeUndefined(); + expect(macAppBundlePath("/Applications/The Knife.app/other/MacOS/The Knife")).toBeUndefined(); }); }); it("drags the running app bundle only for the helper's own renderer", async () => { @@ -138,11 +138,11 @@ it("drags the running app bundle only for the helper's own renderer", async () = send("drag"); expect(mocks.createFromPath).toHaveBeenCalledWith("/bundle/prod-resources/icon.png"); expect(mocks.startDrag).toHaveBeenCalledWith({ - file: "/Applications/T3 Code (Nightly).app", + file: "/Applications/The Knife (Nightly).app", icon: mocks.createFromPath.mock.results[0]!.value.resize(), }); send("finder"); - expect(mocks.showItemInFolder).toHaveBeenCalledWith("/Applications/T3 Code (Nightly).app"); + expect(mocks.showItemInFolder).toHaveBeenCalledWith("/Applications/The Knife (Nightly).app"); }); it("rechecks permissions and releases resources when granted", async () => { await open(); @@ -168,7 +168,7 @@ it("does not open for a permission already granted", async () => { }); it("does not show a helper with a missing packaged icon", async () => { mocks.createFromPath.mockReturnValueOnce({ isEmpty: () => true }); - await expect(open()).rejects.toThrow("packaged T3 Code icon is missing"); + await expect(open()).rejects.toThrow("packaged The Knife icon is missing"); expect(windows).toHaveLength(0); }); it("cleans up when the helper page fails to load", async () => { @@ -184,7 +184,7 @@ it("offers the Finder fallback when native dragging fails", async () => { throw new Error("drag failed"); }); send("drag"); - expect(mocks.showItemInFolder).toHaveBeenCalledWith("/Applications/T3 Code (Nightly).app"); + expect(mocks.showItemInFolder).toHaveBeenCalledWith("/Applications/The Knife (Nightly).app"); expect(windows[0]!.destroyed).toBe(false); }); diff --git a/apps/desktop/src/permissions/MacPermissionHelper.ts b/apps/desktop/src/permissions/MacPermissionHelper.ts index 3f395c81ffc4df2c57c1824fe5618cc8423dbb04..6aab43d699f7db55fed36bcb562cbfeaa5bd8fbb 100644 --- a/apps/desktop/src/permissions/MacPermissionHelper.ts +++ b/apps/desktop/src/permissions/MacPermissionHelper.ts @@ -60,8 +60,8 @@ button:focus-visible { outline: 2px solid #007aff; outline-offset: -2px; } img { width: 32px; height: 32px; pointer-events: none; }
-
↑ Drag T3 Code into the list above
- +
↑ Drag The Knife into the list above
+
`; } @@ -95,7 +95,7 @@ export class MacPermissionHelper { const appIcon = iconPaths .map((iconPath) => Electron.nativeImage.createFromPath(iconPath)) .find((image) => !image.isEmpty()); - if (!appIcon) throw new Error("The packaged T3 Code icon is missing."); + if (!appIcon) throw new Error("The packaged The Knife icon is missing."); const icon = appIcon.resize({ width: 64, height: 64 }); const window = new Electron.BrowserWindow({ width: 560, diff --git a/apps/desktop/src/snapShot/DesktopSnapShot.test.ts b/apps/desktop/src/snapShot/DesktopSnapShot.test.ts index 485ed2aaa0f72373a41edfe8c0cf47d045b1af92..a1265dad25d3f087912449ffdc952fa753feb03f 100644 --- a/apps/desktop/src/snapShot/DesktopSnapShot.test.ts +++ b/apps/desktop/src/snapShot/DesktopSnapShot.test.ts @@ -1630,7 +1630,7 @@ it.effect( const warning = logs.find( (message) => Array.isArray(message) && - message[0] === "The compositor could not activate T3 Code after the snapshot", + message[0] === "The compositor could not activate The Knife after the snapshot", ); assert.strictEqual(Array.isArray(warning) ? warning[1] : undefined, activationFailure); const pending = yield* decodePendingMetadata(saved); @@ -3161,7 +3161,7 @@ it.effect("flags revoked macOS permissions on read and re-registers once they re const revoked = yield* service.state; assert.equal( revoked.message, - "Allow Screen Recording in System Settings, then restart T3 Code.", + "Allow Screen Recording in System Settings, then restart The Knife.", ); assert.deepEqual(revoked.macPermissions, { screenRecording: false, accessibility: true }); @@ -3174,7 +3174,7 @@ it.effect("flags revoked macOS permissions on read and re-registers once they re const blocked = yield* service.state; assert.equal( blocked.message, - "Allow Screen Recording in System Settings, then restart T3 Code.", + "Allow Screen Recording in System Settings, then restart The Knife.", ); assert.isFalse(blocked.shortcutRegistered); diff --git a/apps/desktop/src/snapShot/DesktopSnapShot.ts b/apps/desktop/src/snapShot/DesktopSnapShot.ts index 7585c1c188795c298e59efb5ea99a96593dfa3ee..9427fdbb5bc75576fbe5020980c13e9001d6dd9e 100644 --- a/apps/desktop/src/snapShot/DesktopSnapShot.ts +++ b/apps/desktop/src/snapShot/DesktopSnapShot.ts @@ -101,11 +101,11 @@ const FLASH_FRAME_INTERVAL_MS = 16; const FLASH_PEAK_OPACITY = 0.08; const MAC_SCREEN_CAPTURE_SETTINGS_URL = MAC_PERMISSION_SETTINGS_URLS["screen-recording"]; const MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE = - "Allow Screen Recording in System Settings, then restart T3 Code."; + "Allow Screen Recording in System Settings, then restart The Knife."; const MAC_ACCESSIBILITY_PERMISSION_MESSAGE = - "Allow Accessibility in System Settings, then restart T3 Code."; + "Allow Accessibility in System Settings, then restart The Knife."; const MAC_BOTH_PERMISSIONS_MESSAGE = - "Allow Accessibility and Screen Recording in System Settings, then restart T3 Code."; + "Allow Accessibility and Screen Recording in System Settings, then restart The Knife."; const MAC_PERMISSION_MESSAGES = new Set([ MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE, MAC_ACCESSIBILITY_PERMISSION_MESSAGE, @@ -187,7 +187,7 @@ export class DesktopSnapShot extends Context.Service< shortcut: SnapShotShortcut, ) => Effect.Effect; readonly setShortcutSuppressed: (suppressed: boolean) => Effect.Effect; - /** Capture the foreground window in place, including T3 Code itself. */ + /** Capture the foreground window in place, including The Knife itself. */ readonly capture: Effect.Effect; readonly listPending: Effect.Effect< ReadonlyArray, @@ -898,7 +898,7 @@ export const make = Effect.gen(function* () { const capturedAt = yield* DateTime.now.pipe(Effect.map(DateTime.formatIso)); if (snapshot.linuxActivationFailure) { yield* Effect.logWarning( - "The compositor could not activate T3 Code after the snapshot", + "The compositor could not activate The Knife after the snapshot", snapshot.linuxActivationFailure.cause, ); } @@ -1018,7 +1018,7 @@ export const make = Effect.gen(function* () { if (mode === "portal" && niriSocketPath()) { return { available: false, - message: "Configure the capture shortcut in your Niri config, not in T3 Code.", + message: "Configure the capture shortcut in your Niri config, not in The Knife.", }; } if (mode === "portal" && isHyprlandCaptureSession()) { @@ -1168,7 +1168,7 @@ export const make = Effect.gen(function* () { const { startNiriCaptureShortcut } = await import("./NiriCaptureShortcut.ts"); return startNiriCaptureShortcut(linuxAppId, onCurrentShortcut, () => { void runPromise( - setShortcutFailure("The Niri capture endpoint disconnected. Restart T3 Code."), + setShortcutFailure("The Niri capture endpoint disconnected. Restart The Knife."), ).catch(() => undefined); }); }).pipe( @@ -1190,7 +1190,7 @@ export const make = Effect.gen(function* () { shortcutActionRegistered: registered, shortcutMessage: registered ? "Set up the shortcut to add it to your Niri config." - : "Could not start the Niri capture endpoint. Another T3 Code instance may be using it.", + : "Could not start the Niri capture endpoint. Another The Knife instance may be using it.", message: null, }); return; diff --git a/apps/desktop/src/snapShot/GnomeCaptureSetup.ts b/apps/desktop/src/snapShot/GnomeCaptureSetup.ts index 429a9986d94ca4203c6ee8dcdad271f685776392..80250dc7bf95b628357570befdbf632c4652149e 100644 --- a/apps/desktop/src/snapShot/GnomeCaptureSetup.ts +++ b/apps/desktop/src/snapShot/GnomeCaptureSetup.ts @@ -56,7 +56,7 @@ export async function installGnomeCaptureBundle({ bundle, dataHome }: SetupPaths await NodeFSP.readFile(NodePath.join(target, "metadata.json"), "utf8"), ); if (installed.version > metadata.version) - throw new Error("A newer extension is installed. Update T3 Code instead of replacing it."); + throw new Error("A newer extension is installed. Update The Knife instead of replacing it."); } const staged = await NodeFSP.mkdtemp(NodePath.join(parent, ".t3-capture-install-")); let backup: string | undefined; @@ -184,7 +184,7 @@ export class GnomeCaptureSetup { return { status: "restart-required", message: - "Installed. Save your work, sign out of GNOME and sign back in, then return here to enable the extension. Restarting T3 Code alone is not enough.", + "Installed. Save your work, sign out of GNOME and sign back in, then return here to enable the extension. Restarting The Knife alone is not enough.", }; if ((installed?.version ?? info.version?.value ?? 0) < bundled.version) return { @@ -196,12 +196,12 @@ export class GnomeCaptureSetup { return { status: "extensions-disabled", message: - "GNOME has disabled user extensions. Turn on Extensions in the GNOME Extensions app, then check again. T3 Code will not enable your other extensions for you.", + "GNOME has disabled user extensions. Turn on Extensions in the GNOME Extensions app, then check again. The Knife will not enable your other extensions for you.", }; if (info.state?.value === 1) return { status: "enabled", - message: "The T3 Code extension is running. Active-window snapshots are available.", + message: "The Knife extension is running. Active-window snapshots are available.", }; if (info.state?.value === 3 || info.state?.value === 4) return { @@ -213,7 +213,7 @@ export class GnomeCaptureSetup { return { status: "disabled", message: - "Enable the T3 Code extension to allow active-window snapshots. You can disable it here at any time.", + "Enable The Knife’s extension to allow active-window snapshots. You can disable it here at any time.", }; } catch (error) { return { diff --git a/apps/desktop/src/snapShot/HyprlandSnapShot.ts b/apps/desktop/src/snapShot/HyprlandSnapShot.ts index 4dc57e44448d1ff97e7cef007a27761f9826a9c1..68173f1e10ccf02149d169c2bd3cec3e17159e3e 100644 --- a/apps/desktop/src/snapShot/HyprlandSnapShot.ts +++ b/apps/desktop/src/snapShot/HyprlandSnapShot.ts @@ -96,7 +96,7 @@ export class HyprlandCaptureSetup { const bundle = await regularFile(this.paths.bundle); if (!bundle) throw new Error( - "The Hyprland capture helper is missing from this build. Update or reinstall T3 Code.", + "The Hyprland capture helper is missing from this build. Update or reinstall The Knife.", ); if (!installed.equals(bundle)) return { diff --git a/apps/desktop/src/snapShot/KdeSnapShot.ts b/apps/desktop/src/snapShot/KdeSnapShot.ts index 9b8c8bb90dc09dec303abdc5bb662c683b27cca3..df84dcfcb01aab363a2996fccab077fe5576fda5 100644 --- a/apps/desktop/src/snapShot/KdeSnapShot.ts +++ b/apps/desktop/src/snapShot/KdeSnapShot.ts @@ -33,7 +33,7 @@ export function kdeCaptureDesktopEntry(executable: string): string { return [ "[Desktop Entry]", "Type=Application", - "Name=T3 Code SnapShots", + "Name=The Knife SnapShots", "NoDisplay=true", `Exec=${escapeDesktopEntryExecArgument(executable)} check`, // KService reads this custom property as a KConfig list, not an XDG ';' list. @@ -116,7 +116,7 @@ export class KdeCaptureSetup { if (!bundle) return { status: "error", - message: "The capture helper is missing from this build. Update or reinstall T3 Code.", + message: "The capture helper is missing from this build. Update or reinstall The Knife.", }; if (!installed.equals(bundle) || entry.toString() !== kdeCaptureDesktopEntry(executable)) return { @@ -162,7 +162,7 @@ export class KdeCaptureSetup { const bundle = await regularFile(this.paths.bundle); if (!bundle) throw new Error( - "The capture helper is missing from this build. Update or reinstall T3 Code.", + "The capture helper is missing from this build. Update or reinstall The Knife.", ); await NodeFSP.mkdir(directory, { recursive: true }); await NodeFSP.mkdir(NodePath.dirname(desktop), { recursive: true }); diff --git a/apps/desktop/src/snapShot/LinuxSnapShot.ts b/apps/desktop/src/snapShot/LinuxSnapShot.ts index dffd2a71f36ce71caf6f80f2b0a8e2955e85ada5..264989b1daf0016a1190383afd657a8783ebd352 100644 --- a/apps/desktop/src/snapShot/LinuxSnapShot.ts +++ b/apps/desktop/src/snapShot/LinuxSnapShot.ts @@ -306,7 +306,7 @@ export class LinuxCaptureConnection { this.bus.requestName(`${appId}.SnapShot`, NameFlag.DO_NOT_QUEUE), ); if (result !== RequestNameReply.PRIMARY_OWNER) { - throw new Error("Another T3 Code instance is capturing a window. Try again."); + throw new Error("Another The Knife instance is capturing a window. Try again."); } const withFeedback = this.feedbackAvailable && options !== undefined; const reply = await this.call({ diff --git a/apps/desktop/src/snapShot/NiriCaptureShortcut.ts b/apps/desktop/src/snapShot/NiriCaptureShortcut.ts index bae9c2b962af9bc6c15e5b1186bf9e3081c21e8a..0db9c0af6596b6f25abea54e0e8cf69316905d0a 100644 --- a/apps/desktop/src/snapShot/NiriCaptureShortcut.ts +++ b/apps/desktop/src/snapShot/NiriCaptureShortcut.ts @@ -55,7 +55,7 @@ export async function startNiriCaptureShortcut( }), ]); if (result !== RequestNameReply.PRIMARY_OWNER) - throw new Error("Another T3 Code instance already owns the capture shortcut."); + throw new Error("Another The Knife instance already owns the capture shortcut."); return close; } catch (error) { close(); diff --git a/apps/desktop/src/snapShot/NiriSnapShot.ts b/apps/desktop/src/snapShot/NiriSnapShot.ts index 87133aded5488352025bbbf6379dc7c83cdf56da..ec0c42d715db6806f026b6db7f6f140a90638e8a 100644 --- a/apps/desktop/src/snapShot/NiriSnapShot.ts +++ b/apps/desktop/src/snapShot/NiriSnapShot.ts @@ -175,7 +175,7 @@ async function activateNiriWindow(path: string, title: string, signal: AbortSign (window) => window.pid === process.pid && window.title === title, ); if (matches.length > 1) - throw new Error("More than one T3 Code window matches the capture destination."); + throw new Error("More than one The Knife window matches the capture destination."); return matches[0]; }); connection.send("EventStream"); diff --git a/apps/desktop/src/snapShot/PortalCaptureShortcut.test.ts b/apps/desktop/src/snapShot/PortalCaptureShortcut.test.ts index eb20a4a041eef417cdd578409ec6dc7f6d0a9b45..4f4a07c8905446466c1f21a86d7a415dbb343594 100644 --- a/apps/desktop/src/snapShot/PortalCaptureShortcut.test.ts +++ b/apps/desktop/src/snapShot/PortalCaptureShortcut.test.ts @@ -318,7 +318,7 @@ it("guides users to manual desktop settings when the portal cannot open them", a expect(client.hasSession).toBe(true); expect(client.state.shortcutCanRetry).toBe(false); expect(client.state.shortcutMessage).toBe( - "Shortcut permission wasn't granted. Allow T3 Code in your desktop's shortcut settings.", + "Shortcut permission wasn't granted. Allow The Knife in your desktop's shortcut settings.", ); await expect(client.configure()).rejects.toThrow("Open your desktop's shortcut settings"); expect(bus.calls.some((message) => message.member === "ConfigureShortcuts")).toBe(false); diff --git a/apps/desktop/src/snapShot/PortalCaptureShortcut.ts b/apps/desktop/src/snapShot/PortalCaptureShortcut.ts index 7487e2f582ba35beabdb6924dd2f0ed74b14a936..ecc66081f1ba3945c24182aadb4be60ea3808b8a 100644 --- a/apps/desktop/src/snapShot/PortalCaptureShortcut.ts +++ b/apps/desktop/src/snapShot/PortalCaptureShortcut.ts @@ -135,7 +135,7 @@ export class PortalCaptureShortcut { throw new Error("Change the capture binding in your Hyprland config, then save it."); if (!this.hasSession || this.version < 2) throw new Error( - "Open your desktop's shortcut settings and allow T3 Code's capture shortcut.", + "Open your desktop's shortcut settings and allow The Knife's capture shortcut.", ); await this.call({ destination: this.owner, @@ -164,7 +164,7 @@ export class PortalCaptureShortcut { // This failed session is closing, so retry can register a fresh one. shortcutCanRetry: !this.managedByHyprland, shortcutMessage: this.managedByHyprland - ? "Couldn't connect to Hyprland shortcuts. Make sure xdg-desktop-portal-hyprland is running, then restart T3 Code." + ? "Couldn't connect to Hyprland shortcuts. Make sure xdg-desktop-portal-hyprland is running, then restart The Knife." : error instanceof Error ? error.message : "Could not register the capture shortcut.", @@ -304,7 +304,7 @@ export class PortalCaptureShortcut { shortcutMessage: this.version >= 2 ? "Shortcut permission wasn't granted. Open shortcut permissions to allow it." - : "Shortcut permission wasn't granted. Allow T3 Code in your desktop's shortcut settings.", + : "Shortcut permission wasn't granted. Allow The Knife in your desktop's shortcut settings.", }); return undefined; } @@ -327,7 +327,7 @@ export class PortalCaptureShortcut { shortcutPending: false, shortcutMessage: shortcut ? "Managed by Hyprland. Add the binding to your config and save it." - : "Hyprland did not register the capture action. Check that xdg-desktop-portal-hyprland is running, then restart T3 Code.", + : "Hyprland did not register the capture action. Check that xdg-desktop-portal-hyprland is running, then restart The Knife.", }); return; } diff --git a/apps/desktop/src/snapShot/SnapShotTransition.ts b/apps/desktop/src/snapShot/SnapShotTransition.ts index 01532d0e26c45263858a451b5416835df157bb7e..fd347cdf9a137caa28a4854850db50797540a68e 100644 --- a/apps/desktop/src/snapShot/SnapShotTransition.ts +++ b/apps/desktop/src/snapShot/SnapShotTransition.ts @@ -114,7 +114,7 @@ function createWindow( resizable: false, show: false, skipTaskbar: true, - title: "T3 Code Snapshot Animation", + title: "The Knife Snapshot Animation", transparent: true, webPreferences: { backgroundThrottling: false, diff --git a/apps/desktop/src/snapShot/snapShot.ts b/apps/desktop/src/snapShot/snapShot.ts index 2cfa406d17817a05e5564f13a0724f8806d508d9..181dcf66edf1c7037f4c543c805136f39174408c 100644 --- a/apps/desktop/src/snapShot/snapShot.ts +++ b/apps/desktop/src/snapShot/snapShot.ts @@ -447,7 +447,7 @@ export function hideAndWaitForBlur(window: { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { window.removeListener("blur", onBlur); - reject(new Error("Timed out waiting for T3 Code to lose focus.")); + reject(new Error("Timed out waiting for The Knife to lose focus.")); }, WINDOW_BLUR_TIMEOUT_MS); const onBlur = () => { clearTimeout(timeout); diff --git a/apps/desktop/src/ssh/DesktopSshEnvironment.test.ts b/apps/desktop/src/ssh/DesktopSshEnvironment.test.ts index 1fe2b86aae7b96e41f87675748497a49501047d1..ce39afaffad5cd0e31d4dbc129c03d2b5a64ce09 100644 --- a/apps/desktop/src/ssh/DesktopSshEnvironment.test.ts +++ b/apps/desktop/src/ssh/DesktopSshEnvironment.test.ts @@ -30,7 +30,7 @@ describe("sshEnvironment", () => { assert.equal(cause.message, "Failed to present SSH password prompt for devbox."); assert.equal( DesktopSshEnvironment.toSshPasswordPromptError(cause).message, - "T3 Code window is not available for SSH authentication.", + "The Knife window is not available for SSH authentication.", ); }); diff --git a/apps/desktop/src/ssh/DesktopSshEnvironment.ts b/apps/desktop/src/ssh/DesktopSshEnvironment.ts index 08dd48b718f93f6477261ff43754f282c58561b7..ad1ed65fa2762d8f0531e3d2e01535a9637b5adc 100644 --- a/apps/desktop/src/ssh/DesktopSshEnvironment.ts +++ b/apps/desktop/src/ssh/DesktopSshEnvironment.ts @@ -97,7 +97,7 @@ export function toSshPasswordPromptError( break; case "DesktopSshPromptWindowUnavailableError": case "DesktopSshPromptPresentationError": - message = "T3 Code window is not available for SSH authentication."; + message = "The Knife window is not available for SSH authentication."; break; case "DesktopSshPromptTimedOutError": message = `SSH authentication timed out for ${cause.destination}.`; diff --git a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts index 296b81339e80eb4ef6b228ca9accf42e89edc9c1..ba34731eb0a726f8c6daeb3109177756f3d96365 100644 --- a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts +++ b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts @@ -63,7 +63,7 @@ export class DesktopSshPromptWindowUnavailableError extends Schema.TaggedError { const template = yield* Deferred.await(applicationMenuTemplate); const applicationMenu = template[0]; assert.isDefined(applicationMenu); - assert.equal(applicationMenu.label, "T3 Code (Nightly)"); + assert.equal(applicationMenu.label, "The Knife (Nightly)"); if (!Array.isArray(applicationMenu.submenu)) { throw new Error("Expected application menu submenu to be an array."); } assert.equal( applicationMenu.submenu.find((item) => item.role === "about")?.label, - "About T3 Code (Nightly)", + "About The Knife (Nightly)", ); assert.equal( applicationMenu.submenu.find((item) => item.role === "hide")?.label, - "Hide T3 Code (Nightly)", + "Hide The Knife (Nightly)", ); assert.equal( applicationMenu.submenu.find((item) => item.role === "quit")?.label, - "Quit T3 Code (Nightly)", + "Quit The Knife (Nightly)", ); }), ); diff --git a/apps/desktop/src/window/DesktopApplicationMenu.ts b/apps/desktop/src/window/DesktopApplicationMenu.ts index d0363fb01a272a99a7ef543c47be3ab11d2cae13..ecd901b48758d296c6635c9179cf7c396786116d 100644 --- a/apps/desktop/src/window/DesktopApplicationMenu.ts +++ b/apps/desktop/src/window/DesktopApplicationMenu.ts @@ -74,7 +74,7 @@ const checkForUpdatesFromMenu = Effect.gen(function* () { yield* electronDialog.showMessageBox({ type: "info", title: "You're up to date!", - message: `T3 Code ${updateState.currentVersion} is currently the newest version available.`, + message: `The Knife ${updateState.currentVersion} is currently the newest version available.`, buttons: ["OK"], }); } else if (updateState.status === "error") { diff --git a/apps/desktop/src/wsl/DesktopWslBackend.test.ts b/apps/desktop/src/wsl/DesktopWslBackend.test.ts index e6dcb7bd7133ee4607a4335fcb40d6b18cecacdd..1da9fd50126e1a714b1e3d164d4333a77b8e3956 100644 --- a/apps/desktop/src/wsl/DesktopWslBackend.test.ts +++ b/apps/desktop/src/wsl/DesktopWslBackend.test.ts @@ -172,7 +172,7 @@ describe("DesktopWslBackend", () => { yield* recordOwnershipFailure(); const refusal = yield* backend.lastPreflightError; - assert.include(Option.getOrThrow(refusal), "Another T3 Code server"); + assert.include(Option.getOrThrow(refusal), "Another The Knife server"); yield* backend.reconcile; assert.equal(startCount, 1); assert.deepEqual(yield* backend.lastPreflightError, refusal); diff --git a/apps/desktop/src/wsl/DesktopWslBackend.ts b/apps/desktop/src/wsl/DesktopWslBackend.ts index fe131ca4f9c01cb5a6d81dc2f321735d35ca14f3..926c2e7737bd4bfba699380b0ad485ed25feaf35 100644 --- a/apps/desktop/src/wsl/DesktopWslBackend.ts +++ b/apps/desktop/src/wsl/DesktopWslBackend.ts @@ -177,7 +177,7 @@ export const layer = Layer.effect( Ref.set( preflightErrorRef, Option.some( - "Another T3 Code server is using this distro's T3 home. Stop it, or pair with it instead.", + "Another The Knife server is using this distro's T3 home. Stop it, or pair with it instead.", ), ), ), diff --git a/apps/desktop/src/wsl/DesktopWslEnvironment.ts b/apps/desktop/src/wsl/DesktopWslEnvironment.ts index dedf4a4a342796901b685e820b4bb24eec5b01cf..6ddbfb803b1307ff5091d37908f6f2564af209ac 100644 --- a/apps/desktop/src/wsl/DesktopWslEnvironment.ts +++ b/apps/desktop/src/wsl/DesktopWslEnvironment.ts @@ -505,7 +505,7 @@ const NODE_PTY_BINARY_MISSING_EXIT_CODE = 4; const formatNodePtyProbeFailureReason = (exitCode: number): string | null => exitCode === NODE_PTY_BINARY_MISSING_EXIT_CODE - ? "WSL support is missing from this T3 Code build: the packaged Linux node-pty binary was not included. Install a build that includes WSL support." + ? "WSL support is missing from this build: the packaged Linux node-pty binary was not included. Install a build that includes WSL support." : null; // Captures the login-shell PATH as `resolvedPath:` so the launch can forward the diff --git a/apps/mobile/README.md b/apps/mobile/README.md index 20f98c6c9e34e732aac15529902b920c98f07c82..0d8c7224b649a2bc3b563c5b1ec56090846b38e9 100644 --- a/apps/mobile/README.md +++ b/apps/mobile/README.md @@ -1,7 +1,7 @@ -# T3 Code Mobile +# The Knife Mobile > [!WARNING] -> T3 Code Mobile is currently in development and is not distributed yet. If you want to try it out, you can build it from source. +> The Knife Mobile is currently in development and is not distributed yet. If you want to try it out, you can build it from source. ## Quickstart @@ -10,9 +10,9 @@ This app has three variants: -- `development`: Expo dev client, installable side-by-side as `T3 Code Dev` -- `preview`: persistent internal preview build, installable side-by-side as `T3 Code Preview` -- `production`: store/release build as `T3 Code` +- `development`: Expo dev client, installable side-by-side as `The Knife Dev` +- `preview`: persistent internal preview build, installable side-by-side as `The Knife Preview` +- `production`: store/release build as `The Knife` Run commands from `apps/mobile`. diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts index 57717d2d845899bbc546cfc82729814700110462..e03832705daace6b90cbc95aa0ee15d09fa64ee7 100644 --- a/apps/mobile/app.config.ts +++ b/apps/mobile/app.config.ts @@ -19,8 +19,8 @@ const IOS_BUNDLE_IDENTIFIER_PATTERN = /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/; const fromRepoRoot = (relativePath: string) => `../../${relativePath}`; // Android layers are rendered by scripts/export-android-icons.ts from the Icon Composer sources. -// The wordmark sits inside the adaptive safe zone; the variant artwork is a full-bleed background. -const androidAdaptiveForeground = "./assets/android-icon-foreground.png"; +// The knife sits inside the adaptive icon safe zone. +const androidAdaptiveForeground = "../../assets/knife/android-foreground.png"; if ( isIosPersonalTeamBuild && @@ -37,11 +37,11 @@ const DEVELOPMENT_ASSETS = { iosIcon: fromRepoRoot(BRAND_ASSET_PATHS.developmentIconComposerProject), splashIcon: fromRepoRoot(BRAND_ASSET_PATHS.developmentIosIconPng), androidAdaptiveForeground, - androidAdaptiveBackgroundColor: "#347FF8", - androidAdaptiveBackgroundImage: "./assets/android-icon-background-dev.png", - androidSplashIcon: "./assets/android-splash-icon-dev.png", - androidMonochromeIcon: "./assets/android-icon-mark.png", - androidNotificationIcon: "./assets/android-notification-icon.png", + androidAdaptiveBackgroundColor: "#171422", + androidAdaptiveBackgroundImage: undefined, + androidSplashIcon: "../../assets/knife/android-splash.png", + androidMonochromeIcon: "../../assets/knife/android-foreground.png", + androidNotificationIcon: "../../assets/knife/android-foreground.png", androidNotificationColor: "#00639B", } as const; @@ -50,11 +50,11 @@ const PREVIEW_ASSETS = { iosIcon: fromRepoRoot(BRAND_ASSET_PATHS.nightlyIconComposerProject), splashIcon: fromRepoRoot(BRAND_ASSET_PATHS.nightlyIosIconPng), androidAdaptiveForeground, - androidAdaptiveBackgroundColor: "#111533", - androidAdaptiveBackgroundImage: "./assets/android-icon-background-nightly.png", - androidSplashIcon: "./assets/android-splash-icon-nightly.png", - androidMonochromeIcon: "./assets/android-icon-mark.png", - androidNotificationIcon: "./assets/android-notification-icon.png", + androidAdaptiveBackgroundColor: "#171422", + androidAdaptiveBackgroundImage: undefined, + androidSplashIcon: "../../assets/knife/android-splash.png", + androidMonochromeIcon: "../../assets/knife/android-foreground.png", + androidNotificationIcon: "../../assets/knife/android-foreground.png", androidNotificationColor: "#7565C7", } as const; @@ -63,17 +63,17 @@ const RELEASE_ASSETS = { iosIcon: fromRepoRoot(BRAND_ASSET_PATHS.productionIconComposerProject), splashIcon: fromRepoRoot(BRAND_ASSET_PATHS.productionIosIconPng), androidAdaptiveForeground, - androidAdaptiveBackgroundColor: "#000000", + androidAdaptiveBackgroundColor: "#171422", androidAdaptiveBackgroundImage: undefined, - androidSplashIcon: "./assets/android-splash-icon-prod.png", - androidMonochromeIcon: "./assets/android-icon-mark.png", - androidNotificationIcon: "./assets/android-notification-icon.png", + androidSplashIcon: "../../assets/knife/android-splash.png", + androidMonochromeIcon: "../../assets/knife/android-foreground.png", + androidNotificationIcon: "../../assets/knife/android-foreground.png", androidNotificationColor: "#FFFFFF", } as const; const VARIANT_CONFIG = { development: { - appName: "T3 Code Dev", + appName: "The Knife Dev", scheme: "t3code-dev", iosBundleIdentifier: "com.t3tools.t3code.dev", androidPackage: "com.t3tools.t3code.dev", @@ -81,7 +81,7 @@ const VARIANT_CONFIG = { assets: DEVELOPMENT_ASSETS, }, preview: { - appName: "T3 Code Preview", + appName: "The Knife Preview", scheme: "t3code-preview", iosBundleIdentifier: "com.t3tools.t3code.preview", androidPackage: "com.t3tools.t3code.preview", @@ -89,7 +89,7 @@ const VARIANT_CONFIG = { assets: PREVIEW_ASSETS, }, production: { - appName: "T3 Code", + appName: "The Knife", scheme: "t3code", iosBundleIdentifier: "com.t3tools.t3code", androidPackage: "com.t3tools.t3code", @@ -134,7 +134,7 @@ const widgetsPlugin: NonNullable[number] = [ { name: "SubscriptionUsage", displayName: "Subscription usage", - description: "Subscription quotas from your connected T3 Code environments.", + description: "Subscription quotas from your connected environments.", ios: { configuration: { title: "Subscription usage", @@ -185,7 +185,7 @@ const widgetsPlugin: NonNullable[number] = [ { name: "AgentActivity", displayName: "Agent Activity", - description: "Shows the current state of active T3 Code agents.", + description: "Shows the current state of your active agents.", // Live Activity companion; there is no Android presentation for it. android: null, ios: { supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"] }, @@ -261,7 +261,7 @@ const config: ExpoConfig = { `webcredentials:${variant.relyingParty}`, ], entitlements: { - "keychain-access-groups": [`$(AppIdentifierPrefix)${variant.iosBundleIdentifier}`], + "keychain-access-groups": [`$(AppIdentifierPrefix)${iosBundleIdentifier}`], }, infoPlist: { UISupportedInterfaceOrientations: [ @@ -273,8 +273,8 @@ const config: ExpoConfig = { NSAllowsArbitraryLoads: true, }, NSLocalNetworkUsageDescription: - "Allow T3 Code to connect to T3 Code servers on your local network or tailnet.", - NSPhotoLibraryAddUsageDescription: "Allow T3 Code to save images to your photo library.", + "Allow The Knife to connect to The Knife servers on your local network or tailnet.", + NSPhotoLibraryAddUsageDescription: "Allow The Knife to save images to your photo library.", // "Audio, AirPlay, and Picture in Picture": the browser screen's system // picture in picture needs it to start and to stay up outside the app. UIBackgroundModes: ["audio"], @@ -321,6 +321,7 @@ const config: ExpoConfig = { favicon: variant.assets.appIcon, }, plugins: [ + ...(isIosPersonalTeamBuild ? ["./plugins/withoutIosPersonalTeamCapabilities.cjs"] : []), "expo-asset", [ "expo-font", @@ -359,8 +360,6 @@ const config: ExpoConfig = { mode: APP_VARIANT === "development" ? "development" : "production", }, ], - // appleSignIn must be gated here: withoutIosPersonalTeamCapabilities.cjs runs before - // plugins earlier in this array, so it cannot strip the entitlement Clerk would add. ["@clerk/expo", { theme: "./clerk-theme.json", appleSignIn: !isIosPersonalTeamBuild }], "expo-web-browser", [ @@ -382,7 +381,7 @@ const config: ExpoConfig = { [ "expo-audio", { - microphonePermission: "Allow T3 Code to use your microphone for voice input.", + microphonePermission: "Allow The Knife to use your microphone for voice input.", recordAudioAndroid: false, enableBackgroundPlayback: false, enableBackgroundRecording: false, @@ -391,7 +390,7 @@ const config: ExpoConfig = { [ "expo-camera", { - cameraPermission: "Allow T3 Code to access your camera so you can scan pairing QR codes.", + cameraPermission: "Allow The Knife to access your camera so you can scan pairing QR codes.", microphonePermission: false, barcodeScannerEnabled: true, recordAudioAndroid: false, @@ -466,7 +465,6 @@ const config: ExpoConfig = { "./plugins/withAndroidModernAlertDialog.cjs", "./plugins/withAndroidPredictiveBackCompat.cjs", "./plugins/withAndroidTabletOrientation.cjs", - ...(isIosPersonalTeamBuild ? ["./plugins/withoutIosPersonalTeamCapabilities.cjs"] : []), ], extra: { appVariant: APP_VARIANT, diff --git a/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt index 1703a767020249692046e478ea51b8f9ef109775..2c43b8bb093e2886e0f0811ce510a4a45504d29a 100644 --- a/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt +++ b/apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt @@ -18,7 +18,7 @@ internal object T3ComposerClipboard { } val records = payload?.optJSONArray("records") if (payload == null || records == null) { - clipboard.setPrimaryClip(ClipData.newPlainText("T3 Code", text)) + clipboard.setPrimaryClip(ClipData.newPlainText("The Knife", text)) return } val all = (0 until records.length()).map { records.getJSONObject(it) } @@ -36,12 +36,12 @@ internal object T3ComposerClipboard { clipboard.setPrimaryClip( if (selected.isEmpty()) { ClipData.newPlainText( - "T3 Code", + "The Knife", text ) } else { ClipData.newHtmlText( - "T3 Code", + "The Knife", text, "
$escaped
" ) diff --git a/apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift b/apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift index 1cde09d65f61fa01929daa4d1f4db470865581cd..0ed32cfe541bad22fab1745cc6c3f37a2f621580 100644 --- a/apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift +++ b/apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift @@ -14,7 +14,7 @@ final class T3LayoutMetricsView: ExpoView { registerForTraitChanges([UITraitHorizontalSizeClass.self, UITraitVerticalSizeClass.self]) { (view: T3LayoutMetricsView, _: UITraitCollection) in view.publishMetrics() } - #if compiler(>=6.4) + #if T3_IOS_27_1_SDK if #available(iOS 27.1, *) { hasHinge = nil addInteraction(UIHingeInteraction { [weak self] _, update in @@ -67,7 +67,7 @@ final class T3LayoutMetricsView: ExpoView { } var verticalBarEdge = "none" var regions: [[String: Any]] = [] - #if compiler(>=6.4) + #if T3_IOS_27_1_SDK if #available(iOS 27.1, *) { let rtl = effectiveUserInterfaceLayoutDirection == .rightToLeft switch traitCollection.verticalBarEdge { diff --git a/apps/mobile/modules/t3-native-controls/ios/T3NativeControls.podspec b/apps/mobile/modules/t3-native-controls/ios/T3NativeControls.podspec index 29735c301cc0e643a573eb11e338cb7985080022..66523b0b1564c4f8b741b4f774840fe9048a058e 100644 --- a/apps/mobile/modules/t3-native-controls/ios/T3NativeControls.podspec +++ b/apps/mobile/modules/t3-native-controls/ios/T3NativeControls.podspec @@ -15,5 +15,11 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', } + + ios_sdk_version = Gem::Version.new(`xcrun --sdk iphoneos --show-sdk-version`.strip) + if ios_sdk_version >= Gem::Version.new('27.1') + s.pod_target_xcconfig['SWIFT_ACTIVE_COMPILATION_CONDITIONS'] = '$(inherited) T3_IOS_27_1_SDK' + end + s.source_files = '**/*.{h,m,mm,swift,hpp,cpp}' end diff --git a/apps/mobile/plugins/withWidgetLogoAsset.cjs b/apps/mobile/plugins/withWidgetLogoAsset.cjs index 5c976c2a0cba6602871155a205b021bc56b0be1a..bbc7cb6f88e661bf47752406b170f2b90ba8a441 100644 --- a/apps/mobile/plugins/withWidgetLogoAsset.cjs +++ b/apps/mobile/plugins/withWidgetLogoAsset.cjs @@ -1,9 +1,9 @@ "use strict"; -// Ships the branded T3 mark to the Live Activity / widget extension. +// Ships the knife silhouette to the Live Activity / widget extension. // // expo-widgets generates ExpoWidgetsTarget without a Resources build phase and -// has no asset support, so this plugin (a) writes an SVG template image set into +// has no asset support, so this plugin (a) writes a template image set into // the generated widget asset catalog and (b) wires that catalog into the widget // target with an actool build phase. // @@ -22,17 +22,16 @@ const { addWidgetAssetCatalog } = require("./lib/addWidgetAssetCatalog.cjs"); const TARGET_NAME = "ExpoWidgetsTarget"; const CATALOG_NAME = "Assets.xcassets"; -const IMAGE_SET = "T3Mark.imageset"; -const SVG_NAME = "T3Mark.svg"; +const IMAGE_SET = "KnifeMark.imageset"; +const IMAGE_NAME = "knife.png"; const CATALOG_CONTENTS = JSON.stringify({ info: { author: "expo", version: 1 } }, null, 2) + "\n"; const IMAGE_SET_CONTENTS = JSON.stringify( { - images: [{ idiom: "universal", filename: SVG_NAME }], + images: [{ idiom: "universal", filename: IMAGE_NAME }], info: { author: "expo", version: 1 }, properties: { - "preserves-vector-representation": true, "template-rendering-intent": "template", }, }, @@ -44,13 +43,16 @@ function withAssetFiles(config) { return withDangerousMod(config, [ "ios", (cfg) => { - const source = path.join(cfg.modRequest.projectRoot, "assets", "widget", SVG_NAME); + const source = path.resolve( + cfg.modRequest.projectRoot, + "../../assets/knife/android-foreground.png", + ); const catalogDir = path.join(cfg.modRequest.platformProjectRoot, TARGET_NAME, CATALOG_NAME); const imageSetDir = path.join(catalogDir, IMAGE_SET); fs.mkdirSync(imageSetDir, { recursive: true }); fs.writeFileSync(path.join(catalogDir, "Contents.json"), CATALOG_CONTENTS); fs.writeFileSync(path.join(imageSetDir, "Contents.json"), IMAGE_SET_CONTENTS); - fs.copyFileSync(source, path.join(imageSetDir, SVG_NAME)); + fs.copyFileSync(source, path.join(imageSetDir, IMAGE_NAME)); return cfg; }, ]); diff --git a/apps/mobile/plugins/withoutIosPersonalTeamCapabilities.cjs b/apps/mobile/plugins/withoutIosPersonalTeamCapabilities.cjs index 035c46b34c8ca9d79d48829c4692ffbe28ca7dc3..46a9bea1ca1917eb9a1e76c4044847ad56a1aa7c 100644 --- a/apps/mobile/plugins/withoutIosPersonalTeamCapabilities.cjs +++ b/apps/mobile/plugins/withoutIosPersonalTeamCapabilities.cjs @@ -4,6 +4,7 @@ module.exports = function withoutIosPersonalTeamCapabilities(config) { return withEntitlementsPlist(config, (modConfig) => { delete modConfig.modResults["aps-environment"]; delete modConfig.modResults["com.apple.developer.applesignin"]; + delete modConfig.modResults["com.apple.developer.associated-domains"]; delete modConfig.modResults["com.apple.security.application-groups"]; return modConfig; }); diff --git a/apps/mobile/src/Stack.tsx b/apps/mobile/src/Stack.tsx index 4dfa3f3a985698d9293a84c441834501b401a47b..6a5d9fba632a92bb210bfc789f0d0e3a3542845c 100644 --- a/apps/mobile/src/Stack.tsx +++ b/apps/mobile/src/Stack.tsx @@ -280,7 +280,7 @@ const SettingsContentStack = createV5SheetStackNavigator({ SettingsAbout: createNativeStackScreen({ screen: SettingsAboutRouteScreen, linking: "about", - options: { title: "About T3 Code" }, + options: { title: "About The Knife" }, }), SettingsEnvironmentNew: createNativeStackScreen({ screen: ConnectionsNewRouteScreen, diff --git a/apps/mobile/src/components/BrandMark.tsx b/apps/mobile/src/components/BrandMark.tsx index 1643f3f6fdeb26e0641f0baab46fb3ed713fad34..5405362134538d2bc81705827094cf30d1c190d6 100644 --- a/apps/mobile/src/components/BrandMark.tsx +++ b/apps/mobile/src/components/BrandMark.tsx @@ -3,7 +3,7 @@ import { Image } from "expo-image"; import { View } from "react-native"; import { AppText as Text } from "./AppText"; -import { T3_CODE_BRAND_MARK_SOURCE } from "./brandAssets"; +import { KNIFE_BRAND_MARK_SOURCE } from "./brandAssets"; const appVariant = Constants.expoConfig?.extra?.appVariant; const DEFAULT_STAGE_LABEL = @@ -17,17 +17,16 @@ export function BrandMark(props: { readonly compact?: boolean; readonly stageLab return ( - T3 Code + The Knife {stageLabel} diff --git a/apps/mobile/src/components/CompactBrandTitle.tsx b/apps/mobile/src/components/CompactBrandTitle.tsx index dc5503e13760d2aa148a6d4bf7348dc57d166a10..da2064355a9d3f1c15c7911ee0703da3d3da376b 100644 --- a/apps/mobile/src/components/CompactBrandTitle.tsx +++ b/apps/mobile/src/components/CompactBrandTitle.tsx @@ -4,7 +4,7 @@ import type { NativeStackNavigationOptions } from "@react-navigation/native-stac import { Platform, View } from "react-native"; import { AppText as Text } from "./AppText"; -import { T3Wordmark } from "./T3Wordmark"; +import { KnifeMark } from "./KnifeMark"; import { IPAD_HOME_TITLE_OFFSET } from "../lib/layoutMetrics"; import { resolveMobileStageLabel } from "../lib/mobileBranding"; import { useAndroidControlSizing } from "./useAndroidControlSizing"; @@ -34,19 +34,19 @@ export function CompactBrandTitle( return ( - + - Code + The Knife + ); +} diff --git a/apps/mobile/src/components/T3Wordmark.tsx b/apps/mobile/src/components/T3Wordmark.tsx deleted file mode 100644 index 5f64effffc18d65560ce952ae36c6ad2d1d7c846..0000000000000000000000000000000000000000 --- a/apps/mobile/src/components/T3Wordmark.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { ColorValue } from "react-native"; -import Svg, { Path } from "react-native-svg"; -import { withUniwind } from "uniwind"; - -const ThemedPath = withUniwind(Path); - -/** - * The "T3" brand mark, matching the desktop sidebar's T3Wordmark SVG - * (apps/web Sidebar.tsx). Width derives from the viewBox aspect ratio. - */ -export function T3Wordmark(props: { - readonly height: number; - readonly color?: ColorValue; - readonly colorClassName?: string; -}) { - const aspectRatio = 94.3941 / 56.96; - return ( - - - - ); -} diff --git a/apps/mobile/src/components/brandAssets.ts b/apps/mobile/src/components/brandAssets.ts index dbf7ab552aa8c72bf7d9c095d7de5849c7b23f0a..d9987d6c9fb7bf0a5a88f038dccb4521ffc4f094 100644 --- a/apps/mobile/src/components/brandAssets.ts +++ b/apps/mobile/src/components/brandAssets.ts @@ -1,10 +1 @@ -import Constants from "expo-constants"; - -const appVariant = Constants.expoConfig?.extra?.appVariant; - -export const T3_CODE_BRAND_MARK_SOURCE = - appVariant === "development" - ? require("../../../../assets/dev/blueprint-ios-1024.png") - : appVariant === "preview" - ? require("../../../../assets/nightly/nightly-ios-1024.png") - : require("../../../../assets/prod/black-ios-1024.png"); +export const KNIFE_BRAND_MARK_SOURCE = require("../../../../assets/knife/apple-touch-180.png"); diff --git a/apps/mobile/src/features/agent-awareness/remoteRegistration.ts b/apps/mobile/src/features/agent-awareness/remoteRegistration.ts index 7ed7ddc8bae1b9e21cd4b55f332f4184da7e72f5..62316267b6c3f5d523adf2ce128f9fbe5176e042 100644 --- a/apps/mobile/src/features/agent-awareness/remoteRegistration.ts +++ b/apps/mobile/src/features/agent-awareness/remoteRegistration.ts @@ -534,7 +534,7 @@ function armAgentAwarenessLiveActivityForLocalWorkNow(input: { const nowIso = new Date(Date.now()).toISOString(); const activity = startAgentLiveActivity( { - title: "T3 Code", + title: "The Knife", subtitle: "Agent work in progress", activeCount: 1, updatedAt: nowIso, diff --git a/apps/mobile/src/features/diagnostics/crash-log-model.test.ts b/apps/mobile/src/features/diagnostics/crash-log-model.test.ts index 761d9a49ed96c0f181124aa03dbd4549e06a9580..56439ada8f0b122cee3fb458ad6181ae596d6fb7 100644 --- a/apps/mobile/src/features/diagnostics/crash-log-model.test.ts +++ b/apps/mobile/src/features/diagnostics/crash-log-model.test.ts @@ -61,14 +61,14 @@ describe("formatStartupCrashReport", () => { version: "1.1.1", build: "56", }); - expect(report.startsWith("T3 Code 1.1.1 (56)\n")).toBe(true); + expect(report.startsWith("The Knife 1.1.1 (56)\n")).toBe(true); expect(report).toContain("2026-09-13T05:35:52.000Z"); expect(report).toContain("at NewTaskFlowProvider"); }); it("says so when nothing was recorded", () => { expect(formatStartupCrashReport([], { version: "1.1.1", build: "56" })).toBe( - "T3 Code 1.1.1 (56)\nNo startup crashes recorded.", + "The Knife 1.1.1 (56)\nNo startup crashes recorded.", ); }); }); diff --git a/apps/mobile/src/features/diagnostics/crash-log-model.ts b/apps/mobile/src/features/diagnostics/crash-log-model.ts index 4c222a1f0be93e43fa8c32593b175b93d202441f..ffddade497b9e5bbe43826352374ec746a45213c 100644 --- a/apps/mobile/src/features/diagnostics/crash-log-model.ts +++ b/apps/mobile/src/features/diagnostics/crash-log-model.ts @@ -72,7 +72,7 @@ export function formatStartupCrashReport( records: ReadonlyArray, app: { readonly version: string; readonly build: string }, ): string { - const header = `T3 Code ${app.version} (${app.build})`; + const header = `The Knife ${app.version} (${app.build})`; if (records.length === 0) return `${header}\nNo startup crashes recorded.`; return [ header, diff --git a/apps/mobile/src/features/pull-requests/MaterialPullRequestToolbar.android.tsx b/apps/mobile/src/features/pull-requests/MaterialPullRequestToolbar.android.tsx index b7a3de14bde416dc6d534330eaff7fade30c7db8..8d72736fc3df8836ff17f0af2f93d533c55dc820 100644 --- a/apps/mobile/src/features/pull-requests/MaterialPullRequestToolbar.android.tsx +++ b/apps/mobile/src/features/pull-requests/MaterialPullRequestToolbar.android.tsx @@ -69,7 +69,7 @@ export function MaterialPullRequestToolbar(props: { readonly onRequestVisibility brand={ } onPress={() => diff --git a/apps/mobile/src/features/pull-requests/pullRequestAgentTasks.ts b/apps/mobile/src/features/pull-requests/pullRequestAgentTasks.ts index 57c7d4ee9c05ca6cc4e865aa943c2bf6f0cfc84e..18b504b9ba71f42fd0f61d5a836987d93b1c84fe 100644 --- a/apps/mobile/src/features/pull-requests/pullRequestAgentTasks.ts +++ b/apps/mobile/src/features/pull-requests/pullRequestAgentTasks.ts @@ -23,7 +23,7 @@ export function pullRequestAgentPrompt(task: PullRequestAgentTask): string { case "conflicts": return "Resolve this pull request's conflicts with its base branch. First confirm this thread is on the PR's branch; if it isn't, prepare the PR in its own worktree. Bring it up to date using this repository's convention, preserve the intent of both sides, and run the relevant checks before pushing."; case "watch": - return "Watch this pull request for check results, new feedback, and conflicts using T3's pull request watch. End the turn once the watch is armed. When it wakes you, assess what changed and report what needs attention."; + return "Watch this pull request for check results, new feedback, and conflicts using The Knife's pull request watch. End the turn once the watch is armed. When it wakes you, assess what changed and report what needs attention."; } } diff --git a/apps/mobile/src/features/settings/SettingsAboutRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsAboutRouteScreen.tsx index bbb70b236bead9922bfdac2d04f16775d4b45d91..f32cb6881c70695bd11510cb5e4ac520a6eed6a8 100644 --- a/apps/mobile/src/features/settings/SettingsAboutRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsAboutRouteScreen.tsx @@ -20,7 +20,7 @@ export function SettingsAboutRouteScreen() { const insets = useSafeAreaInsets(); return ( - + {notice} : null} {config ? ( <> - + Version {version} {running ? ( @@ -252,7 +252,7 @@ function EnvironmentDetail({ environmentId }: { readonly environmentId: Environm {capabilities?.serverSelfUpdate === "desktop-managed" ? "Update the desktop app on this machine." - : "Update and restart T3 Code on this machine."} + : "Update and restart The Knife on this machine."} ) : null} diff --git a/apps/mobile/src/features/settings/SettingsMicrophoneRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsMicrophoneRouteScreen.tsx index f4303a659cc75f6b5ecb1e48b90b47976fbb4c4b..ca397fe67a552c697c0280e1bc53bd03f062b1fa 100644 --- a/apps/mobile/src/features/settings/SettingsMicrophoneRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsMicrophoneRouteScreen.tsx @@ -166,7 +166,7 @@ export function SettingsMicrophoneRouteScreen() { {permission === "ask" ? "Voice input needs microphone access before it can record or list your microphones." - : "Microphone access is off for T3 Code. Turn it on in Settings to use voice input."} + : "Microphone access is off for The Knife. Turn it on in Settings to use voice input."} ) : null} diff --git a/apps/mobile/src/features/settings/SettingsNotificationsRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsNotificationsRouteScreen.tsx index 25c2e03d46f7366a92e3e5fd706e565fb72cc85e..bf3f44643e0a6efc964b95a23539c4154ca043c6 100644 --- a/apps/mobile/src/features/settings/SettingsNotificationsRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsNotificationsRouteScreen.tsx @@ -338,7 +338,7 @@ function ConfiguredSettingsNotificationsRouteScreen() { Alert.alert( "Disable notifications", - "Open system Settings to disable notifications for T3 Code.", + "Open system Settings to disable notifications for The Knife.", [ { text: "Cancel", style: "cancel" }, { text: "Open Settings", onPress: () => void Linking.openSettings() }, @@ -492,7 +492,7 @@ function ConfiguredSettingsNotificationsRouteScreen() { void openAndroidLiveUpdateSettings().catch(() => { Alert.alert( "Couldn't open Settings", - "Open Android Settings, select T3 Code, then enable Live Updates in Notifications.", + "Open Android Settings, select The Knife, then enable Live Updates in Notifications.", ); }); }} diff --git a/apps/mobile/src/features/settings/SettingsRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsRouteScreen.tsx index adb6551cf5fa19dfce405afb23295944794ddfb8..1dc1aae57ec6f4d6488f35a3d38548bd6bcd1766 100644 --- a/apps/mobile/src/features/settings/SettingsRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsRouteScreen.tsx @@ -215,7 +215,7 @@ function SettingsIndexSections() { - + ); diff --git a/apps/mobile/src/features/showcase/showcaseAgentActivity.ts b/apps/mobile/src/features/showcase/showcaseAgentActivity.ts index 736c672dff0c22e4044c8a112b68d24108331089..80f9c23ebc49b29cfe3440ff6f0a49117912f995 100644 --- a/apps/mobile/src/features/showcase/showcaseAgentActivity.ts +++ b/apps/mobile/src/features/showcase/showcaseAgentActivity.ts @@ -74,7 +74,7 @@ export function buildShowcaseAgentActivity( }); } return { - title: "T3 Code", + title: "The Knife", subtitle: "Agent work in progress", activeCount: rows.filter((row) => ACTIVE_PHASES.has(row.phase)).length, updatedAt: new Date(now).toISOString(), diff --git a/apps/mobile/src/features/threads/ThreadComposer.tsx b/apps/mobile/src/features/threads/ThreadComposer.tsx index a45f319423fe205ccb14429d1032c0303042bc38..d02e14c3fb4021c744a5d1f7ba10b97bab37772b 100644 --- a/apps/mobile/src/features/threads/ThreadComposer.tsx +++ b/apps/mobile/src/features/threads/ThreadComposer.tsx @@ -636,7 +636,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer armAgentAwarenessLiveActivityForLocalWork({ environmentId: props.environmentId, threadTitle: props.selectedThread.title, - projectTitle: props.environmentLabel ?? "T3 Code", + projectTitle: props.environmentLabel ?? "The Knife", }); } finally { inFlightThreadIdsRef.current.delete(threadKey); diff --git a/apps/mobile/src/features/threads/ThreadFeed.tsx b/apps/mobile/src/features/threads/ThreadFeed.tsx index 5ab042dba12502d975e960c0cc64849108d63ff2..f5fade2153e908b3e639a4996a502b216c502fbb 100644 --- a/apps/mobile/src/features/threads/ThreadFeed.tsx +++ b/apps/mobile/src/features/threads/ThreadFeed.tsx @@ -1200,7 +1200,7 @@ function renderFeedEntry( ) : presentation.attribution === "t3code" ? ( - Sent by T3 Code + Sent by The Knife ) : presentation.attribution === "agent" ? ( + ); } if (props.icon === "computer") { diff --git a/apps/mobile/src/features/workspace/WorkspaceRootScreen.tsx b/apps/mobile/src/features/workspace/WorkspaceRootScreen.tsx index 81474586220b8ec13037afea21606285e9def3be..b6002cbc9271406c0ff0dfc2cd715fa1eb9308b0 100644 --- a/apps/mobile/src/features/workspace/WorkspaceRootScreen.tsx +++ b/apps/mobile/src/features/workspace/WorkspaceRootScreen.tsx @@ -42,7 +42,7 @@ function WorkspaceRootTitle(props: { + } maxWidth={props.maxWidth} onPress={props.onOpenEnvironments} @@ -114,7 +114,7 @@ function WorkspaceRootContent() { /> ) : ( { it("identifies mobile token exchanges for authorized-client presentation", () => { expect(authClientMetadata()).toEqual({ - label: "T3 Code Mobile", + label: "The Knife Mobile", deviceType: "mobile", os: "iOS", osMajorVersion: 18, diff --git a/apps/mobile/src/lib/mobileTheme.ts b/apps/mobile/src/lib/mobileTheme.ts index 2da47f91cd74e4852d3fd95b65dfe79cfccf6435..97f3776a83763700c10bde56a0b5e12d9599d026 100644 --- a/apps/mobile/src/lib/mobileTheme.ts +++ b/apps/mobile/src/lib/mobileTheme.ts @@ -26,7 +26,7 @@ export const MOBILE_THEME_OPTIONS: ReadonlyArray<{ readonly id: MobileThemeId; readonly label: string; }> = [ - { id: DEFAULT_MOBILE_THEME_ID, label: "T3 Code" }, + { id: DEFAULT_MOBILE_THEME_ID, label: "The Knife" }, { id: "material-you", label: "Material You" }, ...BUILT_IN_THEMES.map((theme) => ({ id: theme.id as MobileThemeId, label: theme.label })), ]; diff --git a/apps/mobile/src/widgets/AgentActivity.tsx b/apps/mobile/src/widgets/AgentActivity.tsx index bc27e8b881135ddcffe36d6813a175b2774eb525..8fcc7b928efe9efe3b73d84c2fd25ebd72b104dd 100644 --- a/apps/mobile/src/widgets/AgentActivity.tsx +++ b/apps/mobile/src/widgets/AgentActivity.tsx @@ -244,15 +244,9 @@ export function AgentActivity( ); - // The branded T3 mark. `assetName` resolves the template image set bundled in - // the widget extension's asset catalog. Image views only honor `resizable` - // directly (frame/foregroundStyle are dropped), so we size it via a container - // frame the resizable image fills and tint it through the container's - // foreground style, which the template image inherits. The 3:2 frame matches - // the glyph's aspect ratio so it never distorts. const renderLogo = (height: number, color: Foreground) => ( - - + + ); diff --git a/apps/mobile/src/widgets/SubscriptionUsage.android.tsx b/apps/mobile/src/widgets/SubscriptionUsage.android.tsx index ac47edf8cdd1d0305d9dcd75549717278d19c933..62eed8fab004d7799f420b9756198cae71167f05 100644 --- a/apps/mobile/src/widgets/SubscriptionUsage.android.tsx +++ b/apps/mobile/src/widgets/SubscriptionUsage.android.tsx @@ -29,8 +29,20 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi }); const muted = colors.onSurfaceVariant; const providers = props.providers ?? [ - { name: "Codex", detail: "Open T3 to connect", windows: [], expiresAt: 0, totalWindows: 0 }, - { name: "Claude", detail: "Open T3 to connect", windows: [], expiresAt: 0, totalWindows: 0 }, + { + name: "Codex", + detail: "Open The Knife to connect", + windows: [], + expiresAt: 0, + totalWindows: 0, + }, + { + name: "Claude", + detail: "Open The Knife to connect", + windows: [], + expiresAt: 0, + totalWindows: 0, + }, ]; return ( // The card is one Button so a tap reaches the app's interaction listener, @@ -60,7 +72,7 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi {shown.length === 0 ? ( - {stale ? "Open T3 to refresh" : provider.detail} + {stale ? "Open The Knife to refresh" : provider.detail} ) : null} {shown.map((window) => { @@ -90,7 +102,7 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi })} {hidden > 0 ? ( - {`${hidden} more in T3`} + {`${hidden} more in The Knife`} ) : null} @@ -104,7 +116,7 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi > {props.checkedAt ? `As of ${new Date(props.checkedAt).toLocaleString(undefined, { hour: "numeric", minute: "2-digit", month: "short", day: "numeric" })}` - : "Tap to connect in T3"} + : "Tap to connect in The Knife"} diff --git a/apps/mobile/src/widgets/SubscriptionUsage.tsx b/apps/mobile/src/widgets/SubscriptionUsage.tsx index 78197cd2858ea0e3e8815c76490b7a039a6ef807..b0a63649dd8a2cccb90561c4968379a0af50346b 100644 --- a/apps/mobile/src/widgets/SubscriptionUsage.tsx +++ b/apps/mobile/src/widgets/SubscriptionUsage.tsx @@ -39,8 +39,8 @@ function SubscriptionUsage( const monochrome = environment.widgetRenderingMode !== "fullColor" || environment.isLuminanceReduced; const providers = props.providers ?? [ - { name: "Codex", detail: "Open T3 to connect", windows: [], expiresAt: 0 }, - { name: "Claude", detail: "Open T3 to connect", windows: [], expiresAt: 0 }, + { name: "Codex", detail: "Open The Knife to connect", windows: [], expiresAt: 0 }, + { name: "Claude", detail: "Open The Knife to connect", windows: [], expiresAt: 0 }, ]; const columns = providers.map((provider) => { const stale = provider.windows.length > 0 && now >= provider.expiresAt; @@ -73,7 +73,7 @@ function SubscriptionUsage( ].slice(0, limit) : windows.slice(0, limit); const detail = stale - ? "Open T3 to refresh" + ? "Open The Knife to refresh" : period !== "auto" && windows.length === 0 && provider.windows.length > 0 ? `No ${period} limit reported` : provider.detail; @@ -121,7 +121,7 @@ function SubscriptionUsage( ? `${tightest.remaining}% left` : period !== "auto" && !stale && provider.windows.length > 0 ? "N/A" - : "Open T3"} + : "Open The Knife"} {tightest ? ( @@ -223,7 +223,7 @@ function SubscriptionUsage( > {(period === "auto" ? (provider.totalWindows ?? windows.length) : windows.length) - limit}{" "} - more in T3 + more in The Knife ) : null} @@ -255,7 +255,7 @@ function SubscriptionUsage( > {props.checkedAt ? `As of ${new Date(props.checkedAt).toLocaleString(undefined, { hour: "numeric", minute: "2-digit", month: "short", day: "numeric" })}` - : "Tap to connect in T3"} + : "Tap to connect in The Knife"} ) : null} diff --git a/apps/mobile/src/widgets/publishSubscriptionUsage.android.ts b/apps/mobile/src/widgets/publishSubscriptionUsage.android.ts index 406772d3d530bdba410990d15156fa926cd91475..72d1900e18b705292a54fe040c901f2134836bed 100644 --- a/apps/mobile/src/widgets/publishSubscriptionUsage.android.ts +++ b/apps/mobile/src/widgets/publishSubscriptionUsage.android.ts @@ -22,8 +22,7 @@ export async function publishSubscriptionUsage(snapshot: SubscriptionUsageSnapsh }); } widget.updateSnapshot(snapshot); - // Android has no timeline; an alarm re-renders the stored snapshot at each - // deadline so stale readings flip to "Open T3 to refresh" unattended. + // Android has no timeline; an alarm re-renders the stored snapshot at each deadline. requireOptionalNativeModule<{ schedule: (name: string, deadlines: number[]) => void }>( "T3WidgetExpiry", )?.schedule( diff --git a/apps/mobile/src/widgets/subscriptionUsageSnapshot.ts b/apps/mobile/src/widgets/subscriptionUsageSnapshot.ts index 44f2c6ef4bedf902902bc7bcc6148f4b4011fc9d..732e2c80384e8953a9275452bdb31b376e905544 100644 --- a/apps/mobile/src/widgets/subscriptionUsageSnapshot.ts +++ b/apps/mobile/src/widgets/subscriptionUsageSnapshot.ts @@ -94,7 +94,7 @@ function subscriptionUsageProps( return { name, detail: !fresh - ? "Open T3 to refresh" + ? "Open The Knife to refresh" : pool.accounts.length > 1 ? `${pool.accounts.length} accounts · pooled` : "Subscription remaining", @@ -161,7 +161,7 @@ export function subscriptionUsageTimeline(snapshot: SubscriptionUsageSnapshot, n ...snapshot, providers: snapshot.providers.map((provider) => provider.windows.length > 0 && provider.expiresAt <= date - ? { ...provider, detail: "Open T3 to refresh", windows: [], totalWindows: 0 } + ? { ...provider, detail: "Open The Knife to refresh", windows: [], totalWindows: 0 } : provider, ), }, diff --git a/apps/web/index.html b/apps/web/index.html index c2d164742402c2887125471a6d8dd2640c1612ae..eb0d4d88f8c5f6df2c956a9e5b98835d19993bd3 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -477,13 +477,13 @@ cursor: pointer; } - T3 Code (Alpha) + The Knife
-
- +
+
diff --git a/apps/web/public/apple-touch-icon.png b/apps/web/public/apple-touch-icon.png index 3eed25ea6b7850fc2594ac509e91c29f1710f708..049dd5c60408f73cabc67c4bebe5aa43d9283f9b 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 a3431b8c6dfef80cb22f502cc52e4680045dbe7b..d9bc91bd0329b6b019387b35e32876953533a97d 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 862f7629971facba7a28ca61dc2a491e30495973..029b8012a7943f1bbbcef7c4e786ae932faf8b84 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 750da22602eecab3f8cc8ffae34d6b4a0396084a..029c55739286cc76422491386d67267886024ddc 100644 Binary files a/apps/web/public/favicon.ico and b/apps/web/public/favicon.ico differ diff --git a/apps/web/public/manifest.webmanifest b/apps/web/public/manifest.webmanifest index 0389551332ffcefc1500428b1e77e58186324ede..4473525548b9e1d9eca8dc51022109ab2657e843 100644 --- a/apps/web/public/manifest.webmanifest +++ b/apps/web/public/manifest.webmanifest @@ -1,4 +1,6 @@ { + "name": "The Knife", + "short_name": "The Knife", "id": "/", "start_url": "/", "scope": "/", diff --git a/apps/web/src/bootstrap.test.ts b/apps/web/src/bootstrap.test.ts index c5c0d89597aa3e8ea5cc372dd7cc775ab6b2c4df..eca4619b0faef88eb485d2407ffa7e7ee6481b24 100644 --- a/apps/web/src/bootstrap.test.ts +++ b/apps/web/src/bootstrap.test.ts @@ -64,7 +64,7 @@ describe("app startup failures", () => { await import("./bootstrap"); await vi.dynamicImportSettled(); - expect(bootShell?.text).toContain("T3 Code could not load."); + expect(bootShell?.text).toContain("The Knife could not load."); const reloadButton = bootShell?.children[0]?.children.find( (element) => element.tagName === "button", ); @@ -78,7 +78,7 @@ describe("app startup failures", () => { showBootError(new Error("internal module path")); - expect(bootShell?.text).toContain("T3 Code could not load."); + expect(bootShell?.text).toContain("The Knife could not load."); expect(bootShell?.text.includes("internal module path")).toBe(dev); }); diff --git a/apps/web/src/branding.test.ts b/apps/web/src/branding.test.ts index e1c87bcf0595e071d52bf4e8a3ff8f5cce33b254..2484ba26ebc2a46937022ffd49786b4e3956e8b4 100644 --- a/apps/web/src/branding.test.ts +++ b/apps/web/src/branding.test.ts @@ -24,9 +24,9 @@ describe("branding", () => { value: { desktopBridge: { getAppBranding: () => ({ - baseName: "T3 Code", + baseName: "The Knife", stageLabel: "Nightly", - displayName: "T3 Code (Nightly)", + displayName: "The Knife (Nightly)", }), }, }, @@ -34,9 +34,9 @@ describe("branding", () => { const branding = await import("./branding"); - expect(branding.APP_BASE_NAME).toBe("T3 Code"); + expect(branding.APP_BASE_NAME).toBe("The Knife"); expect(branding.APP_STAGE_LABEL).toBe("Nightly"); - expect(branding.APP_DISPLAY_NAME).toBe("T3 Code (Nightly)"); + expect(branding.APP_DISPLAY_NAME).toBe("The Knife (Nightly)"); }); it("normalizes hosted app channel metadata", async () => { @@ -47,7 +47,7 @@ describe("branding", () => { expect(branding.HOSTED_APP_CHANNEL).toBe("nightly"); expect(branding.HOSTED_APP_CHANNEL_LABEL).toBe("Nightly"); expect(branding.APP_STAGE_LABEL).toBe("Nightly"); - expect(branding.APP_DISPLAY_NAME).toBe("T3 Code (Nightly)"); + expect(branding.APP_DISPLAY_NAME).toBe("The Knife (Nightly)"); }); it("does not label the latest hosted app channel", async () => { @@ -58,7 +58,7 @@ describe("branding", () => { expect(branding.HOSTED_APP_CHANNEL).toBe("latest"); expect(branding.HOSTED_APP_CHANNEL_LABEL).toBe("Latest"); expect(branding.APP_STAGE_LABEL).toBe("Latest"); - expect(branding.APP_DISPLAY_NAME).toBe("T3 Code"); + expect(branding.APP_DISPLAY_NAME).toBe("The Knife"); }); it("ignores unknown hosted app channels", async () => { @@ -84,33 +84,33 @@ describe("branding logic", () => { it("updates the display name for nightly primary server versions", () => { expect( resolveServerBackedAppDisplayName({ - baseName: "T3 Code", - fallbackDisplayName: "T3 Code (Alpha)", + baseName: "The Knife", + fallbackDisplayName: "The Knife (Alpha)", fallbackStageLabel: "Alpha", primaryServerVersion: "0.0.28-nightly.20260616.12", }), - ).toBe("T3 Code (Nightly)"); + ).toBe("The Knife (Nightly)"); }); it("keeps the fallback display name for stable primary server versions", () => { expect( resolveServerBackedAppDisplayName({ - baseName: "T3 Code", - fallbackDisplayName: "T3 Code (Alpha)", + baseName: "The Knife", + fallbackDisplayName: "The Knife (Alpha)", fallbackStageLabel: "Alpha", primaryServerVersion: "0.0.27", }), - ).toBe("T3 Code (Alpha)"); + ).toBe("The Knife (Alpha)"); }); it("keeps the fallback display name for malformed nightly primary server versions", () => { expect( resolveServerBackedAppDisplayName({ - baseName: "T3 Code", - fallbackDisplayName: "T3 Code (Alpha)", + baseName: "The Knife", + fallbackDisplayName: "The Knife (Alpha)", fallbackStageLabel: "Alpha", primaryServerVersion: "0.0.28-nightly.20260616", }), - ).toBe("T3 Code (Alpha)"); + ).toBe("The Knife (Alpha)"); }); }); diff --git a/apps/web/src/branding.ts b/apps/web/src/branding.ts index 7fc57cf0d03fea12b1dbb1c8838fcfe790ba65bf..79c2da757778a539eb3b9bb21decdea9be1a2a9f 100644 --- a/apps/web/src/branding.ts +++ b/apps/web/src/branding.ts @@ -16,11 +16,11 @@ export const HOSTED_APP_CHANNEL = hostedAppChannel === "latest" || hostedAppChannel === "nightly" ? hostedAppChannel : null; export const HOSTED_APP_CHANNEL_LABEL = HOSTED_APP_CHANNEL === "nightly" ? "Nightly" : HOSTED_APP_CHANNEL === "latest" ? "Latest" : null; -export const APP_BASE_NAME = injectedDesktopAppBranding?.baseName ?? "T3 Code"; +export const APP_BASE_NAME = injectedDesktopAppBranding?.baseName ?? "The Knife"; export const APP_STAGE_LABEL = injectedDesktopAppBranding?.stageLabel ?? HOSTED_APP_CHANNEL_LABEL ?? - (import.meta.env.DEV ? "Dev" : "Alpha"); + (import.meta.env.DEV ? "Dev" : "Latest"); export const APP_DISPLAY_NAME = injectedDesktopAppBranding?.displayName ?? formatAppDisplayName({ baseName: APP_BASE_NAME, stageLabel: APP_STAGE_LABEL }); diff --git a/apps/web/src/cloud/linkEnvironment.ts b/apps/web/src/cloud/linkEnvironment.ts index 4e1fba7040eac3fb9805be86a4d2111ed43fe74a..18dab783623b2c3c7431ef7b2c4bdbb63faa0165 100644 --- a/apps/web/src/cloud/linkEnvironment.ts +++ b/apps/web/src/cloud/linkEnvironment.ts @@ -59,7 +59,7 @@ function ensureRelayClientAvailable( if (status.status === "available") return; if (status.status === "unsupported") { return yield* new CloudEnvironmentLinkError({ - message: `T3 Code cannot install the relay client automatically on ${status.platform}-${status.arch}.`, + message: `The Knife cannot install the relay client automatically on ${status.platform}-${status.arch}.`, }); } @@ -95,7 +95,7 @@ function ensureRelayClientAvailable( return yield* new CloudEnvironmentLinkError({ message: installedStatus.status === "unsupported" - ? `T3 Code cannot install the relay client automatically on ${installedStatus.platform}-${installedStatus.arch}.` + ? `The Knife cannot install the relay client automatically on ${installedStatus.platform}-${installedStatus.arch}.` : "The relay client is still unavailable after installation.", }); } diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index b0d1804feffaa0dca180608709a6a598d1aa3c1f..9d642198a2e18e08181c3b77677c8e407f94a2d7 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -11178,7 +11178,7 @@ export default function ChatView(props: ChatViewProps) { ) : renderedRightPanelSurface?.kind === "pull-request" && !supportsPullRequests ? ( ) : renderedRightPanelSurface?.kind === "pull-request" ? ( // No onClose: the surface tab's own X owns closing here, and a second X in the header diff --git a/apps/web/src/components/KnifeMark.tsx b/apps/web/src/components/KnifeMark.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4c3de2cc633b57cc94db119c2aa209939d94f3f5 --- /dev/null +++ b/apps/web/src/components/KnifeMark.tsx @@ -0,0 +1,6 @@ +import type { ImgHTMLAttributes } from "react"; +import knife from "../../../../assets/knife/app-icon.icon/Assets/knife.png"; + +export function KnifeMark(props: Omit, "src" | "alt">) { + return ; +} diff --git a/apps/web/src/components/RightPanelTabs.tsx b/apps/web/src/components/RightPanelTabs.tsx index 5c8323a7ae34d443750d39268a362827ce55f34a..e44dfcad0cbb005a0384779cc6d566dd888375cb 100644 --- a/apps/web/src/components/RightPanelTabs.tsx +++ b/apps/web/src/components/RightPanelTabs.tsx @@ -171,7 +171,7 @@ export function shouldOpenDefaultBrowserProfileFromMenuClick( } const SURFACE_DISABLED_REASONS = { - browser: "Browser previews are only available in the T3 Code desktop app.", + browser: "Browser previews are only available in The Knife desktop app.", terminal: "Terminal surfaces are only available from a project thread.", files: "Files are only available when a project is open.", diff: "Diff is only available for server threads in Git repositories.", diff --git a/apps/web/src/components/ServerUpdateAction.tsx b/apps/web/src/components/ServerUpdateAction.tsx index 012911f920174c0d51739a7a5a0590b148a86d71..48c98d61dc4777dd5418c1ff1345e9566e6e2f43 100644 --- a/apps/web/src/components/ServerUpdateAction.tsx +++ b/apps/web/src/components/ServerUpdateAction.tsx @@ -157,7 +157,7 @@ export function ServerUpdatesAction({ if (desktopTargets.length > 0) { const confirmed = (await requestConfirmDialog( - `Update the T3 Code desktop apps on ${desktopTargets.map((target) => target.serverLabel).join(", ")}? They will close and relaunch on those machines.`, + `Update The Knife desktop apps on ${desktopTargets.map((target) => target.serverLabel).join(", ")}? They will close and relaunch on those machines.`, )) ?? true; if (!confirmed) return; } @@ -279,7 +279,7 @@ export function ServerUpdateAction({ // remote machine installs without asking anyone there. const confirmed = (await requestConfirmDialog( - `Update the T3 Code desktop app that runs the ${serverLabel}? It will close and relaunch on that machine.`, + `Update The Knife desktop app that runs the ${serverLabel}? It will close and relaunch on that machine.`, )) ?? true; if (!confirmed) { return; diff --git a/apps/web/src/components/T3Wordmark.tsx b/apps/web/src/components/T3Wordmark.tsx deleted file mode 100644 index d0882a5dd61e3c4dd8d0f0a63cc8044a949504e6..0000000000000000000000000000000000000000 --- a/apps/web/src/components/T3Wordmark.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { SVGProps } from "react"; - -export function T3Wordmark(props: SVGProps) { - return ( - - - - ); -} diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index d490b980e5f0005155e137228f15bb367d36722b..e8cfadc74b60de79a4192d6851d572adeaab61fa 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -119,7 +119,7 @@ import ChatMarkdown, { ChatMarkdownAssetImage } from "../ChatMarkdown"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import type { Root, RootContent } from "mdast"; -import { T3Wordmark } from "../T3Wordmark"; +import { KnifeMark } from "../KnifeMark"; import { ThreadContextChip } from "../ThreadContextChip"; import { BotIcon, @@ -2462,7 +2462,7 @@ function UserTimelineRow({ row }: { row: Extract - Sent by T3 Code + Sent by The Knife

) : userMessage.attribution === "agent" ? (

@@ -2837,7 +2837,7 @@ function AssistantTimelineRow({ row }: { row: Extract

- T3 Code + The Knife
; case "t3-code": - return ; + return ; case "check": return ; case "circle-alert": diff --git a/apps/web/src/components/chat/V2LifecycleRow.tsx b/apps/web/src/components/chat/V2LifecycleRow.tsx index 5206d33acde022fa8c42990743b613dd440b4ed0..f6e9f8489e04ea1d2c3870699200062cdab5224b 100644 --- a/apps/web/src/components/chat/V2LifecycleRow.tsx +++ b/apps/web/src/components/chat/V2LifecycleRow.tsx @@ -48,7 +48,7 @@ import { ProviderInstanceIcon, providerTextColor } from "./ProviderInstanceIcon" import { cn } from "~/lib/utils"; import { TimelineSystemDivider } from "./TimelineSystemDivider"; import { Button, InlineButton } from "../ui/button"; -import { T3Wordmark } from "../T3Wordmark"; +import { KnifeMark } from "../KnifeMark"; const LIFECYCLE_TYPES = new Set([ "run_interrupt_request", @@ -170,7 +170,7 @@ export function V2LifecycleRow(props: { return ( } + icon={} label={<>Created thread{item.title ? ` · ${item.title}` : ""}} trailing={ { it("describes the client platform and enabled notification events", () => { const client = device(); - expect(mobileClientPlatformLabel(client)).toBe("iOS 18 · T3 Code 1.2.3"); + expect(mobileClientPlatformLabel(client)).toBe("iOS 18 · The Knife 1.2.3"); expect(mobileClientNotificationDetail(client)).toBe( "Alerts enabled for approvals, completions.", ); @@ -42,7 +42,7 @@ describe("mobile client presentation", () => { mobileClientPlatformLabel( device({ platform: "android", iosMajorVersion: null, androidApiLevel: 36 }), ), - ).toBe("Android · T3 Code 1.2.3"); + ).toBe("Android · The Knife 1.2.3"); }); it("distinguishes disabled notifications from an empty event selection", () => { @@ -68,7 +68,7 @@ describe("mobile client presentation", () => { it("handles missing app versions and invalid update timestamps", () => { expect(mobileClientPlatformLabel(device({ iosMajorVersion: null }))).toBe( - "iOS · T3 Code 1.2.3", + "iOS · The Knife 1.2.3", ); expect(mobileClientPlatformLabel(device({ appVersion: null }))).toBe("iOS 18"); expect(mobileClientUpdatedAtLabel("not-a-date")).toBe("Update time unavailable"); diff --git a/apps/web/src/components/clerk/MobileClientsUserProfilePage.logic.ts b/apps/web/src/components/clerk/MobileClientsUserProfilePage.logic.ts index b52d381f6ddfb025f8fa0ca8c2ddd07c97d09326..c7c637c191742a08dd41ea965bd09e62ceccd9c5 100644 --- a/apps/web/src/components/clerk/MobileClientsUserProfilePage.logic.ts +++ b/apps/web/src/components/clerk/MobileClientsUserProfilePage.logic.ts @@ -21,7 +21,7 @@ export function mobileClientPlatformLabel(device: RelayClientDeviceRecord): stri : device.iosMajorVersion === null ? "iOS" : `iOS ${device.iosMajorVersion}`; - return `${platform}${device.appVersion ? ` · T3 Code ${device.appVersion}` : ""}`; + return `${platform}${device.appVersion ? ` · The Knife ${device.appVersion}` : ""}`; } export function mobileClientNotificationDetail(device: RelayClientDeviceRecord): string { diff --git a/apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx b/apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx index 016099805eb2ac4ad2c4ec75f3fe959133944baa..6f0d99393e539284158332995a6ce6e0f1863f95 100644 --- a/apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx +++ b/apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx @@ -94,7 +94,7 @@ function EmptyMobileClients() { No mobile clients - Install T3 Code on your phone and sign in to T3 Connect to get push notifications and Live + Install The Knife on your phone and sign in to T3 Connect to get push notifications and Live Activities. diff --git a/apps/web/src/components/cloud/RelayClientInstallDialog.tsx b/apps/web/src/components/cloud/RelayClientInstallDialog.tsx index 78282f659151fc924a8b9cc12a3b4c4ec65dc56a..ad54e121c6f52bcba1cd033863df1efa6b5d58d2 100644 --- a/apps/web/src/components/cloud/RelayClientInstallDialog.tsx +++ b/apps/web/src/components/cloud/RelayClientInstallDialog.tsx @@ -69,8 +69,8 @@ export function RelayClientInstallDialog() { {isInstalling - ? "T3 Code is preparing this environment for secure access through T3 Connect." - : "T3 Code needs the relay client to make this environment available through T3 Connect."} + ? "The Knife is preparing this environment for secure access through T3 Connect." + : "The Knife needs the relay client to make this environment available through T3 Connect."} @@ -91,14 +91,14 @@ export function RelayClientInstallDialog() { value={activeStepIndex + 1} />

- Keep T3 Code open while the relay client is installed. + Keep The Knife open while the relay client is installed.

) : (

Managed relay client

- T3 Code will download and install version{" "} + The Knife will download and install version{" "} {view.status === "confirming" ? view.version : ""} locally.

diff --git a/apps/web/src/components/desktop/DesktopAppActivationCoordinator.tsx b/apps/web/src/components/desktop/DesktopAppActivationCoordinator.tsx index 3e941e69dd5329fbd9ec77ebf1442bdb91d42f86..dba42a09233b55cafa7d7437f1a1b44880fe253d 100644 --- a/apps/web/src/components/desktop/DesktopAppActivationCoordinator.tsx +++ b/apps/web/src/components/desktop/DesktopAppActivationCoordinator.tsx @@ -63,7 +63,7 @@ export function DesktopAppActivationCoordinator() { }); if (result._tag === "Failure") { const error = squashAtomCommandFailure(result); - throw error instanceof Error ? error : new Error("T3 Code could not add the project."); + throw error instanceof Error ? error : new Error("The Knife could not add the project."); } return projectId; }, diff --git a/apps/web/src/components/desktop/SshPasswordPromptDialog.tsx b/apps/web/src/components/desktop/SshPasswordPromptDialog.tsx index d3727ae210bdfed516f7fe46e14c77cd92bca566..2b46d408daa85a07b7074817db7bc0406d28d00d 100644 --- a/apps/web/src/components/desktop/SshPasswordPromptDialog.tsx +++ b/apps/web/src/components/desktop/SshPasswordPromptDialog.tsx @@ -161,7 +161,7 @@ function ActiveSshPasswordPrompt({ SSH Password Required T3 needs your SSH password to connect to {target}. The password is passed - to the local SSH process for this connection attempt and is not saved by T3 Code. + to the local SSH process for this connection attempt and is not saved by The Knife. diff --git a/apps/web/src/components/desktopUpdate.logic.test.ts b/apps/web/src/components/desktopUpdate.logic.test.ts index 04b1bac77fa9249580d74580691a5edbb3424c3d..e95b5d2a7d099d29928f2197e86cae4a4dccd6f2 100644 --- a/apps/web/src/components/desktopUpdate.logic.test.ts +++ b/apps/web/src/components/desktopUpdate.logic.test.ts @@ -279,7 +279,7 @@ describe("desktop update UI helpers", () => { availableVersion: "1.1.0", downloadedVersion: "1.1.1", }), - ).toContain("Install update 1.1.1 and restart T3 Code?"); + ).toContain("Install update 1.1.1 and restart The Knife?"); }); it("falls back to generic install confirmation copy when no version is available", () => { @@ -288,7 +288,7 @@ describe("desktop update UI helpers", () => { availableVersion: null, downloadedVersion: null, }), - ).toContain("Install update and restart T3 Code?"); + ).toContain("Install update and restart The Knife?"); }); it("keeps the same install confirmation copy across desktop platforms", () => { @@ -298,7 +298,7 @@ describe("desktop update UI helpers", () => { downloadedVersion: "1.1.0", }), ).toBe( - "Install update 1.1.0 and restart T3 Code?\n\nAny running tasks will be interrupted. Make sure you're ready before continuing.", + "Install update 1.1.0 and restart The Knife?\n\nAny running tasks will be interrupted. Make sure you're ready before continuing.", ); }); }); diff --git a/apps/web/src/components/desktopUpdate.logic.ts b/apps/web/src/components/desktopUpdate.logic.ts index 3eb2726bfd350b3b17bef82b14847e633beb2b1d..a5d10773a1fd4ea0c215824937d838e1678f2643 100644 --- a/apps/web/src/components/desktopUpdate.logic.ts +++ b/apps/web/src/components/desktopUpdate.logic.ts @@ -62,12 +62,12 @@ export function getArm64IntelBuildWarningDescription(state: DesktopUpdateState): const action = resolveDesktopUpdateButtonAction(state); if (action === "download") { - return "This Mac has Apple Silicon, but T3 Code is still running the Intel build under Rosetta. Download the available update to switch to the native Apple Silicon build."; + return "This Mac has Apple Silicon, but The Knife is still running the Intel build under Rosetta. Download the available update to switch to the native Apple Silicon build."; } if (action === "install") { - return "This Mac has Apple Silicon, but T3 Code is still running the Intel build under Rosetta. Restart to install the downloaded Apple Silicon build."; + return "This Mac has Apple Silicon, but The Knife is still running the Intel build under Rosetta. Restart to install the downloaded Apple Silicon build."; } - return "This Mac has Apple Silicon, but T3 Code is still running the Intel build under Rosetta. The next app update will replace it with the native Apple Silicon build."; + return "This Mac has Apple Silicon, but The Knife is still running the Intel build under Rosetta. The next app update will replace it with the native Apple Silicon build."; } export function getDesktopUpdateButtonTooltip(state: DesktopUpdateState): string { @@ -108,7 +108,7 @@ export function getDesktopUpdateInstallConfirmationMessage( state: Pick, ): string { const version = state.downloadedVersion ?? state.availableVersion; - return `Install update${version ? ` ${version}` : ""} and restart T3 Code?\n\nAny running tasks will be interrupted. Make sure you're ready before continuing.`; + return `Install update${version ? ` ${version}` : ""} and restart The Knife?\n\nAny running tasks will be interrupted. Make sure you're ready before continuing.`; } export function getDesktopUpdateActionError(result: DesktopUpdateActionResult): string | null { diff --git a/apps/web/src/components/onboarding/FirstRunGate.tsx b/apps/web/src/components/onboarding/FirstRunGate.tsx index 15187b90defa049c7832233628a9e5e910ab9d0c..6a47998f0b53f8017d07554b42204f10868fc9f1 100644 --- a/apps/web/src/components/onboarding/FirstRunGate.tsx +++ b/apps/web/src/components/onboarding/FirstRunGate.tsx @@ -214,7 +214,7 @@ function FirstRunRecovery({

{settingsReadFailed ? "Your saved settings could not be loaded." - : "T3 Code could not confirm this workspace."} + : "The Knife could not confirm this workspace."}

- Turn capture off in T3 Code to stop it. Remove the shortcut from {desktop} to free up + Turn capture off in The Knife to stop it. Remove the shortcut from {desktop} to free up the keys.

{state.shortcutActionRegistered === false ? ( diff --git a/apps/web/src/components/settings/ChatGptWelcomeCoordinator.tsx b/apps/web/src/components/settings/ChatGptWelcomeCoordinator.tsx index 2d689ef6e6eeaf11aebf952108530992d9974408..543271712b1d15a24907d7d0ea763af555ded8df 100644 --- a/apps/web/src/components/settings/ChatGptWelcomeCoordinator.tsx +++ b/apps/web/src/components/settings/ChatGptWelcomeCoordinator.tsx @@ -66,7 +66,7 @@ export function ChatGptWelcomeCoordinator() {