| author | |
| committer | |
| log | 1dced964e7161946a640107004968697e5b258b8 |
| tree | cd98d50fd695ba6c072d917a9b55b9d257890c8a |
| parent | 339179f50a84dc599cce373dbb48f172f182f44e |
| signature | Signed by SSH key SHA256:cOKiuRFOeSRxne6EWgHtdQQSlBxjOXm2hOCFnCdLQbQ (~clover) |
Establish The Knife as Clover’s fork identity across desktop, web, and mobile surfaces. Keep her README and agent guidance here; render the sculpted blade through one canonical Icon Composer project, use its transparent layer in the UI, and derive platform assets with the classic macOS inset. Preserve shipped bundle identifiers, protocols, data paths, and Electron’s credential-storage runtime name.
Use The Knife in mobile widget copy, theme choices, and native display names. Keep generated iOS build paths aligned with the renamed target and custom keychain groups aligned with the installed bundle identity. Strip unsupported capabilities after other Expo plugins run for reduced-capability local builds.
Enable local iPhone builds with Xcode 27.0 by gating iOS 27.1 hinge and reserved-region APIs on the installed SDK, while retaining safe-area layout on earlier SDKs.
Keep licensed fonts in ignored local storage; include them in private builds without adding their bytes to source history.
Record behavior-based patch boundaries, dependency-aware ordering, concrete unscoped titles, at-work labels, and private workplace configuration. Fold iterations into their owning patches with jj squash. Fork-only work is exempt from upstream contribution conventions; setup and agent guidance share this patch. Publish every self-tested change through the shared release feed, with automatic downloads, safe idle UI refreshes, and confirmed backend or shell restarts.
Assisted-by: gpt-6.1-sol
Assisted-by: gpt-6-astra175 files changed, 666 insertions(+), 1014 deletions(-)
.agents/skills/test-t3-mobile/SKILL.md+2-2| ... | @@ -26,7 +26,7 @@ Test with meaningful project and thread data. Read the shared | ... | @@ -26,7 +26,7 @@ Test with meaningful project and thread data. Read the shared |
| 26 | [SQLite fixture reference](../test-t3-app/references/sqlite-fixtures.md) only | 26 | [SQLite fixture reference](../test-t3-app/references/sqlite-fixtures.md) only |
| 27 | when inspecting or seeding SQLite. Stop the test server before fixture writes. | 27 | when inspecting or seeding SQLite. Stop the test server before fixture writes. |
| 28 | 28 | ||
| 29 | ## Launch T3 Code Dev | 29 | ## Launch The Knife Dev |
| 30 | 30 | ||
| 31 | From the checkout being tested on the selected device host, run: | 31 | From the checkout being tested on the selected device host, run: |
| 32 | 32 | ||
| ... | @@ -57,7 +57,7 @@ arguments stored in `agent_device_command` and the Bash array | ... | @@ -57,7 +57,7 @@ arguments stored in `agent_device_command` and the Bash array |
| 57 | "$agent_device_command" "${agent_device_target_args[@]}" | 57 | "$agent_device_command" "${agent_device_target_args[@]}" |
| 58 | ``` | 58 | ``` |
| 59 | 59 | ||
| 60 | It issues a fresh credential and opens T3 Code Dev's existing pairing route | 60 | It issues a fresh credential and opens The Knife Dev's existing pairing route |
| 61 | through AgentDevice. For a backend on the device host, use | 61 | through AgentDevice. For a backend on the device host, use |
| 62 | `http://127.0.0.1:<server-port>` on iOS or `http://10.0.2.2:<server-port>` | 62 | `http://127.0.0.1:<server-port>` on iOS or `http://10.0.2.2:<server-port>` |
| 63 | on Android. For a remote backend, use its reachable origin. | 63 | on Android. For a remote backend, use its reachable origin. |
.gitignore+1| ... | @@ -45,3 +45,4 @@ node_modules/ | ... | @@ -45,3 +45,4 @@ node_modules/ |
| 45 | 45 | ||
| 46 | # pnpm content-addressable store; never belongs in the repo. | 46 | # pnpm content-addressable store; never belongs in the repo. |
| 47 | .pnpm-store/ | 47 | .pnpm-store/ |
| 48 | /apps/web/public/fonts/private/ |
AGENTS.md+42-11| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | # T3 Code | 1 | # The Knife |
| 2 | |||
| 3 | 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. | ||
| 2 | 4 | ||
| 3 | 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. | 5 | 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. |
| 4 | 6 | ||
| ... | @@ -104,16 +106,6 @@ An empty database is a bad test. Seed your worktree's `.t3` with a copy of real | ... | @@ -104,16 +106,6 @@ An empty database is a bad test. Seed your worktree's `.t3` with a copy of real |
| 104 | 106 | ||
| 105 | 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 <ios|android> <device-id>` 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. | 107 | 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 <ios|android> <device-id>` 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. |
| 106 | 108 | ||
| 107 | ## Pull requests | ||
| 108 | |||
| 109 | - Never make a PR unless the developer explicitly asks you to do so. | ||
| 110 | - Conventional commit titles, plain language: `fix(web): new threads no longer spike CPU`. | ||
| 111 | - Body: the problem in a sentence or two, then how you fixed it. End with the model and harness that did the work. | ||
| 112 | - UI changes need before/after images. Motion or timing needs a short video. | ||
| 113 | - Upload PR evidence to GitHub. Never commit PR-only screenshots or assets such as `.github/pr-assets/`. | ||
| 114 | - 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. | ||
| 115 | - 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. | ||
| 116 | |||
| 117 | ## Documentation | 109 | ## Documentation |
| 118 | 110 | ||
| 119 | Most code changes do not need an internal documentation change. Agents can read the code. | 111 | 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 | ... | @@ -126,6 +118,45 @@ Most code changes do not need an internal documentation change. Agents can read |
| 126 | - 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. | 118 | - 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. |
| 127 | - `docs/operations/` holds maintainer setup, release, and debugging procedures. Keep instructions for operating an installed T3 Code server in the user guides. | 119 | - `docs/operations/` holds maintainer setup, release, and debugging procedures. Keep instructions for operating an installed T3 Code server in the user guides. |
| 128 | 120 | ||
| 121 | ## Fork patch workflow | ||
| 122 | |||
| 123 | This checkout is a fork of T3 code for Clover. All changes should be | ||
| 124 | categorized into a set of well-maintained changes on top of the upstream. When | ||
| 125 | a change to something you already patched is requested, the changes are | ||
| 126 | squashed in. This allows rebasing without having hundreds of changes. The | ||
| 127 | patches can also be cherry-picked for upstreaming if Theo or someone elses fork | ||
| 128 | wants the change. Exact operating rules: | ||
| 129 | |||
| 130 | - Fork-only work follows these rules instead of upstream contribution and PR | ||
| 131 | conventions. Apply the upstream process when Clover requests an upstream submission. | ||
| 132 | - Keep one Jujutsu change per complete behavior above `main@upstream`, not per | ||
| 133 | iteration or touched file. Fold revisions into their owning patch with | ||
| 134 | `jj squash --from @ --into <patch-change-id> --use-destination-message`. | ||
| 135 | - Order setup first, general UI patches next, and work integrations last. Within | ||
| 136 | each group, put dependencies before their consumers. | ||
| 137 | - Use unscoped titles such as `feat: ...` and `fix: ...`. The only scope is | ||
| 138 | `at-work`, for work-specific patches. | ||
| 139 | - Titles name the concrete change. For replacements, name the original T3 | ||
| 140 | component and replacement package; explain behavior and retained features in | ||
| 141 | the description. Rewrite descriptions when a patch's scope changes. | ||
| 142 | - Keep fork setup and agent guidance together in the setup patch. | ||
| 143 | - After testing a change yourself, publish it with `vp run fork:release` before | ||
| 144 | handing it back. Follow the | ||
| 145 | [private release procedure](docs/operations/development.md#private-fork-releases-for-apple-silicon). | ||
| 146 | Publish every supported desktop platform affected by the change, using the | ||
| 147 | native build and ingestion steps in that procedure. | ||
| 148 | Reuse unchanged UI, static assets, application code, and shell artifacts; all machines consume | ||
| 149 | the same public feed. Keep the upload API key and signing credentials in | ||
| 150 | `~/.config/t3-fork-release.json`, outside the repository. | ||
| 151 | - Downloads are automatic. Backend and shell restarts require confirmation. | ||
| 152 | Automatically refresh a pending UI after one minute unfocused or ten minutes | ||
| 153 | idle only when browser, terminal, draft, composer, and scroll state survive. | ||
| 154 | Defer the refresh when a live view or unsaved work cannot be restored. | ||
| 155 | - Keep employer names, logos, repository identifiers, and service origins in | ||
| 156 | private host configuration, outside tracked files and change descriptions. | ||
| 157 | - Describe the intended behavior and why Clover requested it in each change's description. | ||
| 158 | - Use managed Jujutsu workspaces when they help parallel subagents; integrate their changes into the same stack. | ||
| 159 | |||
| 129 | ## Plans and work artifacts | 160 | ## Plans and work artifacts |
| 130 | 161 | ||
| 131 | - 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. | 162 | - 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. |
README.md+5-139| ... | @@ -1,141 +1,7 @@ | ... | @@ -1,141 +1,7 @@ |
| 1 | # T3 Code | 1 | # The Knife |
| 2 | 2 | ||
| 3 | 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). | 3 | A fork of [T3 Code](https://t3.codes) with more precise engineering from the |
| 4 | backend to the UI/UX. | ||
| 4 | 5 | ||
| 5 | 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. | 6 | - [Read patches](https://shale.paperclover.net/the-knife/log). |
| 6 | 7 | - 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). | |
| 7 | ## "Wait, what are you selling me?" | ||
| 8 | |||
| 9 | 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. | ||
| 10 | |||
| 11 | 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. | ||
| 12 | |||
| 13 | ## Installation | ||
| 14 | |||
| 15 | > [!WARNING] | ||
| 16 | > T3 Code currently supports Codex, Claude, Cursor, Grok Build, OpenCode, and Antigravity. Install and authenticate at least one provider before use: | ||
| 17 | > | ||
| 18 | > - Codex: install [Codex CLI](https://developers.openai.com/codex/cli) and run `codex login` | ||
| 19 | > - Claude: install [Claude Code](https://claude.com/product/claude-code) and run `claude auth login` | ||
| 20 | > - Cursor: install [Cursor CLI](https://cursor.com/cli) and run `agent login` | ||
| 21 | > - Grok Build: install [Grok Build CLI](https://x.ai/cli) and run `grok login` | ||
| 22 | > - OpenCode: install [OpenCode](https://opencode.ai) and run `opencode auth login` | ||
| 23 | > - Antigravity: enable it in Settings, then use **Install Antigravity** and **Sign in with Google**. No CLI is required. | ||
| 24 | |||
| 25 | ### Command line | ||
| 26 | |||
| 27 | ```bash | ||
| 28 | curl -fsSL https://t3.codes/install.sh | sh | ||
| 29 | ``` | ||
| 30 | |||
| 31 | On Windows, in PowerShell: | ||
| 32 | |||
| 33 | ```powershell | ||
| 34 | irm https://t3.codes/install.ps1 | iex | ||
| 35 | ``` | ||
| 36 | |||
| 37 | 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. | ||
| 38 | |||
| 39 | To try it once without installing, run `npx t3@latest` instead. | ||
| 40 | |||
| 41 | ### Desktop app | ||
| 42 | |||
| 43 | Install the latest version of the desktop app from [GitHub Releases](https://github.com/pingdotgg/t3code/releases), or from your favorite package registry: | ||
| 44 | |||
| 45 | #### Windows (`winget`) | ||
| 46 | |||
| 47 | ```bash | ||
| 48 | winget install T3Tools.T3Code | ||
| 49 | ``` | ||
| 50 | |||
| 51 | #### Windows (`scoop`) | ||
| 52 | |||
| 53 | ```bash | ||
| 54 | scoop install extras/t3code | ||
| 55 | ``` | ||
| 56 | |||
| 57 | #### macOS (Homebrew) | ||
| 58 | |||
| 59 | ```bash | ||
| 60 | brew install --cask t3-code | ||
| 61 | ``` | ||
| 62 | |||
| 63 | #### Debian, Ubuntu (`.deb`) | ||
| 64 | |||
| 65 | Download the `.deb` from [GitHub Releases](https://github.com/pingdotgg/t3code/releases), then: | ||
| 66 | |||
| 67 | ```bash | ||
| 68 | sudo apt install ./T3-Code-*.deb | ||
| 69 | ``` | ||
| 70 | |||
| 71 | #### Arch Linux (AUR) | ||
| 72 | |||
| 73 | Stable: | ||
| 74 | |||
| 75 | ```bash | ||
| 76 | yay -S t3code-bin | ||
| 77 | ``` | ||
| 78 | |||
| 79 | Nightly: | ||
| 80 | |||
| 81 | ```bash | ||
| 82 | yay -S t3code-nightly-bin | ||
| 83 | ``` | ||
| 84 | |||
| 85 | The AUR packaging is maintained in this repository under [`packaging/aur`](./packaging/aur). | ||
| 86 | |||
| 87 | ## Some notes | ||
| 88 | |||
| 89 | We are very very early in this project. Expect bugs. | ||
| 90 | |||
| 91 | We are (mostly) not accepting contributions yet. Small fixes may be considered. Big features will not be. | ||
| 92 | |||
| 93 | ## Documentation | ||
| 94 | |||
| 95 | Full docs live in [docs/](./docs). There's no docs site yet. | ||
| 96 | |||
| 97 | - [Install and first run](./docs/user/install.md) | ||
| 98 | - [Permission modes](./docs/user/permission-modes.md) | ||
| 99 | - [Keyboard shortcuts](./docs/user/keybindings.md) | ||
| 100 | - [Project settings](./docs/user/project-settings.md) | ||
| 101 | - [Appearance preferences](./docs/user/appearance.md) | ||
| 102 | - [Remote access from a phone or another machine](./docs/user/remote-access.md) | ||
| 103 | - [Connect Claude Code, Codex, ChatGPT and other agents over MCP](./docs/user/outside-agents.md) | ||
| 104 | - [Keeping app and server in sync](./docs/user/updating.md) | ||
| 105 | - [Source control integrations](./docs/user/source-control.md) | ||
| 106 | - Multiple accounts: [Codex](./docs/user/providers-codex.md) · [Claude](./docs/user/providers-claude.md) | ||
| 107 | - [Run T3 Code as a background service](./docs/user/background-service.md) | ||
| 108 | |||
| 109 | Building from source? Start at [docs/internals/overview.md](./docs/internals/overview.md). | ||
| 110 | |||
| 111 | ## If you REALLY want to contribute still.... read this first | ||
| 112 | |||
| 113 | ### Install `vp` | ||
| 114 | |||
| 115 | T3 Code uses Vite+ so you'll need to install the global `vp` command-line tool. | ||
| 116 | |||
| 117 | #### macOS / Linux | ||
| 118 | |||
| 119 | ```bash | ||
| 120 | curl -fsSL https://vite.plus | bash | ||
| 121 | ``` | ||
| 122 | |||
| 123 | #### Windows | ||
| 124 | |||
| 125 | ```bash | ||
| 126 | irm https://vite.plus/ps1 | iex | ||
| 127 | ``` | ||
| 128 | |||
| 129 | Checkout their getting started guide for more information: https://viteplus.dev/guide/ | ||
| 130 | |||
| 131 | ### Install dependencies | ||
| 132 | |||
| 133 | ```bash | ||
| 134 | vp i | ||
| 135 | ``` | ||
| 136 | |||
| 137 | Read [CONTRIBUTING.md](./CONTRIBUTING.md) before reporting a bug or opening a PR. | ||
| 138 | |||
| 139 | Have a feature request? Start an [Ideas discussion](https://github.com/pingdotgg/t3code/discussions/categories/ideas). | ||
| 140 | |||
| 141 | Need support? Join the [Discord](https://discord.gg/jn4EGJjrvv). |
apps/desktop/gnome-extension/captureFeedback.js+1-1| ... | @@ -110,7 +110,7 @@ export class CaptureFeedback { | ... | @@ -110,7 +110,7 @@ export class CaptureFeedback { |
| 110 | check(); | 110 | check(); |
| 111 | })); | 111 | })); |
| 112 | if (this._owned(sender) !== session || !target) | 112 | if (this._owned(sender) !== session || !target) |
| 113 | throw new Error("T3 Code's window is not available for activation."); | 113 | throw new Error("The Knife's window is not available for activation."); |
| 114 | session.target = target; | 114 | session.target = target; |
| 115 | Main.activateWindow(target, global.get_current_time()); | 115 | Main.activateWindow(target, global.get_current_time()); |
| 116 | } | 116 | } |
apps/desktop/gnome-extension/captureService.js+1-1| ... | @@ -43,7 +43,7 @@ export class CaptureService { | ... | @@ -43,7 +43,7 @@ export class CaptureService { |
| 43 | break; | 43 | break; |
| 44 | } | 44 | } |
| 45 | } | 45 | } |
| 46 | if (!allowed) throw new Error("Only T3 Code may request a snapshot."); | 46 | if (!allowed) throw new Error("Only The Knife may request a snapshot."); |
| 47 | const pid = options ? await this._getProcessId(sender) : undefined; | 47 | const pid = options ? await this._getProcessId(sender) : undefined; |
| 48 | this._checkSession(); | 48 | this._checkSession(); |
| 49 | const snapshot = await this._takeSnapshot(options?.animate ?? false); | 49 | const snapshot = await this._takeSnapshot(options?.animate ?? false); |
apps/desktop/gnome-extension/captureService.test.js+3-3| ... | @@ -23,7 +23,7 @@ function fixture(overrides = {}) { | ... | @@ -23,7 +23,7 @@ function fixture(overrides = {}) { |
| 23 | 23 | ||
| 24 | it("rejects other clients before looking at the active window", async () => { | 24 | it("rejects other clients before looking at the active window", async () => { |
| 25 | const { service, snapshots } = fixture(); | 25 | const { service, snapshots } = fixture(); |
| 26 | await expect(service.capture(":1.99")).rejects.toThrow("Only T3 Code"); | 26 | await expect(service.capture(":1.99")).rejects.toThrow("Only The Knife"); |
| 27 | expect(snapshots()).toBe(0); | 27 | expect(snapshots()).toBe(0); |
| 28 | expect(await service.capture(":1.23")).toBe("pixels"); | 28 | expect(await service.capture(":1.23")).toBe("pixels"); |
| 29 | }); | 29 | }); |
| ... | @@ -38,7 +38,7 @@ it("rechecks the name owner on each capture", async () => { | ... | @@ -38,7 +38,7 @@ it("rechecks the name owner on each capture", async () => { |
| 38 | const { service } = fixture({ getNameOwner: async () => owner }); | 38 | const { service } = fixture({ getNameOwner: async () => owner }); |
| 39 | expect(await service.capture(owner)).toBe("pixels"); | 39 | expect(await service.capture(owner)).toBe("pixels"); |
| 40 | owner = ":1.24"; | 40 | owner = ":1.24"; |
| 41 | await expect(service.capture(":1.23")).rejects.toThrow("Only T3 Code"); | 41 | await expect(service.capture(":1.23")).rejects.toThrow("Only The Knife"); |
| 42 | }); | 42 | }); |
| 43 | 43 | ||
| 44 | it("rejects locked and non-Wayland sessions without taking a screenshot", async () => { | 44 | 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 ( | ... | @@ -94,7 +94,7 @@ it("rejects concurrent requests and clears busy after a failed capture", async ( |
| 94 | await expect(service.capture(":1.23")).rejects.toThrow("already in progress"); | 94 | await expect(service.capture(":1.23")).rejects.toThrow("already in progress"); |
| 95 | finish(new Error("gone")); | 95 | finish(new Error("gone")); |
| 96 | await expect(first).rejects.toThrow("gone"); | 96 | await expect(first).rejects.toThrow("gone"); |
| 97 | await expect(service.capture(":1.99")).rejects.toThrow("Only T3 Code"); | 97 | await expect(service.capture(":1.99")).rejects.toThrow("Only The Knife"); |
| 98 | }); | 98 | }); |
| 99 | 99 | ||
| 100 | it("prepares focus/effects only after pixels and identity have been captured", async () => { | 100 | it("prepares focus/effects only after pixels and identity have been captured", async () => { |
apps/desktop/gnome-extension/metadata.json+2-2| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | { | 1 | { |
| 2 | "uuid": "snap-shot@t3.codes", | 2 | "uuid": "snap-shot@t3.codes", |
| 3 | "name": "T3 Code SnapShots", | 3 | "name": "The Knife SnapShots", |
| 4 | "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.", | 4 | "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.", |
| 5 | "shell-version": ["45", "46", "47", "48", "49", "50"], | 5 | "shell-version": ["45", "46", "47", "48", "49", "50"], |
| 6 | "version": 2, | 6 | "version": 2, |
| 7 | "url": "https://github.com/pingdotgg/t3code" | 7 | "url": "https://github.com/pingdotgg/t3code" |
apps/desktop/package.json+1-1| ... | @@ -42,5 +42,5 @@ | ... | @@ -42,5 +42,5 @@ |
| 42 | "tailwindcss": "^4.0.0", | 42 | "tailwindcss": "^4.0.0", |
| 43 | "vite-plus": "catalog:" | 43 | "vite-plus": "catalog:" |
| 44 | }, | 44 | }, |
| 45 | "productName": "T3 Code (Alpha)" | 45 | "productName": "The Knife" |
| 46 | } | 46 | } |
apps/desktop/resources/dmg/dmg-background-latest.svg+1-1| ... | @@ -20,7 +20,7 @@ | ... | @@ -20,7 +20,7 @@ |
| 20 | <svg x="32" y="23" width="30" height="18" viewBox="15.5309 37 94.3941 56.96" fill="#303034"> | 20 | <svg x="32" y="23" width="30" height="18" viewBox="15.5309 37 94.3941 56.96" fill="#303034"> |
| 21 | <path d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z"/> | 21 | <path d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z"/> |
| 22 | </svg> | 22 | </svg> |
| 23 | <text x="320" y="104" fill="#303034" font-family="Helvetica" font-size="23" font-weight="700" text-anchor="middle">Drag T3 Code into Applications.</text> | 23 | <text x="320" y="104" fill="#303034" font-family="Helvetica" font-size="23" font-weight="700" text-anchor="middle">Drag The Knife into Applications.</text> |
| 24 | <path d="M270 204Q320 179 370 204M358 190L370 204L351 209" stroke="#505057" stroke-width="2.2" fill="none" opacity="1" stroke-linecap="round" stroke-linejoin="round"/> | 24 | <path d="M270 204Q320 179 370 204M358 190L370 204L351 209" stroke="#505057" stroke-width="2.2" fill="none" opacity="1" stroke-linecap="round" stroke-linejoin="round"/> |
| 25 | <text x="320" y="367" fill="#626269" font-family="Helvetica" font-size="12" font-weight="400" text-anchor="middle">Once copied, open it from Applications.</text> | 25 | <text x="320" y="367" fill="#626269" font-family="Helvetica" font-size="12" font-weight="400" text-anchor="middle">Once copied, open it from Applications.</text> |
| 26 | </svg> | 26 | </svg> |
apps/desktop/resources/dmg/dmg-background-nightly.svg+1-1| ... | @@ -27,7 +27,7 @@ | ... | @@ -27,7 +27,7 @@ |
| 27 | <path d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z"/> | 27 | <path d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z"/> |
| 28 | </svg> | 28 | </svg> |
| 29 | <text x="608" y="38" fill="#C3C8E5" font-family="Helvetica" font-size="9" font-weight="400" text-anchor="end">NIGHTLY</text> | 29 | <text x="608" y="38" fill="#C3C8E5" font-family="Helvetica" font-size="9" font-weight="400" text-anchor="end">NIGHTLY</text> |
| 30 | <text x="320" y="104" fill="#edf0ff" font-family="Helvetica" font-size="23" font-weight="700" text-anchor="middle">Drag T3 Code into Applications.</text> | 30 | <text x="320" y="104" fill="#edf0ff" font-family="Helvetica" font-size="23" font-weight="700" text-anchor="middle">Drag The Knife into Applications.</text> |
| 31 | <path d="M270 204Q320 179 370 204M358 190L370 204L351 209" stroke="#ECEDFF" stroke-width="2.2" fill="none" opacity="1" stroke-linecap="round" stroke-linejoin="round"/> | 31 | <path d="M270 204Q320 179 370 204M358 190L370 204L351 209" stroke="#ECEDFF" stroke-width="2.2" fill="none" opacity="1" stroke-linecap="round" stroke-linejoin="round"/> |
| 32 | <text x="320" y="367" fill="#D0CFE6" font-family="Helvetica" font-size="12" font-weight="400" text-anchor="middle">Once copied, open it from Applications.</text> | 32 | <text x="320" y="367" fill="#D0CFE6" font-family="Helvetica" font-size="12" font-weight="400" text-anchor="middle">Once copied, open it from Applications.</text> |
| 33 | </svg> | 33 | </svg> |
apps/desktop/resources/linux/com.t3tools.t3code.metainfo.xml+2-2| ... | @@ -3,14 +3,14 @@ | ... | @@ -3,14 +3,14 @@ |
| 3 | <id>com.t3tools.t3code</id> | 3 | <id>com.t3tools.t3code</id> |
| 4 | <metadata_license>CC0-1.0</metadata_license> | 4 | <metadata_license>CC0-1.0</metadata_license> |
| 5 | <project_license>MIT</project_license> | 5 | <project_license>MIT</project_license> |
| 6 | <name>T3 Code</name> | 6 | <name>The Knife</name> |
| 7 | <summary>Desktop GUI for coding agents</summary> | 7 | <summary>Desktop GUI for coding agents</summary> |
| 8 | <developer id="com.t3tools"> | 8 | <developer id="com.t3tools"> |
| 9 | <name>T3 Tools Inc.</name> | 9 | <name>T3 Tools Inc.</name> |
| 10 | </developer> | 10 | </developer> |
| 11 | <description> | 11 | <description> |
| 12 | <p> | 12 | <p> |
| 13 | T3 Code is an open-source desktop app for coding agents. Work with your | 13 | The Knife is an open-source desktop app for coding agents. Work with your |
| 14 | existing agent subscriptions, review code changes, and run commands in | 14 | existing agent subscriptions, review code changes, and run commands in |
| 15 | your projects. | 15 | your projects. |
| 16 | </p> | 16 | </p> |
apps/desktop/scripts/electron-launcher.mjs+9-18| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | // This file mostly exists because we want dev mode to say "T3 Code (Dev)" instead of "electron" | 1 | // This file mostly exists because we want dev mode to say "The Knife (Dev)" instead of "electron" |
| 2 | 2 | ||
| 3 | import * as NodeChildProcess from "node:child_process"; | 3 | import * as NodeChildProcess from "node:child_process"; |
| 4 | import * as NodeFS from "node:fs"; | 4 | import * as NodeFS from "node:fs"; |
| ... | @@ -15,19 +15,13 @@ const repoRoot = NodePath.resolve(desktopDir, "..", ".."); | ... | @@ -15,19 +15,13 @@ const repoRoot = NodePath.resolve(desktopDir, "..", ".."); |
| 15 | const devBundleIdSuffix = NodePath.basename(repoRoot) | 15 | const devBundleIdSuffix = NodePath.basename(repoRoot) |
| 16 | .toLowerCase() | 16 | .toLowerCase() |
| 17 | .replaceAll(/[^a-z0-9]+/g, ""); | 17 | .replaceAll(/[^a-z0-9]+/g, ""); |
| 18 | const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)"; | 18 | const APP_DISPLAY_NAME = isDevelopment ? "The Knife (Dev)" : "The Knife"; |
| 19 | const APP_BUNDLE_ID = isDevelopment | 19 | const APP_BUNDLE_ID = isDevelopment |
| 20 | ? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}` | 20 | ? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}` |
| 21 | : "com.t3tools.t3code"; | 21 | : "com.t3tools.t3code"; |
| 22 | const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"]; | 22 | const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"]; |
| 23 | const LAUNCHER_VERSION = 20; | 23 | const LAUNCHER_VERSION = 20; |
| 24 | const developmentMacIconPngPath = NodePath.join( | 24 | const macIconPngPath = NodePath.join(repoRoot, "assets", "knife", "macos-1024.png"); |
| 25 | repoRoot, | ||
| 26 | "assets", | ||
| 27 | "dev", | ||
| 28 | "blueprint-macos-1024.png", | ||
| 29 | ); | ||
| 30 | const productionMacIconPngPath = NodePath.join(repoRoot, "assets", "prod", "black-macos-1024.png"); | ||
| 31 | // oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher script has no Effect runtime. | 25 | // oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone launcher script has no Effect runtime. |
| 32 | const hostPlatform = NodeOS.platform(); | 26 | const hostPlatform = NodeOS.platform(); |
| 33 | 27 | ||
| ... | @@ -203,13 +197,10 @@ function registerMacLauncherBundle(appBundlePath) { | ... | @@ -203,13 +197,10 @@ function registerMacLauncherBundle(appBundlePath) { |
| 203 | } | 197 | } |
| 204 | 198 | ||
| 205 | // Bundle-internal paths are macOS paths whatever host builds them. | 199 | // Bundle-internal paths are macOS paths whatever host builds them. |
| 206 | export function resolveMacLauncherIconPaths(runtimeDir, development = isDevelopment) { | 200 | export function resolveMacLauncherIconPaths(runtimeDir) { |
| 207 | return { | 201 | return { |
| 208 | sourceIconPath: development ? developmentMacIconPngPath : productionMacIconPngPath, | 202 | sourceIconPath: macIconPngPath, |
| 209 | generatedIconPath: NodePath.posix.join( | 203 | generatedIconPath: NodePath.posix.join(runtimeDir, "icon.icns"), |
| 210 | runtimeDir, | ||
| 211 | development ? "icon-dev.icns" : "icon-prod.icns", | ||
| 212 | ), | ||
| 213 | }; | 204 | }; |
| 214 | } | 205 | } |
| 215 | 206 | ||
| ... | @@ -270,8 +261,8 @@ export function resolveMacBundleInfoPlistStrings(executableName) { | ... | @@ -270,8 +261,8 @@ export function resolveMacBundleInfoPlistStrings(executableName) { |
| 270 | CFBundleExecutable: executableName, | 261 | CFBundleExecutable: executableName, |
| 271 | CFBundleIconFile: "icon.icns", | 262 | CFBundleIconFile: "icon.icns", |
| 272 | NSScreenCaptureUsageDescription: | 263 | NSScreenCaptureUsageDescription: |
| 273 | "T3 Code captures the active window when you use the snapshot shortcut.", | 264 | "The Knife captures the active window when you use the snapshot shortcut.", |
| 274 | NSDocumentsFolderUsageDescription: "T3 Code reads project files you open in the desktop app.", | 265 | NSDocumentsFolderUsageDescription: "The Knife reads project files you open in the desktop app.", |
| 275 | }; | 266 | }; |
| 276 | } | 267 | } |
| 277 | 268 | ||
| ... | @@ -404,7 +395,7 @@ function buildMacLauncher(electronBinaryPath) { | ... | @@ -404,7 +395,7 @@ function buildMacLauncher(electronBinaryPath) { |
| 404 | if (isDevelopment) { | 395 | if (isDevelopment) { |
| 405 | // Keep Electron's native executable inside the branded bundle. Launching the | 396 | // Keep Electron's native executable inside the branded bundle. Launching the |
| 406 | // node_modules copy makes macOS associate the process (and Dock label) with | 397 | // node_modules copy makes macOS associate the process (and Dock label) with |
| 407 | // Electron.app even though this bundle's Info.plist has the T3 Code name. | 398 | // Electron.app even though this bundle's Info.plist names The Knife. |
| 408 | // Its conventional executable name also keeps Electron's default-app runtime | 399 | // Its conventional executable name also keeps Electron's default-app runtime |
| 409 | // in development mode instead of making app.isPackaged report true. | 400 | // in development mode instead of making app.isPackaged report true. |
| 410 | writeDevelopmentEnvironmentScript(); | 401 | writeDevelopmentEnvironmentScript(); |
apps/desktop/scripts/electron-launcher.test.mjs+15-20| ... | @@ -76,18 +76,18 @@ describe("electron development launcher", () => { | ... | @@ -76,18 +76,18 @@ describe("electron development launcher", () => { |
| 76 | 76 | ||
| 77 | it("keeps the native Electron executable name inside the branded macOS bundle", () => { | 77 | it("keeps the native Electron executable name inside the branded macOS bundle", () => { |
| 78 | const paths = resolveMacLauncherPaths( | 78 | const paths = resolveMacLauncherPaths( |
| 79 | "/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app", | 79 | "/repo/apps/desktop/.electron-runtime/The Knife (Dev).app", |
| 80 | "T3 Code (Dev)", | 80 | "The Knife (Dev)", |
| 81 | ); | 81 | ); |
| 82 | 82 | ||
| 83 | assert.equal(paths.launcherExecutableName, "T3 Code (Dev) Launcher"); | 83 | assert.equal(paths.launcherExecutableName, "The Knife (Dev) Launcher"); |
| 84 | assert.equal( | 84 | assert.equal( |
| 85 | paths.launcherBinaryPath, | 85 | paths.launcherBinaryPath, |
| 86 | "/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/T3 Code (Dev) Launcher", | 86 | "/repo/apps/desktop/.electron-runtime/The Knife (Dev).app/Contents/MacOS/The Knife (Dev) Launcher", |
| 87 | ); | 87 | ); |
| 88 | assert.equal( | 88 | assert.equal( |
| 89 | paths.runtimeElectronBinaryPath, | 89 | paths.runtimeElectronBinaryPath, |
| 90 | "/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/Electron", | 90 | "/repo/apps/desktop/.electron-runtime/The Knife (Dev).app/Contents/MacOS/Electron", |
| 91 | ); | 91 | ); |
| 92 | 92 | ||
| 93 | const script = makeDevelopmentLauncherScript({ | 93 | const script = makeDevelopmentLauncherScript({ |
| ... | @@ -98,32 +98,32 @@ describe("electron development launcher", () => { | ... | @@ -98,32 +98,32 @@ describe("electron development launcher", () => { |
| 98 | }); | 98 | }); |
| 99 | assert.include( | 99 | assert.include( |
| 100 | script, | 100 | script, |
| 101 | "exec '/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/Electron'", | 101 | "exec '/repo/apps/desktop/.electron-runtime/The Knife (Dev).app/Contents/MacOS/Electron'", |
| 102 | ); | 102 | ); |
| 103 | assert.notInclude(script, "node_modules/electron"); | 103 | assert.notInclude(script, "node_modules/electron"); |
| 104 | }); | 104 | }); |
| 105 | 105 | ||
| 106 | it("declares why the macOS app needs protected access", () => { | 106 | it("declares why the macOS app needs protected access", () => { |
| 107 | const values = resolveMacBundleInfoPlistStrings("T3 Code (Dev) Launcher"); | 107 | const values = resolveMacBundleInfoPlistStrings("The Knife (Dev) Launcher"); |
| 108 | 108 | ||
| 109 | assert.equal( | 109 | assert.equal( |
| 110 | values.NSScreenCaptureUsageDescription, | 110 | values.NSScreenCaptureUsageDescription, |
| 111 | "T3 Code captures the active window when you use the snapshot shortcut.", | 111 | "The Knife captures the active window when you use the snapshot shortcut.", |
| 112 | ); | 112 | ); |
| 113 | assert.equal( | 113 | assert.equal( |
| 114 | values.NSDocumentsFolderUsageDescription, | 114 | values.NSDocumentsFolderUsageDescription, |
| 115 | "T3 Code reads project files you open in the desktop app.", | 115 | "The Knife reads project files you open in the desktop app.", |
| 116 | ); | 116 | ); |
| 117 | }); | 117 | }); |
| 118 | 118 | ||
| 119 | it("ad-hoc signs the complete development app bundle", () => { | 119 | it("ad-hoc signs the complete development app bundle", () => { |
| 120 | assert.deepEqual(resolveMacCodeSignArguments("/runtime/T3 Code (Dev).app"), [ | 120 | assert.deepEqual(resolveMacCodeSignArguments("/runtime/The Knife (Dev).app"), [ |
| 121 | "--force", | 121 | "--force", |
| 122 | "--deep", | 122 | "--deep", |
| 123 | "--sign", | 123 | "--sign", |
| 124 | "-", | 124 | "-", |
| 125 | "--timestamp=none", | 125 | "--timestamp=none", |
| 126 | "/runtime/T3 Code (Dev).app", | 126 | "/runtime/The Knife (Dev).app", |
| 127 | ]); | 127 | ]); |
| 128 | }); | 128 | }); |
| 129 | 129 | ||
| ... | @@ -141,14 +141,9 @@ describe("electron development launcher", () => { | ... | @@ -141,14 +141,9 @@ describe("electron development launcher", () => { |
| 141 | } | 141 | } |
| 142 | }); | 142 | }); |
| 143 | 143 | ||
| 144 | it("derives launcher icons from canonical development and production assets", () => { | 144 | it("derives launcher icons from the canonical Knife artwork", () => { |
| 145 | const development = resolveMacLauncherIconPaths("/runtime", true); | 145 | const icon = resolveMacLauncherIconPaths("/runtime"); |
| 146 | const production = resolveMacLauncherIconPaths("/runtime", false); | 146 | assert.match(icon.sourceIconPath, /assets[\\/]knife[\\/]macos-1024\.png$/); |
| 147 | 147 | assert.equal(icon.generatedIconPath, "/runtime/icon.icns"); | |
| 148 | // The source icons are real repo paths, joined for the host. | ||
| 149 | assert.match(development.sourceIconPath, /assets[\\/]dev[\\/]blueprint-macos-1024\.png$/); | ||
| 150 | assert.equal(development.generatedIconPath, "/runtime/icon-dev.icns"); | ||
| 151 | assert.match(production.sourceIconPath, /assets[\\/]prod[\\/]black-macos-1024\.png$/); | ||
| 152 | assert.equal(production.generatedIconPath, "/runtime/icon-prod.icns"); | ||
| 153 | }); | 148 | }); |
| 154 | }); | 149 | }); |
apps/desktop/scripts/verify-preload-bundle.mjs+1| ... | @@ -108,6 +108,7 @@ const executeBundle = (source, sandboxModules) => { | ... | @@ -108,6 +108,7 @@ const executeBundle = (source, sandboxModules) => { |
| 108 | { | 108 | { |
| 109 | process: sandboxProcess, | 109 | process: sandboxProcess, |
| 110 | require: requireSandboxModule, | 110 | require: requireSandboxModule, |
| 111 | window: { addEventListener: () => undefined }, | ||
| 111 | }, | 112 | }, |
| 112 | { | 113 | { |
| 113 | filename: "desktop-preload.cjs", | 114 | filename: "desktop-preload.cjs", |
apps/desktop/src/app/DesktopApp.ts+1-1| ... | @@ -136,7 +136,7 @@ const handleFatalStartupError = Effect.fn("desktop.startup.handleFatalStartupErr | ... | @@ -136,7 +136,7 @@ const handleFatalStartupError = Effect.fn("desktop.startup.handleFatalStartupErr |
| 136 | const wasQuitting = yield* Ref.getAndSet(state.quitting, true); | 136 | const wasQuitting = yield* Ref.getAndSet(state.quitting, true); |
| 137 | if (!wasQuitting) { | 137 | if (!wasQuitting) { |
| 138 | yield* electronDialog.showErrorBox( | 138 | yield* electronDialog.showErrorBox( |
| 139 | "T3 Code failed to start", | 139 | "The Knife failed to start", |
| 140 | `Stage: ${stage}\n${message}${detail}`, | 140 | `Stage: ${stage}\n${message}${detail}`, |
| 141 | ); | 141 | ); |
| 142 | } | 142 | } |
apps/desktop/src/app/DesktopAppActivation.ts+1-1| ... | @@ -171,7 +171,7 @@ export async function startDesktopAppControlServer(input: { | ... | @@ -171,7 +171,7 @@ export async function startDesktopAppControlServer(input: { |
| 171 | activeRequestId = parsed.requestId; | 171 | activeRequestId = parsed.requestId; |
| 172 | void input.handle(parsed).then(finish, () => { | 172 | void input.handle(parsed).then(finish, () => { |
| 173 | finish( | 173 | finish( |
| 174 | invalidResponse(parsed.requestId, "T3 Code could not process the desktop app request."), | 174 | invalidResponse(parsed.requestId, "The Knife could not process the desktop app request."), |
| 175 | ); | 175 | ); |
| 176 | }); | 176 | }); |
| 177 | }); | 177 | }); |
apps/desktop/src/app/DesktopAppActivationBroker.ts+4-4| ... | @@ -45,7 +45,7 @@ export class DesktopAppActivationBroker { | ... | @@ -45,7 +45,7 @@ export class DesktopAppActivationBroker { |
| 45 | request(request: DesktopAppActivationRequest): Promise<DesktopAppActivationResponse> { | 45 | request(request: DesktopAppActivationRequest): Promise<DesktopAppActivationResponse> { |
| 46 | if (this.#closed) { | 46 | if (this.#closed) { |
| 47 | return Promise.resolve( | 47 | return Promise.resolve( |
| 48 | failure(request.requestId, "renderer-unavailable", "T3 Code is shutting down."), | 48 | failure(request.requestId, "renderer-unavailable", "The Knife is shutting down."), |
| 49 | ); | 49 | ); |
| 50 | } | 50 | } |
| 51 | if (this.#pending.has(request.requestId)) { | 51 | if (this.#pending.has(request.requestId)) { |
| ... | @@ -90,7 +90,7 @@ export class DesktopAppActivationBroker { | ... | @@ -90,7 +90,7 @@ export class DesktopAppActivationBroker { |
| 90 | failure( | 90 | failure( |
| 91 | pending.request.requestId, | 91 | pending.request.requestId, |
| 92 | "renderer-unavailable", | 92 | "renderer-unavailable", |
| 93 | "The T3 Code window closed before it opened the project.", | 93 | "The Knife closed before opening the project.", |
| 94 | ), | 94 | ), |
| 95 | ); | 95 | ); |
| 96 | } | 96 | } |
| ... | @@ -103,7 +103,7 @@ export class DesktopAppActivationBroker { | ... | @@ -103,7 +103,7 @@ export class DesktopAppActivationBroker { |
| 103 | 103 | ||
| 104 | cancel(requestId: string): void { | 104 | cancel(requestId: string): void { |
| 105 | this.#settle( | 105 | this.#settle( |
| 106 | failure(requestId, "renderer-unavailable", "The command closed before T3 Code was ready."), | 106 | failure(requestId, "renderer-unavailable", "The command closed before The Knife was ready."), |
| 107 | ); | 107 | ); |
| 108 | } | 108 | } |
| 109 | 109 | ||
| ... | @@ -112,7 +112,7 @@ export class DesktopAppActivationBroker { | ... | @@ -112,7 +112,7 @@ export class DesktopAppActivationBroker { |
| 112 | this.#renderer = null; | 112 | this.#renderer = null; |
| 113 | for (const pending of this.#pending.values()) { | 113 | for (const pending of this.#pending.values()) { |
| 114 | this.#settle( | 114 | this.#settle( |
| 115 | failure(pending.request.requestId, "renderer-unavailable", "T3 Code is shutting down."), | 115 | failure(pending.request.requestId, "renderer-unavailable", "The Knife is shutting down."), |
| 116 | ); | 116 | ); |
| 117 | } | 117 | } |
| 118 | } | 118 | } |
apps/desktop/src/app/DesktopAppIdentity.test.ts+27-21| ... | @@ -218,7 +218,7 @@ describe("DesktopAppIdentity", () => { | ... | @@ -218,7 +218,7 @@ describe("DesktopAppIdentity", () => { |
| 218 | yield* identity.configure; | 218 | yield* identity.configure; |
| 219 | 219 | ||
| 220 | assert.deepEqual(calls.setName, ["T3 Code Alpha"]); | 220 | assert.deepEqual(calls.setName, ["T3 Code Alpha"]); |
| 221 | assert.equal(calls.setAboutPanelOptions[0]?.applicationName, "T3 Code (Alpha)"); | 221 | assert.equal(calls.setAboutPanelOptions[0]?.applicationName, "The Knife"); |
| 222 | assert.equal(calls.setAboutPanelOptions[0]?.applicationVersion, "1.2.3"); | 222 | assert.equal(calls.setAboutPanelOptions[0]?.applicationVersion, "1.2.3"); |
| 223 | assert.equal(calls.setAboutPanelOptions[0]?.version, "0123456789ab"); | 223 | assert.equal(calls.setAboutPanelOptions[0]?.version, "0123456789ab"); |
| 224 | // Packaged: the bundle's own icon stands, so a custom one the user | 224 | // Packaged: the bundle's own icon stands, so a custom one the user |
| ... | @@ -247,28 +247,34 @@ describe("DesktopAppIdentity", () => { | ... | @@ -247,28 +247,34 @@ describe("DesktopAppIdentity", () => { |
| 247 | stage: "Dev", | 247 | stage: "Dev", |
| 248 | environment: { env: { VITE_DEV_SERVER_URL: "http://localhost:5173" } }, | 248 | environment: { env: { VITE_DEV_SERVER_URL: "http://localhost:5173" } }, |
| 249 | }, | 249 | }, |
| 250 | ])("uses a valid native User-Agent product name for $stage", ({ stage, environment }) => { | 250 | ])( |
| 251 | const calls: ElectronAppCalls = { | 251 | "preserves the credential identity and valid native User-Agent for $stage", |
| 252 | setAboutPanelOptions: [], | 252 | ({ stage, environment }) => { |
| 253 | setDockIcon: [], | 253 | const calls: ElectronAppCalls = { |
| 254 | setName: [], | 254 | setAboutPanelOptions: [], |
| 255 | }; | 255 | setDockIcon: [], |
| 256 | setName: [], | ||
| 257 | }; | ||
| 256 | 258 | ||
| 257 | return withIdentity( | 259 | return withIdentity( |
| 258 | Effect.gen(function* () { | 260 | Effect.gen(function* () { |
| 259 | const identity = yield* DesktopAppIdentity.DesktopAppIdentity; | 261 | const identity = yield* DesktopAppIdentity.DesktopAppIdentity; |
| 260 | yield* identity.configure; | 262 | yield* identity.configure; |
| 261 | 263 | ||
| 262 | const runtimeName = calls.setName[0]; | 264 | const runtimeName = calls.setName[0]; |
| 263 | assert.isDefined(runtimeName); | 265 | assert.isDefined(runtimeName); |
| 264 | assert.equal(runtimeName, `T3 Code ${stage}`); | 266 | assert.equal(runtimeName, `T3 Code ${stage}`); |
| 265 | // RFC 9110's token grammar, after Electron removes ASCII spaces. | 267 | // RFC 9110's token grammar, after Electron removes ASCII spaces. |
| 266 | assert.match(runtimeName.replaceAll(" ", ""), /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/); | 268 | assert.match(runtimeName.replaceAll(" ", ""), /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/); |
| 267 | assert.equal(calls.setAboutPanelOptions[0]?.applicationName, `T3 Code (${stage})`); | 269 | assert.equal( |
| 268 | }), | 270 | calls.setAboutPanelOptions[0]?.applicationName, |
| 269 | { calls, environment }, | 271 | stage === "Alpha" ? "The Knife" : `The Knife (${stage})`, |
| 270 | ); | 272 | ); |
| 271 | }); | 273 | }), |
| 274 | { calls, environment }, | ||
| 275 | ); | ||
| 276 | }, | ||
| 277 | ); | ||
| 272 | 278 | ||
| 273 | it.effect("sets the dock icon only when running unpackaged", () => { | 279 | it.effect("sets the dock icon only when running unpackaged", () => { |
| 274 | const calls: ElectronAppCalls = { | 280 | const calls: ElectronAppCalls = { |
apps/desktop/src/app/DesktopAppIdentity.ts+2-6| ... | @@ -91,12 +91,8 @@ export const make = Effect.gen(function* () { | ... | @@ -91,12 +91,8 @@ export const make = Effect.gen(function* () { |
| 91 | 91 | ||
| 92 | const configure = Effect.gen(function* () { | 92 | const configure = Effect.gen(function* () { |
| 93 | const commitHash = yield* resolveAboutCommitHash; | 93 | const commitHash = yield* resolveAboutCommitHash; |
| 94 | // Electron removes spaces from this name to build the native User-Agent | 94 | // Electron's runtime name also identifies the shipped Safe Storage/keyring entry. |
| 95 | // product token, but leaves parentheses intact. Keep the runtime name valid | 95 | yield* electronApp.setName(`T3 Code ${environment.branding.stageLabel}`); |
| 96 | // without rewriting preview sessions (which breaks Turnstile, #7110). | ||
| 97 | yield* electronApp.setName( | ||
| 98 | `${environment.branding.baseName} ${environment.branding.stageLabel}`, | ||
| 99 | ); | ||
| 100 | yield* electronApp.setAboutPanelOptions({ | 96 | yield* electronApp.setAboutPanelOptions({ |
| 101 | applicationName: environment.displayName, | 97 | applicationName: environment.displayName, |
| 102 | applicationVersion: environment.appVersion, | 98 | applicationVersion: environment.appVersion, |
apps/desktop/src/app/DesktopAssets.test.ts+3-3| ... | @@ -50,7 +50,7 @@ describe("DesktopAssets", () => { | ... | @@ -50,7 +50,7 @@ describe("DesktopAssets", () => { |
| 50 | ), | 50 | ), |
| 51 | ); | 51 | ); |
| 52 | const layerFileSystem = FileSystem.layerNoop({ | 52 | const layerFileSystem = FileSystem.layerNoop({ |
| 53 | exists: (path) => Effect.succeed(String(path).includes("/assets/dev/")), | 53 | exists: (path) => Effect.succeed(String(path).includes("/assets/knife/")), |
| 54 | }); | 54 | }); |
| 55 | const assets = yield* DesktopAssets.DesktopAssets.pipe( | 55 | const assets = yield* DesktopAssets.DesktopAssets.pipe( |
| 56 | Effect.provide( | 56 | Effect.provide( |
| ... | @@ -62,8 +62,8 @@ describe("DesktopAssets", () => { | ... | @@ -62,8 +62,8 @@ describe("DesktopAssets", () => { |
| 62 | 62 | ||
| 63 | const icons = yield* assets.iconPaths; | 63 | const icons = yield* assets.iconPaths; |
| 64 | 64 | ||
| 65 | assert.match(Option.getOrThrow(icons.ico), /assets\/dev\/blueprint-windows\.ico$/); | 65 | assert.match(Option.getOrThrow(icons.ico), /assets\/knife\/icon\.ico$/); |
| 66 | assert.match(Option.getOrThrow(icons.png), /assets\/dev\/blueprint-universal-1024\.png$/); | 66 | assert.match(Option.getOrThrow(icons.png), /assets\/knife\/icon-1024\.png$/); |
| 67 | assert.isTrue(Option.isNone(icons.icns)); | 67 | assert.isTrue(Option.isNone(icons.icns)); |
| 68 | }), | 68 | }), |
| 69 | ); | 69 | ); |
apps/desktop/src/app/DesktopAssets.ts+4-19| ... | @@ -61,33 +61,18 @@ const resolveResourcePath = Effect.fn("desktop.assets.resolveResourcePath")(func | ... | @@ -61,33 +61,18 @@ const resolveResourcePath = Effect.fn("desktop.assets.resolveResourcePath")(func |
| 61 | return Option.none<string>(); | 61 | return Option.none<string>(); |
| 62 | }); | 62 | }); |
| 63 | 63 | ||
| 64 | const sourceTreeIconFileNames = { | ||
| 65 | dev: { | ||
| 66 | ico: "blueprint-windows.ico", | ||
| 67 | macPng: "blueprint-macos-1024.png", | ||
| 68 | universalPng: "blueprint-universal-1024.png", | ||
| 69 | }, | ||
| 70 | prod: { | ||
| 71 | ico: "t3-black-windows.ico", | ||
| 72 | macPng: "black-macos-1024.png", | ||
| 73 | universalPng: "black-universal-1024.png", | ||
| 74 | }, | ||
| 75 | } as const; | ||
| 76 | |||
| 77 | function resolveSourceTreeIconPath( | 64 | function resolveSourceTreeIconPath( |
| 78 | environment: DesktopEnvironment.DesktopEnvironment["Service"], | 65 | environment: DesktopEnvironment.DesktopEnvironment["Service"], |
| 79 | ext: keyof DesktopIconPaths, | 66 | ext: keyof DesktopIconPaths, |
| 80 | ): string | undefined { | 67 | ): string | undefined { |
| 81 | if (environment.isPackaged || ext === "icns") return undefined; | 68 | if (environment.isPackaged || ext === "icns") return undefined; |
| 82 | const brand = environment.isDevelopment ? "dev" : "prod"; | ||
| 83 | const fileNames = sourceTreeIconFileNames[brand]; | ||
| 84 | const fileName = | 69 | const fileName = |
| 85 | ext === "ico" | 70 | ext === "ico" |
| 86 | ? fileNames.ico | 71 | ? "icon.ico" |
| 87 | : environment.platform === "darwin" | 72 | : environment.platform === "darwin" |
| 88 | ? fileNames.macPng | 73 | ? "macos-1024.png" |
| 89 | : fileNames.universalPng; | 74 | : "icon-1024.png"; |
| 90 | return environment.path.join(environment.rootDir, "assets", brand, fileName); | 75 | return environment.path.join(environment.rootDir, "assets", "knife", fileName); |
| 91 | } | 76 | } |
| 92 | 77 | ||
| 93 | const resolveIconPath = Effect.fn("desktop.assets.resolveIconPath")(function* ( | 78 | const resolveIconPath = Effect.fn("desktop.assets.resolveIconPath")(function* ( |
apps/desktop/src/app/DesktopCliShim.ts+1-1| ... | @@ -24,7 +24,7 @@ const shellWord = (value: string) => `'${value.replaceAll("'", `'"'"'`)}'`; | ... | @@ -24,7 +24,7 @@ const shellWord = (value: string) => `'${value.replaceAll("'", `'"'"'`)}'`; |
| 24 | const cmdText = (value: string) => value.replaceAll("%", "%%"); | 24 | const cmdText = (value: string) => value.replaceAll("%", "%%"); |
| 25 | const cmdWord = (value: string) => `"${cmdText(value)}"`; | 25 | const cmdWord = (value: string) => `"${cmdText(value)}"`; |
| 26 | 26 | ||
| 27 | const MOVED = "T3 Code has moved or been removed. Open the app once to update this command."; | 27 | const MOVED = "The Knife has moved or been removed. Open the app once to update this command."; |
| 28 | 28 | ||
| 29 | export type CliShimTarget = | 29 | export type CliShimTarget = |
| 30 | | { readonly kind: "appimage"; readonly appImage: string; readonly executableName: string } | 30 | | { readonly kind: "appimage"; readonly appImage: string; readonly executableName: string } |
apps/desktop/src/app/DesktopEnvironment.ts+2-2| ... | @@ -93,7 +93,7 @@ export class DesktopEnvironment extends Context.Service< | ... | @@ -93,7 +93,7 @@ export class DesktopEnvironment extends Context.Service< |
| 93 | } | 93 | } |
| 94 | >()("@t3tools/desktop/app/DesktopEnvironment") {} | 94 | >()("@t3tools/desktop/app/DesktopEnvironment") {} |
| 95 | 95 | ||
| 96 | const APP_BASE_NAME = "T3 Code"; | 96 | const APP_BASE_NAME = "The Knife"; |
| 97 | 97 | ||
| 98 | function resolveDesktopAppStageLabel(input: { | 98 | function resolveDesktopAppStageLabel(input: { |
| 99 | readonly isDevelopment: boolean; | 99 | readonly isDevelopment: boolean; |
| ... | @@ -114,7 +114,7 @@ export function resolveDesktopAppBranding(input: { | ... | @@ -114,7 +114,7 @@ export function resolveDesktopAppBranding(input: { |
| 114 | return { | 114 | return { |
| 115 | baseName: APP_BASE_NAME, | 115 | baseName: APP_BASE_NAME, |
| 116 | stageLabel, | 116 | stageLabel, |
| 117 | displayName: `${APP_BASE_NAME} (${stageLabel})`, | 117 | displayName: stageLabel === "Alpha" ? APP_BASE_NAME : `${APP_BASE_NAME} (${stageLabel})`, |
| 118 | }; | 118 | }; |
| 119 | } | 119 | } |
| 120 | 120 |
apps/desktop/src/app/DesktopWebLinks.ts+2-2| ... | @@ -6,14 +6,14 @@ import * as Option from "effect/Option"; | ... | @@ -6,14 +6,14 @@ import * as Option from "effect/Option"; |
| 6 | import * as ElectronWindow from "../electron/ElectronWindow.ts"; | 6 | import * as ElectronWindow from "../electron/ElectronWindow.ts"; |
| 7 | import { WEB_LINK_OPEN_CHANNEL } from "../ipc/channels.ts"; | 7 | import { WEB_LINK_OPEN_CHANNEL } from "../ipc/channels.ts"; |
| 8 | 8 | ||
| 9 | /** An http(s) link the operating system handed to T3 Code as the default browser. */ | 9 | /** An http(s) link the operating system handed to The Knife as the default browser. */ |
| 10 | export const isWebLink = (value: string) => { | 10 | export const isWebLink = (value: string) => { |
| 11 | if (!URL.canParse(value)) return false; | 11 | if (!URL.canParse(value)) return false; |
| 12 | const { protocol } = new URL(value); | 12 | const { protocol } = new URL(value); |
| 13 | return protocol === "http:" || protocol === "https:"; | 13 | return protocol === "http:" || protocol === "https:"; |
| 14 | }; | 14 | }; |
| 15 | 15 | ||
| 16 | /** An HTML file the operating system asked T3 Code to open, as browsers are asked to. */ | 16 | /** An HTML file the operating system asked The Knife to open, as browsers are asked to. */ |
| 17 | export const isWebPageFile = (path: string) => /\.x?html?$/i.test(path); | 17 | export const isWebPageFile = (path: string) => /\.x?html?$/i.test(path); |
| 18 | 18 | ||
| 19 | /** | 19 | /** |
apps/desktop/src/backend/DesktopBackendPool.ts+3-3| ... | @@ -247,7 +247,7 @@ export const layer = Layer.effect( | ... | @@ -247,7 +247,7 @@ export const layer = Layer.effect( |
| 247 | ); | 247 | ); |
| 248 | yield* electronDialog.showErrorBox( | 248 | yield* electronDialog.showErrorBox( |
| 249 | "WSL backend is still unavailable", | 249 | "WSL backend is still unavailable", |
| 250 | `${reason}\n\nT3 Code will use the Windows backend for this launch and retry WSL the next time the app starts.`, | 250 | `${reason}\n\nThe Knife will use the Windows backend for this launch and retry WSL the next time the app starts.`, |
| 251 | ); | 251 | ); |
| 252 | yield* appSettings.applyWslWindowsFallbackInMemory; | 252 | yield* appSettings.applyWslWindowsFallbackInMemory; |
| 253 | return true; | 253 | return true; |
| ... | @@ -258,7 +258,7 @@ export const layer = Layer.effect( | ... | @@ -258,7 +258,7 @@ export const layer = Layer.effect( |
| 258 | }); | 258 | }); |
| 259 | yield* electronDialog.showErrorBox( | 259 | yield* electronDialog.showErrorBox( |
| 260 | "WSL backend couldn't start", | 260 | "WSL backend couldn't start", |
| 261 | `${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.`, | 261 | `${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.`, |
| 262 | ); | 262 | ); |
| 263 | // Fully disable the WSL backend — both flags, matching the "Switch to | 263 | // Fully disable the WSL backend — both flags, matching the "Switch to |
| 264 | // Windows" recovery path — so the manager's next restart re-resolves the | 264 | // Windows" recovery path — so the manager's next restart re-resolves the |
| ... | @@ -309,7 +309,7 @@ export const layer = Layer.effect( | ... | @@ -309,7 +309,7 @@ export const layer = Layer.effect( |
| 309 | electronDialog | 309 | electronDialog |
| 310 | .showErrorBox( | 310 | .showErrorBox( |
| 311 | "This T3 home is unavailable", | 311 | "This T3 home is unavailable", |
| 312 | "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.", | 312 | "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.", |
| 313 | ) | 313 | ) |
| 314 | .pipe(Effect.andThen(electronApp.quit)), | 314 | .pipe(Effect.andThen(electronApp.quit)), |
| 315 | }); | 315 | }); |
apps/desktop/src/backend/DesktopLocalEnvironmentAuth.ts+1-1| ... | @@ -104,7 +104,7 @@ export const make = Effect.gen(function* () { | ... | @@ -104,7 +104,7 @@ export const make = Effect.gen(function* () { |
| 104 | httpBaseUrl: config.httpBaseUrl.href, | 104 | httpBaseUrl: config.httpBaseUrl.href, |
| 105 | credential, | 105 | credential, |
| 106 | clientMetadata: { | 106 | clientMetadata: { |
| 107 | label: "T3 Code Desktop", | 107 | label: "The Knife Desktop", |
| 108 | deviceType: "desktop", | 108 | deviceType: "desktop", |
| 109 | }, | 109 | }, |
| 110 | }).pipe( | 110 | }).pipe( |
apps/desktop/src/electron/WindowsForeground.ts+1-1| ... | @@ -216,7 +216,7 @@ export function loadWindowsForegroundApi(): Promise<WindowsForegroundApi> { | ... | @@ -216,7 +216,7 @@ export function loadWindowsForegroundApi(): Promise<WindowsForegroundApi> { |
| 216 | export async function activateWindowsForeground(handleBuffer: Buffer): Promise<void> { | 216 | export async function activateWindowsForeground(handleBuffer: Buffer): Promise<void> { |
| 217 | const api = await loadWindowsForegroundApi(); | 217 | const api = await loadWindowsForegroundApi(); |
| 218 | if (activateWindowsForegroundWithApi(handleBuffer, api)) return; | 218 | if (activateWindowsForegroundWithApi(handleBuffer, api)) return; |
| 219 | throw new Error("Windows refused to activate the T3 Code window."); | 219 | throw new Error("Windows refused to activate The Knife."); |
| 220 | } | 220 | } |
| 221 | 221 | ||
| 222 | export async function isWindowsShellHostedForeground(): Promise<boolean> { | 222 | export async function isWindowsShellHostedForeground(): Promise<boolean> { |
apps/desktop/src/permissions/MacPermissionHelper.test.ts+8-8| ... | @@ -61,7 +61,7 @@ vi.mock("electron", async () => { | ... | @@ -61,7 +61,7 @@ vi.mock("electron", async () => { |
| 61 | } | 61 | } |
| 62 | return { | 62 | return { |
| 63 | app: { | 63 | app: { |
| 64 | getPath: () => "/Applications/T3 Code (Nightly).app/Contents/MacOS/T3 Code", | 64 | getPath: () => "/Applications/The Knife (Nightly).app/Contents/MacOS/The Knife", |
| 65 | }, | 65 | }, |
| 66 | nativeImage: { createFromPath: mocks.createFromPath }, | 66 | nativeImage: { createFromPath: mocks.createFromPath }, |
| 67 | BrowserWindow: class extends MockWindow {}, | 67 | BrowserWindow: class extends MockWindow {}, |
| ... | @@ -124,11 +124,11 @@ function send(action: string, trusted = true) { | ... | @@ -124,11 +124,11 @@ function send(action: string, trusted = true) { |
| 124 | 124 | ||
| 125 | describe("macAppBundlePath", () => { | 125 | describe("macAppBundlePath", () => { |
| 126 | it("resolves bundles with spaces and refuses non-bundle executables", () => { | 126 | it("resolves bundles with spaces and refuses non-bundle executables", () => { |
| 127 | expect(macAppBundlePath("/Applications/T3 Code.app/Contents/MacOS/T3 Code")).toBe( | 127 | expect(macAppBundlePath("/Applications/The Knife.app/Contents/MacOS/The Knife")).toBe( |
| 128 | "/Applications/T3 Code.app", | 128 | "/Applications/The Knife.app", |
| 129 | ); | 129 | ); |
| 130 | expect(macAppBundlePath("/usr/local/bin/electron")).toBeUndefined(); | 130 | expect(macAppBundlePath("/usr/local/bin/electron")).toBeUndefined(); |
| 131 | expect(macAppBundlePath("/Applications/T3 Code.app/other/MacOS/T3 Code")).toBeUndefined(); | 131 | expect(macAppBundlePath("/Applications/The Knife.app/other/MacOS/The Knife")).toBeUndefined(); |
| 132 | }); | 132 | }); |
| 133 | }); | 133 | }); |
| 134 | it("drags the running app bundle only for the helper's own renderer", async () => { | 134 | 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 () = | ... | @@ -138,11 +138,11 @@ it("drags the running app bundle only for the helper's own renderer", async () = |
| 138 | send("drag"); | 138 | send("drag"); |
| 139 | expect(mocks.createFromPath).toHaveBeenCalledWith("/bundle/prod-resources/icon.png"); | 139 | expect(mocks.createFromPath).toHaveBeenCalledWith("/bundle/prod-resources/icon.png"); |
| 140 | expect(mocks.startDrag).toHaveBeenCalledWith({ | 140 | expect(mocks.startDrag).toHaveBeenCalledWith({ |
| 141 | file: "/Applications/T3 Code (Nightly).app", | 141 | file: "/Applications/The Knife (Nightly).app", |
| 142 | icon: mocks.createFromPath.mock.results[0]!.value.resize(), | 142 | icon: mocks.createFromPath.mock.results[0]!.value.resize(), |
| 143 | }); | 143 | }); |
| 144 | send("finder"); | 144 | send("finder"); |
| 145 | expect(mocks.showItemInFolder).toHaveBeenCalledWith("/Applications/T3 Code (Nightly).app"); | 145 | expect(mocks.showItemInFolder).toHaveBeenCalledWith("/Applications/The Knife (Nightly).app"); |
| 146 | }); | 146 | }); |
| 147 | it("rechecks permissions and releases resources when granted", async () => { | 147 | it("rechecks permissions and releases resources when granted", async () => { |
| 148 | await open(); | 148 | await open(); |
| ... | @@ -168,7 +168,7 @@ it("does not open for a permission already granted", async () => { | ... | @@ -168,7 +168,7 @@ it("does not open for a permission already granted", async () => { |
| 168 | }); | 168 | }); |
| 169 | it("does not show a helper with a missing packaged icon", async () => { | 169 | it("does not show a helper with a missing packaged icon", async () => { |
| 170 | mocks.createFromPath.mockReturnValueOnce({ isEmpty: () => true }); | 170 | mocks.createFromPath.mockReturnValueOnce({ isEmpty: () => true }); |
| 171 | await expect(open()).rejects.toThrow("packaged T3 Code icon is missing"); | 171 | await expect(open()).rejects.toThrow("packaged The Knife icon is missing"); |
| 172 | expect(windows).toHaveLength(0); | 172 | expect(windows).toHaveLength(0); |
| 173 | }); | 173 | }); |
| 174 | it("cleans up when the helper page fails to load", async () => { | 174 | 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 () => { | ... | @@ -184,7 +184,7 @@ it("offers the Finder fallback when native dragging fails", async () => { |
| 184 | throw new Error("drag failed"); | 184 | throw new Error("drag failed"); |
| 185 | }); | 185 | }); |
| 186 | send("drag"); | 186 | send("drag"); |
| 187 | expect(mocks.showItemInFolder).toHaveBeenCalledWith("/Applications/T3 Code (Nightly).app"); | 187 | expect(mocks.showItemInFolder).toHaveBeenCalledWith("/Applications/The Knife (Nightly).app"); |
| 188 | expect(windows[0]!.destroyed).toBe(false); | 188 | expect(windows[0]!.destroyed).toBe(false); |
| 189 | }); | 189 | }); |
| 190 | 190 |
apps/desktop/src/permissions/MacPermissionHelper.ts+3-3| ... | @@ -60,8 +60,8 @@ button:focus-visible { outline: 2px solid #007aff; outline-offset: -2px; } | ... | @@ -60,8 +60,8 @@ button:focus-visible { outline: 2px solid #007aff; outline-offset: -2px; } |
| 60 | img { width: 32px; height: 32px; pointer-events: none; } | 60 | img { width: 32px; height: 32px; pointer-events: none; } |
| 61 | </style></head><body><main id="panel"> | 61 | </style></head><body><main id="panel"> |
| 62 | <button id="close" aria-label="Close permission helper">×</button> | 62 | <button id="close" aria-label="Close permission helper">×</button> |
| 63 | <header>↑ Drag T3 Code into the list above</header> | 63 | <header>↑ Drag The Knife into the list above</header> |
| 64 | <button id="app" draggable="true" aria-label="Drag T3 Code to System Settings, or click to reveal in Finder"><img src="${escapeHtml(icon)}" alt="" draggable="false">T3 Code</button> | 64 | <button id="app" draggable="true" aria-label="Drag The Knife to System Settings, or click to reveal in Finder"><img src="${escapeHtml(icon)}" alt="" draggable="false">The Knife</button> |
| 65 | </main></body></html>`; | 65 | </main></body></html>`; |
| 66 | } | 66 | } |
| 67 | 67 | ||
| ... | @@ -95,7 +95,7 @@ export class MacPermissionHelper { | ... | @@ -95,7 +95,7 @@ export class MacPermissionHelper { |
| 95 | const appIcon = iconPaths | 95 | const appIcon = iconPaths |
| 96 | .map((iconPath) => Electron.nativeImage.createFromPath(iconPath)) | 96 | .map((iconPath) => Electron.nativeImage.createFromPath(iconPath)) |
| 97 | .find((image) => !image.isEmpty()); | 97 | .find((image) => !image.isEmpty()); |
| 98 | if (!appIcon) throw new Error("The packaged T3 Code icon is missing."); | 98 | if (!appIcon) throw new Error("The packaged The Knife icon is missing."); |
| 99 | const icon = appIcon.resize({ width: 64, height: 64 }); | 99 | const icon = appIcon.resize({ width: 64, height: 64 }); |
| 100 | const window = new Electron.BrowserWindow({ | 100 | const window = new Electron.BrowserWindow({ |
| 101 | width: 560, | 101 | width: 560, |
apps/desktop/src/snapShot/DesktopSnapShot.test.ts+3-3| ... | @@ -1630,7 +1630,7 @@ it.effect( | ... | @@ -1630,7 +1630,7 @@ it.effect( |
| 1630 | const warning = logs.find( | 1630 | const warning = logs.find( |
| 1631 | (message) => | 1631 | (message) => |
| 1632 | Array.isArray(message) && | 1632 | Array.isArray(message) && |
| 1633 | message[0] === "The compositor could not activate T3 Code after the snapshot", | 1633 | message[0] === "The compositor could not activate The Knife after the snapshot", |
| 1634 | ); | 1634 | ); |
| 1635 | assert.strictEqual(Array.isArray(warning) ? warning[1] : undefined, activationFailure); | 1635 | assert.strictEqual(Array.isArray(warning) ? warning[1] : undefined, activationFailure); |
| 1636 | const pending = yield* decodePendingMetadata(saved); | 1636 | const pending = yield* decodePendingMetadata(saved); |
| ... | @@ -3161,7 +3161,7 @@ it.effect("flags revoked macOS permissions on read and re-registers once they re | ... | @@ -3161,7 +3161,7 @@ it.effect("flags revoked macOS permissions on read and re-registers once they re |
| 3161 | const revoked = yield* service.state; | 3161 | const revoked = yield* service.state; |
| 3162 | assert.equal( | 3162 | assert.equal( |
| 3163 | revoked.message, | 3163 | revoked.message, |
| 3164 | "Allow Screen Recording in System Settings, then restart T3 Code.", | 3164 | "Allow Screen Recording in System Settings, then restart The Knife.", |
| 3165 | ); | 3165 | ); |
| 3166 | assert.deepEqual(revoked.macPermissions, { screenRecording: false, accessibility: true }); | 3166 | assert.deepEqual(revoked.macPermissions, { screenRecording: false, accessibility: true }); |
| 3167 | 3167 | ||
| ... | @@ -3174,7 +3174,7 @@ it.effect("flags revoked macOS permissions on read and re-registers once they re | ... | @@ -3174,7 +3174,7 @@ it.effect("flags revoked macOS permissions on read and re-registers once they re |
| 3174 | const blocked = yield* service.state; | 3174 | const blocked = yield* service.state; |
| 3175 | assert.equal( | 3175 | assert.equal( |
| 3176 | blocked.message, | 3176 | blocked.message, |
| 3177 | "Allow Screen Recording in System Settings, then restart T3 Code.", | 3177 | "Allow Screen Recording in System Settings, then restart The Knife.", |
| 3178 | ); | 3178 | ); |
| 3179 | assert.isFalse(blocked.shortcutRegistered); | 3179 | assert.isFalse(blocked.shortcutRegistered); |
| 3180 | 3180 |
apps/desktop/src/snapShot/DesktopSnapShot.ts+8-8| ... | @@ -101,11 +101,11 @@ const FLASH_FRAME_INTERVAL_MS = 16; | ... | @@ -101,11 +101,11 @@ const FLASH_FRAME_INTERVAL_MS = 16; |
| 101 | const FLASH_PEAK_OPACITY = 0.08; | 101 | const FLASH_PEAK_OPACITY = 0.08; |
| 102 | const MAC_SCREEN_CAPTURE_SETTINGS_URL = MAC_PERMISSION_SETTINGS_URLS["screen-recording"]; | 102 | const MAC_SCREEN_CAPTURE_SETTINGS_URL = MAC_PERMISSION_SETTINGS_URLS["screen-recording"]; |
| 103 | const MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE = | 103 | const MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE = |
| 104 | "Allow Screen Recording in System Settings, then restart T3 Code."; | 104 | "Allow Screen Recording in System Settings, then restart The Knife."; |
| 105 | const MAC_ACCESSIBILITY_PERMISSION_MESSAGE = | 105 | const MAC_ACCESSIBILITY_PERMISSION_MESSAGE = |
| 106 | "Allow Accessibility in System Settings, then restart T3 Code."; | 106 | "Allow Accessibility in System Settings, then restart The Knife."; |
| 107 | const MAC_BOTH_PERMISSIONS_MESSAGE = | 107 | const MAC_BOTH_PERMISSIONS_MESSAGE = |
| 108 | "Allow Accessibility and Screen Recording in System Settings, then restart T3 Code."; | 108 | "Allow Accessibility and Screen Recording in System Settings, then restart The Knife."; |
| 109 | const MAC_PERMISSION_MESSAGES = new Set([ | 109 | const MAC_PERMISSION_MESSAGES = new Set([ |
| 110 | MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE, | 110 | MAC_SCREEN_CAPTURE_PERMISSION_MESSAGE, |
| 111 | MAC_ACCESSIBILITY_PERMISSION_MESSAGE, | 111 | MAC_ACCESSIBILITY_PERMISSION_MESSAGE, |
| ... | @@ -187,7 +187,7 @@ export class DesktopSnapShot extends Context.Service< | ... | @@ -187,7 +187,7 @@ export class DesktopSnapShot extends Context.Service< |
| 187 | shortcut: SnapShotShortcut, | 187 | shortcut: SnapShotShortcut, |
| 188 | ) => Effect.Effect<DesktopSnapShotShortcutAvailability>; | 188 | ) => Effect.Effect<DesktopSnapShotShortcutAvailability>; |
| 189 | readonly setShortcutSuppressed: (suppressed: boolean) => Effect.Effect<void>; | 189 | readonly setShortcutSuppressed: (suppressed: boolean) => Effect.Effect<void>; |
| 190 | /** Capture the foreground window in place, including T3 Code itself. */ | 190 | /** Capture the foreground window in place, including The Knife itself. */ |
| 191 | readonly capture: Effect.Effect<void, DesktopSnapShotError>; | 191 | readonly capture: Effect.Effect<void, DesktopSnapShotError>; |
| 192 | readonly listPending: Effect.Effect< | 192 | readonly listPending: Effect.Effect< |
| 193 | ReadonlyArray<DesktopPendingSnapShot>, | 193 | ReadonlyArray<DesktopPendingSnapShot>, |
| ... | @@ -898,7 +898,7 @@ export const make = Effect.gen(function* () { | ... | @@ -898,7 +898,7 @@ export const make = Effect.gen(function* () { |
| 898 | const capturedAt = yield* DateTime.now.pipe(Effect.map(DateTime.formatIso)); | 898 | const capturedAt = yield* DateTime.now.pipe(Effect.map(DateTime.formatIso)); |
| 899 | if (snapshot.linuxActivationFailure) { | 899 | if (snapshot.linuxActivationFailure) { |
| 900 | yield* Effect.logWarning( | 900 | yield* Effect.logWarning( |
| 901 | "The compositor could not activate T3 Code after the snapshot", | 901 | "The compositor could not activate The Knife after the snapshot", |
| 902 | snapshot.linuxActivationFailure.cause, | 902 | snapshot.linuxActivationFailure.cause, |
| 903 | ); | 903 | ); |
| 904 | } | 904 | } |
| ... | @@ -1018,7 +1018,7 @@ export const make = Effect.gen(function* () { | ... | @@ -1018,7 +1018,7 @@ export const make = Effect.gen(function* () { |
| 1018 | if (mode === "portal" && niriSocketPath()) { | 1018 | if (mode === "portal" && niriSocketPath()) { |
| 1019 | return { | 1019 | return { |
| 1020 | available: false, | 1020 | available: false, |
| 1021 | message: "Configure the capture shortcut in your Niri config, not in T3 Code.", | 1021 | message: "Configure the capture shortcut in your Niri config, not in The Knife.", |
| 1022 | }; | 1022 | }; |
| 1023 | } | 1023 | } |
| 1024 | if (mode === "portal" && isHyprlandCaptureSession()) { | 1024 | if (mode === "portal" && isHyprlandCaptureSession()) { |
| ... | @@ -1168,7 +1168,7 @@ export const make = Effect.gen(function* () { | ... | @@ -1168,7 +1168,7 @@ export const make = Effect.gen(function* () { |
| 1168 | const { startNiriCaptureShortcut } = await import("./NiriCaptureShortcut.ts"); | 1168 | const { startNiriCaptureShortcut } = await import("./NiriCaptureShortcut.ts"); |
| 1169 | return startNiriCaptureShortcut(linuxAppId, onCurrentShortcut, () => { | 1169 | return startNiriCaptureShortcut(linuxAppId, onCurrentShortcut, () => { |
| 1170 | void runPromise( | 1170 | void runPromise( |
| 1171 | setShortcutFailure("The Niri capture endpoint disconnected. Restart T3 Code."), | 1171 | setShortcutFailure("The Niri capture endpoint disconnected. Restart The Knife."), |
| 1172 | ).catch(() => undefined); | 1172 | ).catch(() => undefined); |
| 1173 | }); | 1173 | }); |
| 1174 | }).pipe( | 1174 | }).pipe( |
| ... | @@ -1190,7 +1190,7 @@ export const make = Effect.gen(function* () { | ... | @@ -1190,7 +1190,7 @@ export const make = Effect.gen(function* () { |
| 1190 | shortcutActionRegistered: registered, | 1190 | shortcutActionRegistered: registered, |
| 1191 | shortcutMessage: registered | 1191 | shortcutMessage: registered |
| 1192 | ? "Set up the shortcut to add it to your Niri config." | 1192 | ? "Set up the shortcut to add it to your Niri config." |
| 1193 | : "Could not start the Niri capture endpoint. Another T3 Code instance may be using it.", | 1193 | : "Could not start the Niri capture endpoint. Another The Knife instance may be using it.", |
| 1194 | message: null, | 1194 | message: null, |
| 1195 | }); | 1195 | }); |
| 1196 | return; | 1196 | return; |
apps/desktop/src/snapShot/GnomeCaptureSetup.ts+5-5| ... | @@ -56,7 +56,7 @@ export async function installGnomeCaptureBundle({ bundle, dataHome }: SetupPaths | ... | @@ -56,7 +56,7 @@ export async function installGnomeCaptureBundle({ bundle, dataHome }: SetupPaths |
| 56 | await NodeFSP.readFile(NodePath.join(target, "metadata.json"), "utf8"), | 56 | await NodeFSP.readFile(NodePath.join(target, "metadata.json"), "utf8"), |
| 57 | ); | 57 | ); |
| 58 | if (installed.version > metadata.version) | 58 | if (installed.version > metadata.version) |
| 59 | throw new Error("A newer extension is installed. Update T3 Code instead of replacing it."); | 59 | throw new Error("A newer extension is installed. Update The Knife instead of replacing it."); |
| 60 | } | 60 | } |
| 61 | const staged = await NodeFSP.mkdtemp(NodePath.join(parent, ".t3-capture-install-")); | 61 | const staged = await NodeFSP.mkdtemp(NodePath.join(parent, ".t3-capture-install-")); |
| 62 | let backup: string | undefined; | 62 | let backup: string | undefined; |
| ... | @@ -184,7 +184,7 @@ export class GnomeCaptureSetup { | ... | @@ -184,7 +184,7 @@ export class GnomeCaptureSetup { |
| 184 | return { | 184 | return { |
| 185 | status: "restart-required", | 185 | status: "restart-required", |
| 186 | message: | 186 | message: |
| 187 | "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.", | 187 | "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.", |
| 188 | }; | 188 | }; |
| 189 | if ((installed?.version ?? info.version?.value ?? 0) < bundled.version) | 189 | if ((installed?.version ?? info.version?.value ?? 0) < bundled.version) |
| 190 | return { | 190 | return { |
| ... | @@ -196,12 +196,12 @@ export class GnomeCaptureSetup { | ... | @@ -196,12 +196,12 @@ export class GnomeCaptureSetup { |
| 196 | return { | 196 | return { |
| 197 | status: "extensions-disabled", | 197 | status: "extensions-disabled", |
| 198 | message: | 198 | message: |
| 199 | "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.", | 199 | "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.", |
| 200 | }; | 200 | }; |
| 201 | if (info.state?.value === 1) | 201 | if (info.state?.value === 1) |
| 202 | return { | 202 | return { |
| 203 | status: "enabled", | 203 | status: "enabled", |
| 204 | message: "The T3 Code extension is running. Active-window snapshots are available.", | 204 | message: "The Knife extension is running. Active-window snapshots are available.", |
| 205 | }; | 205 | }; |
| 206 | if (info.state?.value === 3 || info.state?.value === 4) | 206 | if (info.state?.value === 3 || info.state?.value === 4) |
| 207 | return { | 207 | return { |
| ... | @@ -213,7 +213,7 @@ export class GnomeCaptureSetup { | ... | @@ -213,7 +213,7 @@ export class GnomeCaptureSetup { |
| 213 | return { | 213 | return { |
| 214 | status: "disabled", | 214 | status: "disabled", |
| 215 | message: | 215 | message: |
| 216 | "Enable the T3 Code extension to allow active-window snapshots. You can disable it here at any time.", | 216 | "Enable The Knife’s extension to allow active-window snapshots. You can disable it here at any time.", |
| 217 | }; | 217 | }; |
| 218 | } catch (error) { | 218 | } catch (error) { |
| 219 | return { | 219 | return { |
apps/desktop/src/snapShot/HyprlandSnapShot.ts+1-1| ... | @@ -96,7 +96,7 @@ export class HyprlandCaptureSetup { | ... | @@ -96,7 +96,7 @@ export class HyprlandCaptureSetup { |
| 96 | const bundle = await regularFile(this.paths.bundle); | 96 | const bundle = await regularFile(this.paths.bundle); |
| 97 | if (!bundle) | 97 | if (!bundle) |
| 98 | throw new Error( | 98 | throw new Error( |
| 99 | "The Hyprland capture helper is missing from this build. Update or reinstall T3 Code.", | 99 | "The Hyprland capture helper is missing from this build. Update or reinstall The Knife.", |
| 100 | ); | 100 | ); |
| 101 | if (!installed.equals(bundle)) | 101 | if (!installed.equals(bundle)) |
| 102 | return { | 102 | return { |
apps/desktop/src/snapShot/KdeSnapShot.ts+3-3| ... | @@ -33,7 +33,7 @@ export function kdeCaptureDesktopEntry(executable: string): string { | ... | @@ -33,7 +33,7 @@ export function kdeCaptureDesktopEntry(executable: string): string { |
| 33 | return [ | 33 | return [ |
| 34 | "[Desktop Entry]", | 34 | "[Desktop Entry]", |
| 35 | "Type=Application", | 35 | "Type=Application", |
| 36 | "Name=T3 Code SnapShots", | 36 | "Name=The Knife SnapShots", |
| 37 | "NoDisplay=true", | 37 | "NoDisplay=true", |
| 38 | `Exec=${escapeDesktopEntryExecArgument(executable)} check`, | 38 | `Exec=${escapeDesktopEntryExecArgument(executable)} check`, |
| 39 | // KService reads this custom property as a KConfig list, not an XDG ';' list. | 39 | // KService reads this custom property as a KConfig list, not an XDG ';' list. |
| ... | @@ -116,7 +116,7 @@ export class KdeCaptureSetup { | ... | @@ -116,7 +116,7 @@ export class KdeCaptureSetup { |
| 116 | if (!bundle) | 116 | if (!bundle) |
| 117 | return { | 117 | return { |
| 118 | status: "error", | 118 | status: "error", |
| 119 | message: "The capture helper is missing from this build. Update or reinstall T3 Code.", | 119 | message: "The capture helper is missing from this build. Update or reinstall The Knife.", |
| 120 | }; | 120 | }; |
| 121 | if (!installed.equals(bundle) || entry.toString() !== kdeCaptureDesktopEntry(executable)) | 121 | if (!installed.equals(bundle) || entry.toString() !== kdeCaptureDesktopEntry(executable)) |
| 122 | return { | 122 | return { |
| ... | @@ -162,7 +162,7 @@ export class KdeCaptureSetup { | ... | @@ -162,7 +162,7 @@ export class KdeCaptureSetup { |
| 162 | const bundle = await regularFile(this.paths.bundle); | 162 | const bundle = await regularFile(this.paths.bundle); |
| 163 | if (!bundle) | 163 | if (!bundle) |
| 164 | throw new Error( | 164 | throw new Error( |
| 165 | "The capture helper is missing from this build. Update or reinstall T3 Code.", | 165 | "The capture helper is missing from this build. Update or reinstall The Knife.", |
| 166 | ); | 166 | ); |
| 167 | await NodeFSP.mkdir(directory, { recursive: true }); | 167 | await NodeFSP.mkdir(directory, { recursive: true }); |
| 168 | await NodeFSP.mkdir(NodePath.dirname(desktop), { recursive: true }); | 168 | await NodeFSP.mkdir(NodePath.dirname(desktop), { recursive: true }); |
apps/desktop/src/snapShot/LinuxSnapShot.ts+1-1| ... | @@ -306,7 +306,7 @@ export class LinuxCaptureConnection { | ... | @@ -306,7 +306,7 @@ export class LinuxCaptureConnection { |
| 306 | this.bus.requestName(`${appId}.SnapShot`, NameFlag.DO_NOT_QUEUE), | 306 | this.bus.requestName(`${appId}.SnapShot`, NameFlag.DO_NOT_QUEUE), |
| 307 | ); | 307 | ); |
| 308 | if (result !== RequestNameReply.PRIMARY_OWNER) { | 308 | if (result !== RequestNameReply.PRIMARY_OWNER) { |
| 309 | throw new Error("Another T3 Code instance is capturing a window. Try again."); | 309 | throw new Error("Another The Knife instance is capturing a window. Try again."); |
| 310 | } | 310 | } |
| 311 | const withFeedback = this.feedbackAvailable && options !== undefined; | 311 | const withFeedback = this.feedbackAvailable && options !== undefined; |
| 312 | const reply = await this.call({ | 312 | const reply = await this.call({ |
apps/desktop/src/snapShot/NiriCaptureShortcut.ts+1-1| ... | @@ -55,7 +55,7 @@ export async function startNiriCaptureShortcut( | ... | @@ -55,7 +55,7 @@ export async function startNiriCaptureShortcut( |
| 55 | }), | 55 | }), |
| 56 | ]); | 56 | ]); |
| 57 | if (result !== RequestNameReply.PRIMARY_OWNER) | 57 | if (result !== RequestNameReply.PRIMARY_OWNER) |
| 58 | throw new Error("Another T3 Code instance already owns the capture shortcut."); | 58 | throw new Error("Another The Knife instance already owns the capture shortcut."); |
| 59 | return close; | 59 | return close; |
| 60 | } catch (error) { | 60 | } catch (error) { |
| 61 | close(); | 61 | close(); |
apps/desktop/src/snapShot/NiriSnapShot.ts+1-1| ... | @@ -175,7 +175,7 @@ async function activateNiriWindow(path: string, title: string, signal: AbortSign | ... | @@ -175,7 +175,7 @@ async function activateNiriWindow(path: string, title: string, signal: AbortSign |
| 175 | (window) => window.pid === process.pid && window.title === title, | 175 | (window) => window.pid === process.pid && window.title === title, |
| 176 | ); | 176 | ); |
| 177 | if (matches.length > 1) | 177 | if (matches.length > 1) |
| 178 | throw new Error("More than one T3 Code window matches the capture destination."); | 178 | throw new Error("More than one The Knife window matches the capture destination."); |
| 179 | return matches[0]; | 179 | return matches[0]; |
| 180 | }); | 180 | }); |
| 181 | connection.send("EventStream"); | 181 | connection.send("EventStream"); |
apps/desktop/src/snapShot/PortalCaptureShortcut.test.ts+1-1| ... | @@ -318,7 +318,7 @@ it("guides users to manual desktop settings when the portal cannot open them", a | ... | @@ -318,7 +318,7 @@ it("guides users to manual desktop settings when the portal cannot open them", a |
| 318 | expect(client.hasSession).toBe(true); | 318 | expect(client.hasSession).toBe(true); |
| 319 | expect(client.state.shortcutCanRetry).toBe(false); | 319 | expect(client.state.shortcutCanRetry).toBe(false); |
| 320 | expect(client.state.shortcutMessage).toBe( | 320 | expect(client.state.shortcutMessage).toBe( |
| 321 | "Shortcut permission wasn't granted. Allow T3 Code in your desktop's shortcut settings.", | 321 | "Shortcut permission wasn't granted. Allow The Knife in your desktop's shortcut settings.", |
| 322 | ); | 322 | ); |
| 323 | await expect(client.configure()).rejects.toThrow("Open your desktop's shortcut settings"); | 323 | await expect(client.configure()).rejects.toThrow("Open your desktop's shortcut settings"); |
| 324 | expect(bus.calls.some((message) => message.member === "ConfigureShortcuts")).toBe(false); | 324 | expect(bus.calls.some((message) => message.member === "ConfigureShortcuts")).toBe(false); |
apps/desktop/src/snapShot/PortalCaptureShortcut.ts+4-4| ... | @@ -135,7 +135,7 @@ export class PortalCaptureShortcut { | ... | @@ -135,7 +135,7 @@ export class PortalCaptureShortcut { |
| 135 | throw new Error("Change the capture binding in your Hyprland config, then save it."); | 135 | throw new Error("Change the capture binding in your Hyprland config, then save it."); |
| 136 | if (!this.hasSession || this.version < 2) | 136 | if (!this.hasSession || this.version < 2) |
| 137 | throw new Error( | 137 | throw new Error( |
| 138 | "Open your desktop's shortcut settings and allow T3 Code's capture shortcut.", | 138 | "Open your desktop's shortcut settings and allow The Knife's capture shortcut.", |
| 139 | ); | 139 | ); |
| 140 | await this.call({ | 140 | await this.call({ |
| 141 | destination: this.owner, | 141 | destination: this.owner, |
| ... | @@ -164,7 +164,7 @@ export class PortalCaptureShortcut { | ... | @@ -164,7 +164,7 @@ export class PortalCaptureShortcut { |
| 164 | // This failed session is closing, so retry can register a fresh one. | 164 | // This failed session is closing, so retry can register a fresh one. |
| 165 | shortcutCanRetry: !this.managedByHyprland, | 165 | shortcutCanRetry: !this.managedByHyprland, |
| 166 | shortcutMessage: this.managedByHyprland | 166 | shortcutMessage: this.managedByHyprland |
| 167 | ? "Couldn't connect to Hyprland shortcuts. Make sure xdg-desktop-portal-hyprland is running, then restart T3 Code." | 167 | ? "Couldn't connect to Hyprland shortcuts. Make sure xdg-desktop-portal-hyprland is running, then restart The Knife." |
| 168 | : error instanceof Error | 168 | : error instanceof Error |
| 169 | ? error.message | 169 | ? error.message |
| 170 | : "Could not register the capture shortcut.", | 170 | : "Could not register the capture shortcut.", |
| ... | @@ -304,7 +304,7 @@ export class PortalCaptureShortcut { | ... | @@ -304,7 +304,7 @@ export class PortalCaptureShortcut { |
| 304 | shortcutMessage: | 304 | shortcutMessage: |
| 305 | this.version >= 2 | 305 | this.version >= 2 |
| 306 | ? "Shortcut permission wasn't granted. Open shortcut permissions to allow it." | 306 | ? "Shortcut permission wasn't granted. Open shortcut permissions to allow it." |
| 307 | : "Shortcut permission wasn't granted. Allow T3 Code in your desktop's shortcut settings.", | 307 | : "Shortcut permission wasn't granted. Allow The Knife in your desktop's shortcut settings.", |
| 308 | }); | 308 | }); |
| 309 | return undefined; | 309 | return undefined; |
| 310 | } | 310 | } |
| ... | @@ -327,7 +327,7 @@ export class PortalCaptureShortcut { | ... | @@ -327,7 +327,7 @@ export class PortalCaptureShortcut { |
| 327 | shortcutPending: false, | 327 | shortcutPending: false, |
| 328 | shortcutMessage: shortcut | 328 | shortcutMessage: shortcut |
| 329 | ? "Managed by Hyprland. Add the binding to your config and save it." | 329 | ? "Managed by Hyprland. Add the binding to your config and save it." |
| 330 | : "Hyprland did not register the capture action. Check that xdg-desktop-portal-hyprland is running, then restart T3 Code.", | 330 | : "Hyprland did not register the capture action. Check that xdg-desktop-portal-hyprland is running, then restart The Knife.", |
| 331 | }); | 331 | }); |
| 332 | return; | 332 | return; |
| 333 | } | 333 | } |
apps/desktop/src/snapShot/SnapShotTransition.ts+1-1| ... | @@ -114,7 +114,7 @@ function createWindow( | ... | @@ -114,7 +114,7 @@ function createWindow( |
| 114 | resizable: false, | 114 | resizable: false, |
| 115 | show: false, | 115 | show: false, |
| 116 | skipTaskbar: true, | 116 | skipTaskbar: true, |
| 117 | title: "T3 Code Snapshot Animation", | 117 | title: "The Knife Snapshot Animation", |
| 118 | transparent: true, | 118 | transparent: true, |
| 119 | webPreferences: { | 119 | webPreferences: { |
| 120 | backgroundThrottling: false, | 120 | backgroundThrottling: false, |
apps/desktop/src/snapShot/snapShot.ts+1-1| ... | @@ -447,7 +447,7 @@ export function hideAndWaitForBlur(window: { | ... | @@ -447,7 +447,7 @@ export function hideAndWaitForBlur(window: { |
| 447 | return new Promise((resolve, reject) => { | 447 | return new Promise((resolve, reject) => { |
| 448 | const timeout = setTimeout(() => { | 448 | const timeout = setTimeout(() => { |
| 449 | window.removeListener("blur", onBlur); | 449 | window.removeListener("blur", onBlur); |
| 450 | reject(new Error("Timed out waiting for T3 Code to lose focus.")); | 450 | reject(new Error("Timed out waiting for The Knife to lose focus.")); |
| 451 | }, WINDOW_BLUR_TIMEOUT_MS); | 451 | }, WINDOW_BLUR_TIMEOUT_MS); |
| 452 | const onBlur = () => { | 452 | const onBlur = () => { |
| 453 | clearTimeout(timeout); | 453 | clearTimeout(timeout); |
apps/desktop/src/ssh/DesktopSshEnvironment.test.ts+1-1| ... | @@ -30,7 +30,7 @@ describe("sshEnvironment", () => { | ... | @@ -30,7 +30,7 @@ describe("sshEnvironment", () => { |
| 30 | assert.equal(cause.message, "Failed to present SSH password prompt for devbox."); | 30 | assert.equal(cause.message, "Failed to present SSH password prompt for devbox."); |
| 31 | assert.equal( | 31 | assert.equal( |
| 32 | DesktopSshEnvironment.toSshPasswordPromptError(cause).message, | 32 | DesktopSshEnvironment.toSshPasswordPromptError(cause).message, |
| 33 | "T3 Code window is not available for SSH authentication.", | 33 | "The Knife window is not available for SSH authentication.", |
| 34 | ); | 34 | ); |
| 35 | }); | 35 | }); |
| 36 | 36 |
apps/desktop/src/ssh/DesktopSshEnvironment.ts+1-1| ... | @@ -97,7 +97,7 @@ export function toSshPasswordPromptError( | ... | @@ -97,7 +97,7 @@ export function toSshPasswordPromptError( |
| 97 | break; | 97 | break; |
| 98 | case "DesktopSshPromptWindowUnavailableError": | 98 | case "DesktopSshPromptWindowUnavailableError": |
| 99 | case "DesktopSshPromptPresentationError": | 99 | case "DesktopSshPromptPresentationError": |
| 100 | message = "T3 Code window is not available for SSH authentication."; | 100 | message = "The Knife window is not available for SSH authentication."; |
| 101 | break; | 101 | break; |
| 102 | case "DesktopSshPromptTimedOutError": | 102 | case "DesktopSshPromptTimedOutError": |
| 103 | message = `SSH authentication timed out for ${cause.destination}.`; | 103 | message = `SSH authentication timed out for ${cause.destination}.`; |
apps/desktop/src/ssh/DesktopSshPasswordPrompts.ts+1-1| ... | @@ -63,7 +63,7 @@ export class DesktopSshPromptWindowUnavailableError extends Schema.TaggedError<D | ... | @@ -63,7 +63,7 @@ export class DesktopSshPromptWindowUnavailableError extends Schema.TaggedError<D |
| 63 | ) { | 63 | ) { |
| 64 | override get message(): string { | 64 | override get message(): string { |
| 65 | const request = this.requestId === null ? "before a request id was assigned" : this.requestId; | 65 | const request = this.requestId === null ? "before a request id was assigned" : this.requestId; |
| 66 | return `T3 Code window is unavailable during ${this.stage} for SSH authentication to ${this.destination} (request: ${request}).`; | 66 | return `The Knife window is unavailable during ${this.stage} for SSH authentication to ${this.destination} (request: ${request}).`; |
| 67 | } | 67 | } |
| 68 | } | 68 | } |
| 69 | 69 |
apps/desktop/src/window/DesktopApplicationMenu.test.ts+5-5| ... | @@ -29,7 +29,7 @@ const environmentInput = { | ... | @@ -29,7 +29,7 @@ const environmentInput = { |
| 29 | 29 | ||
| 30 | const layerElectronApp = Layer.succeed(ElectronApp.ElectronApp, { | 30 | const layerElectronApp = Layer.succeed(ElectronApp.ElectronApp, { |
| 31 | metadata: Effect.die("unexpected metadata read"), | 31 | metadata: Effect.die("unexpected metadata read"), |
| 32 | name: Effect.succeed("T3 Code"), | 32 | name: Effect.succeed("The Knife"), |
| 33 | systemLocale: Effect.succeed("en-US"), | 33 | systemLocale: Effect.succeed("en-US"), |
| 34 | whenReady: Effect.void, | 34 | whenReady: Effect.void, |
| 35 | quit: Effect.void, | 35 | quit: Effect.void, |
| ... | @@ -142,21 +142,21 @@ describe("DesktopApplicationMenu", () => { | ... | @@ -142,21 +142,21 @@ describe("DesktopApplicationMenu", () => { |
| 142 | const template = yield* Deferred.await(applicationMenuTemplate); | 142 | const template = yield* Deferred.await(applicationMenuTemplate); |
| 143 | const applicationMenu = template[0]; | 143 | const applicationMenu = template[0]; |
| 144 | assert.isDefined(applicationMenu); | 144 | assert.isDefined(applicationMenu); |
| 145 | assert.equal(applicationMenu.label, "T3 Code (Nightly)"); | 145 | assert.equal(applicationMenu.label, "The Knife (Nightly)"); |
| 146 | if (!Array.isArray(applicationMenu.submenu)) { | 146 | if (!Array.isArray(applicationMenu.submenu)) { |
| 147 | throw new Error("Expected application menu submenu to be an array."); | 147 | throw new Error("Expected application menu submenu to be an array."); |
| 148 | } | 148 | } |
| 149 | assert.equal( | 149 | assert.equal( |
| 150 | applicationMenu.submenu.find((item) => item.role === "about")?.label, | 150 | applicationMenu.submenu.find((item) => item.role === "about")?.label, |
| 151 | "About T3 Code (Nightly)", | 151 | "About The Knife (Nightly)", |
| 152 | ); | 152 | ); |
| 153 | assert.equal( | 153 | assert.equal( |
| 154 | applicationMenu.submenu.find((item) => item.role === "hide")?.label, | 154 | applicationMenu.submenu.find((item) => item.role === "hide")?.label, |
| 155 | "Hide T3 Code (Nightly)", | 155 | "Hide The Knife (Nightly)", |
| 156 | ); | 156 | ); |
| 157 | assert.equal( | 157 | assert.equal( |
| 158 | applicationMenu.submenu.find((item) => item.role === "quit")?.label, | 158 | applicationMenu.submenu.find((item) => item.role === "quit")?.label, |
| 159 | "Quit T3 Code (Nightly)", | 159 | "Quit The Knife (Nightly)", |
| 160 | ); | 160 | ); |
| 161 | }), | 161 | }), |
| 162 | ); | 162 | ); |
apps/desktop/src/window/DesktopApplicationMenu.ts+1-1| ... | @@ -74,7 +74,7 @@ const checkForUpdatesFromMenu = Effect.gen(function* () { | ... | @@ -74,7 +74,7 @@ const checkForUpdatesFromMenu = Effect.gen(function* () { |
| 74 | yield* electronDialog.showMessageBox({ | 74 | yield* electronDialog.showMessageBox({ |
| 75 | type: "info", | 75 | type: "info", |
| 76 | title: "You're up to date!", | 76 | title: "You're up to date!", |
| 77 | message: `T3 Code ${updateState.currentVersion} is currently the newest version available.`, | 77 | message: `The Knife ${updateState.currentVersion} is currently the newest version available.`, |
| 78 | buttons: ["OK"], | 78 | buttons: ["OK"], |
| 79 | }); | 79 | }); |
| 80 | } else if (updateState.status === "error") { | 80 | } else if (updateState.status === "error") { |
apps/desktop/src/wsl/DesktopWslBackend.test.ts+1-1| ... | @@ -172,7 +172,7 @@ describe("DesktopWslBackend", () => { | ... | @@ -172,7 +172,7 @@ describe("DesktopWslBackend", () => { |
| 172 | 172 | ||
| 173 | yield* recordOwnershipFailure(); | 173 | yield* recordOwnershipFailure(); |
| 174 | const refusal = yield* backend.lastPreflightError; | 174 | const refusal = yield* backend.lastPreflightError; |
| 175 | assert.include(Option.getOrThrow(refusal), "Another T3 Code server"); | 175 | assert.include(Option.getOrThrow(refusal), "Another The Knife server"); |
| 176 | yield* backend.reconcile; | 176 | yield* backend.reconcile; |
| 177 | assert.equal(startCount, 1); | 177 | assert.equal(startCount, 1); |
| 178 | assert.deepEqual(yield* backend.lastPreflightError, refusal); | 178 | assert.deepEqual(yield* backend.lastPreflightError, refusal); |
apps/desktop/src/wsl/DesktopWslBackend.ts+1-1| ... | @@ -177,7 +177,7 @@ export const layer = Layer.effect( | ... | @@ -177,7 +177,7 @@ export const layer = Layer.effect( |
| 177 | Ref.set( | 177 | Ref.set( |
| 178 | preflightErrorRef, | 178 | preflightErrorRef, |
| 179 | Option.some( | 179 | Option.some( |
| 180 | "Another T3 Code server is using this distro's T3 home. Stop it, or pair with it instead.", | 180 | "Another The Knife server is using this distro's T3 home. Stop it, or pair with it instead.", |
| 181 | ), | 181 | ), |
| 182 | ), | 182 | ), |
| 183 | ), | 183 | ), |
apps/desktop/src/wsl/DesktopWslEnvironment.ts+1-1| ... | @@ -505,7 +505,7 @@ const NODE_PTY_BINARY_MISSING_EXIT_CODE = 4; | ... | @@ -505,7 +505,7 @@ const NODE_PTY_BINARY_MISSING_EXIT_CODE = 4; |
| 505 | 505 | ||
| 506 | const formatNodePtyProbeFailureReason = (exitCode: number): string | null => | 506 | const formatNodePtyProbeFailureReason = (exitCode: number): string | null => |
| 507 | exitCode === NODE_PTY_BINARY_MISSING_EXIT_CODE | 507 | exitCode === NODE_PTY_BINARY_MISSING_EXIT_CODE |
| 508 | ? "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." | 508 | ? "WSL support is missing from this build: the packaged Linux node-pty binary was not included. Install a build that includes WSL support." |
| 509 | : null; | 509 | : null; |
| 510 | 510 | ||
| 511 | // Captures the login-shell PATH as `resolvedPath:` so the launch can forward the | 511 | // Captures the login-shell PATH as `resolvedPath:` so the launch can forward the |
apps/mobile/README.md+5-5| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | # T3 Code Mobile | 1 | # The Knife Mobile |
| 2 | 2 | ||
| 3 | > [!WARNING] | 3 | > [!WARNING] |
| 4 | > 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. | 4 | > 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. |
| 5 | 5 | ||
| 6 | ## Quickstart | 6 | ## Quickstart |
| 7 | 7 | ||
| ... | @@ -10,9 +10,9 @@ | ... | @@ -10,9 +10,9 @@ |
| 10 | 10 | ||
| 11 | This app has three variants: | 11 | This app has three variants: |
| 12 | 12 | ||
| 13 | - `development`: Expo dev client, installable side-by-side as `T3 Code Dev` | 13 | - `development`: Expo dev client, installable side-by-side as `The Knife Dev` |
| 14 | - `preview`: persistent internal preview build, installable side-by-side as `T3 Code Preview` | 14 | - `preview`: persistent internal preview build, installable side-by-side as `The Knife Preview` |
| 15 | - `production`: store/release build as `T3 Code` | 15 | - `production`: store/release build as `The Knife` |
| 16 | 16 | ||
| 17 | Run commands from `apps/mobile`. | 17 | Run commands from `apps/mobile`. |
| 18 | 18 |
apps/mobile/app.config.ts+27-29| ... | @@ -19,8 +19,8 @@ const IOS_BUNDLE_IDENTIFIER_PATTERN = /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/; | ... | @@ -19,8 +19,8 @@ const IOS_BUNDLE_IDENTIFIER_PATTERN = /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/; |
| 19 | 19 | ||
| 20 | const fromRepoRoot = (relativePath: string) => `../../${relativePath}`; | 20 | const fromRepoRoot = (relativePath: string) => `../../${relativePath}`; |
| 21 | // Android layers are rendered by scripts/export-android-icons.ts from the Icon Composer sources. | 21 | // Android layers are rendered by scripts/export-android-icons.ts from the Icon Composer sources. |
| 22 | // The wordmark sits inside the adaptive safe zone; the variant artwork is a full-bleed background. | 22 | // The knife sits inside the adaptive icon safe zone. |
| 23 | const androidAdaptiveForeground = "./assets/android-icon-foreground.png"; | 23 | const androidAdaptiveForeground = "../../assets/knife/android-foreground.png"; |
| 24 | 24 | ||
| 25 | if ( | 25 | if ( |
| 26 | isIosPersonalTeamBuild && | 26 | isIosPersonalTeamBuild && |
| ... | @@ -37,11 +37,11 @@ const DEVELOPMENT_ASSETS = { | ... | @@ -37,11 +37,11 @@ const DEVELOPMENT_ASSETS = { |
| 37 | iosIcon: fromRepoRoot(BRAND_ASSET_PATHS.developmentIconComposerProject), | 37 | iosIcon: fromRepoRoot(BRAND_ASSET_PATHS.developmentIconComposerProject), |
| 38 | splashIcon: fromRepoRoot(BRAND_ASSET_PATHS.developmentIosIconPng), | 38 | splashIcon: fromRepoRoot(BRAND_ASSET_PATHS.developmentIosIconPng), |
| 39 | androidAdaptiveForeground, | 39 | androidAdaptiveForeground, |
| 40 | androidAdaptiveBackgroundColor: "#347FF8", | 40 | androidAdaptiveBackgroundColor: "#171422", |
| 41 | androidAdaptiveBackgroundImage: "./assets/android-icon-background-dev.png", | 41 | androidAdaptiveBackgroundImage: undefined, |
| 42 | androidSplashIcon: "./assets/android-splash-icon-dev.png", | 42 | androidSplashIcon: "../../assets/knife/android-splash.png", |
| 43 | androidMonochromeIcon: "./assets/android-icon-mark.png", | 43 | androidMonochromeIcon: "../../assets/knife/android-foreground.png", |
| 44 | androidNotificationIcon: "./assets/android-notification-icon.png", | 44 | androidNotificationIcon: "../../assets/knife/android-foreground.png", |
| 45 | androidNotificationColor: "#00639B", | 45 | androidNotificationColor: "#00639B", |
| 46 | } as const; | 46 | } as const; |
| 47 | 47 | ||
| ... | @@ -50,11 +50,11 @@ const PREVIEW_ASSETS = { | ... | @@ -50,11 +50,11 @@ const PREVIEW_ASSETS = { |
| 50 | iosIcon: fromRepoRoot(BRAND_ASSET_PATHS.nightlyIconComposerProject), | 50 | iosIcon: fromRepoRoot(BRAND_ASSET_PATHS.nightlyIconComposerProject), |
| 51 | splashIcon: fromRepoRoot(BRAND_ASSET_PATHS.nightlyIosIconPng), | 51 | splashIcon: fromRepoRoot(BRAND_ASSET_PATHS.nightlyIosIconPng), |
| 52 | androidAdaptiveForeground, | 52 | androidAdaptiveForeground, |
| 53 | androidAdaptiveBackgroundColor: "#111533", | 53 | androidAdaptiveBackgroundColor: "#171422", |
| 54 | androidAdaptiveBackgroundImage: "./assets/android-icon-background-nightly.png", | 54 | androidAdaptiveBackgroundImage: undefined, |
| 55 | androidSplashIcon: "./assets/android-splash-icon-nightly.png", | 55 | androidSplashIcon: "../../assets/knife/android-splash.png", |
| 56 | androidMonochromeIcon: "./assets/android-icon-mark.png", | 56 | androidMonochromeIcon: "../../assets/knife/android-foreground.png", |
| 57 | androidNotificationIcon: "./assets/android-notification-icon.png", | 57 | androidNotificationIcon: "../../assets/knife/android-foreground.png", |
| 58 | androidNotificationColor: "#7565C7", | 58 | androidNotificationColor: "#7565C7", |
| 59 | } as const; | 59 | } as const; |
| 60 | 60 | ||
| ... | @@ -63,17 +63,17 @@ const RELEASE_ASSETS = { | ... | @@ -63,17 +63,17 @@ const RELEASE_ASSETS = { |
| 63 | iosIcon: fromRepoRoot(BRAND_ASSET_PATHS.productionIconComposerProject), | 63 | iosIcon: fromRepoRoot(BRAND_ASSET_PATHS.productionIconComposerProject), |
| 64 | splashIcon: fromRepoRoot(BRAND_ASSET_PATHS.productionIosIconPng), | 64 | splashIcon: fromRepoRoot(BRAND_ASSET_PATHS.productionIosIconPng), |
| 65 | androidAdaptiveForeground, | 65 | androidAdaptiveForeground, |
| 66 | androidAdaptiveBackgroundColor: "#000000", | 66 | androidAdaptiveBackgroundColor: "#171422", |
| 67 | androidAdaptiveBackgroundImage: undefined, | 67 | androidAdaptiveBackgroundImage: undefined, |
| 68 | androidSplashIcon: "./assets/android-splash-icon-prod.png", | 68 | androidSplashIcon: "../../assets/knife/android-splash.png", |
| 69 | androidMonochromeIcon: "./assets/android-icon-mark.png", | 69 | androidMonochromeIcon: "../../assets/knife/android-foreground.png", |
| 70 | androidNotificationIcon: "./assets/android-notification-icon.png", | 70 | androidNotificationIcon: "../../assets/knife/android-foreground.png", |
| 71 | androidNotificationColor: "#FFFFFF", | 71 | androidNotificationColor: "#FFFFFF", |
| 72 | } as const; | 72 | } as const; |
| 73 | 73 | ||
| 74 | const VARIANT_CONFIG = { | 74 | const VARIANT_CONFIG = { |
| 75 | development: { | 75 | development: { |
| 76 | appName: "T3 Code Dev", | 76 | appName: "The Knife Dev", |
| 77 | scheme: "t3code-dev", | 77 | scheme: "t3code-dev", |
| 78 | iosBundleIdentifier: "com.t3tools.t3code.dev", | 78 | iosBundleIdentifier: "com.t3tools.t3code.dev", |
| 79 | androidPackage: "com.t3tools.t3code.dev", | 79 | androidPackage: "com.t3tools.t3code.dev", |
| ... | @@ -81,7 +81,7 @@ const VARIANT_CONFIG = { | ... | @@ -81,7 +81,7 @@ const VARIANT_CONFIG = { |
| 81 | assets: DEVELOPMENT_ASSETS, | 81 | assets: DEVELOPMENT_ASSETS, |
| 82 | }, | 82 | }, |
| 83 | preview: { | 83 | preview: { |
| 84 | appName: "T3 Code Preview", | 84 | appName: "The Knife Preview", |
| 85 | scheme: "t3code-preview", | 85 | scheme: "t3code-preview", |
| 86 | iosBundleIdentifier: "com.t3tools.t3code.preview", | 86 | iosBundleIdentifier: "com.t3tools.t3code.preview", |
| 87 | androidPackage: "com.t3tools.t3code.preview", | 87 | androidPackage: "com.t3tools.t3code.preview", |
| ... | @@ -89,7 +89,7 @@ const VARIANT_CONFIG = { | ... | @@ -89,7 +89,7 @@ const VARIANT_CONFIG = { |
| 89 | assets: PREVIEW_ASSETS, | 89 | assets: PREVIEW_ASSETS, |
| 90 | }, | 90 | }, |
| 91 | production: { | 91 | production: { |
| 92 | appName: "T3 Code", | 92 | appName: "The Knife", |
| 93 | scheme: "t3code", | 93 | scheme: "t3code", |
| 94 | iosBundleIdentifier: "com.t3tools.t3code", | 94 | iosBundleIdentifier: "com.t3tools.t3code", |
| 95 | androidPackage: "com.t3tools.t3code", | 95 | androidPackage: "com.t3tools.t3code", |
| ... | @@ -134,7 +134,7 @@ const widgetsPlugin: NonNullable<ExpoConfig["plugins"]>[number] = [ | ... | @@ -134,7 +134,7 @@ const widgetsPlugin: NonNullable<ExpoConfig["plugins"]>[number] = [ |
| 134 | { | 134 | { |
| 135 | name: "SubscriptionUsage", | 135 | name: "SubscriptionUsage", |
| 136 | displayName: "Subscription usage", | 136 | displayName: "Subscription usage", |
| 137 | description: "Subscription quotas from your connected T3 Code environments.", | 137 | description: "Subscription quotas from your connected environments.", |
| 138 | ios: { | 138 | ios: { |
| 139 | configuration: { | 139 | configuration: { |
| 140 | title: "Subscription usage", | 140 | title: "Subscription usage", |
| ... | @@ -185,7 +185,7 @@ const widgetsPlugin: NonNullable<ExpoConfig["plugins"]>[number] = [ | ... | @@ -185,7 +185,7 @@ const widgetsPlugin: NonNullable<ExpoConfig["plugins"]>[number] = [ |
| 185 | { | 185 | { |
| 186 | name: "AgentActivity", | 186 | name: "AgentActivity", |
| 187 | displayName: "Agent Activity", | 187 | displayName: "Agent Activity", |
| 188 | description: "Shows the current state of active T3 Code agents.", | 188 | description: "Shows the current state of your active agents.", |
| 189 | // Live Activity companion; there is no Android presentation for it. | 189 | // Live Activity companion; there is no Android presentation for it. |
| 190 | android: null, | 190 | android: null, |
| 191 | ios: { supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"] }, | 191 | ios: { supportedFamilies: ["systemSmall", "systemMedium", "accessoryRectangular"] }, |
| ... | @@ -261,7 +261,7 @@ const config: ExpoConfig = { | ... | @@ -261,7 +261,7 @@ const config: ExpoConfig = { |
| 261 | `webcredentials:${variant.relyingParty}`, | 261 | `webcredentials:${variant.relyingParty}`, |
| 262 | ], | 262 | ], |
| 263 | entitlements: { | 263 | entitlements: { |
| 264 | "keychain-access-groups": [`$(AppIdentifierPrefix)${variant.iosBundleIdentifier}`], | 264 | "keychain-access-groups": [`$(AppIdentifierPrefix)${iosBundleIdentifier}`], |
| 265 | }, | 265 | }, |
| 266 | infoPlist: { | 266 | infoPlist: { |
| 267 | UISupportedInterfaceOrientations: [ | 267 | UISupportedInterfaceOrientations: [ |
| ... | @@ -273,8 +273,8 @@ const config: ExpoConfig = { | ... | @@ -273,8 +273,8 @@ const config: ExpoConfig = { |
| 273 | NSAllowsArbitraryLoads: true, | 273 | NSAllowsArbitraryLoads: true, |
| 274 | }, | 274 | }, |
| 275 | NSLocalNetworkUsageDescription: | 275 | NSLocalNetworkUsageDescription: |
| 276 | "Allow T3 Code to connect to T3 Code servers on your local network or tailnet.", | 276 | "Allow The Knife to connect to The Knife servers on your local network or tailnet.", |
| 277 | NSPhotoLibraryAddUsageDescription: "Allow T3 Code to save images to your photo library.", | 277 | NSPhotoLibraryAddUsageDescription: "Allow The Knife to save images to your photo library.", |
| 278 | // "Audio, AirPlay, and Picture in Picture": the browser screen's system | 278 | // "Audio, AirPlay, and Picture in Picture": the browser screen's system |
| 279 | // picture in picture needs it to start and to stay up outside the app. | 279 | // picture in picture needs it to start and to stay up outside the app. |
| 280 | UIBackgroundModes: ["audio"], | 280 | UIBackgroundModes: ["audio"], |
| ... | @@ -321,6 +321,7 @@ const config: ExpoConfig = { | ... | @@ -321,6 +321,7 @@ const config: ExpoConfig = { |
| 321 | favicon: variant.assets.appIcon, | 321 | favicon: variant.assets.appIcon, |
| 322 | }, | 322 | }, |
| 323 | plugins: [ | 323 | plugins: [ |
| 324 | ...(isIosPersonalTeamBuild ? ["./plugins/withoutIosPersonalTeamCapabilities.cjs"] : []), | ||
| 324 | "expo-asset", | 325 | "expo-asset", |
| 325 | [ | 326 | [ |
| 326 | "expo-font", | 327 | "expo-font", |
| ... | @@ -359,8 +360,6 @@ const config: ExpoConfig = { | ... | @@ -359,8 +360,6 @@ const config: ExpoConfig = { |
| 359 | mode: APP_VARIANT === "development" ? "development" : "production", | 360 | mode: APP_VARIANT === "development" ? "development" : "production", |
| 360 | }, | 361 | }, |
| 361 | ], | 362 | ], |
| 362 | // appleSignIn must be gated here: withoutIosPersonalTeamCapabilities.cjs runs before | ||
| 363 | // plugins earlier in this array, so it cannot strip the entitlement Clerk would add. | ||
| 364 | ["@clerk/expo", { theme: "./clerk-theme.json", appleSignIn: !isIosPersonalTeamBuild }], | 363 | ["@clerk/expo", { theme: "./clerk-theme.json", appleSignIn: !isIosPersonalTeamBuild }], |
| 365 | "expo-web-browser", | 364 | "expo-web-browser", |
| 366 | [ | 365 | [ |
| ... | @@ -382,7 +381,7 @@ const config: ExpoConfig = { | ... | @@ -382,7 +381,7 @@ const config: ExpoConfig = { |
| 382 | [ | 381 | [ |
| 383 | "expo-audio", | 382 | "expo-audio", |
| 384 | { | 383 | { |
| 385 | microphonePermission: "Allow T3 Code to use your microphone for voice input.", | 384 | microphonePermission: "Allow The Knife to use your microphone for voice input.", |
| 386 | recordAudioAndroid: false, | 385 | recordAudioAndroid: false, |
| 387 | enableBackgroundPlayback: false, | 386 | enableBackgroundPlayback: false, |
| 388 | enableBackgroundRecording: false, | 387 | enableBackgroundRecording: false, |
| ... | @@ -391,7 +390,7 @@ const config: ExpoConfig = { | ... | @@ -391,7 +390,7 @@ const config: ExpoConfig = { |
| 391 | [ | 390 | [ |
| 392 | "expo-camera", | 391 | "expo-camera", |
| 393 | { | 392 | { |
| 394 | cameraPermission: "Allow T3 Code to access your camera so you can scan pairing QR codes.", | 393 | cameraPermission: "Allow The Knife to access your camera so you can scan pairing QR codes.", |
| 395 | microphonePermission: false, | 394 | microphonePermission: false, |
| 396 | barcodeScannerEnabled: true, | 395 | barcodeScannerEnabled: true, |
| 397 | recordAudioAndroid: false, | 396 | recordAudioAndroid: false, |
| ... | @@ -466,7 +465,6 @@ const config: ExpoConfig = { | ... | @@ -466,7 +465,6 @@ const config: ExpoConfig = { |
| 466 | "./plugins/withAndroidModernAlertDialog.cjs", | 465 | "./plugins/withAndroidModernAlertDialog.cjs", |
| 467 | "./plugins/withAndroidPredictiveBackCompat.cjs", | 466 | "./plugins/withAndroidPredictiveBackCompat.cjs", |
| 468 | "./plugins/withAndroidTabletOrientation.cjs", | 467 | "./plugins/withAndroidTabletOrientation.cjs", |
| 469 | ...(isIosPersonalTeamBuild ? ["./plugins/withoutIosPersonalTeamCapabilities.cjs"] : []), | ||
| 470 | ], | 468 | ], |
| 471 | extra: { | 469 | extra: { |
| 472 | appVariant: APP_VARIANT, | 470 | appVariant: APP_VARIANT, |
apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorModule.kt+3-3| ... | @@ -18,7 +18,7 @@ internal object T3ComposerClipboard { | ... | @@ -18,7 +18,7 @@ internal object T3ComposerClipboard { |
| 18 | } | 18 | } |
| 19 | val records = payload?.optJSONArray("records") | 19 | val records = payload?.optJSONArray("records") |
| 20 | if (payload == null || records == null) { | 20 | if (payload == null || records == null) { |
| 21 | clipboard.setPrimaryClip(ClipData.newPlainText("T3 Code", text)) | 21 | clipboard.setPrimaryClip(ClipData.newPlainText("The Knife", text)) |
| 22 | return | 22 | return |
| 23 | } | 23 | } |
| 24 | val all = (0 until records.length()).map { records.getJSONObject(it) } | 24 | val all = (0 until records.length()).map { records.getJSONObject(it) } |
| ... | @@ -36,12 +36,12 @@ internal object T3ComposerClipboard { | ... | @@ -36,12 +36,12 @@ internal object T3ComposerClipboard { |
| 36 | clipboard.setPrimaryClip( | 36 | clipboard.setPrimaryClip( |
| 37 | if (selected.isEmpty()) { | 37 | if (selected.isEmpty()) { |
| 38 | ClipData.newPlainText( | 38 | ClipData.newPlainText( |
| 39 | "T3 Code", | 39 | "The Knife", |
| 40 | text | 40 | text |
| 41 | ) | 41 | ) |
| 42 | } else { | 42 | } else { |
| 43 | ClipData.newHtmlText( | 43 | ClipData.newHtmlText( |
| 44 | "T3 Code", | 44 | "The Knife", |
| 45 | text, | 45 | text, |
| 46 | "<pre data-t3-context-fragment=\"$encoded\">$escaped</pre>" | 46 | "<pre data-t3-context-fragment=\"$encoded\">$escaped</pre>" |
| 47 | ) | 47 | ) |
apps/mobile/modules/t3-native-controls/ios/T3LayoutMetricsView.swift+2-2| ... | @@ -14,7 +14,7 @@ final class T3LayoutMetricsView: ExpoView { | ... | @@ -14,7 +14,7 @@ final class T3LayoutMetricsView: ExpoView { |
| 14 | registerForTraitChanges([UITraitHorizontalSizeClass.self, UITraitVerticalSizeClass.self]) { (view: T3LayoutMetricsView, _: UITraitCollection) in | 14 | registerForTraitChanges([UITraitHorizontalSizeClass.self, UITraitVerticalSizeClass.self]) { (view: T3LayoutMetricsView, _: UITraitCollection) in |
| 15 | view.publishMetrics() | 15 | view.publishMetrics() |
| 16 | } | 16 | } |
| 17 | #if compiler(>=6.4) | 17 | #if T3_IOS_27_1_SDK |
| 18 | if #available(iOS 27.1, *) { | 18 | if #available(iOS 27.1, *) { |
| 19 | hasHinge = nil | 19 | hasHinge = nil |
| 20 | addInteraction(UIHingeInteraction { [weak self] _, update in | 20 | addInteraction(UIHingeInteraction { [weak self] _, update in |
| ... | @@ -67,7 +67,7 @@ final class T3LayoutMetricsView: ExpoView { | ... | @@ -67,7 +67,7 @@ final class T3LayoutMetricsView: ExpoView { |
| 67 | } | 67 | } |
| 68 | var verticalBarEdge = "none" | 68 | var verticalBarEdge = "none" |
| 69 | var regions: [[String: Any]] = [] | 69 | var regions: [[String: Any]] = [] |
| 70 | #if compiler(>=6.4) | 70 | #if T3_IOS_27_1_SDK |
| 71 | if #available(iOS 27.1, *) { | 71 | if #available(iOS 27.1, *) { |
| 72 | let rtl = effectiveUserInterfaceLayoutDirection == .rightToLeft | 72 | let rtl = effectiveUserInterfaceLayoutDirection == .rightToLeft |
| 73 | switch traitCollection.verticalBarEdge { | 73 | switch traitCollection.verticalBarEdge { |
apps/mobile/modules/t3-native-controls/ios/T3NativeControls.podspec+6| ... | @@ -15,5 +15,11 @@ Pod::Spec.new do |s| | ... | @@ -15,5 +15,11 @@ Pod::Spec.new do |s| |
| 15 | s.pod_target_xcconfig = { | 15 | s.pod_target_xcconfig = { |
| 16 | 'DEFINES_MODULE' => 'YES', | 16 | 'DEFINES_MODULE' => 'YES', |
| 17 | } | 17 | } |
| 18 | |||
| 19 | ios_sdk_version = Gem::Version.new(`xcrun --sdk iphoneos --show-sdk-version`.strip) | ||
| 20 | if ios_sdk_version >= Gem::Version.new('27.1') | ||
| 21 | s.pod_target_xcconfig['SWIFT_ACTIVE_COMPILATION_CONDITIONS'] = '$(inherited) T3_IOS_27_1_SDK' | ||
| 22 | end | ||
| 23 | |||
| 18 | s.source_files = '**/*.{h,m,mm,swift,hpp,cpp}' | 24 | s.source_files = '**/*.{h,m,mm,swift,hpp,cpp}' |
| 19 | end | 25 | end |
apps/mobile/plugins/withWidgetLogoAsset.cjs+10-8| ... | @@ -1,9 +1,9 @@ | ... | @@ -1,9 +1,9 @@ |
| 1 | "use strict"; | 1 | "use strict"; |
| 2 | 2 | ||
| 3 | // Ships the branded T3 mark to the Live Activity / widget extension. | 3 | // Ships the knife silhouette to the Live Activity / widget extension. |
| 4 | // | 4 | // |
| 5 | // expo-widgets generates ExpoWidgetsTarget without a Resources build phase and | 5 | // expo-widgets generates ExpoWidgetsTarget without a Resources build phase and |
| 6 | // has no asset support, so this plugin (a) writes an SVG template image set into | 6 | // has no asset support, so this plugin (a) writes a template image set into |
| 7 | // the generated widget asset catalog and (b) wires that catalog into the widget | 7 | // the generated widget asset catalog and (b) wires that catalog into the widget |
| 8 | // target with an actool build phase. | 8 | // target with an actool build phase. |
| 9 | // | 9 | // |
| ... | @@ -22,17 +22,16 @@ const { addWidgetAssetCatalog } = require("./lib/addWidgetAssetCatalog.cjs"); | ... | @@ -22,17 +22,16 @@ const { addWidgetAssetCatalog } = require("./lib/addWidgetAssetCatalog.cjs"); |
| 22 | 22 | ||
| 23 | const TARGET_NAME = "ExpoWidgetsTarget"; | 23 | const TARGET_NAME = "ExpoWidgetsTarget"; |
| 24 | const CATALOG_NAME = "Assets.xcassets"; | 24 | const CATALOG_NAME = "Assets.xcassets"; |
| 25 | const IMAGE_SET = "T3Mark.imageset"; | 25 | const IMAGE_SET = "KnifeMark.imageset"; |
| 26 | const SVG_NAME = "T3Mark.svg"; | 26 | const IMAGE_NAME = "knife.png"; |
| 27 | 27 | ||
| 28 | const CATALOG_CONTENTS = JSON.stringify({ info: { author: "expo", version: 1 } }, null, 2) + "\n"; | 28 | const CATALOG_CONTENTS = JSON.stringify({ info: { author: "expo", version: 1 } }, null, 2) + "\n"; |
| 29 | const IMAGE_SET_CONTENTS = | 29 | const IMAGE_SET_CONTENTS = |
| 30 | JSON.stringify( | 30 | JSON.stringify( |
| 31 | { | 31 | { |
| 32 | images: [{ idiom: "universal", filename: SVG_NAME }], | 32 | images: [{ idiom: "universal", filename: IMAGE_NAME }], |
| 33 | info: { author: "expo", version: 1 }, | 33 | info: { author: "expo", version: 1 }, |
| 34 | properties: { | 34 | properties: { |
| 35 | "preserves-vector-representation": true, | ||
| 36 | "template-rendering-intent": "template", | 35 | "template-rendering-intent": "template", |
| 37 | }, | 36 | }, |
| 38 | }, | 37 | }, |
| ... | @@ -44,13 +43,16 @@ function withAssetFiles(config) { | ... | @@ -44,13 +43,16 @@ function withAssetFiles(config) { |
| 44 | return withDangerousMod(config, [ | 43 | return withDangerousMod(config, [ |
| 45 | "ios", | 44 | "ios", |
| 46 | (cfg) => { | 45 | (cfg) => { |
| 47 | const source = path.join(cfg.modRequest.projectRoot, "assets", "widget", SVG_NAME); | 46 | const source = path.resolve( |
| 47 | cfg.modRequest.projectRoot, | ||
| 48 | "../../assets/knife/android-foreground.png", | ||
| 49 | ); | ||
| 48 | const catalogDir = path.join(cfg.modRequest.platformProjectRoot, TARGET_NAME, CATALOG_NAME); | 50 | const catalogDir = path.join(cfg.modRequest.platformProjectRoot, TARGET_NAME, CATALOG_NAME); |
| 49 | const imageSetDir = path.join(catalogDir, IMAGE_SET); | 51 | const imageSetDir = path.join(catalogDir, IMAGE_SET); |
| 50 | fs.mkdirSync(imageSetDir, { recursive: true }); | 52 | fs.mkdirSync(imageSetDir, { recursive: true }); |
| 51 | fs.writeFileSync(path.join(catalogDir, "Contents.json"), CATALOG_CONTENTS); | 53 | fs.writeFileSync(path.join(catalogDir, "Contents.json"), CATALOG_CONTENTS); |
| 52 | fs.writeFileSync(path.join(imageSetDir, "Contents.json"), IMAGE_SET_CONTENTS); | 54 | fs.writeFileSync(path.join(imageSetDir, "Contents.json"), IMAGE_SET_CONTENTS); |
| 53 | fs.copyFileSync(source, path.join(imageSetDir, SVG_NAME)); | 55 | fs.copyFileSync(source, path.join(imageSetDir, IMAGE_NAME)); |
| 54 | return cfg; | 56 | return cfg; |
| 55 | }, | 57 | }, |
| 56 | ]); | 58 | ]); |
apps/mobile/plugins/withoutIosPersonalTeamCapabilities.cjs+1| ... | @@ -4,6 +4,7 @@ module.exports = function withoutIosPersonalTeamCapabilities(config) { | ... | @@ -4,6 +4,7 @@ module.exports = function withoutIosPersonalTeamCapabilities(config) { |
| 4 | return withEntitlementsPlist(config, (modConfig) => { | 4 | return withEntitlementsPlist(config, (modConfig) => { |
| 5 | delete modConfig.modResults["aps-environment"]; | 5 | delete modConfig.modResults["aps-environment"]; |
| 6 | delete modConfig.modResults["com.apple.developer.applesignin"]; | 6 | delete modConfig.modResults["com.apple.developer.applesignin"]; |
| 7 | delete modConfig.modResults["com.apple.developer.associated-domains"]; | ||
| 7 | delete modConfig.modResults["com.apple.security.application-groups"]; | 8 | delete modConfig.modResults["com.apple.security.application-groups"]; |
| 8 | return modConfig; | 9 | return modConfig; |
| 9 | }); | 10 | }); |
apps/mobile/src/Stack.tsx+1-1| ... | @@ -280,7 +280,7 @@ const SettingsContentStack = createV5SheetStackNavigator({ | ... | @@ -280,7 +280,7 @@ const SettingsContentStack = createV5SheetStackNavigator({ |
| 280 | SettingsAbout: createNativeStackScreen({ | 280 | SettingsAbout: createNativeStackScreen({ |
| 281 | screen: SettingsAboutRouteScreen, | 281 | screen: SettingsAboutRouteScreen, |
| 282 | linking: "about", | 282 | linking: "about", |
| 283 | options: { title: "About T3 Code" }, | 283 | options: { title: "About The Knife" }, |
| 284 | }), | 284 | }), |
| 285 | SettingsEnvironmentNew: createNativeStackScreen({ | 285 | SettingsEnvironmentNew: createNativeStackScreen({ |
| 286 | screen: ConnectionsNewRouteScreen, | 286 | screen: ConnectionsNewRouteScreen, |
apps/mobile/src/components/BrandMark.tsx+3-4| ... | @@ -3,7 +3,7 @@ import { Image } from "expo-image"; | ... | @@ -3,7 +3,7 @@ import { Image } from "expo-image"; |
| 3 | import { View } from "react-native"; | 3 | import { View } from "react-native"; |
| 4 | 4 | ||
| 5 | import { AppText as Text } from "./AppText"; | 5 | import { AppText as Text } from "./AppText"; |
| 6 | import { T3_CODE_BRAND_MARK_SOURCE } from "./brandAssets"; | 6 | import { KNIFE_BRAND_MARK_SOURCE } from "./brandAssets"; |
| 7 | 7 | ||
| 8 | const appVariant = Constants.expoConfig?.extra?.appVariant; | 8 | const appVariant = Constants.expoConfig?.extra?.appVariant; |
| 9 | const DEFAULT_STAGE_LABEL = | 9 | const DEFAULT_STAGE_LABEL = |
| ... | @@ -17,17 +17,16 @@ export function BrandMark(props: { readonly compact?: boolean; readonly stageLab | ... | @@ -17,17 +17,16 @@ export function BrandMark(props: { readonly compact?: boolean; readonly stageLab |
| 17 | return ( | 17 | return ( |
| 18 | <View className="flex-row items-center gap-3"> | 18 | <View className="flex-row items-center gap-3"> |
| 19 | <Image | 19 | <Image |
| 20 | source={T3_CODE_BRAND_MARK_SOURCE} | 20 | source={KNIFE_BRAND_MARK_SOURCE} |
| 21 | accessibilityIgnoresInvertColors | 21 | accessibilityIgnoresInvertColors |
| 22 | style={{ | 22 | style={{ |
| 23 | width: iconSize, | 23 | width: iconSize, |
| 24 | height: iconSize, | 24 | height: iconSize, |
| 25 | borderRadius: compact ? 10 : 14, | ||
| 26 | }} | 25 | }} |
| 27 | /> | 26 | /> |
| 28 | <View className="gap-1"> | 27 | <View className="gap-1"> |
| 29 | <View className="flex-row items-center gap-2"> | 28 | <View className="flex-row items-center gap-2"> |
| 30 | <Text className="text-lg font-t3-bold tracking-[-0.4px] text-foreground">T3 Code</Text> | 29 | <Text className="text-lg font-t3-bold tracking-[-0.4px] text-foreground">The Knife</Text> |
| 31 | <View className="rounded-full bg-subtle px-2 py-1"> | 30 | <View className="rounded-full bg-subtle px-2 py-1"> |
| 32 | <Text className="text-3xs font-t3-bold tracking-[1.1px] uppercase text-foreground-muted"> | 31 | <Text className="text-3xs font-t3-bold tracking-[1.1px] uppercase text-foreground-muted"> |
| 33 | {stageLabel} | 32 | {stageLabel} |
apps/mobile/src/components/CompactBrandTitle.tsx+4-4| ... | @@ -4,7 +4,7 @@ import type { NativeStackNavigationOptions } from "@react-navigation/native-stac | ... | @@ -4,7 +4,7 @@ import type { NativeStackNavigationOptions } from "@react-navigation/native-stac |
| 4 | import { Platform, View } from "react-native"; | 4 | import { Platform, View } from "react-native"; |
| 5 | 5 | ||
| 6 | import { AppText as Text } from "./AppText"; | 6 | import { AppText as Text } from "./AppText"; |
| 7 | import { T3Wordmark } from "./T3Wordmark"; | 7 | import { KnifeMark } from "./KnifeMark"; |
| 8 | import { IPAD_HOME_TITLE_OFFSET } from "../lib/layoutMetrics"; | 8 | import { IPAD_HOME_TITLE_OFFSET } from "../lib/layoutMetrics"; |
| 9 | import { resolveMobileStageLabel } from "../lib/mobileBranding"; | 9 | import { resolveMobileStageLabel } from "../lib/mobileBranding"; |
| 10 | import { useAndroidControlSizing } from "./useAndroidControlSizing"; | 10 | import { useAndroidControlSizing } from "./useAndroidControlSizing"; |
| ... | @@ -34,19 +34,19 @@ export function CompactBrandTitle( | ... | @@ -34,19 +34,19 @@ export function CompactBrandTitle( |
| 34 | return ( | 34 | return ( |
| 35 | <View | 35 | <View |
| 36 | aria-level={1} | 36 | aria-level={1} |
| 37 | accessibilityLabel={props.accessibilityLabel ?? "T3 Code, Threads"} | 37 | accessibilityLabel={props.accessibilityLabel ?? "The Knife, Threads"} |
| 38 | accessible | 38 | accessible |
| 39 | role="heading" | 39 | role="heading" |
| 40 | className="flex-row items-center gap-1.5" | 40 | className="flex-row items-center gap-1.5" |
| 41 | style={[{ marginLeft: titleOffset }, Platform.OS === "android" && { gap: 5.25 * scale }]} | 41 | style={[{ marginLeft: titleOffset }, Platform.OS === "android" && { gap: 5.25 * scale }]} |
| 42 | > | 42 | > |
| 43 | <T3Wordmark colorClassName="accent-icon" height={Math.round(15 * scale)} /> | 43 | <KnifeMark height={Math.round(26 * scale)} /> |
| 44 | <Text | 44 | <Text |
| 45 | allowFontScaling={props.allowFontScaling} | 45 | allowFontScaling={props.allowFontScaling} |
| 46 | className="font-t3-medium text-foreground-muted" | 46 | className="font-t3-medium text-foreground-muted" |
| 47 | style={{ fontSize: 21 * scale, letterSpacing: -0.5 * scale }} | 47 | style={{ fontSize: 21 * scale, letterSpacing: -0.5 * scale }} |
| 48 | > | 48 | > |
| 49 | Code | 49 | The Knife |
| 50 | </Text> | 50 | </Text> |
| 51 | <View | 51 | <View |
| 52 | className="rounded-full bg-subtle px-1.5 py-0.5" | 52 | className="rounded-full bg-subtle px-1.5 py-0.5" |
apps/mobile/src/components/KnifeMark.tsx created+14| ... | @@ -0,0 +1,14 @@ | ||
| 1 | import { Image } from "expo-image"; | ||
| 2 | import { KNIFE_BRAND_MARK_SOURCE } from "./brandAssets"; | ||
| 3 | |||
| 4 | export function KnifeMark(props: { readonly height: number }) { | ||
| 5 | return ( | ||
| 6 | <Image | ||
| 7 | source={KNIFE_BRAND_MARK_SOURCE} | ||
| 8 | accessibilityIgnoresInvertColors | ||
| 9 | accessible={false} | ||
| 10 | contentFit="contain" | ||
| 11 | style={{ height: props.height, width: props.height }} | ||
| 12 | /> | ||
| 13 | ); | ||
| 14 | } | ||
apps/mobile/src/components/T3Wordmark.tsx deleted-32| ... | @@ -1,32 +0,0 @@ | ||
| 1 | import type { ColorValue } from "react-native"; | ||
| 2 | import Svg, { Path } from "react-native-svg"; | ||
| 3 | import { withUniwind } from "uniwind"; | ||
| 4 | |||
| 5 | const ThemedPath = withUniwind(Path); | ||
| 6 | |||
| 7 | /** | ||
| 8 | * The "T3" brand mark, matching the desktop sidebar's T3Wordmark SVG | ||
| 9 | * (apps/web Sidebar.tsx). Width derives from the viewBox aspect ratio. | ||
| 10 | */ | ||
| 11 | export function T3Wordmark(props: { | ||
| 12 | readonly height: number; | ||
| 13 | readonly color?: ColorValue; | ||
| 14 | readonly colorClassName?: string; | ||
| 15 | }) { | ||
| 16 | const aspectRatio = 94.3941 / 56.96; | ||
| 17 | return ( | ||
| 18 | <Svg | ||
| 19 | accessibilityLabel="T3" | ||
| 20 | height={props.height} | ||
| 21 | width={props.height * aspectRatio} | ||
| 22 | viewBox="15.5309 37 94.3941 56.96" | ||
| 23 | > | ||
| 24 | <ThemedPath | ||
| 25 | d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z" | ||
| 26 | color={props.color} | ||
| 27 | colorClassName={props.colorClassName} | ||
| 28 | fill="currentColor" | ||
| 29 | /> | ||
| 30 | </Svg> | ||
| 31 | ); | ||
| 32 | } | ||
apps/mobile/src/components/brandAssets.ts+1-10| ... | @@ -1,10 +1 @@ | ... | @@ -1,10 +1 @@ |
| 1 | import Constants from "expo-constants"; | 1 | export const KNIFE_BRAND_MARK_SOURCE = require("../../../../assets/knife/apple-touch-180.png"); |
| 2 | |||
| 3 | const appVariant = Constants.expoConfig?.extra?.appVariant; | ||
| 4 | |||
| 5 | export const T3_CODE_BRAND_MARK_SOURCE = | ||
| 6 | appVariant === "development" | ||
| 7 | ? require("../../../../assets/dev/blueprint-ios-1024.png") | ||
| 8 | : appVariant === "preview" | ||
| 9 | ? require("../../../../assets/nightly/nightly-ios-1024.png") | ||
| 10 | : require("../../../../assets/prod/black-ios-1024.png"); |
apps/mobile/src/features/agent-awareness/remoteRegistration.ts+1-1| ... | @@ -534,7 +534,7 @@ function armAgentAwarenessLiveActivityForLocalWorkNow(input: { | ... | @@ -534,7 +534,7 @@ function armAgentAwarenessLiveActivityForLocalWorkNow(input: { |
| 534 | const nowIso = new Date(Date.now()).toISOString(); | 534 | const nowIso = new Date(Date.now()).toISOString(); |
| 535 | const activity = startAgentLiveActivity( | 535 | const activity = startAgentLiveActivity( |
| 536 | { | 536 | { |
| 537 | title: "T3 Code", | 537 | title: "The Knife", |
| 538 | subtitle: "Agent work in progress", | 538 | subtitle: "Agent work in progress", |
| 539 | activeCount: 1, | 539 | activeCount: 1, |
| 540 | updatedAt: nowIso, | 540 | updatedAt: nowIso, |
apps/mobile/src/features/diagnostics/crash-log-model.test.ts+2-2| ... | @@ -61,14 +61,14 @@ describe("formatStartupCrashReport", () => { | ... | @@ -61,14 +61,14 @@ describe("formatStartupCrashReport", () => { |
| 61 | version: "1.1.1", | 61 | version: "1.1.1", |
| 62 | build: "56", | 62 | build: "56", |
| 63 | }); | 63 | }); |
| 64 | expect(report.startsWith("T3 Code 1.1.1 (56)\n")).toBe(true); | 64 | expect(report.startsWith("The Knife 1.1.1 (56)\n")).toBe(true); |
| 65 | expect(report).toContain("2026-09-13T05:35:52.000Z"); | 65 | expect(report).toContain("2026-09-13T05:35:52.000Z"); |
| 66 | expect(report).toContain("at NewTaskFlowProvider"); | 66 | expect(report).toContain("at NewTaskFlowProvider"); |
| 67 | }); | 67 | }); |
| 68 | 68 | ||
| 69 | it("says so when nothing was recorded", () => { | 69 | it("says so when nothing was recorded", () => { |
| 70 | expect(formatStartupCrashReport([], { version: "1.1.1", build: "56" })).toBe( | 70 | expect(formatStartupCrashReport([], { version: "1.1.1", build: "56" })).toBe( |
| 71 | "T3 Code 1.1.1 (56)\nNo startup crashes recorded.", | 71 | "The Knife 1.1.1 (56)\nNo startup crashes recorded.", |
| 72 | ); | 72 | ); |
| 73 | }); | 73 | }); |
| 74 | }); | 74 | }); |
apps/mobile/src/features/diagnostics/crash-log-model.ts+1-1| ... | @@ -72,7 +72,7 @@ export function formatStartupCrashReport( | ... | @@ -72,7 +72,7 @@ export function formatStartupCrashReport( |
| 72 | records: ReadonlyArray<StartupCrashRecord>, | 72 | records: ReadonlyArray<StartupCrashRecord>, |
| 73 | app: { readonly version: string; readonly build: string }, | 73 | app: { readonly version: string; readonly build: string }, |
| 74 | ): string { | 74 | ): string { |
| 75 | const header = `T3 Code ${app.version} (${app.build})`; | 75 | const header = `The Knife ${app.version} (${app.build})`; |
| 76 | if (records.length === 0) return `${header}\nNo startup crashes recorded.`; | 76 | if (records.length === 0) return `${header}\nNo startup crashes recorded.`; |
| 77 | return [ | 77 | return [ |
| 78 | header, | 78 | header, |
apps/mobile/src/features/pull-requests/MaterialPullRequestToolbar.android.tsx+1-1| ... | @@ -69,7 +69,7 @@ export function MaterialPullRequestToolbar(props: { readonly onRequestVisibility | ... | @@ -69,7 +69,7 @@ export function MaterialPullRequestToolbar(props: { readonly onRequestVisibility |
| 69 | brand={ | 69 | brand={ |
| 70 | <CompactBrandTitle | 70 | <CompactBrandTitle |
| 71 | allowFontScaling={false} | 71 | allowFontScaling={false} |
| 72 | accessibilityLabel="T3 Code, Pull requests" | 72 | accessibilityLabel="The Knife, Pull requests" |
| 73 | /> | 73 | /> |
| 74 | } | 74 | } |
| 75 | onPress={() => | 75 | onPress={() => |
apps/mobile/src/features/pull-requests/pullRequestAgentTasks.ts+1-1| ... | @@ -23,7 +23,7 @@ export function pullRequestAgentPrompt(task: PullRequestAgentTask): string { | ... | @@ -23,7 +23,7 @@ export function pullRequestAgentPrompt(task: PullRequestAgentTask): string { |
| 23 | case "conflicts": | 23 | case "conflicts": |
| 24 | 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."; | 24 | 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."; |
| 25 | case "watch": | 25 | case "watch": |
| 26 | 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."; | 26 | 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."; |
| 27 | } | 27 | } |
| 28 | } | 28 | } |
| 29 | 29 |
apps/mobile/src/features/settings/SettingsAboutRouteScreen.tsx+1-1| ... | @@ -20,7 +20,7 @@ export function SettingsAboutRouteScreen() { | ... | @@ -20,7 +20,7 @@ export function SettingsAboutRouteScreen() { |
| 20 | const insets = useSafeAreaInsets(); | 20 | const insets = useSafeAreaInsets(); |
| 21 | 21 | ||
| 22 | return ( | 22 | return ( |
| 23 | <SettingsScreen title="About T3 Code"> | 23 | <SettingsScreen title="About The Knife"> |
| 24 | <ScrollView | 24 | <ScrollView |
| 25 | contentInsetAdjustmentBehavior="automatic" | 25 | contentInsetAdjustmentBehavior="automatic" |
| 26 | showsVerticalScrollIndicator={false} | 26 | showsVerticalScrollIndicator={false} |
apps/mobile/src/features/settings/SettingsEnvironmentDetailRouteScreen.tsx+3-3| ... | @@ -113,7 +113,7 @@ function EnvironmentDetail({ environmentId }: { readonly environmentId: Environm | ... | @@ -113,7 +113,7 @@ function EnvironmentDetail({ environmentId }: { readonly environmentId: Environm |
| 113 | return; | 113 | return; |
| 114 | Alert.alert( | 114 | Alert.alert( |
| 115 | `Update ${environment?.environmentLabel ?? "environment"}?`, | 115 | `Update ${environment?.environmentLabel ?? "environment"}?`, |
| 116 | `Install T3 Code ${targetVersion}. ${capabilities.serverSelfUpdate === "desktop-managed" ? "The desktop app will close and relaunch." : "The server will restart and reconnect."} Running threads may be interrupted.`, | 116 | `Install The Knife ${targetVersion}. ${capabilities.serverSelfUpdate === "desktop-managed" ? "The desktop app will close and relaunch." : "The server will restart and reconnect."} Running threads may be interrupted.`, |
| 117 | [ | 117 | [ |
| 118 | { text: "Cancel", style: "cancel" }, | 118 | { text: "Cancel", style: "cancel" }, |
| 119 | { | 119 | { |
| ... | @@ -227,7 +227,7 @@ function EnvironmentDetail({ environmentId }: { readonly environmentId: Environm | ... | @@ -227,7 +227,7 @@ function EnvironmentDetail({ environmentId }: { readonly environmentId: Environm |
| 227 | {notice ? <Text className="px-2 text-sm text-foreground-muted">{notice}</Text> : null} | 227 | {notice ? <Text className="px-2 text-sm text-foreground-muted">{notice}</Text> : null} |
| 228 | {config ? ( | 228 | {config ? ( |
| 229 | <> | 229 | <> |
| 230 | <SettingsSection title="T3 Code"> | 230 | <SettingsSection title="The Knife"> |
| 231 | <View className="gap-1 p-4"> | 231 | <View className="gap-1 p-4"> |
| 232 | <Text className="text-base text-foreground">Version {version}</Text> | 232 | <Text className="text-base text-foreground">Version {version}</Text> |
| 233 | {running ? ( | 233 | {running ? ( |
| ... | @@ -252,7 +252,7 @@ function EnvironmentDetail({ environmentId }: { readonly environmentId: Environm | ... | @@ -252,7 +252,7 @@ function EnvironmentDetail({ environmentId }: { readonly environmentId: Environm |
| 252 | <Text className="text-sm text-foreground-muted"> | 252 | <Text className="text-sm text-foreground-muted"> |
| 253 | {capabilities?.serverSelfUpdate === "desktop-managed" | 253 | {capabilities?.serverSelfUpdate === "desktop-managed" |
| 254 | ? "Update the desktop app on this machine." | 254 | ? "Update the desktop app on this machine." |
| 255 | : "Update and restart T3 Code on this machine."} | 255 | : "Update and restart The Knife on this machine."} |
| 256 | </Text> | 256 | </Text> |
| 257 | ) : null} | 257 | ) : null} |
| 258 | </View> | 258 | </View> |
apps/mobile/src/features/settings/SettingsMicrophoneRouteScreen.tsx+1-1| ... | @@ -166,7 +166,7 @@ export function SettingsMicrophoneRouteScreen() { | ... | @@ -166,7 +166,7 @@ export function SettingsMicrophoneRouteScreen() { |
| 166 | <Text className="px-2 text-sm text-foreground-muted"> | 166 | <Text className="px-2 text-sm text-foreground-muted"> |
| 167 | {permission === "ask" | 167 | {permission === "ask" |
| 168 | ? "Voice input needs microphone access before it can record or list your microphones." | 168 | ? "Voice input needs microphone access before it can record or list your microphones." |
| 169 | : "Microphone access is off for T3 Code. Turn it on in Settings to use voice input."} | 169 | : "Microphone access is off for The Knife. Turn it on in Settings to use voice input."} |
| 170 | </Text> | 170 | </Text> |
| 171 | </> | 171 | </> |
| 172 | ) : null} | 172 | ) : null} |
apps/mobile/src/features/settings/SettingsNotificationsRouteScreen.tsx+2-2| ... | @@ -338,7 +338,7 @@ function ConfiguredSettingsNotificationsRouteScreen() { | ... | @@ -338,7 +338,7 @@ function ConfiguredSettingsNotificationsRouteScreen() { |
| 338 | 338 | ||
| 339 | Alert.alert( | 339 | Alert.alert( |
| 340 | "Disable notifications", | 340 | "Disable notifications", |
| 341 | "Open system Settings to disable notifications for T3 Code.", | 341 | "Open system Settings to disable notifications for The Knife.", |
| 342 | [ | 342 | [ |
| 343 | { text: "Cancel", style: "cancel" }, | 343 | { text: "Cancel", style: "cancel" }, |
| 344 | { text: "Open Settings", onPress: () => void Linking.openSettings() }, | 344 | { text: "Open Settings", onPress: () => void Linking.openSettings() }, |
| ... | @@ -492,7 +492,7 @@ function ConfiguredSettingsNotificationsRouteScreen() { | ... | @@ -492,7 +492,7 @@ function ConfiguredSettingsNotificationsRouteScreen() { |
| 492 | void openAndroidLiveUpdateSettings().catch(() => { | 492 | void openAndroidLiveUpdateSettings().catch(() => { |
| 493 | Alert.alert( | 493 | Alert.alert( |
| 494 | "Couldn't open Settings", | 494 | "Couldn't open Settings", |
| 495 | "Open Android Settings, select T3 Code, then enable Live Updates in Notifications.", | 495 | "Open Android Settings, select The Knife, then enable Live Updates in Notifications.", |
| 496 | ); | 496 | ); |
| 497 | }); | 497 | }); |
| 498 | }} | 498 | }} |
apps/mobile/src/features/settings/SettingsRouteScreen.tsx+1-1| ... | @@ -215,7 +215,7 @@ function SettingsIndexSections() { | ... | @@ -215,7 +215,7 @@ function SettingsIndexSections() { |
| 215 | 215 | ||
| 216 | <SettingsSection title="App"> | 216 | <SettingsSection title="App"> |
| 217 | <SettingsRow icon="chart.bar.xaxis" label="Usage" target="SettingsUsage" /> | 217 | <SettingsRow icon="chart.bar.xaxis" label="Usage" target="SettingsUsage" /> |
| 218 | <SettingsRow icon="info.circle" label="About T3 Code" target="SettingsAbout" /> | 218 | <SettingsRow icon="info.circle" label="About The Knife" target="SettingsAbout" /> |
| 219 | </SettingsSection> | 219 | </SettingsSection> |
| 220 | </> | 220 | </> |
| 221 | ); | 221 | ); |
apps/mobile/src/features/showcase/showcaseAgentActivity.ts+1-1| ... | @@ -74,7 +74,7 @@ export function buildShowcaseAgentActivity( | ... | @@ -74,7 +74,7 @@ export function buildShowcaseAgentActivity( |
| 74 | }); | 74 | }); |
| 75 | } | 75 | } |
| 76 | return { | 76 | return { |
| 77 | title: "T3 Code", | 77 | title: "The Knife", |
| 78 | subtitle: "Agent work in progress", | 78 | subtitle: "Agent work in progress", |
| 79 | activeCount: rows.filter((row) => ACTIVE_PHASES.has(row.phase)).length, | 79 | activeCount: rows.filter((row) => ACTIVE_PHASES.has(row.phase)).length, |
| 80 | updatedAt: new Date(now).toISOString(), | 80 | updatedAt: new Date(now).toISOString(), |
apps/mobile/src/features/threads/ThreadComposer.tsx+1-1| ... | @@ -636,7 +636,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer | ... | @@ -636,7 +636,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer |
| 636 | armAgentAwarenessLiveActivityForLocalWork({ | 636 | armAgentAwarenessLiveActivityForLocalWork({ |
| 637 | environmentId: props.environmentId, | 637 | environmentId: props.environmentId, |
| 638 | threadTitle: props.selectedThread.title, | 638 | threadTitle: props.selectedThread.title, |
| 639 | projectTitle: props.environmentLabel ?? "T3 Code", | 639 | projectTitle: props.environmentLabel ?? "The Knife", |
| 640 | }); | 640 | }); |
| 641 | } finally { | 641 | } finally { |
| 642 | inFlightThreadIdsRef.current.delete(threadKey); | 642 | inFlightThreadIdsRef.current.delete(threadKey); |
apps/mobile/src/features/threads/ThreadFeed.tsx+1-1| ... | @@ -1200,7 +1200,7 @@ function renderFeedEntry( | ... | @@ -1200,7 +1200,7 @@ function renderFeedEntry( |
| 1200 | </Text> | 1200 | </Text> |
| 1201 | ) : presentation.attribution === "t3code" ? ( | 1201 | ) : presentation.attribution === "t3code" ? ( |
| 1202 | <Text className="mb-1 pr-1 font-t3-medium text-2xs text-foreground-muted opacity-60"> | 1202 | <Text className="mb-1 pr-1 font-t3-medium text-2xs text-foreground-muted opacity-60"> |
| 1203 | Sent by T3 Code | 1203 | Sent by The Knife |
| 1204 | </Text> | 1204 | </Text> |
| 1205 | ) : presentation.attribution === "agent" ? ( | 1205 | ) : presentation.attribution === "agent" ? ( |
| 1206 | <AgentMessageAttribution | 1206 | <AgentMessageAttribution |
apps/mobile/src/features/threads/thread-work-log.tsx+2-2| ... | @@ -57,7 +57,7 @@ import { isJavaScriptReplTool } from "@t3tools/shared/toolActivity"; | ... | @@ -57,7 +57,7 @@ import { isJavaScriptReplTool } from "@t3tools/shared/toolActivity"; |
| 57 | import { useMarkdownCodeHighlight } from "./markdownCodeHighlightState"; | 57 | import { useMarkdownCodeHighlight } from "./markdownCodeHighlightState"; |
| 58 | 58 | ||
| 59 | import { AppText as Text } from "../../components/AppText"; | 59 | import { AppText as Text } from "../../components/AppText"; |
| 60 | import { T3Wordmark } from "../../components/T3Wordmark"; | 60 | import { KnifeMark } from "../../components/KnifeMark"; |
| 61 | import { BrowserAppIcon } from "../../components/BrowserAppIcon"; | 61 | import { BrowserAppIcon } from "../../components/BrowserAppIcon"; |
| 62 | import { cn } from "../../lib/cn"; | 62 | import { cn } from "../../lib/cn"; |
| 63 | import { THREAD_WORK_ROW_MIN_HEIGHT, type deriveThreadWorkLogSizing } from "../../lib/layout"; | 63 | import { THREAD_WORK_ROW_MIN_HEIGHT, type deriveThreadWorkLogSizing } from "../../lib/layout"; |
| ... | @@ -131,7 +131,7 @@ function WorkLogIcon(props: { | ... | @@ -131,7 +131,7 @@ function WorkLogIcon(props: { |
| 131 | const colorClassName = props.highlighted ? "accent-foreground" : props.colorClassName; | 131 | const colorClassName = props.highlighted ? "accent-foreground" : props.colorClassName; |
| 132 | if (props.icon === "t3-code") { | 132 | if (props.icon === "t3-code") { |
| 133 | return ( | 133 | return ( |
| 134 | <T3Wordmark height={10} {...(colorClassName ? { colorClassName } : { color: props.color })} /> | 134 | <KnifeMark height={14} /> |
| 135 | ); | 135 | ); |
| 136 | } | 136 | } |
| 137 | if (props.icon === "computer") { | 137 | if (props.icon === "computer") { |
apps/mobile/src/features/workspace/WorkspaceRootScreen.tsx+2-2| ... | @@ -42,7 +42,7 @@ function WorkspaceRootTitle(props: { | ... | @@ -42,7 +42,7 @@ function WorkspaceRootTitle(props: { |
| 42 | <View style={{ width: props.maxWidth, minHeight: 32, justifyContent: "center" }}> | 42 | <View style={{ width: props.maxWidth, minHeight: 32, justifyContent: "center" }}> |
| 43 | <WorkspaceConnectionTitle | 43 | <WorkspaceConnectionTitle |
| 44 | brand={ | 44 | brand={ |
| 45 | <CompactBrandTitle accessibilityLabel={`T3 Code, ${TAB_TITLES[state.selectedTab]}`} /> | 45 | <CompactBrandTitle accessibilityLabel={`The Knife, ${TAB_TITLES[state.selectedTab]}`} /> |
| 46 | } | 46 | } |
| 47 | maxWidth={props.maxWidth} | 47 | maxWidth={props.maxWidth} |
| 48 | onPress={props.onOpenEnvironments} | 48 | onPress={props.onOpenEnvironments} |
| ... | @@ -114,7 +114,7 @@ function WorkspaceRootContent() { | ... | @@ -114,7 +114,7 @@ function WorkspaceRootContent() { |
| 114 | /> | 114 | /> |
| 115 | ) : ( | 115 | ) : ( |
| 116 | <AndroidScreenHeader | 116 | <AndroidScreenHeader |
| 117 | title="T3 Code" | 117 | title="The Knife" |
| 118 | titleContent={renderHeaderTitle()} | 118 | titleContent={renderHeaderTitle()} |
| 119 | actions={[ | 119 | actions={[ |
| 120 | { | 120 | { |
apps/mobile/src/lib/authClientMetadata.ts+1-1| ... | @@ -7,7 +7,7 @@ export function authClientMetadata(appVersion?: string): AuthClientPresentationM | ... | @@ -7,7 +7,7 @@ export function authClientMetadata(appVersion?: string): AuthClientPresentationM |
| 7 | const deviceModel = Device.modelName?.trim(); | 7 | const deviceModel = Device.modelName?.trim(); |
| 8 | 8 | ||
| 9 | return { | 9 | return { |
| 10 | label: "T3 Code Mobile", | 10 | label: "The Knife Mobile", |
| 11 | deviceType: | 11 | deviceType: |
| 12 | Device.deviceType === Device.DeviceType.TABLET | 12 | Device.deviceType === Device.DeviceType.TABLET |
| 13 | ? "tablet" | 13 | ? "tablet" |
apps/mobile/src/lib/connection.test.ts+1-1| ... | @@ -40,7 +40,7 @@ describe("mobile remote connection records", () => { | ... | @@ -40,7 +40,7 @@ describe("mobile remote connection records", () => { |
| 40 | 40 | ||
| 41 | it("identifies mobile token exchanges for authorized-client presentation", () => { | 41 | it("identifies mobile token exchanges for authorized-client presentation", () => { |
| 42 | expect(authClientMetadata()).toEqual({ | 42 | expect(authClientMetadata()).toEqual({ |
| 43 | label: "T3 Code Mobile", | 43 | label: "The Knife Mobile", |
| 44 | deviceType: "mobile", | 44 | deviceType: "mobile", |
| 45 | os: "iOS", | 45 | os: "iOS", |
| 46 | osMajorVersion: 18, | 46 | osMajorVersion: 18, |
apps/mobile/src/lib/mobileTheme.ts+1-1| ... | @@ -26,7 +26,7 @@ export const MOBILE_THEME_OPTIONS: ReadonlyArray<{ | ... | @@ -26,7 +26,7 @@ export const MOBILE_THEME_OPTIONS: ReadonlyArray<{ |
| 26 | readonly id: MobileThemeId; | 26 | readonly id: MobileThemeId; |
| 27 | readonly label: string; | 27 | readonly label: string; |
| 28 | }> = [ | 28 | }> = [ |
| 29 | { id: DEFAULT_MOBILE_THEME_ID, label: "T3 Code" }, | 29 | { id: DEFAULT_MOBILE_THEME_ID, label: "The Knife" }, |
| 30 | { id: "material-you", label: "Material You" }, | 30 | { id: "material-you", label: "Material You" }, |
| 31 | ...BUILT_IN_THEMES.map((theme) => ({ id: theme.id as MobileThemeId, label: theme.label })), | 31 | ...BUILT_IN_THEMES.map((theme) => ({ id: theme.id as MobileThemeId, label: theme.label })), |
| 32 | ]; | 32 | ]; |
apps/mobile/src/widgets/AgentActivity.tsx+2-8| ... | @@ -244,15 +244,9 @@ export function AgentActivity( | ... | @@ -244,15 +244,9 @@ export function AgentActivity( |
| 244 | </HStack> | 244 | </HStack> |
| 245 | ); | 245 | ); |
| 246 | 246 | ||
| 247 | // The branded T3 mark. `assetName` resolves the template image set bundled in | ||
| 248 | // the widget extension's asset catalog. Image views only honor `resizable` | ||
| 249 | // directly (frame/foregroundStyle are dropped), so we size it via a container | ||
| 250 | // frame the resizable image fills and tint it through the container's | ||
| 251 | // foreground style, which the template image inherits. The 3:2 frame matches | ||
| 252 | // the glyph's aspect ratio so it never distorts. | ||
| 253 | const renderLogo = (height: number, color: Foreground) => ( | 247 | const renderLogo = (height: number, color: Foreground) => ( |
| 254 | <HStack modifiers={[frame({ width: height * 1.5, height }), foregroundStyle(color)]}> | 248 | <HStack modifiers={[frame({ width: height, height }), foregroundStyle(color)]}> |
| 255 | <Image assetName="T3Mark" modifiers={[resizable()]} /> | 249 | <Image assetName="KnifeMark" modifiers={[resizable()]} /> |
| 256 | </HStack> | 250 | </HStack> |
| 257 | ); | 251 | ); |
| 258 | 252 |
apps/mobile/src/widgets/SubscriptionUsage.android.tsx+17-5| ... | @@ -29,8 +29,20 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi | ... | @@ -29,8 +29,20 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi |
| 29 | }); | 29 | }); |
| 30 | const muted = colors.onSurfaceVariant; | 30 | const muted = colors.onSurfaceVariant; |
| 31 | const providers = props.providers ?? [ | 31 | const providers = props.providers ?? [ |
| 32 | { name: "Codex", detail: "Open T3 to connect", windows: [], expiresAt: 0, totalWindows: 0 }, | 32 | { |
| 33 | { name: "Claude", detail: "Open T3 to connect", windows: [], expiresAt: 0, totalWindows: 0 }, | 33 | name: "Codex", |
| 34 | detail: "Open The Knife to connect", | ||
| 35 | windows: [], | ||
| 36 | expiresAt: 0, | ||
| 37 | totalWindows: 0, | ||
| 38 | }, | ||
| 39 | { | ||
| 40 | name: "Claude", | ||
| 41 | detail: "Open The Knife to connect", | ||
| 42 | windows: [], | ||
| 43 | expiresAt: 0, | ||
| 44 | totalWindows: 0, | ||
| 45 | }, | ||
| 34 | ]; | 46 | ]; |
| 35 | return ( | 47 | return ( |
| 36 | // The card is one Button so a tap reaches the app's interaction listener, | 48 | // 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 | ... | @@ -60,7 +72,7 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi |
| 60 | </Text> | 72 | </Text> |
| 61 | {shown.length === 0 ? ( | 73 | {shown.length === 0 ? ( |
| 62 | <Text color={muted} maxLines={1} style={{ fontSize: 11 }}> | 74 | <Text color={muted} maxLines={1} style={{ fontSize: 11 }}> |
| 63 | {stale ? "Open T3 to refresh" : provider.detail} | 75 | {stale ? "Open The Knife to refresh" : provider.detail} |
| 64 | </Text> | 76 | </Text> |
| 65 | ) : null} | 77 | ) : null} |
| 66 | {shown.map((window) => { | 78 | {shown.map((window) => { |
| ... | @@ -90,7 +102,7 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi | ... | @@ -90,7 +102,7 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi |
| 90 | })} | 102 | })} |
| 91 | {hidden > 0 ? ( | 103 | {hidden > 0 ? ( |
| 92 | <Text color={muted} maxLines={1} style={{ fontSize: 10 }}> | 104 | <Text color={muted} maxLines={1} style={{ fontSize: 10 }}> |
| 93 | {`${hidden} more in T3`} | 105 | {`${hidden} more in The Knife`} |
| 94 | </Text> | 106 | </Text> |
| 95 | ) : null} | 107 | ) : null} |
| 96 | </Column> | 108 | </Column> |
| ... | @@ -104,7 +116,7 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi | ... | @@ -104,7 +116,7 @@ export function SubscriptionUsage(props: SubscriptionUsageProps, environment: Wi |
| 104 | > | 116 | > |
| 105 | {props.checkedAt | 117 | {props.checkedAt |
| 106 | ? `As of ${new Date(props.checkedAt).toLocaleString(undefined, { hour: "numeric", minute: "2-digit", month: "short", day: "numeric" })}` | 118 | ? `As of ${new Date(props.checkedAt).toLocaleString(undefined, { hour: "numeric", minute: "2-digit", month: "short", day: "numeric" })}` |
| 107 | : "Tap to connect in T3"} | 119 | : "Tap to connect in The Knife"} |
| 108 | </Text> | 120 | </Text> |
| 109 | </Column> | 121 | </Column> |
| 110 | </Button> | 122 | </Button> |
apps/mobile/src/widgets/SubscriptionUsage.tsx+6-6| ... | @@ -39,8 +39,8 @@ function SubscriptionUsage( | ... | @@ -39,8 +39,8 @@ function SubscriptionUsage( |
| 39 | const monochrome = | 39 | const monochrome = |
| 40 | environment.widgetRenderingMode !== "fullColor" || environment.isLuminanceReduced; | 40 | environment.widgetRenderingMode !== "fullColor" || environment.isLuminanceReduced; |
| 41 | const providers = props.providers ?? [ | 41 | const providers = props.providers ?? [ |
| 42 | { name: "Codex", detail: "Open T3 to connect", windows: [], expiresAt: 0 }, | 42 | { name: "Codex", detail: "Open The Knife to connect", windows: [], expiresAt: 0 }, |
| 43 | { name: "Claude", detail: "Open T3 to connect", windows: [], expiresAt: 0 }, | 43 | { name: "Claude", detail: "Open The Knife to connect", windows: [], expiresAt: 0 }, |
| 44 | ]; | 44 | ]; |
| 45 | const columns = providers.map((provider) => { | 45 | const columns = providers.map((provider) => { |
| 46 | const stale = provider.windows.length > 0 && now >= provider.expiresAt; | 46 | const stale = provider.windows.length > 0 && now >= provider.expiresAt; |
| ... | @@ -73,7 +73,7 @@ function SubscriptionUsage( | ... | @@ -73,7 +73,7 @@ function SubscriptionUsage( |
| 73 | ].slice(0, limit) | 73 | ].slice(0, limit) |
| 74 | : windows.slice(0, limit); | 74 | : windows.slice(0, limit); |
| 75 | const detail = stale | 75 | const detail = stale |
| 76 | ? "Open T3 to refresh" | 76 | ? "Open The Knife to refresh" |
| 77 | : period !== "auto" && windows.length === 0 && provider.windows.length > 0 | 77 | : period !== "auto" && windows.length === 0 && provider.windows.length > 0 |
| 78 | ? `No ${period} limit reported` | 78 | ? `No ${period} limit reported` |
| 79 | : provider.detail; | 79 | : provider.detail; |
| ... | @@ -121,7 +121,7 @@ function SubscriptionUsage( | ... | @@ -121,7 +121,7 @@ function SubscriptionUsage( |
| 121 | ? `${tightest.remaining}% left` | 121 | ? `${tightest.remaining}% left` |
| 122 | : period !== "auto" && !stale && provider.windows.length > 0 | 122 | : period !== "auto" && !stale && provider.windows.length > 0 |
| 123 | ? "N/A" | 123 | ? "N/A" |
| 124 | : "Open T3"} | 124 | : "Open The Knife"} |
| 125 | </Text> | 125 | </Text> |
| 126 | </HStack> | 126 | </HStack> |
| 127 | {tightest ? ( | 127 | {tightest ? ( |
| ... | @@ -223,7 +223,7 @@ function SubscriptionUsage( | ... | @@ -223,7 +223,7 @@ function SubscriptionUsage( |
| 223 | > | 223 | > |
| 224 | {(period === "auto" ? (provider.totalWindows ?? windows.length) : windows.length) - | 224 | {(period === "auto" ? (provider.totalWindows ?? windows.length) : windows.length) - |
| 225 | limit}{" "} | 225 | limit}{" "} |
| 226 | more in T3 | 226 | more in The Knife |
| 227 | </Text> | 227 | </Text> |
| 228 | ) : null} | 228 | ) : null} |
| 229 | </VStack> | 229 | </VStack> |
| ... | @@ -255,7 +255,7 @@ function SubscriptionUsage( | ... | @@ -255,7 +255,7 @@ function SubscriptionUsage( |
| 255 | > | 255 | > |
| 256 | {props.checkedAt | 256 | {props.checkedAt |
| 257 | ? `As of ${new Date(props.checkedAt).toLocaleString(undefined, { hour: "numeric", minute: "2-digit", month: "short", day: "numeric" })}` | 257 | ? `As of ${new Date(props.checkedAt).toLocaleString(undefined, { hour: "numeric", minute: "2-digit", month: "short", day: "numeric" })}` |
| 258 | : "Tap to connect in T3"} | 258 | : "Tap to connect in The Knife"} |
| 259 | </Text> | 259 | </Text> |
| 260 | ) : null} | 260 | ) : null} |
| 261 | </VStack> | 261 | </VStack> |
apps/mobile/src/widgets/publishSubscriptionUsage.android.ts+1-2| ... | @@ -22,8 +22,7 @@ export async function publishSubscriptionUsage(snapshot: SubscriptionUsageSnapsh | ... | @@ -22,8 +22,7 @@ export async function publishSubscriptionUsage(snapshot: SubscriptionUsageSnapsh |
| 22 | }); | 22 | }); |
| 23 | } | 23 | } |
| 24 | widget.updateSnapshot(snapshot); | 24 | widget.updateSnapshot(snapshot); |
| 25 | // Android has no timeline; an alarm re-renders the stored snapshot at each | 25 | // Android has no timeline; an alarm re-renders the stored snapshot at each deadline. |
| 26 | // deadline so stale readings flip to "Open T3 to refresh" unattended. | ||
| 27 | requireOptionalNativeModule<{ schedule: (name: string, deadlines: number[]) => void }>( | 26 | requireOptionalNativeModule<{ schedule: (name: string, deadlines: number[]) => void }>( |
| 28 | "T3WidgetExpiry", | 27 | "T3WidgetExpiry", |
| 29 | )?.schedule( | 28 | )?.schedule( |
apps/mobile/src/widgets/subscriptionUsageSnapshot.ts+2-2| ... | @@ -94,7 +94,7 @@ function subscriptionUsageProps( | ... | @@ -94,7 +94,7 @@ function subscriptionUsageProps( |
| 94 | return { | 94 | return { |
| 95 | name, | 95 | name, |
| 96 | detail: !fresh | 96 | detail: !fresh |
| 97 | ? "Open T3 to refresh" | 97 | ? "Open The Knife to refresh" |
| 98 | : pool.accounts.length > 1 | 98 | : pool.accounts.length > 1 |
| 99 | ? `${pool.accounts.length} accounts · pooled` | 99 | ? `${pool.accounts.length} accounts · pooled` |
| 100 | : "Subscription remaining", | 100 | : "Subscription remaining", |
| ... | @@ -161,7 +161,7 @@ export function subscriptionUsageTimeline(snapshot: SubscriptionUsageSnapshot, n | ... | @@ -161,7 +161,7 @@ export function subscriptionUsageTimeline(snapshot: SubscriptionUsageSnapshot, n |
| 161 | ...snapshot, | 161 | ...snapshot, |
| 162 | providers: snapshot.providers.map((provider) => | 162 | providers: snapshot.providers.map((provider) => |
| 163 | provider.windows.length > 0 && provider.expiresAt <= date | 163 | provider.windows.length > 0 && provider.expiresAt <= date |
| 164 | ? { ...provider, detail: "Open T3 to refresh", windows: [], totalWindows: 0 } | 164 | ? { ...provider, detail: "Open The Knife to refresh", windows: [], totalWindows: 0 } |
| 165 | : provider, | 165 | : provider, |
| 166 | ), | 166 | ), |
| 167 | }, | 167 | }, |
apps/web/index.html+3-3| ... | @@ -477,13 +477,13 @@ | ... | @@ -477,13 +477,13 @@ |
| 477 | cursor: pointer; | 477 | cursor: pointer; |
| 478 | } | 478 | } |
| 479 | </style> | 479 | </style> |
| 480 | <title>T3 Code (Alpha)</title> | 480 | <title>The Knife</title> |
| 481 | </head> | 481 | </head> |
| 482 | <body> | 482 | <body> |
| 483 | <div id="root"> | 483 | <div id="root"> |
| 484 | <div id="boot-shell"> | 484 | <div id="boot-shell"> |
| 485 | <div id="boot-shell-card" aria-label="T3 Code splash screen"> | 485 | <div id="boot-shell-card" aria-label="The Knife splash screen"> |
| 486 | <img id="boot-shell-logo" src="/apple-touch-icon.png" alt="T3 Code" /> | 486 | <img id="boot-shell-logo" src="/apple-touch-icon.png" alt="The Knife" /> |
| 487 | </div> | 487 | </div> |
| 488 | </div> | 488 | </div> |
| 489 | </div> | 489 | </div> |
apps/web/public/apple-touch-icon.png| Binary files a/apps/web/public/apple-touch-icon.png and b/apps/web/public/apple-touch-icon.png differ | |||
apps/web/public/favicon-16x16.png| Binary files a/apps/web/public/favicon-16x16.png and b/apps/web/public/favicon-16x16.png differ | |||
apps/web/public/favicon-32x32.png| Binary files a/apps/web/public/favicon-32x32.png and b/apps/web/public/favicon-32x32.png differ | |||
apps/web/public/favicon.ico| Binary files a/apps/web/public/favicon.ico and b/apps/web/public/favicon.ico differ | |||
apps/web/public/manifest.webmanifest+2| ... | @@ -1,4 +1,6 @@ | ... | @@ -1,4 +1,6 @@ |
| 1 | { | 1 | { |
| 2 | "name": "The Knife", | ||
| 3 | "short_name": "The Knife", | ||
| 2 | "id": "/", | 4 | "id": "/", |
| 3 | "start_url": "/", | 5 | "start_url": "/", |
| 4 | "scope": "/", | 6 | "scope": "/", |
apps/web/src/bootstrap.test.ts+2-2| ... | @@ -64,7 +64,7 @@ describe("app startup failures", () => { | ... | @@ -64,7 +64,7 @@ describe("app startup failures", () => { |
| 64 | await import("./bootstrap"); | 64 | await import("./bootstrap"); |
| 65 | await vi.dynamicImportSettled(); | 65 | await vi.dynamicImportSettled(); |
| 66 | 66 | ||
| 67 | expect(bootShell?.text).toContain("T3 Code could not load."); | 67 | expect(bootShell?.text).toContain("The Knife could not load."); |
| 68 | const reloadButton = bootShell?.children[0]?.children.find( | 68 | const reloadButton = bootShell?.children[0]?.children.find( |
| 69 | (element) => element.tagName === "button", | 69 | (element) => element.tagName === "button", |
| 70 | ); | 70 | ); |
| ... | @@ -78,7 +78,7 @@ describe("app startup failures", () => { | ... | @@ -78,7 +78,7 @@ describe("app startup failures", () => { |
| 78 | 78 | ||
| 79 | showBootError(new Error("internal module path")); | 79 | showBootError(new Error("internal module path")); |
| 80 | 80 | ||
| 81 | expect(bootShell?.text).toContain("T3 Code could not load."); | 81 | expect(bootShell?.text).toContain("The Knife could not load."); |
| 82 | expect(bootShell?.text.includes("internal module path")).toBe(dev); | 82 | expect(bootShell?.text.includes("internal module path")).toBe(dev); |
| 83 | }); | 83 | }); |
| 84 | 84 |
apps/web/src/branding.test.ts+15-15| ... | @@ -24,9 +24,9 @@ describe("branding", () => { | ... | @@ -24,9 +24,9 @@ describe("branding", () => { |
| 24 | value: { | 24 | value: { |
| 25 | desktopBridge: { | 25 | desktopBridge: { |
| 26 | getAppBranding: () => ({ | 26 | getAppBranding: () => ({ |
| 27 | baseName: "T3 Code", | 27 | baseName: "The Knife", |
| 28 | stageLabel: "Nightly", | 28 | stageLabel: "Nightly", |
| 29 | displayName: "T3 Code (Nightly)", | 29 | displayName: "The Knife (Nightly)", |
| 30 | }), | 30 | }), |
| 31 | }, | 31 | }, |
| 32 | }, | 32 | }, |
| ... | @@ -34,9 +34,9 @@ describe("branding", () => { | ... | @@ -34,9 +34,9 @@ describe("branding", () => { |
| 34 | 34 | ||
| 35 | const branding = await import("./branding"); | 35 | const branding = await import("./branding"); |
| 36 | 36 | ||
| 37 | expect(branding.APP_BASE_NAME).toBe("T3 Code"); | 37 | expect(branding.APP_BASE_NAME).toBe("The Knife"); |
| 38 | expect(branding.APP_STAGE_LABEL).toBe("Nightly"); | 38 | expect(branding.APP_STAGE_LABEL).toBe("Nightly"); |
| 39 | expect(branding.APP_DISPLAY_NAME).toBe("T3 Code (Nightly)"); | 39 | expect(branding.APP_DISPLAY_NAME).toBe("The Knife (Nightly)"); |
| 40 | }); | 40 | }); |
| 41 | 41 | ||
| 42 | it("normalizes hosted app channel metadata", async () => { | 42 | it("normalizes hosted app channel metadata", async () => { |
| ... | @@ -47,7 +47,7 @@ describe("branding", () => { | ... | @@ -47,7 +47,7 @@ describe("branding", () => { |
| 47 | expect(branding.HOSTED_APP_CHANNEL).toBe("nightly"); | 47 | expect(branding.HOSTED_APP_CHANNEL).toBe("nightly"); |
| 48 | expect(branding.HOSTED_APP_CHANNEL_LABEL).toBe("Nightly"); | 48 | expect(branding.HOSTED_APP_CHANNEL_LABEL).toBe("Nightly"); |
| 49 | expect(branding.APP_STAGE_LABEL).toBe("Nightly"); | 49 | expect(branding.APP_STAGE_LABEL).toBe("Nightly"); |
| 50 | expect(branding.APP_DISPLAY_NAME).toBe("T3 Code (Nightly)"); | 50 | expect(branding.APP_DISPLAY_NAME).toBe("The Knife (Nightly)"); |
| 51 | }); | 51 | }); |
| 52 | 52 | ||
| 53 | it("does not label the latest hosted app channel", async () => { | 53 | it("does not label the latest hosted app channel", async () => { |
| ... | @@ -58,7 +58,7 @@ describe("branding", () => { | ... | @@ -58,7 +58,7 @@ describe("branding", () => { |
| 58 | expect(branding.HOSTED_APP_CHANNEL).toBe("latest"); | 58 | expect(branding.HOSTED_APP_CHANNEL).toBe("latest"); |
| 59 | expect(branding.HOSTED_APP_CHANNEL_LABEL).toBe("Latest"); | 59 | expect(branding.HOSTED_APP_CHANNEL_LABEL).toBe("Latest"); |
| 60 | expect(branding.APP_STAGE_LABEL).toBe("Latest"); | 60 | expect(branding.APP_STAGE_LABEL).toBe("Latest"); |
| 61 | expect(branding.APP_DISPLAY_NAME).toBe("T3 Code"); | 61 | expect(branding.APP_DISPLAY_NAME).toBe("The Knife"); |
| 62 | }); | 62 | }); |
| 63 | 63 | ||
| 64 | it("ignores unknown hosted app channels", async () => { | 64 | it("ignores unknown hosted app channels", async () => { |
| ... | @@ -84,33 +84,33 @@ describe("branding logic", () => { | ... | @@ -84,33 +84,33 @@ describe("branding logic", () => { |
| 84 | it("updates the display name for nightly primary server versions", () => { | 84 | it("updates the display name for nightly primary server versions", () => { |
| 85 | expect( | 85 | expect( |
| 86 | resolveServerBackedAppDisplayName({ | 86 | resolveServerBackedAppDisplayName({ |
| 87 | baseName: "T3 Code", | 87 | baseName: "The Knife", |
| 88 | fallbackDisplayName: "T3 Code (Alpha)", | 88 | fallbackDisplayName: "The Knife (Alpha)", |
| 89 | fallbackStageLabel: "Alpha", | 89 | fallbackStageLabel: "Alpha", |
| 90 | primaryServerVersion: "0.0.28-nightly.20260616.12", | 90 | primaryServerVersion: "0.0.28-nightly.20260616.12", |
| 91 | }), | 91 | }), |
| 92 | ).toBe("T3 Code (Nightly)"); | 92 | ).toBe("The Knife (Nightly)"); |
| 93 | }); | 93 | }); |
| 94 | 94 | ||
| 95 | it("keeps the fallback display name for stable primary server versions", () => { | 95 | it("keeps the fallback display name for stable primary server versions", () => { |
| 96 | expect( | 96 | expect( |
| 97 | resolveServerBackedAppDisplayName({ | 97 | resolveServerBackedAppDisplayName({ |
| 98 | baseName: "T3 Code", | 98 | baseName: "The Knife", |
| 99 | fallbackDisplayName: "T3 Code (Alpha)", | 99 | fallbackDisplayName: "The Knife (Alpha)", |
| 100 | fallbackStageLabel: "Alpha", | 100 | fallbackStageLabel: "Alpha", |
| 101 | primaryServerVersion: "0.0.27", | 101 | primaryServerVersion: "0.0.27", |
| 102 | }), | 102 | }), |
| 103 | ).toBe("T3 Code (Alpha)"); | 103 | ).toBe("The Knife (Alpha)"); |
| 104 | }); | 104 | }); |
| 105 | 105 | ||
| 106 | it("keeps the fallback display name for malformed nightly primary server versions", () => { | 106 | it("keeps the fallback display name for malformed nightly primary server versions", () => { |
| 107 | expect( | 107 | expect( |
| 108 | resolveServerBackedAppDisplayName({ | 108 | resolveServerBackedAppDisplayName({ |
| 109 | baseName: "T3 Code", | 109 | baseName: "The Knife", |
| 110 | fallbackDisplayName: "T3 Code (Alpha)", | 110 | fallbackDisplayName: "The Knife (Alpha)", |
| 111 | fallbackStageLabel: "Alpha", | 111 | fallbackStageLabel: "Alpha", |
| 112 | primaryServerVersion: "0.0.28-nightly.20260616", | 112 | primaryServerVersion: "0.0.28-nightly.20260616", |
| 113 | }), | 113 | }), |
| 114 | ).toBe("T3 Code (Alpha)"); | 114 | ).toBe("The Knife (Alpha)"); |
| 115 | }); | 115 | }); |
| 116 | }); | 116 | }); |
apps/web/src/branding.ts+2-2| ... | @@ -16,11 +16,11 @@ export const HOSTED_APP_CHANNEL = | ... | @@ -16,11 +16,11 @@ export const HOSTED_APP_CHANNEL = |
| 16 | hostedAppChannel === "latest" || hostedAppChannel === "nightly" ? hostedAppChannel : null; | 16 | hostedAppChannel === "latest" || hostedAppChannel === "nightly" ? hostedAppChannel : null; |
| 17 | export const HOSTED_APP_CHANNEL_LABEL = | 17 | export const HOSTED_APP_CHANNEL_LABEL = |
| 18 | HOSTED_APP_CHANNEL === "nightly" ? "Nightly" : HOSTED_APP_CHANNEL === "latest" ? "Latest" : null; | 18 | HOSTED_APP_CHANNEL === "nightly" ? "Nightly" : HOSTED_APP_CHANNEL === "latest" ? "Latest" : null; |
| 19 | export const APP_BASE_NAME = injectedDesktopAppBranding?.baseName ?? "T3 Code"; | 19 | export const APP_BASE_NAME = injectedDesktopAppBranding?.baseName ?? "The Knife"; |
| 20 | export const APP_STAGE_LABEL = | 20 | export const APP_STAGE_LABEL = |
| 21 | injectedDesktopAppBranding?.stageLabel ?? | 21 | injectedDesktopAppBranding?.stageLabel ?? |
| 22 | HOSTED_APP_CHANNEL_LABEL ?? | 22 | HOSTED_APP_CHANNEL_LABEL ?? |
| 23 | (import.meta.env.DEV ? "Dev" : "Alpha"); | 23 | (import.meta.env.DEV ? "Dev" : "Latest"); |
| 24 | export const APP_DISPLAY_NAME = | 24 | export const APP_DISPLAY_NAME = |
| 25 | injectedDesktopAppBranding?.displayName ?? | 25 | injectedDesktopAppBranding?.displayName ?? |
| 26 | formatAppDisplayName({ baseName: APP_BASE_NAME, stageLabel: APP_STAGE_LABEL }); | 26 | formatAppDisplayName({ baseName: APP_BASE_NAME, stageLabel: APP_STAGE_LABEL }); |
apps/web/src/cloud/linkEnvironment.ts+2-2| ... | @@ -59,7 +59,7 @@ function ensureRelayClientAvailable( | ... | @@ -59,7 +59,7 @@ function ensureRelayClientAvailable( |
| 59 | if (status.status === "available") return; | 59 | if (status.status === "available") return; |
| 60 | if (status.status === "unsupported") { | 60 | if (status.status === "unsupported") { |
| 61 | return yield* new CloudEnvironmentLinkError({ | 61 | return yield* new CloudEnvironmentLinkError({ |
| 62 | message: `T3 Code cannot install the relay client automatically on ${status.platform}-${status.arch}.`, | 62 | message: `The Knife cannot install the relay client automatically on ${status.platform}-${status.arch}.`, |
| 63 | }); | 63 | }); |
| 64 | } | 64 | } |
| 65 | 65 | ||
| ... | @@ -95,7 +95,7 @@ function ensureRelayClientAvailable( | ... | @@ -95,7 +95,7 @@ function ensureRelayClientAvailable( |
| 95 | return yield* new CloudEnvironmentLinkError({ | 95 | return yield* new CloudEnvironmentLinkError({ |
| 96 | message: | 96 | message: |
| 97 | installedStatus.status === "unsupported" | 97 | installedStatus.status === "unsupported" |
| 98 | ? `T3 Code cannot install the relay client automatically on ${installedStatus.platform}-${installedStatus.arch}.` | 98 | ? `The Knife cannot install the relay client automatically on ${installedStatus.platform}-${installedStatus.arch}.` |
| 99 | : "The relay client is still unavailable after installation.", | 99 | : "The relay client is still unavailable after installation.", |
| 100 | }); | 100 | }); |
| 101 | } | 101 | } |
apps/web/src/components/ChatView.tsx+1-1| ... | @@ -11178,7 +11178,7 @@ export default function ChatView(props: ChatViewProps) { | ... | @@ -11178,7 +11178,7 @@ export default function ChatView(props: ChatViewProps) { |
| 11178 | ) : renderedRightPanelSurface?.kind === "pull-request" && !supportsPullRequests ? ( | 11178 | ) : renderedRightPanelSurface?.kind === "pull-request" && !supportsPullRequests ? ( |
| 11179 | <PullRequestsUnavailableState | 11179 | <PullRequestsUnavailableState |
| 11180 | title="Pull requests unavailable" | 11180 | title="Pull requests unavailable" |
| 11181 | error="Update this environment's T3 Code server to browse pull requests." | 11181 | error="Update The Knife server on this environment to browse pull requests." |
| 11182 | /> | 11182 | /> |
| 11183 | ) : renderedRightPanelSurface?.kind === "pull-request" ? ( | 11183 | ) : renderedRightPanelSurface?.kind === "pull-request" ? ( |
| 11184 | // No onClose: the surface tab's own X owns closing here, and a second X in the header | 11184 | // No onClose: the surface tab's own X owns closing here, and a second X in the header |
apps/web/src/components/KnifeMark.tsx created+6| ... | @@ -0,0 +1,6 @@ | ||
| 1 | import type { ImgHTMLAttributes } from "react"; | ||
| 2 | import knife from "../../../../assets/knife/app-icon.icon/Assets/knife.png"; | ||
| 3 | |||
| 4 | export function KnifeMark(props: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">) { | ||
| 5 | return <img {...props} src={knife} alt="" />; | ||
| 6 | } | ||
apps/web/src/components/RightPanelTabs.tsx+1-1| ... | @@ -171,7 +171,7 @@ export function shouldOpenDefaultBrowserProfileFromMenuClick( | ... | @@ -171,7 +171,7 @@ export function shouldOpenDefaultBrowserProfileFromMenuClick( |
| 171 | } | 171 | } |
| 172 | 172 | ||
| 173 | const SURFACE_DISABLED_REASONS = { | 173 | const SURFACE_DISABLED_REASONS = { |
| 174 | browser: "Browser previews are only available in the T3 Code desktop app.", | 174 | browser: "Browser previews are only available in The Knife desktop app.", |
| 175 | terminal: "Terminal surfaces are only available from a project thread.", | 175 | terminal: "Terminal surfaces are only available from a project thread.", |
| 176 | files: "Files are only available when a project is open.", | 176 | files: "Files are only available when a project is open.", |
| 177 | diff: "Diff is only available for server threads in Git repositories.", | 177 | diff: "Diff is only available for server threads in Git repositories.", |
apps/web/src/components/ServerUpdateAction.tsx+2-2| ... | @@ -157,7 +157,7 @@ export function ServerUpdatesAction({ | ... | @@ -157,7 +157,7 @@ export function ServerUpdatesAction({ |
| 157 | if (desktopTargets.length > 0) { | 157 | if (desktopTargets.length > 0) { |
| 158 | const confirmed = | 158 | const confirmed = |
| 159 | (await requestConfirmDialog( | 159 | (await requestConfirmDialog( |
| 160 | `Update the T3 Code desktop apps on ${desktopTargets.map((target) => target.serverLabel).join(", ")}? They will close and relaunch on those machines.`, | 160 | `Update The Knife desktop apps on ${desktopTargets.map((target) => target.serverLabel).join(", ")}? They will close and relaunch on those machines.`, |
| 161 | )) ?? true; | 161 | )) ?? true; |
| 162 | if (!confirmed) return; | 162 | if (!confirmed) return; |
| 163 | } | 163 | } |
| ... | @@ -279,7 +279,7 @@ export function ServerUpdateAction({ | ... | @@ -279,7 +279,7 @@ export function ServerUpdateAction({ |
| 279 | // remote machine installs without asking anyone there. | 279 | // remote machine installs without asking anyone there. |
| 280 | const confirmed = | 280 | const confirmed = |
| 281 | (await requestConfirmDialog( | 281 | (await requestConfirmDialog( |
| 282 | `Update the T3 Code desktop app that runs the ${serverLabel}? It will close and relaunch on that machine.`, | 282 | `Update The Knife desktop app that runs the ${serverLabel}? It will close and relaunch on that machine.`, |
| 283 | )) ?? true; | 283 | )) ?? true; |
| 284 | if (!confirmed) { | 284 | if (!confirmed) { |
| 285 | return; | 285 | return; |
apps/web/src/components/T3Wordmark.tsx deleted-12| ... | @@ -1,12 +0,0 @@ | ||
| 1 | import type { SVGProps } from "react"; | ||
| 2 | |||
| 3 | export function T3Wordmark(props: SVGProps<SVGSVGElement>) { | ||
| 4 | return ( | ||
| 5 | <svg {...props} viewBox="15.5309 37 94.3941 56.96" xmlns="http://www.w3.org/2000/svg"> | ||
| 6 | <path | ||
| 7 | d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z" | ||
| 8 | fill="currentColor" | ||
| 9 | /> | ||
| 10 | </svg> | ||
| 11 | ); | ||
| 12 | } | ||
apps/web/src/components/chat/MessagesTimeline.tsx+4-4| ... | @@ -119,7 +119,7 @@ import ChatMarkdown, { ChatMarkdownAssetImage } from "../ChatMarkdown"; | ... | @@ -119,7 +119,7 @@ import ChatMarkdown, { ChatMarkdownAssetImage } from "../ChatMarkdown"; |
| 119 | import ReactMarkdown from "react-markdown"; | 119 | import ReactMarkdown from "react-markdown"; |
| 120 | import remarkGfm from "remark-gfm"; | 120 | import remarkGfm from "remark-gfm"; |
| 121 | import type { Root, RootContent } from "mdast"; | 121 | import type { Root, RootContent } from "mdast"; |
| 122 | import { T3Wordmark } from "../T3Wordmark"; | 122 | import { KnifeMark } from "../KnifeMark"; |
| 123 | import { ThreadContextChip } from "../ThreadContextChip"; | 123 | import { ThreadContextChip } from "../ThreadContextChip"; |
| 124 | import { | 124 | import { |
| 125 | BotIcon, | 125 | BotIcon, |
| ... | @@ -2462,7 +2462,7 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message" | ... | @@ -2462,7 +2462,7 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message" |
| 2462 | className="me-1 text-2xs text-muted-foreground/70" | 2462 | className="me-1 text-2xs text-muted-foreground/70" |
| 2463 | data-user-message-attribution="t3code" | 2463 | data-user-message-attribution="t3code" |
| 2464 | > | 2464 | > |
| 2465 | Sent by T3 Code | 2465 | Sent by The Knife |
| 2466 | </p> | 2466 | </p> |
| 2467 | ) : userMessage.attribution === "agent" ? ( | 2467 | ) : userMessage.attribution === "agent" ? ( |
| 2468 | <p className="me-1 text-2xs text-muted-foreground/70" data-user-message-attribution="agent"> | 2468 | <p className="me-1 text-2xs text-muted-foreground/70" data-user-message-attribution="agent"> |
| ... | @@ -2837,7 +2837,7 @@ function AssistantTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "mess | ... | @@ -2837,7 +2837,7 @@ function AssistantTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "mess |
| 2837 | return ( | 2837 | return ( |
| 2838 | <> | 2838 | <> |
| 2839 | <div className="relative min-w-0 px-1 py-0.5"> | 2839 | <div className="relative min-w-0 px-1 py-0.5"> |
| 2840 | <MessageAuthorHeading>T3 Code</MessageAuthorHeading> | 2840 | <MessageAuthorHeading>The Knife</MessageAuthorHeading> |
| 2841 | <div data-thread-find-text="true"> | 2841 | <div data-thread-find-text="true"> |
| 2842 | <AssistantCitationSource | 2842 | <AssistantCitationSource |
| 2843 | messageId={row.message.id} | 2843 | messageId={row.message.id} |
| ... | @@ -5236,7 +5236,7 @@ function WorkEntryIcon({ name, className }: { name: WorkEntryIconName; className | ... | @@ -5236,7 +5236,7 @@ function WorkEntryIcon({ name, className }: { name: WorkEntryIconName; className |
| 5236 | case "device": | 5236 | case "device": |
| 5237 | return <SmartphoneIcon className={className} aria-hidden />; | 5237 | return <SmartphoneIcon className={className} aria-hidden />; |
| 5238 | case "t3-code": | 5238 | case "t3-code": |
| 5239 | return <T3Wordmark className={className} aria-hidden />; | 5239 | return <KnifeMark className={className} aria-hidden />; |
| 5240 | case "check": | 5240 | case "check": |
| 5241 | return <CheckIcon className={className} aria-hidden />; | 5241 | return <CheckIcon className={className} aria-hidden />; |
| 5242 | case "circle-alert": | 5242 | case "circle-alert": |
apps/web/src/components/chat/V2LifecycleRow.tsx+2-2| ... | @@ -48,7 +48,7 @@ import { ProviderInstanceIcon, providerTextColor } from "./ProviderInstanceIcon" | ... | @@ -48,7 +48,7 @@ import { ProviderInstanceIcon, providerTextColor } from "./ProviderInstanceIcon" |
| 48 | import { cn } from "~/lib/utils"; | 48 | import { cn } from "~/lib/utils"; |
| 49 | import { TimelineSystemDivider } from "./TimelineSystemDivider"; | 49 | import { TimelineSystemDivider } from "./TimelineSystemDivider"; |
| 50 | import { Button, InlineButton } from "../ui/button"; | 50 | import { Button, InlineButton } from "../ui/button"; |
| 51 | import { T3Wordmark } from "../T3Wordmark"; | 51 | import { KnifeMark } from "../KnifeMark"; |
| 52 | 52 | ||
| 53 | const LIFECYCLE_TYPES = new Set<OrchestrationV2TurnItem["type"]>([ | 53 | const LIFECYCLE_TYPES = new Set<OrchestrationV2TurnItem["type"]>([ |
| 54 | "run_interrupt_request", | 54 | "run_interrupt_request", |
| ... | @@ -170,7 +170,7 @@ export function V2LifecycleRow(props: { | ... | @@ -170,7 +170,7 @@ export function V2LifecycleRow(props: { |
| 170 | return ( | 170 | return ( |
| 171 | <WorkLogRow | 171 | <WorkLogRow |
| 172 | data-v2-item-type={item.type} | 172 | data-v2-item-type={item.type} |
| 173 | icon={<T3Wordmark className="size-4 text-icon-muted" aria-hidden />} | 173 | icon={<KnifeMark className="size-4 text-icon-muted" aria-hidden />} |
| 174 | label={<>Created thread{item.title ? ` · ${item.title}` : ""}</>} | 174 | label={<>Created thread{item.title ? ` · ${item.title}` : ""}</>} |
| 175 | trailing={ | 175 | trailing={ |
| 176 | <InlineButton | 176 | <InlineButton |
apps/web/src/components/clerk/MobileClientsUserProfilePage.logic.test.ts+3-3| ... | @@ -31,7 +31,7 @@ describe("mobile client presentation", () => { | ... | @@ -31,7 +31,7 @@ describe("mobile client presentation", () => { |
| 31 | it("describes the client platform and enabled notification events", () => { | 31 | it("describes the client platform and enabled notification events", () => { |
| 32 | const client = device(); | 32 | const client = device(); |
| 33 | 33 | ||
| 34 | expect(mobileClientPlatformLabel(client)).toBe("iOS 18 · T3 Code 1.2.3"); | 34 | expect(mobileClientPlatformLabel(client)).toBe("iOS 18 · The Knife 1.2.3"); |
| 35 | expect(mobileClientNotificationDetail(client)).toBe( | 35 | expect(mobileClientNotificationDetail(client)).toBe( |
| 36 | "Alerts enabled for approvals, completions.", | 36 | "Alerts enabled for approvals, completions.", |
| 37 | ); | 37 | ); |
| ... | @@ -42,7 +42,7 @@ describe("mobile client presentation", () => { | ... | @@ -42,7 +42,7 @@ describe("mobile client presentation", () => { |
| 42 | mobileClientPlatformLabel( | 42 | mobileClientPlatformLabel( |
| 43 | device({ platform: "android", iosMajorVersion: null, androidApiLevel: 36 }), | 43 | device({ platform: "android", iosMajorVersion: null, androidApiLevel: 36 }), |
| 44 | ), | 44 | ), |
| 45 | ).toBe("Android · T3 Code 1.2.3"); | 45 | ).toBe("Android · The Knife 1.2.3"); |
| 46 | }); | 46 | }); |
| 47 | 47 | ||
| 48 | it("distinguishes disabled notifications from an empty event selection", () => { | 48 | it("distinguishes disabled notifications from an empty event selection", () => { |
| ... | @@ -68,7 +68,7 @@ describe("mobile client presentation", () => { | ... | @@ -68,7 +68,7 @@ describe("mobile client presentation", () => { |
| 68 | 68 | ||
| 69 | it("handles missing app versions and invalid update timestamps", () => { | 69 | it("handles missing app versions and invalid update timestamps", () => { |
| 70 | expect(mobileClientPlatformLabel(device({ iosMajorVersion: null }))).toBe( | 70 | expect(mobileClientPlatformLabel(device({ iosMajorVersion: null }))).toBe( |
| 71 | "iOS · T3 Code 1.2.3", | 71 | "iOS · The Knife 1.2.3", |
| 72 | ); | 72 | ); |
| 73 | expect(mobileClientPlatformLabel(device({ appVersion: null }))).toBe("iOS 18"); | 73 | expect(mobileClientPlatformLabel(device({ appVersion: null }))).toBe("iOS 18"); |
| 74 | expect(mobileClientUpdatedAtLabel("not-a-date")).toBe("Update time unavailable"); | 74 | expect(mobileClientUpdatedAtLabel("not-a-date")).toBe("Update time unavailable"); |
apps/web/src/components/clerk/MobileClientsUserProfilePage.logic.ts+1-1| ... | @@ -21,7 +21,7 @@ export function mobileClientPlatformLabel(device: RelayClientDeviceRecord): stri | ... | @@ -21,7 +21,7 @@ export function mobileClientPlatformLabel(device: RelayClientDeviceRecord): stri |
| 21 | : device.iosMajorVersion === null | 21 | : device.iosMajorVersion === null |
| 22 | ? "iOS" | 22 | ? "iOS" |
| 23 | : `iOS ${device.iosMajorVersion}`; | 23 | : `iOS ${device.iosMajorVersion}`; |
| 24 | return `${platform}${device.appVersion ? ` · T3 Code ${device.appVersion}` : ""}`; | 24 | return `${platform}${device.appVersion ? ` · The Knife ${device.appVersion}` : ""}`; |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | export function mobileClientNotificationDetail(device: RelayClientDeviceRecord): string { | 27 | export function mobileClientNotificationDetail(device: RelayClientDeviceRecord): string { |
apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx+1-1| ... | @@ -94,7 +94,7 @@ function EmptyMobileClients() { | ... | @@ -94,7 +94,7 @@ function EmptyMobileClients() { |
| 94 | <EmptyHeader> | 94 | <EmptyHeader> |
| 95 | <EmptyTitle>No mobile clients</EmptyTitle> | 95 | <EmptyTitle>No mobile clients</EmptyTitle> |
| 96 | <EmptyDescription> | 96 | <EmptyDescription> |
| 97 | Install T3 Code on your phone and sign in to T3 Connect to get push notifications and Live | 97 | Install The Knife on your phone and sign in to T3 Connect to get push notifications and Live |
| 98 | Activities. | 98 | Activities. |
| 99 | </EmptyDescription> | 99 | </EmptyDescription> |
| 100 | </EmptyHeader> | 100 | </EmptyHeader> |
apps/web/src/components/cloud/RelayClientInstallDialog.tsx+4-4| ... | @@ -69,8 +69,8 @@ export function RelayClientInstallDialog() { | ... | @@ -69,8 +69,8 @@ export function RelayClientInstallDialog() { |
| 69 | </DialogTitle> | 69 | </DialogTitle> |
| 70 | <DialogDescription> | 70 | <DialogDescription> |
| 71 | {isInstalling | 71 | {isInstalling |
| 72 | ? "T3 Code is preparing this environment for secure access through T3 Connect." | 72 | ? "The Knife is preparing this environment for secure access through T3 Connect." |
| 73 | : "T3 Code needs the relay client to make this environment available through T3 Connect."} | 73 | : "The Knife needs the relay client to make this environment available through T3 Connect."} |
| 74 | </DialogDescription> | 74 | </DialogDescription> |
| 75 | </DialogHeader> | 75 | </DialogHeader> |
| 76 | <DialogPanel scrollFade={false}> | 76 | <DialogPanel scrollFade={false}> |
| ... | @@ -91,14 +91,14 @@ export function RelayClientInstallDialog() { | ... | @@ -91,14 +91,14 @@ export function RelayClientInstallDialog() { |
| 91 | value={activeStepIndex + 1} | 91 | value={activeStepIndex + 1} |
| 92 | /> | 92 | /> |
| 93 | <p className="text-xs leading-relaxed text-muted-foreground"> | 93 | <p className="text-xs leading-relaxed text-muted-foreground"> |
| 94 | Keep T3 Code open while the relay client is installed. | 94 | Keep The Knife open while the relay client is installed. |
| 95 | </p> | 95 | </p> |
| 96 | </div> | 96 | </div> |
| 97 | ) : ( | 97 | ) : ( |
| 98 | <div className="rounded-xl border border-border/70 bg-muted/35 p-3"> | 98 | <div className="rounded-xl border border-border/70 bg-muted/35 p-3"> |
| 99 | <p className="text-sm font-medium text-foreground">Managed relay client</p> | 99 | <p className="text-sm font-medium text-foreground">Managed relay client</p> |
| 100 | <p className="mt-1 text-sm leading-relaxed text-muted-foreground"> | 100 | <p className="mt-1 text-sm leading-relaxed text-muted-foreground"> |
| 101 | T3 Code will download and install version{" "} | 101 | The Knife will download and install version{" "} |
| 102 | {view.status === "confirming" ? view.version : ""} locally. | 102 | {view.status === "confirming" ? view.version : ""} locally. |
| 103 | </p> | 103 | </p> |
| 104 | </div> | 104 | </div> |
apps/web/src/components/desktop/DesktopAppActivationCoordinator.tsx+1-1| ... | @@ -63,7 +63,7 @@ export function DesktopAppActivationCoordinator() { | ... | @@ -63,7 +63,7 @@ export function DesktopAppActivationCoordinator() { |
| 63 | }); | 63 | }); |
| 64 | if (result._tag === "Failure") { | 64 | if (result._tag === "Failure") { |
| 65 | const error = squashAtomCommandFailure(result); | 65 | const error = squashAtomCommandFailure(result); |
| 66 | throw error instanceof Error ? error : new Error("T3 Code could not add the project."); | 66 | throw error instanceof Error ? error : new Error("The Knife could not add the project."); |
| 67 | } | 67 | } |
| 68 | return projectId; | 68 | return projectId; |
| 69 | }, | 69 | }, |
apps/web/src/components/desktop/SshPasswordPromptDialog.tsx+1-1| ... | @@ -161,7 +161,7 @@ function ActiveSshPasswordPrompt({ | ... | @@ -161,7 +161,7 @@ function ActiveSshPasswordPrompt({ |
| 161 | <DialogTitle>SSH Password Required</DialogTitle> | 161 | <DialogTitle>SSH Password Required</DialogTitle> |
| 162 | <DialogDescription> | 162 | <DialogDescription> |
| 163 | T3 needs your SSH password to connect to <code>{target}</code>. The password is passed | 163 | T3 needs your SSH password to connect to <code>{target}</code>. The password is passed |
| 164 | to the local SSH process for this connection attempt and is not saved by T3 Code. | 164 | to the local SSH process for this connection attempt and is not saved by The Knife. |
| 165 | </DialogDescription> | 165 | </DialogDescription> |
| 166 | </DialogHeader> | 166 | </DialogHeader> |
| 167 | <DialogPanel scrollFade={false}> | 167 | <DialogPanel scrollFade={false}> |
apps/web/src/components/desktopUpdate.logic.test.ts+3-3| ... | @@ -279,7 +279,7 @@ describe("desktop update UI helpers", () => { | ... | @@ -279,7 +279,7 @@ describe("desktop update UI helpers", () => { |
| 279 | availableVersion: "1.1.0", | 279 | availableVersion: "1.1.0", |
| 280 | downloadedVersion: "1.1.1", | 280 | downloadedVersion: "1.1.1", |
| 281 | }), | 281 | }), |
| 282 | ).toContain("Install update 1.1.1 and restart T3 Code?"); | 282 | ).toContain("Install update 1.1.1 and restart The Knife?"); |
| 283 | }); | 283 | }); |
| 284 | 284 | ||
| 285 | it("falls back to generic install confirmation copy when no version is available", () => { | 285 | it("falls back to generic install confirmation copy when no version is available", () => { |
| ... | @@ -288,7 +288,7 @@ describe("desktop update UI helpers", () => { | ... | @@ -288,7 +288,7 @@ describe("desktop update UI helpers", () => { |
| 288 | availableVersion: null, | 288 | availableVersion: null, |
| 289 | downloadedVersion: null, | 289 | downloadedVersion: null, |
| 290 | }), | 290 | }), |
| 291 | ).toContain("Install update and restart T3 Code?"); | 291 | ).toContain("Install update and restart The Knife?"); |
| 292 | }); | 292 | }); |
| 293 | 293 | ||
| 294 | it("keeps the same install confirmation copy across desktop platforms", () => { | 294 | it("keeps the same install confirmation copy across desktop platforms", () => { |
| ... | @@ -298,7 +298,7 @@ describe("desktop update UI helpers", () => { | ... | @@ -298,7 +298,7 @@ describe("desktop update UI helpers", () => { |
| 298 | downloadedVersion: "1.1.0", | 298 | downloadedVersion: "1.1.0", |
| 299 | }), | 299 | }), |
| 300 | ).toBe( | 300 | ).toBe( |
| 301 | "Install update 1.1.0 and restart T3 Code?\n\nAny running tasks will be interrupted. Make sure you're ready before continuing.", | 301 | "Install update 1.1.0 and restart The Knife?\n\nAny running tasks will be interrupted. Make sure you're ready before continuing.", |
| 302 | ); | 302 | ); |
| 303 | }); | 303 | }); |
| 304 | }); | 304 | }); |
apps/web/src/components/desktopUpdate.logic.ts+4-4| ... | @@ -62,12 +62,12 @@ export function getArm64IntelBuildWarningDescription(state: DesktopUpdateState): | ... | @@ -62,12 +62,12 @@ export function getArm64IntelBuildWarningDescription(state: DesktopUpdateState): |
| 62 | 62 | ||
| 63 | const action = resolveDesktopUpdateButtonAction(state); | 63 | const action = resolveDesktopUpdateButtonAction(state); |
| 64 | if (action === "download") { | 64 | if (action === "download") { |
| 65 | 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."; | 65 | 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."; |
| 66 | } | 66 | } |
| 67 | if (action === "install") { | 67 | if (action === "install") { |
| 68 | 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."; | 68 | 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."; |
| 69 | } | 69 | } |
| 70 | 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."; | 70 | 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."; |
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | export function getDesktopUpdateButtonTooltip(state: DesktopUpdateState): string { | 73 | export function getDesktopUpdateButtonTooltip(state: DesktopUpdateState): string { |
| ... | @@ -108,7 +108,7 @@ export function getDesktopUpdateInstallConfirmationMessage( | ... | @@ -108,7 +108,7 @@ export function getDesktopUpdateInstallConfirmationMessage( |
| 108 | state: Pick<DesktopUpdateState, "availableVersion" | "downloadedVersion">, | 108 | state: Pick<DesktopUpdateState, "availableVersion" | "downloadedVersion">, |
| 109 | ): string { | 109 | ): string { |
| 110 | const version = state.downloadedVersion ?? state.availableVersion; | 110 | const version = state.downloadedVersion ?? state.availableVersion; |
| 111 | return `Install update${version ? ` ${version}` : ""} and restart T3 Code?\n\nAny running tasks will be interrupted. Make sure you're ready before continuing.`; | 111 | return `Install update${version ? ` ${version}` : ""} and restart The Knife?\n\nAny running tasks will be interrupted. Make sure you're ready before continuing.`; |
| 112 | } | 112 | } |
| 113 | 113 | ||
| 114 | export function getDesktopUpdateActionError(result: DesktopUpdateActionResult): string | null { | 114 | export function getDesktopUpdateActionError(result: DesktopUpdateActionResult): string | null { |
apps/web/src/components/onboarding/FirstRunGate.tsx+1-1| ... | @@ -214,7 +214,7 @@ function FirstRunRecovery({ | ... | @@ -214,7 +214,7 @@ function FirstRunRecovery({ |
| 214 | <p className="mt-2 text-sm text-muted-foreground"> | 214 | <p className="mt-2 text-sm text-muted-foreground"> |
| 215 | {settingsReadFailed | 215 | {settingsReadFailed |
| 216 | ? "Your saved settings could not be loaded." | 216 | ? "Your saved settings could not be loaded." |
| 217 | : "T3 Code could not confirm this workspace."} | 217 | : "The Knife could not confirm this workspace."} |
| 218 | </p> | 218 | </p> |
| 219 | <Button | 219 | <Button |
| 220 | className="mt-5" | 220 | className="mt-5" |
apps/web/src/components/onboarding/WelcomeWizard.tsx+9-9| ... | @@ -84,7 +84,7 @@ import { TerminalViewport } from "../ThreadTerminalDrawer"; | ... | @@ -84,7 +84,7 @@ import { TerminalViewport } from "../ThreadTerminalDrawer"; |
| 84 | import { CloudEnvironmentConnectRows } from "../cloud/CloudEnvironmentConnectList"; | 84 | import { CloudEnvironmentConnectRows } from "../cloud/CloudEnvironmentConnectList"; |
| 85 | import { presentSavedCloudEnvironmentConnection } from "../cloud/cloudEnvironmentConnectionPresentation"; | 85 | import { presentSavedCloudEnvironmentConnection } from "../cloud/cloudEnvironmentConnectionPresentation"; |
| 86 | import { ProviderInstanceIcon } from "../chat/ProviderInstanceIcon"; | 86 | import { ProviderInstanceIcon } from "../chat/ProviderInstanceIcon"; |
| 87 | import { T3Wordmark } from "../T3Wordmark"; | 87 | import { KnifeMark } from "../KnifeMark"; |
| 88 | import { Alert, AlertDescription } from "../ui/alert"; | 88 | import { Alert, AlertDescription } from "../ui/alert"; |
| 89 | import { Button } from "../ui/button"; | 89 | import { Button } from "../ui/button"; |
| 90 | import { CommandBlock } from "../CommandBlock"; | 90 | import { CommandBlock } from "../CommandBlock"; |
| ... | @@ -233,12 +233,12 @@ export function WelcomeWizard({ | ... | @@ -233,12 +233,12 @@ export function WelcomeWizard({ |
| 233 | initialFocus={() => document.getElementById("onboarding-pairing-url") ?? true} | 233 | initialFocus={() => document.getElementById("onboarding-pairing-url") ?? true} |
| 234 | > | 234 | > |
| 235 | <WizardHeader | 235 | <WizardHeader |
| 236 | title="Set up T3 Code" | 236 | title="Set up The Knife" |
| 237 | identity={ | 237 | identity={ |
| 238 | <div className="flex items-baseline gap-1.5" role="img" aria-label="T3 Code"> | 238 | <div className="flex items-center gap-2" role="img" aria-label="The Knife"> |
| 239 | <T3Wordmark className="h-4 w-auto shrink-0" aria-hidden /> | 239 | <KnifeMark className="size-8 shrink-0" aria-hidden /> |
| 240 | <span className="text-2xl font-medium tracking-tight text-muted-foreground"> | 240 | <span className="text-2xl font-medium tracking-tight text-muted-foreground"> |
| 241 | Code | 241 | The Knife |
| 242 | </span> | 242 | </span> |
| 243 | </div> | 243 | </div> |
| 244 | } | 244 | } |
| ... | @@ -432,8 +432,8 @@ function ConnectionStep({ | ... | @@ -432,8 +432,8 @@ function ConnectionStep({ |
| 432 | ) : null} | 432 | ) : null} |
| 433 | <div className="mt-6 flex flex-col-reverse gap-4 sm:flex-row sm:items-center sm:justify-between sm:gap-6"> | 433 | <div className="mt-6 flex flex-col-reverse gap-4 sm:flex-row sm:items-center sm:justify-between sm:gap-6"> |
| 434 | <p className="min-w-0 text-xs leading-relaxed text-muted-foreground"> | 434 | <p className="min-w-0 text-xs leading-relaxed text-muted-foreground"> |
| 435 | T3 Code collects anonymous usage data to help us improve it. To read more about how your | 435 | Anonymous usage data helps improve T3 Code. To read more about how your data is used and |
| 436 | data is used and how to opt out, see our{" "} | 436 | how to opt out, see our{" "} |
| 437 | <a | 437 | <a |
| 438 | className="underline underline-offset-2 hover:text-foreground" | 438 | className="underline underline-offset-2 hover:text-foreground" |
| 439 | href={PRIVACY_POLICY_URL} | 439 | href={PRIVACY_POLICY_URL} |
| ... | @@ -530,7 +530,7 @@ function ConnectAccountOption({ | ... | @@ -530,7 +530,7 @@ function ConnectAccountOption({ |
| 530 | </p> | 530 | </p> |
| 531 | <CommandBlock command="npx t3 connect" className="mt-3" /> | 531 | <CommandBlock command="npx t3 connect" className="mt-3" /> |
| 532 | <p className="mt-3 text-xs text-muted-foreground"> | 532 | <p className="mt-3 text-xs text-muted-foreground"> |
| 533 | Keep T3 Code running. Select the computers you want to set up above. | 533 | Keep The Knife running. Select the computers you want to set up above. |
| 534 | </p> | 534 | </p> |
| 535 | </div> | 535 | </div> |
| 536 | </CollapsiblePanel> | 536 | </CollapsiblePanel> |
| ... | @@ -644,7 +644,7 @@ function PairingForm({ | ... | @@ -644,7 +644,7 @@ function PairingForm({ |
| 644 | </p> | 644 | </p> |
| 645 | <CommandBlock command="npx t3 pair" className="mt-2" /> | 645 | <CommandBlock command="npx t3 pair" className="mt-2" /> |
| 646 | <p className="mt-2 text-xs leading-relaxed text-muted-foreground"> | 646 | <p className="mt-2 text-xs leading-relaxed text-muted-foreground"> |
| 647 | Start T3 Code first, or run <code className="font-mono">npx t3 serve</code>. Add{" "} | 647 | Start The Knife first, or run <code className="font-mono">npx t3 serve</code>. Add{" "} |
| 648 | <code className="font-mono">--tailscale</code> to use your tailnet. | 648 | <code className="font-mono">--tailscale</code> to use your tailnet. |
| 649 | </p> | 649 | </p> |
| 650 | </CollapsiblePanel> | 650 | </CollapsiblePanel> |
apps/web/src/components/preview/PreviewPanel.tsx+1-1| ... | @@ -41,7 +41,7 @@ export function PreviewPanel({ | ... | @@ -41,7 +41,7 @@ export function PreviewPanel({ |
| 41 | <div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3 p-8 text-center"> | 41 | <div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3 p-8 text-center"> |
| 42 | <p className="max-w-sm text-sm text-muted-foreground"> | 42 | <p className="max-w-sm text-sm text-muted-foreground"> |
| 43 | {canOperatePreview | 43 | {canOperatePreview |
| 44 | ? "Preview is only available in the T3 Code desktop app." | 44 | ? "Preview is only available in The Knife desktop app." |
| 45 | : "Pair this client again with preview access to control browser previews."} | 45 | : "Pair this client again with preview access to control browser previews."} |
| 46 | </p> | 46 | </p> |
| 47 | </div> | 47 | </div> |
apps/web/src/components/preview/PreviewView.tsx+1-1| ... | @@ -1097,7 +1097,7 @@ export function PreviewView({ | ... | @@ -1097,7 +1097,7 @@ export function PreviewView({ |
| 1097 | ) : ( | 1097 | ) : ( |
| 1098 | <div className="flex h-full items-center justify-center p-8 text-center"> | 1098 | <div className="flex h-full items-center justify-center p-8 text-center"> |
| 1099 | <p className="max-w-sm text-sm text-muted-foreground"> | 1099 | <p className="max-w-sm text-sm text-muted-foreground"> |
| 1100 | This tab is open in the T3 Code desktop app. | 1100 | This tab is open in The Knife desktop app. |
| 1101 | </p> | 1101 | </p> |
| 1102 | </div> | 1102 | </div> |
| 1103 | ) | 1103 | ) |
apps/web/src/components/promptLinks/HostedPromptLinkLanding.tsx+1-1| ... | @@ -146,7 +146,7 @@ function HostedPromptLinkLanding({ | ... | @@ -146,7 +146,7 @@ function HostedPromptLinkLanding({ |
| 146 | <Alert className="mt-5" variant="error"> | 146 | <Alert className="mt-5" variant="error"> |
| 147 | <AlertTitle>This prompt had hidden characters, it may be malicious</AlertTitle> | 147 | <AlertTitle>This prompt had hidden characters, it may be malicious</AlertTitle> |
| 148 | <AlertDescription> | 148 | <AlertDescription> |
| 149 | T3 Code removed {prompt.hiddenCharacters} hidden character | 149 | The Knife removed {prompt.hiddenCharacters} hidden character |
| 150 | {prompt.hiddenCharacters === 1 ? "" : "s"}. Read the prompt before you send it. | 150 | {prompt.hiddenCharacters === 1 ? "" : "s"}. Read the prompt before you send it. |
| 151 | </AlertDescription> | 151 | </AlertDescription> |
| 152 | </Alert> | 152 | </Alert> |
apps/web/src/components/promptLinks/NewThreadLinkView.tsx+2-2| ... | @@ -99,7 +99,7 @@ export function NewThreadLinkView({ link }: { readonly link: NewThreadLink }) { | ... | @@ -99,7 +99,7 @@ export function NewThreadLinkView({ link }: { readonly link: NewThreadLink }) { |
| 99 | replace: true, | 99 | replace: true, |
| 100 | }).catch(() => null); | 100 | }).catch(() => null); |
| 101 | if (opened === null) { | 101 | if (opened === null) { |
| 102 | setState({ status: "failed", message: "T3 Code could not open a new thread." }); | 102 | setState({ status: "failed", message: "The Knife could not open a new thread." }); |
| 103 | return; | 103 | return; |
| 104 | } | 104 | } |
| 105 | setState({ status: "opened" }); | 105 | setState({ status: "opened" }); |
| ... | @@ -169,7 +169,7 @@ export function NewThreadLinkView({ link }: { readonly link: NewThreadLink }) { | ... | @@ -169,7 +169,7 @@ export function NewThreadLinkView({ link }: { readonly link: NewThreadLink }) { |
| 169 | setState({ | 169 | setState({ |
| 170 | status: "failed", | 170 | status: "failed", |
| 171 | message: | 171 | message: |
| 172 | "This machine cannot clone from a link. Update T3 Code on it, or clone the repository from the command palette.", | 172 | "This machine cannot clone from a link. Update The Knife on it, or clone the repository from the command palette.", |
| 173 | }); | 173 | }); |
| 174 | return; | 174 | return; |
| 175 | } | 175 | } |
apps/web/src/components/promptLinks/linkedPrompt.ts+1-1| ... | @@ -10,7 +10,7 @@ export function warnAboutHiddenCharacters(prompt: LinkedPrompt): void { | ... | @@ -10,7 +10,7 @@ export function warnAboutHiddenCharacters(prompt: LinkedPrompt): void { |
| 10 | toastManager.add({ | 10 | toastManager.add({ |
| 11 | type: "warning", | 11 | type: "warning", |
| 12 | title: "This prompt had hidden characters, it may be malicious", | 12 | title: "This prompt had hidden characters, it may be malicious", |
| 13 | description: `T3 Code removed ${prompt.hiddenCharacters} hidden character${prompt.hiddenCharacters === 1 ? "" : "s"}. Read the prompt before you send it.`, | 13 | description: `The Knife removed ${prompt.hiddenCharacters} hidden character${prompt.hiddenCharacters === 1 ? "" : "s"}. Read the prompt before you send it.`, |
| 14 | timeout: 0, | 14 | timeout: 0, |
| 15 | }); | 15 | }); |
| 16 | } | 16 | } |
apps/web/src/components/settings/BrowserImportWizard.tsx+4-4| ... | @@ -285,9 +285,9 @@ function FullDiskAccessStep({ | ... | @@ -285,9 +285,9 @@ function FullDiskAccessStep({ |
| 285 | return ( | 285 | return ( |
| 286 | <> | 286 | <> |
| 287 | <DialogHeader> | 287 | <DialogHeader> |
| 288 | <DialogTitle>Let T3 Code read {source.name}&rsquo;s cookies</DialogTitle> | 288 | <DialogTitle>Let The Knife read {source.name}&rsquo;s cookies</DialogTitle> |
| 289 | <DialogDescription> | 289 | <DialogDescription> |
| 290 | To import cookies from {source.name}, T3 Code needs Full Disk Access. Turn it on in System | 290 | To import cookies from {source.name}, The Knife needs Full Disk Access. Turn it on in System |
| 291 | Settings, then come back to finish the import — you can revoke it again once the import is | 291 | Settings, then come back to finish the import — you can revoke it again once the import is |
| 292 | done. | 292 | done. |
| 293 | </DialogDescription> | 293 | </DialogDescription> |
| ... | @@ -319,8 +319,8 @@ function FullDiskAccessStep({ | ... | @@ -319,8 +319,8 @@ function FullDiskAccessStep({ |
| 319 | {!permission.isReady(["fullDiskAccess"]) ? ( | 319 | {!permission.isReady(["fullDiskAccess"]) ? ( |
| 320 | <p className="mt-3 text-xs text-muted-foreground"> | 320 | <p className="mt-3 text-xs text-muted-foreground"> |
| 321 | {stillRequired | 321 | {stillRequired |
| 322 | ? "Access is still required. Quit and reopen T3 Code if you just allowed it, then retry the import." | 322 | ? "Access is still required. Quit and reopen The Knife if you just allowed it, then retry the import." |
| 323 | : "If access doesn't update after you allow it, quit and reopen T3 Code, then retry the import."} | 323 | : "If access doesn't update after you allow it, quit and reopen The Knife, then retry the import."} |
| 324 | </p> | 324 | </p> |
| 325 | ) : null} | 325 | ) : null} |
| 326 | </DialogPanel> | 326 | </DialogPanel> |
apps/web/src/components/settings/CaptureShortcutConfig.tsx+5-5| ... | @@ -217,7 +217,7 @@ export function CaptureShortcutConfig({ | ... | @@ -217,7 +217,7 @@ export function CaptureShortcutConfig({ |
| 217 | ) : ( | 217 | ) : ( |
| 218 | <> | 218 | <> |
| 219 | <p className="text-muted-foreground"> | 219 | <p className="text-muted-foreground"> |
| 220 | Allow T3 Code to read your desktop settings. You'll review any changes here before | 220 | Allow The Knife to read your desktop settings. You'll review any changes here before |
| 221 | saving. | 221 | saving. |
| 222 | </p> | 222 | </p> |
| 223 | <Button | 223 | <Button |
| ... | @@ -229,7 +229,7 @@ export function CaptureShortcutConfig({ | ... | @@ -229,7 +229,7 @@ export function CaptureShortcutConfig({ |
| 229 | </Button> | 229 | </Button> |
| 230 | {!supported ? ( | 230 | {!supported ? ( |
| 231 | <p className="text-xs text-muted-foreground"> | 231 | <p className="text-xs text-muted-foreground"> |
| 232 | Update T3 Code to finish setting up your shortcut. | 232 | Update The Knife to finish setting up your shortcut. |
| 233 | </p> | 233 | </p> |
| 234 | ) : null} | 234 | ) : null} |
| 235 | </> | 235 | </> |
| ... | @@ -243,7 +243,7 @@ export function CaptureShortcutConfig({ | ... | @@ -243,7 +243,7 @@ export function CaptureShortcutConfig({ |
| 243 | <p role="status" className="text-muted-foreground"> | 243 | <p role="status" className="text-muted-foreground"> |
| 244 | {state.shortcutPending | 244 | {state.shortcutPending |
| 245 | ? "Connecting to your desktop…" | 245 | ? "Connecting to your desktop…" |
| 246 | : "Restart T3 Code to finish connecting your shortcut."} | 246 | : "Restart The Knife to finish connecting your shortcut."} |
| 247 | </p> | 247 | </p> |
| 248 | ) : null} | 248 | ) : null} |
| 249 | <details className="text-xs text-muted-foreground"> | 249 | <details className="text-xs text-muted-foreground"> |
| ... | @@ -262,7 +262,7 @@ export function CaptureShortcutConfig({ | ... | @@ -262,7 +262,7 @@ export function CaptureShortcutConfig({ |
| 262 | state.shortcutConfigPath ?? | 262 | state.shortcutConfigPath ?? |
| 263 | (niri ? "~/.config/niri/config.kdl" : "~/.config/hypr/hyprland.conf")} | 263 | (niri ? "~/.config/niri/config.kdl" : "~/.config/hypr/hyprland.conf")} |
| 264 | </p> | 264 | </p> |
| 265 | {niri ? <p>T3 Code also reads any files included by this file.</p> : null} | 265 | {niri ? <p>The Knife also reads any files included by this file.</p> : null} |
| 266 | {preview && preview.resolvedPath !== preview.path ? ( | 266 | {preview && preview.resolvedPath !== preview.path ? ( |
| 267 | <p className="break-all">Linked to {preview.resolvedPath}. The link will be kept.</p> | 267 | <p className="break-all">Linked to {preview.resolvedPath}. The link will be kept.</p> |
| 268 | ) : null} | 268 | ) : null} |
| ... | @@ -323,7 +323,7 @@ export function CaptureShortcutConfig({ | ... | @@ -323,7 +323,7 @@ export function CaptureShortcutConfig({ |
| 323 | {isCopied ? "Copied" : "Copy shortcut"} | 323 | {isCopied ? "Copied" : "Copy shortcut"} |
| 324 | </Button> | 324 | </Button> |
| 325 | <p> | 325 | <p> |
| 326 | Turn capture off in T3 Code to stop it. Remove the shortcut from {desktop} to free up | 326 | Turn capture off in The Knife to stop it. Remove the shortcut from {desktop} to free up |
| 327 | the keys. | 327 | the keys. |
| 328 | </p> | 328 | </p> |
| 329 | {state.shortcutActionRegistered === false ? ( | 329 | {state.shortcutActionRegistered === false ? ( |
apps/web/src/components/settings/ChatGptWelcomeCoordinator.tsx+1-1| ... | @@ -66,7 +66,7 @@ export function ChatGptWelcomeCoordinator() { | ... | @@ -66,7 +66,7 @@ export function ChatGptWelcomeCoordinator() { |
| 66 | <OpenAI className="mb-2 size-8" aria-hidden="true" /> | 66 | <OpenAI className="mb-2 size-8" aria-hidden="true" /> |
| 67 | <DialogTitle>Your ChatGPT plan is connected</DialogTitle> | 67 | <DialogTitle>Your ChatGPT plan is connected</DialogTitle> |
| 68 | <DialogDescription> | 68 | <DialogDescription> |
| 69 | Eligible usage in T3 Code uses your ChatGPT plan. Manage your shared usage and any | 69 | Eligible usage in The Knife uses your ChatGPT plan. Manage your shared usage and any |
| 70 | credit settings in ChatGPT. | 70 | credit settings in ChatGPT. |
| 71 | </DialogDescription> | 71 | </DialogDescription> |
| 72 | <p className="text-xs text-muted-foreground"> | 72 | <p className="text-xs text-muted-foreground"> |
apps/web/src/components/settings/CliCommandSettingsRow.tsx+2-2| ... | @@ -52,9 +52,9 @@ export function CliCommandSettingsRow() { | ... | @@ -52,9 +52,9 @@ export function CliCommandSettingsRow() { |
| 52 | if (!bridge || !state?.supported) return null; | 52 | if (!bridge || !state?.supported) return null; |
| 53 | const installed = state.installedPath !== null; | 53 | const installed = state.installedPath !== null; |
| 54 | const description = state.shadowedBy | 54 | const description = state.shadowedBy |
| 55 | ? `Another t3 at ${state.shadowedBy} runs first in a new terminal. Remove it to use T3 Code's.` | 55 | ? `Another t3 at ${state.shadowedBy} runs first in a new terminal. Remove it to use The Knife's.` |
| 56 | : !installed | 56 | : !installed |
| 57 | ? "Run T3 Code's CLI as `t3` from any terminal." | 57 | ? "Run The Knife's CLI as `t3` from any terminal." |
| 58 | : state.onPath | 58 | : state.onPath |
| 59 | ? `Installed at ${state.installedPath}. Open a new terminal to use it.` | 59 | ? `Installed at ${state.installedPath}. Open a new terminal to use it.` |
| 60 | : `Installed at ${state.installedPath}, which is not on your PATH yet. Add its folder to your PATH to run \`t3\`.`; | 60 | : `Installed at ${state.installedPath}, which is not on your PATH yet. Add its folder to your PATH to run \`t3\`.`; |
apps/web/src/components/settings/CodexSetupSection.tsx+4-4| ... | @@ -577,8 +577,8 @@ function ManagedCodexSetup({ | ... | @@ -577,8 +577,8 @@ function ManagedCodexSetup({ |
| 577 | : installation?.phase === "verifying" | 577 | : installation?.phase === "verifying" |
| 578 | ? "Checking Codex." | 578 | ? "Checking Codex." |
| 579 | : installed | 579 | : installed |
| 580 | ? `${installation?.source === "local" ? "Using your installed Codex" : "Managed by T3 Code"}${installation?.installedVersion ? ` · v${installation.installedVersion}` : ""}.` | 580 | ? `${installation?.source === "local" ? "Using your installed Codex" : "Managed by The Knife"}${installation?.installedVersion ? ` · v${installation.installedVersion}` : ""}.` |
| 581 | : (installation?.message ?? "T3 Code downloads and manages Codex for you."); | 581 | : (installation?.message ?? "The Knife downloads and manages Codex for you."); |
| 582 | const accountDescription = finishingSignIn ? ( | 582 | const accountDescription = finishingSignIn ? ( |
| 583 | "Finishing sign-in..." | 583 | "Finishing sign-in..." |
| 584 | ) : installActive ? ( | 584 | ) : installActive ? ( |
| ... | @@ -655,7 +655,7 @@ function ManagedCodexSetup({ | ... | @@ -655,7 +655,7 @@ function ManagedCodexSetup({ |
| 655 | const callbackCompletion = | 655 | const callbackCompletion = |
| 656 | !handoff && auth?.phase === "waiting" && url ? ( | 656 | !handoff && auth?.phase === "waiting" && url ? ( |
| 657 | <div className="flex w-full flex-col gap-3 text-xs leading-relaxed text-muted-foreground"> | 657 | <div className="flex w-full flex-col gap-3 text-xs leading-relaxed text-muted-foreground"> |
| 658 | <p>If sign-in doesn't return to T3 Code, paste the URL from the final localhost page.</p> | 658 | <p>If sign-in doesn't return to The Knife, paste the URL from the final localhost page.</p> |
| 659 | <form | 659 | <form |
| 660 | className="flex flex-col gap-2 sm:flex-row sm:items-center" | 660 | className="flex flex-col gap-2 sm:flex-row sm:items-center" |
| 661 | onSubmit={(event) => { | 661 | onSubmit={(event) => { |
| ... | @@ -1032,7 +1032,7 @@ export function CodexManagedRuntimeFields({ | ... | @@ -1032,7 +1032,7 @@ export function CodexManagedRuntimeFields({ |
| 1032 | <> | 1032 | <> |
| 1033 | <SettingsRow | 1033 | <SettingsRow |
| 1034 | title="Binary path" | 1034 | title="Binary path" |
| 1035 | description="Selected by T3 Code." | 1035 | description="Selected by The Knife." |
| 1036 | control={ | 1036 | control={ |
| 1037 | <div className="w-full sm:w-80"> | 1037 | <div className="w-full sm:w-80"> |
| 1038 | <Input | 1038 | <Input |
apps/web/src/components/settings/ConnectionsSettings.tsx+10-10| ... | @@ -3480,7 +3480,7 @@ export function ConnectionsSettings() { | ... | @@ -3480,7 +3480,7 @@ export function ConnectionsSettings() { |
| 3480 | {desktopWslState.enabled ? ( | 3480 | {desktopWslState.enabled ? ( |
| 3481 | <SettingsRow | 3481 | <SettingsRow |
| 3482 | title="WSL only" | 3482 | title="WSL only" |
| 3483 | description="Run only the WSL backend. T3 Code restarts when this changes." | 3483 | description="Run only the WSL backend. The Knife restarts when this changes." |
| 3484 | className="bg-muted/20 pl-7 sm:pl-8" | 3484 | className="bg-muted/20 pl-7 sm:pl-8" |
| 3485 | control={ | 3485 | control={ |
| 3486 | <Switch | 3486 | <Switch |
| ... | @@ -3805,8 +3805,8 @@ export function ConnectionsSettings() { | ... | @@ -3805,8 +3805,8 @@ export function ConnectionsSettings() { |
| 3805 | </AlertDialogTitle> | 3805 | </AlertDialogTitle> |
| 3806 | <AlertDialogDescription> | 3806 | <AlertDialogDescription> |
| 3807 | {pendingDesktopServerExposureMode === "network-accessible" | 3807 | {pendingDesktopServerExposureMode === "network-accessible" |
| 3808 | ? "Let your other devices connect to T3 Code over the network. Pair devices to give them access. T3 Code will restart." | 3808 | ? "Let your other devices connect to The Knife over the network. Pair devices to give them access. The Knife will restart." |
| 3809 | : "Devices connected over your local network will disconnect. Existing tunnels, such as T3 Connect or Tailscale HTTPS, keep working. T3 Code will restart."} | 3809 | : "Devices connected over your local network will disconnect. Existing tunnels, such as T3 Connect or Tailscale HTTPS, keep working. The Knife will restart."} |
| 3810 | </AlertDialogDescription> | 3810 | </AlertDialogDescription> |
| 3811 | </AlertDialogHeader> | 3811 | </AlertDialogHeader> |
| 3812 | <AlertDialogFooter> | 3812 | <AlertDialogFooter> |
| ... | @@ -3862,15 +3862,15 @@ export function ConnectionsSettings() { | ... | @@ -3862,15 +3862,15 @@ export function ConnectionsSettings() { |
| 3862 | <AlertDialogDescription> | 3862 | <AlertDialogDescription> |
| 3863 | {pendingWslChange?.kind === "disable" | 3863 | {pendingWslChange?.kind === "disable" |
| 3864 | ? pendingWslChange.wasWslOnly | 3864 | ? pendingWslChange.wasWslOnly |
| 3865 | ? "T3 Code will restart on the Windows backend. Threads and projects opened against WSL stay safe inside the distro and become available again when you re-enable WSL." | 3865 | ? "The Knife will restart on the Windows backend. Threads and projects opened against WSL stay safe inside the distro and become available again when you re-enable WSL." |
| 3866 | : "The WSL backend will stop. Threads and projects opened against WSL stay safe inside the distro, but they'll be unavailable in T3 Code until you re-enable WSL." | 3866 | : "The WSL backend will stop. Threads and projects opened against WSL stay safe inside the distro, but they'll be unavailable in The Knife until you re-enable WSL." |
| 3867 | : pendingWslChange?.kind === "distro" | 3867 | : pendingWslChange?.kind === "distro" |
| 3868 | ? "T3 Code will restart the WSL backend on the new distro. Sessions still running on the current distro will be interrupted." | 3868 | ? "The Knife will restart the WSL backend on the new distro. Sessions still running on the current distro will be interrupted." |
| 3869 | : pendingWslChange?.kind === "enable" | 3869 | : pendingWslChange?.kind === "enable" |
| 3870 | ? "Run the WSL backend alongside the Windows one, or stop the Windows backend and use only WSL? You can change this later from Settings." | 3870 | ? "Run the WSL backend alongside the Windows one, or stop the Windows backend and use only WSL? You can change this later from Settings." |
| 3871 | : pendingWslChange?.nextValue | 3871 | : pendingWslChange?.nextValue |
| 3872 | ? "T3 Code will restart and start only the WSL backend. Your Windows-side projects won't be accessible until you turn this off again." | 3872 | ? "The Knife will restart and start only the WSL backend. Your Windows-side projects won't be accessible until you turn this off again." |
| 3873 | : "T3 Code will restart and bring the Windows backend back up alongside WSL."} | 3873 | : "The Knife will restart and bring the Windows backend back up alongside WSL."} |
| 3874 | </AlertDialogDescription> | 3874 | </AlertDialogDescription> |
| 3875 | </AlertDialogHeader> | 3875 | </AlertDialogHeader> |
| 3876 | <AlertDialogFooter> | 3876 | <AlertDialogFooter> |
| ... | @@ -3956,7 +3956,7 @@ export function ConnectionsSettings() { | ... | @@ -3956,7 +3956,7 @@ export function ConnectionsSettings() { |
| 3956 | <AlertDialogHeader> | 3956 | <AlertDialogHeader> |
| 3957 | <AlertDialogTitle>Disable Tailscale HTTPS?</AlertDialogTitle> | 3957 | <AlertDialogTitle>Disable Tailscale HTTPS?</AlertDialogTitle> |
| 3958 | <AlertDialogDescription> | 3958 | <AlertDialogDescription> |
| 3959 | T3 Code will restart the local backend without Tailscale Serve. | 3959 | The Knife will restart the local backend without Tailscale Serve. |
| 3960 | </AlertDialogDescription> | 3960 | </AlertDialogDescription> |
| 3961 | </AlertDialogHeader> | 3961 | </AlertDialogHeader> |
| 3962 | <AlertDialogFooter> | 3962 | <AlertDialogFooter> |
| ... | @@ -3994,7 +3994,7 @@ export function ConnectionsSettings() { | ... | @@ -3994,7 +3994,7 @@ export function ConnectionsSettings() { |
| 3994 | <DialogHeader> | 3994 | <DialogHeader> |
| 3995 | <DialogTitle>Set up Tailscale HTTPS?</DialogTitle> | 3995 | <DialogTitle>Set up Tailscale HTTPS?</DialogTitle> |
| 3996 | <DialogDescription> | 3996 | <DialogDescription> |
| 3997 | T3 Code will restart the local backend with Tailscale Serve enabled and ask | 3997 | The Knife will restart the local backend with Tailscale Serve enabled and ask |
| 3998 | Tailscale to proxy HTTPS traffic to this backend. | 3998 | Tailscale to proxy HTTPS traffic to this backend. |
| 3999 | </DialogDescription> | 3999 | </DialogDescription> |
| 4000 | </DialogHeader> | 4000 | </DialogHeader> |
apps/web/src/components/settings/IntegrationsSettings.tsx+1-1| ... | @@ -577,7 +577,7 @@ function BrowserRecordingFrameRateSetting({ disabled }: { readonly disabled: boo | ... | @@ -577,7 +577,7 @@ function BrowserRecordingFrameRateSetting({ disabled }: { readonly disabled: boo |
| 577 | 577 | ||
| 578 | const LINK_TARGET_LABELS: Readonly<Record<BrowserLinkTarget, string>> = { | 578 | const LINK_TARGET_LABELS: Readonly<Record<BrowserLinkTarget, string>> = { |
| 579 | system: "Your default browser", | 579 | system: "Your default browser", |
| 580 | app: "T3 Code", | 580 | app: "The Knife", |
| 581 | }; | 581 | }; |
| 582 | 582 | ||
| 583 | function BrowserLinkTargetSetting({ disabled }: { readonly disabled: boolean }) { | 583 | function BrowserLinkTargetSetting({ disabled }: { readonly disabled: boolean }) { |
apps/web/src/components/settings/KeybindingsSettings.tsx+1-1| ... | @@ -257,7 +257,7 @@ function UnknownWhenVariableWarning({ | ... | @@ -257,7 +257,7 @@ function UnknownWhenVariableWarning({ |
| 257 | 257 | ||
| 258 | return ( | 258 | return ( |
| 259 | <WarningTooltipIcon label={label} focusable={focusable} className="size-4.5"> | 259 | <WarningTooltipIcon label={label} focusable={focusable} className="size-4.5"> |
| 260 | T3 Code does not recognize this condition yet. It can still be saved, but it may not match | 260 | The Knife does not recognize this condition yet. It can still be saved, but it may not match |
| 261 | unless the runtime provides it. | 261 | unless the runtime provides it. |
| 262 | </WarningTooltipIcon> | 262 | </WarningTooltipIcon> |
| 263 | ); | 263 | ); |
apps/web/src/components/settings/LocalEnvironmentSetting.tsx+3-3| ... | @@ -43,7 +43,7 @@ export function LocalEnvironmentSetting() { | ... | @@ -43,7 +43,7 @@ export function LocalEnvironmentSetting() { |
| 43 | {...searchableSetting("local-environment")} | 43 | {...searchableSetting("local-environment")} |
| 44 | description={ | 44 | description={ |
| 45 | enabled | 45 | enabled |
| 46 | ? "Run agents on this computer. Turn off to use T3 Code only with remote environments." | 46 | ? "Run agents on this computer. Turn off to use The Knife only with remote environments." |
| 47 | : "Turned off. Agents only run in remote environments." | 47 | : "Turned off. Agents only run in remote environments." |
| 48 | } | 48 | } |
| 49 | control={ | 49 | control={ |
| ... | @@ -70,8 +70,8 @@ export function LocalEnvironmentSetting() { | ... | @@ -70,8 +70,8 @@ export function LocalEnvironmentSetting() { |
| 70 | </AlertDialogTitle> | 70 | </AlertDialogTitle> |
| 71 | <AlertDialogDescription> | 71 | <AlertDialogDescription> |
| 72 | {enabled | 72 | {enabled |
| 73 | ? "T3 Code will restart without running a server on this computer. Any agents and terminals running here will stop, and other devices will no longer be able to connect to this computer. Your projects, history, and remote environments are unaffected." | 73 | ? "The Knife will restart without running a server on this computer. Any agents and terminals running here will stop, and other devices will no longer be able to connect to this computer. Your projects, history, and remote environments are unaffected." |
| 74 | : "T3 Code will restart and start running a server on this computer again."} | 74 | : "The Knife will restart and start running a server on this computer again."} |
| 75 | </AlertDialogDescription> | 75 | </AlertDialogDescription> |
| 76 | </AlertDialogHeader> | 76 | </AlertDialogHeader> |
| 77 | {error ? <p className="px-6 pb-4 text-sm text-destructive">{error}</p> : null} | 77 | {error ? <p className="px-6 pb-4 text-sm text-destructive">{error}</p> : null} |
apps/web/src/components/settings/NotificationSettings.tsx+1-1| ... | @@ -22,7 +22,7 @@ export function NotificationSettings() { | ... | @@ -22,7 +22,7 @@ export function NotificationSettings() { |
| 22 | {...searchableSetting("thread-notifications")} | 22 | {...searchableSetting("thread-notifications")} |
| 23 | description={ | 23 | description={ |
| 24 | permissionMessage ?? | 24 | permissionMessage ?? |
| 25 | "System alerts when a thread finishes, fails, or needs input or approval. Applies to this device while T3 Code is open." | 25 | "System alerts when a thread finishes, fails, or needs input or approval. Applies to this device while The Knife is open." |
| 26 | } | 26 | } |
| 27 | control={ | 27 | control={ |
| 28 | <Select | 28 | <Select |
apps/web/src/components/settings/SettingsPanels.tsx+1-1| ... | @@ -3422,7 +3422,7 @@ export function GeneralSettingsPanel() { | ... | @@ -3422,7 +3422,7 @@ export function GeneralSettingsPanel() { |
| 3422 | /> | 3422 | /> |
| 3423 | <SettingsRow | 3423 | <SettingsRow |
| 3424 | {...searchableSetting("open-source-licenses")} | 3424 | {...searchableSetting("open-source-licenses")} |
| 3425 | description="Notices for dependencies, assets, and optional tools used by T3 Code." | 3425 | description="Notices for dependencies, assets, and optional tools used by The Knife." |
| 3426 | control={ | 3426 | control={ |
| 3427 | <Button | 3427 | <Button |
| 3428 | render={<Link to="/settings/open-source-licenses" />} | 3428 | render={<Link to="/settings/open-source-licenses" />} |
apps/web/src/components/settings/SnapShotSettings.tsx+2-2| ... | @@ -263,7 +263,7 @@ export function SnapShotSettings() { | ... | @@ -263,7 +263,7 @@ export function SnapShotSettings() { |
| 263 | const shortcutStatus = recording | 263 | const shortcutStatus = recording |
| 264 | ? "Press your shortcut. Esc cancels." | 264 | ? "Press your shortcut. Esc cancels." |
| 265 | : candidateConflict | 265 | : candidateConflict |
| 266 | ? `T3 Code already uses this for "${commandLabel(candidateConflict)}".` | 266 | ? `The Knife already uses this for "${commandLabel(candidateConflict)}".` |
| 267 | : shortcutCheck.status === "checking" | 267 | : shortcutCheck.status === "checking" |
| 268 | ? "Checking shortcut…" | 268 | ? "Checking shortcut…" |
| 269 | : shortcutCheck.availability | 269 | : shortcutCheck.availability |
| ... | @@ -315,7 +315,7 @@ export function SnapShotSettings() { | ... | @@ -315,7 +315,7 @@ export function SnapShotSettings() { |
| 315 | try { | 315 | try { |
| 316 | if (state?.macPermissions) { | 316 | if (state?.macPermissions) { |
| 317 | saveSnapShotSetupResume(wizard?.wasEnabled ?? settings.snapShotEnabled); | 317 | saveSnapShotSetupResume(wizard?.wasEnabled ?? settings.snapShotEnabled); |
| 318 | if (!bridge?.setupSnapShot) throw new Error("Restart T3 Code to finish capture setup."); | 318 | if (!bridge?.setupSnapShot) throw new Error("Restart The Knife to finish capture setup."); |
| 319 | await bridge.setupSnapShot("test-mac-capture"); | 319 | await bridge.setupSnapShot("test-mac-capture"); |
| 320 | } | 320 | } |
| 321 | if (state?.mode === "direct") | 321 | if (state?.mode === "direct") |
apps/web/src/components/settings/SnapShotSetupDialog.tsx+2-2| ... | @@ -174,7 +174,7 @@ export function SnapShotSetupDialog({ | ... | @@ -174,7 +174,7 @@ export function SnapShotSetupDialog({ |
| 174 | ? "Update the capture helper" | 174 | ? "Update the capture helper" |
| 175 | : "Allow snapshots", | 175 | : "Allow snapshots", |
| 176 | description: | 176 | description: |
| 177 | "T3 Code's capture helper lets you capture other apps and return to your draft. It's included with T3 Code.", | 177 | "The Knife's capture helper lets you capture other apps and return to your draft. It's included with The Knife.", |
| 178 | } | 178 | } |
| 179 | : backend === "niri" | 179 | : backend === "niri" |
| 180 | ? { | 180 | ? { |
| ... | @@ -355,7 +355,7 @@ export function SnapShotSetupDialog({ | ... | @@ -355,7 +355,7 @@ export function SnapShotSetupDialog({ |
| 355 | </p> | 355 | </p> |
| 356 | ))} | 356 | ))} |
| 357 | {step === "access" && (backend === "gnome" || helperBackend) ? ( | 357 | {step === "access" && (backend === "gnome" || helperBackend) ? ( |
| 358 | <p>Included with T3 Code. No download needed.</p> | 358 | <p>Included with The Knife. No download needed.</p> |
| 359 | ) : null} | 359 | ) : null} |
| 360 | {step === "access" && backend === "gnome" && extension?.status === "enabled" ? ( | 360 | {step === "access" && backend === "gnome" && extension?.status === "enabled" ? ( |
| 361 | <Button | 361 | <Button |
apps/web/src/components/settings/providerStatus.ts+1-1| ... | @@ -44,7 +44,7 @@ export function getProviderSummary(provider: ServerProvider | undefined) { | ... | @@ -44,7 +44,7 @@ export function getProviderSummary(provider: ServerProvider | undefined) { |
| 44 | return { | 44 | return { |
| 45 | headline: "Disabled", | 45 | headline: "Disabled", |
| 46 | detail: | 46 | detail: |
| 47 | provider.message ?? "This provider is installed but disabled for new sessions in T3 Code.", | 47 | provider.message ?? "This provider is installed but disabled for new sessions in The Knife.", |
| 48 | }; | 48 | }; |
| 49 | } | 49 | } |
| 50 | if (!provider.installed) { | 50 | if (!provider.installed) { |
apps/web/src/components/settings/scopedSettings.ts+1-1| ... | @@ -475,7 +475,7 @@ export async function persistScopedSettingsPatch( | ... | @@ -475,7 +475,7 @@ export async function persistScopedSettingsPatch( |
| 475 | return [ | 475 | return [ |
| 476 | { | 476 | { |
| 477 | ...environment, | 477 | ...environment, |
| 478 | message: "This environment does not support this setting. Update T3 Code on it.", | 478 | message: "This environment does not support this setting. Update The Knife on it.", |
| 479 | }, | 479 | }, |
| 480 | ]; | 480 | ]; |
| 481 | } | 481 | } |
apps/web/src/components/settings/settingsLayout.tsx+1| ... | @@ -573,6 +573,7 @@ export function SettingsPageContainer({ | ... | @@ -573,6 +573,7 @@ export function SettingsPageContainer({ |
| 573 | <div | 573 | <div |
| 574 | className="topbar-scroll-fade scrollbar-gutter-both flex-1 overflow-y-auto" | 574 | className="topbar-scroll-fade scrollbar-gutter-both flex-1 overflow-y-auto" |
| 575 | data-settings-page-scroll | 575 | data-settings-page-scroll |
| 576 | data-scroll-restoration-id="settings-page" | ||
| 576 | > | 577 | > |
| 577 | <WorkspacePageContainer width={width} className={cn("gap-8", className)}> | 578 | <WorkspacePageContainer width={width} className={cn("gap-8", className)}> |
| 578 | <SettingsScopeSentence /> | 579 | <SettingsScopeSentence /> |
apps/web/src/components/sidebar/mainAppLocation.ts+5-4| ... | @@ -14,8 +14,6 @@ export function isSidebarUtilityPage(pathname: string) { | ... | @@ -14,8 +14,6 @@ export function isSidebarUtilityPage(pathname: string) { |
| 14 | ); | 14 | ); |
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | let mainAppHref: string | null = null; | ||
| 18 | |||
| 19 | // Mount once in the app shell. Records the latest main app URL so Back can | 17 | // Mount once in the app shell. Records the latest main app URL so Back can |
| 20 | // return there no matter how many utility pages were visited since. | 18 | // return there no matter how many utility pages were visited since. |
| 21 | export function MainAppLocationTracker() { | 19 | export function MainAppLocationTracker() { |
| ... | @@ -23,7 +21,7 @@ export function MainAppLocationTracker() { | ... | @@ -23,7 +21,7 @@ export function MainAppLocationTracker() { |
| 23 | select: (location) => (isSidebarUtilityPage(location.pathname) ? null : location.href), | 21 | select: (location) => (isSidebarUtilityPage(location.pathname) ? null : location.href), |
| 24 | }); | 22 | }); |
| 25 | useEffect(() => { | 23 | useEffect(() => { |
| 26 | if (href !== null) mainAppHref = href; | 24 | if (href !== null) sessionStorage.setItem("t3code-main-app-href", href); |
| 27 | }, [href]); | 25 | }, [href]); |
| 28 | return null; | 26 | return null; |
| 29 | } | 27 | } |
| ... | @@ -32,5 +30,8 @@ export function MainAppLocationTracker() { | ... | @@ -32,5 +30,8 @@ export function MainAppLocationTracker() { |
| 32 | // the app was opened directly on a utility page. | 30 | // the app was opened directly on a utility page. |
| 33 | export function useNavigateToMainApp() { | 31 | export function useNavigateToMainApp() { |
| 34 | const navigate = useNavigate(); | 32 | const navigate = useNavigate(); |
| 35 | return useCallback(() => navigate({ href: mainAppHref ?? "/" }), [navigate]); | 33 | return useCallback( |
| 34 | () => navigate({ href: sessionStorage.getItem("t3code-main-app-href") ?? "/" }), | ||
| 35 | [navigate], | ||
| 36 | ); | ||
| 36 | } | 37 | } |
apps/web/src/components/sidebar/updateEverythingRun.tsx+1-1| ... | @@ -133,7 +133,7 @@ export function DesktopUpdateLaunchNotice() { | ... | @@ -133,7 +133,7 @@ export function DesktopUpdateLaunchNotice() { |
| 133 | stackedThreadToast({ | 133 | stackedThreadToast({ |
| 134 | type: "error", | 134 | type: "error", |
| 135 | title: "Update did not apply", | 135 | title: "Update did not apply", |
| 136 | description: `T3 Code is still on ${APP_VERSION}. Install ${notAppliedVersion} again from the update button.`, | 136 | description: `The Knife is still on ${APP_VERSION}. Install ${notAppliedVersion} again from the update button.`, |
| 137 | }), | 137 | }), |
| 138 | ); | 138 | ); |
| 139 | }, [notAppliedVersion]); | 139 | }, [notAppliedVersion]); |
apps/web/src/connection/clientMetadata.test.ts+1-1| ... | @@ -80,7 +80,7 @@ describe("client telemetry metadata", () => { | ... | @@ -80,7 +80,7 @@ describe("client telemetry metadata", () => { |
| 80 | desktopBridge: { getClientPlatform: () => "darwin" }, | 80 | desktopBridge: { getClientPlatform: () => "darwin" }, |
| 81 | }), | 81 | }), |
| 82 | ).toEqual({ | 82 | ).toEqual({ |
| 83 | label: "T3 Code Desktop", | 83 | label: "The Knife Desktop", |
| 84 | deviceType: "desktop", | 84 | deviceType: "desktop", |
| 85 | os: "macOS", | 85 | os: "macOS", |
| 86 | surface: "desktop", | 86 | surface: "desktop", |
apps/web/src/connection/clientMetadata.ts+2-2| ... | @@ -77,7 +77,7 @@ export function clientPresentationMetadata(input: { | ... | @@ -77,7 +77,7 @@ export function clientPresentationMetadata(input: { |
| 77 | }): AuthClientPresentationMetadata { | 77 | }): AuthClientPresentationMetadata { |
| 78 | if (input.desktopBridge !== undefined) { | 78 | if (input.desktopBridge !== undefined) { |
| 79 | return { | 79 | return { |
| 80 | label: "T3 Code Desktop", | 80 | label: "The Knife Desktop", |
| 81 | deviceType: "desktop", | 81 | deviceType: "desktop", |
| 82 | os: clientOsFromElectronPlatform(input.desktopBridge.getClientPlatform?.()), | 82 | os: clientOsFromElectronPlatform(input.desktopBridge.getClientPlatform?.()), |
| 83 | surface: "desktop", | 83 | surface: "desktop", |
| ... | @@ -86,7 +86,7 @@ export function clientPresentationMetadata(input: { | ... | @@ -86,7 +86,7 @@ export function clientPresentationMetadata(input: { |
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | return { | 88 | return { |
| 89 | label: "T3 Code Web", | 89 | label: "The Knife Web", |
| 90 | deviceType: browserDeviceType(input.identity), | 90 | deviceType: browserDeviceType(input.identity), |
| 91 | os: browserClientOs(input.identity), | 91 | os: browserClientOs(input.identity), |
| 92 | surface: "web", | 92 | surface: "web", |
apps/web/src/desktopAppActivation.ts+3-3| ... | @@ -85,7 +85,7 @@ export async function handleDesktopAppActivationRequest( | ... | @@ -85,7 +85,7 @@ export async function handleDesktopAppActivationRequest( |
| 85 | return failure( | 85 | return failure( |
| 86 | request.requestId, | 86 | request.requestId, |
| 87 | "project-create-failed", | 87 | "project-create-failed", |
| 88 | errorMessage(error, "T3 Code could not add the project."), | 88 | errorMessage(error, "The Knife could not add the project."), |
| 89 | ); | 89 | ); |
| 90 | } | 90 | } |
| 91 | } | 91 | } |
| ... | @@ -99,7 +99,7 @@ export async function handleDesktopAppActivationRequest( | ... | @@ -99,7 +99,7 @@ export async function handleDesktopAppActivationRequest( |
| 99 | return failure( | 99 | return failure( |
| 100 | request.requestId, | 100 | request.requestId, |
| 101 | "thread-open-failed", | 101 | "thread-open-failed", |
| 102 | "T3 Code could not open a new thread for the project.", | 102 | "The Knife could not open a new thread for the project.", |
| 103 | ); | 103 | ); |
| 104 | } | 104 | } |
| 105 | return { | 105 | return { |
| ... | @@ -113,7 +113,7 @@ export async function handleDesktopAppActivationRequest( | ... | @@ -113,7 +113,7 @@ export async function handleDesktopAppActivationRequest( |
| 113 | return failure( | 113 | return failure( |
| 114 | request.requestId, | 114 | request.requestId, |
| 115 | "thread-open-failed", | 115 | "thread-open-failed", |
| 116 | errorMessage(error, "T3 Code could not open a new thread for the project."), | 116 | errorMessage(error, "The Knife could not open a new thread for the project."), |
| 117 | ); | 117 | ); |
| 118 | } | 118 | } |
| 119 | } | 119 | } |
apps/web/src/lib/bootError.ts+2-2| ... | @@ -1,6 +1,6 @@ | ... | @@ -1,6 +1,6 @@ |
| 1 | /** Shows startup failures before React can replace the boot splash. */ | 1 | /** Shows startup failures before React can replace the boot splash. */ |
| 2 | export function showBootError(error: unknown) { | 2 | export function showBootError(error: unknown) { |
| 3 | console.error("T3 Code failed to start.", error); | 3 | console.error("The Knife failed to start.", error); |
| 4 | const bootShell = document.getElementById("boot-shell"); | 4 | const bootShell = document.getElementById("boot-shell"); |
| 5 | if (!bootShell) return; | 5 | if (!bootShell) return; |
| 6 | 6 | ||
| ... | @@ -9,7 +9,7 @@ export function showBootError(error: unknown) { | ... | @@ -9,7 +9,7 @@ export function showBootError(error: unknown) { |
| 9 | content.setAttribute("role", "alert"); | 9 | content.setAttribute("role", "alert"); |
| 10 | 10 | ||
| 11 | const message = document.createElement("p"); | 11 | const message = document.createElement("p"); |
| 12 | message.textContent = "T3 Code could not load."; | 12 | message.textContent = "The Knife could not load."; |
| 13 | content.append(message); | 13 | content.append(message); |
| 14 | 14 | ||
| 15 | if (import.meta.env.DEV && error instanceof Error) { | 15 | if (import.meta.env.DEV && error instanceof Error) { |
apps/web/src/router.ts+2| ... | @@ -1,12 +1,14 @@ | ... | @@ -1,12 +1,14 @@ |
| 1 | import { createRouter, RouterHistory } from "@tanstack/react-router"; | 1 | import { createRouter, RouterHistory } from "@tanstack/react-router"; |
| 2 | 2 | ||
| 3 | import { routeTree } from "./routeTree.gen"; | 3 | import { routeTree } from "./routeTree.gen"; |
| 4 | import { isSidebarUtilityPage } from "./components/sidebar/mainAppLocation"; | ||
| 4 | 5 | ||
| 5 | export function getRouter(history: RouterHistory) { | 6 | export function getRouter(history: RouterHistory) { |
| 6 | return createRouter({ | 7 | return createRouter({ |
| 7 | routeTree, | 8 | routeTree, |
| 8 | history, | 9 | history, |
| 9 | context: {}, | 10 | context: {}, |
| 11 | scrollRestoration: ({ location }) => isSidebarUtilityPage(location.pathname), | ||
| 10 | // Route components are split chunks (autoCodeSplitting in vite.config); | 12 | // Route components are split chunks (autoCodeSplitting in vite.config); |
| 11 | // fetching them on hover/focus intent hides the load from the first | 13 | // fetching them on hover/focus intent hides the load from the first |
| 12 | // settings or pull-request navigation. | 14 | // settings or pull-request navigation. |
apps/web/src/routes/_chat.index.tsx+2-2| ... | @@ -158,9 +158,9 @@ function HostedStaticOnboardingState() { | ... | @@ -158,9 +158,9 @@ function HostedStaticOnboardingState() { |
| 158 | <div className="mx-auto mb-5 flex size-11 items-center justify-center rounded-xl border border-border/70 bg-background/70 text-muted-foreground"> | 158 | <div className="mx-auto mb-5 flex size-11 items-center justify-center rounded-xl border border-border/70 bg-background/70 text-muted-foreground"> |
| 159 | <LinkIcon className="size-5" /> | 159 | <LinkIcon className="size-5" /> |
| 160 | </div> | 160 | </div> |
| 161 | <EmptyTitle>Connect to a computer running T3 Code</EmptyTitle> | 161 | <EmptyTitle>Connect to a computer running The Knife</EmptyTitle> |
| 162 | <EmptyDescription> | 162 | <EmptyDescription> |
| 163 | This app connects to T3 Code running on your computer or a server. Start the T3 Code | 163 | This app connects to The Knife running on your computer or a server. Start The Knife |
| 164 | desktop app or command-line server on that machine and keep it running. | 164 | desktop app or command-line server on that machine and keep it running. |
| 165 | </EmptyDescription> | 165 | </EmptyDescription> |
| 166 | <EmptyDescription>{description}</EmptyDescription> | 166 | <EmptyDescription>{description}</EmptyDescription> |
apps/web/src/routes/_chat.pull-requests.tsx+1-1| ... | @@ -1914,7 +1914,7 @@ function PullRequestsRouteView() { | ... | @@ -1914,7 +1914,7 @@ function PullRequestsRouteView() { |
| 1914 | ) : !pullRequestsSupported ? ( | 1914 | ) : !pullRequestsSupported ? ( |
| 1915 | <PullRequestsUnavailableState | 1915 | <PullRequestsUnavailableState |
| 1916 | title="Pull requests unavailable" | 1916 | title="Pull requests unavailable" |
| 1917 | error="Update your T3 Code servers to browse pull requests." | 1917 | error="Update your servers to browse pull requests." |
| 1918 | /> | 1918 | /> |
| 1919 | ) : firstLoad ? ( | 1919 | ) : firstLoad ? ( |
| 1920 | <PullRequestListGhost rows={7} /> | 1920 | <PullRequestListGhost rows={7} /> |
apps/web/src/routes/_chat.tsx+1-1| ... | @@ -153,7 +153,7 @@ function ChatRouteGlobalShortcuts() { | ... | @@ -153,7 +153,7 @@ function ChatRouteGlobalShortcuts() { |
| 153 | stackedThreadToast({ | 153 | stackedThreadToast({ |
| 154 | type: "info", | 154 | type: "info", |
| 155 | title: "Preview is desktop-only", | 155 | title: "Preview is desktop-only", |
| 156 | description: "Open T3 Code in the desktop app to use the in-app preview.", | 156 | description: "Open The Knife in the desktop app to use the in-app preview.", |
| 157 | }), | 157 | }), |
| 158 | ); | 158 | ); |
| 159 | return; | 159 | return; |
apps/web/src/versionSkew.test.ts+1-1| ... | @@ -24,7 +24,7 @@ import { | ... | @@ -24,7 +24,7 @@ import { |
| 24 | } from "./versionSkew"; | 24 | } from "./versionSkew"; |
| 25 | 25 | ||
| 26 | const MISMATCH_HINT = | 26 | const MISMATCH_HINT = |
| 27 | "Version mismatch. Try syncing the client and server to the same T3 Code version."; | 27 | "Version mismatch. Try syncing the client and server to the same version of The Knife."; |
| 28 | 28 | ||
| 29 | describe("versionSkew", () => { | 29 | describe("versionSkew", () => { |
| 30 | it("updates only the proven npm prefix and safely quotes its path", () => { | 30 | it("updates only the proven npm prefix and safely quotes its path", () => { |
apps/web/src/versionSkew.ts+1-1| ... | @@ -87,7 +87,7 @@ export function resolveVersionMismatch( | ... | @@ -87,7 +87,7 @@ export function resolveVersionMismatch( |
| 87 | return { | 87 | return { |
| 88 | clientVersion: normalizedClientVersion, | 88 | clientVersion: normalizedClientVersion, |
| 89 | serverVersion: normalizedServerVersion, | 89 | serverVersion: normalizedServerVersion, |
| 90 | hint: "Version mismatch. Try syncing the client and server to the same T3 Code version.", | 90 | hint: "Version mismatch. Try syncing the client and server to the same version of The Knife.", |
| 91 | }; | 91 | }; |
| 92 | } | 92 | } |
| 93 | 93 |
assets/README.md+20-60| ... | @@ -1,67 +1,27 @@ | ... | @@ -1,67 +1,27 @@ |
| 1 | # Brand icons | 1 | # The Knife artwork |
| 2 | 2 | ||
| 3 | The three Icon Composer projects are the source of truth for full application icons: | 3 | `knife/app-icon.icon` is the canonical Icon Composer project. Its rendered knife |
| 4 | layer lives in `Assets/knife.png`; the project owns the obsidian backplate. All | ||
| 5 | release channels use this one identity. The older T3 projects remain as upstream | ||
| 6 | artwork references. | ||
| 4 | 7 | ||
| 5 | - `dev/app-icon.icon` | 8 | `vp run icons:export` regenerates the macOS, iOS, Linux, Windows, and web renditions, |
| 6 | - `nightly/app-icon.icon` | 9 | including browser public assets. `vp run icons:check` verifies those exports. |
| 7 | - `prod/app-icon.icon` | 10 | Icon Composer 2 or newer is required; `ICON_COMPOSER_TOOL` can select its `ictool`. |
| 8 | 11 | ||
| 9 | Each project uses `text.svg` for the T3 mark and `background.svg` when the background is a vector layer. Additional layers use semantic names that describe their role and placement. | 12 | ## macOS |
| 10 | 13 | ||
| 11 | Run `vp run icons:export` from the repository root to regenerate the tracked iOS, Linux, Windows, and web assets. The development web exports are also copied to `apps/web/public` for the browser favicon and splash screen. Run `vp run icons:check` to verify that the generated assets and public copies match their sources without changing files. | 14 | Icon Composer renders the macOS artwork with design generation 26. The exporter |
| 15 | fits its full-bleed output to an 824×824 body on a transparent 1024×1024 canvas, | ||
| 16 | inset 100px on every side. A black shadow uses 20% opacity, a 12px blur, and an | ||
| 17 | 8px downward offset. Only the shadow extends outside the body's safe area. | ||
| 12 | 18 | ||
| 13 | Exporting requires Icon Composer 2 or newer on macOS. The script selects the newest compatible exporter from Xcode or a standalone Icon Composer installation and pins design generation 26. Set `ICON_COMPOSER_TOOL` to the full path of `Icon Composer.app/Contents/Executables/ictool` to override automatic discovery. | 19 | `vp run icons:export` writes `knife/macos-1024.png`; no manual GUI export is |
| 20 | needed. Desktop packaging derives its ICNS and smaller resource PNG from it. | ||
| 14 | 21 | ||
| 15 | ## macOS exports | 22 | ## Android |
| 16 | 23 | ||
| 17 | Icon Composer's command-line exporter does not expose the `macOS pre-Tahoe` preset. A plain command-line `macOS` export is full bleed and is not suitable for the desktop app, so the export script intentionally leaves the tracked macOS PNGs unchanged and prints a reminder after every run. | 24 | `vp run icons:export:android` reads the same knife layer and backplate from the |
| 18 | 25 | Icon Composer project. It creates `knife/android-foreground.png` and | |
| 19 | After changing an Icon Composer project, open it in Icon Composer and export the macOS PNG with exactly these settings: | 26 | `knife/android-splash.png`, keeping the blade inside Android's circular safe |
| 20 | 27 | area. The adaptive and monochrome icons share the foreground alpha silhouette. | |
| 21 | - Platform: `macOS pre-Tahoe` | ||
| 22 | - Appearance: `Default` | ||
| 23 | - Size: `1024pt` | ||
| 24 | - Scale: `1×` | ||
| 25 | |||
| 26 | Save the three exports to: | ||
| 27 | |||
| 28 | - `dev/app-icon.icon` -> `dev/blueprint-macos-1024.png` | ||
| 29 | - `nightly/app-icon.icon` -> `nightly/nightly-macos-1024.png` | ||
| 30 | - `prod/app-icon.icon` -> `prod/black-macos-1024.png` | ||
| 31 | |||
| 32 | The result must be a 1024×1024 PNG with the classic macOS safe area: the opaque icon body is 824×824, inset 100 pixels on every side, with only the native Icon Composer shadow extending into the surrounding transparent canvas. | ||
| 33 | |||
| 34 | To have Codex perform the native exports, paste this prompt into a task opened at the repository root: | ||
| 35 | |||
| 36 | ```text | ||
| 37 | Use [@Computer](plugin://computer-use@openai-bundled) and the Icon Composer app to export the three macOS app icons in this repository. | ||
| 38 | |||
| 39 | For each project below, use Platform: macOS pre-Tahoe, Appearance: Default, Size: 1024pt, and Scale: 1×, then save the PNG to the exact destination: | ||
| 40 | |||
| 41 | - assets/dev/app-icon.icon -> assets/dev/blueprint-macos-1024.png | ||
| 42 | - assets/nightly/app-icon.icon -> assets/nightly/nightly-macos-1024.png | ||
| 43 | - assets/prod/app-icon.icon -> assets/prod/black-macos-1024.png | ||
| 44 | |||
| 45 | Do not resize, composite, or otherwise post-process the exported PNGs. | ||
| 46 | |||
| 47 | Verify every result is 1024×1024 and has the classic macOS safe area: an 824×824 opaque body inset 100px on every side, with only Icon Composer's native shadow extending beyond it. | ||
| 48 | ``` | ||
| 49 | |||
| 50 | Do not edit the generated PNG or ICO files directly. | ||
| 51 | |||
| 52 | ## Android launcher and splash artwork | ||
| 53 | |||
| 54 | Android masks the central 72dp of a 108dp adaptive canvas, and the Android 12+ splash screen masks | ||
| 55 | the central two thirds of a 288dp canvas, so the Icon Composer exports cannot be used directly: | ||
| 56 | their rounded-square silhouette gets framed again and the wordmark is cropped. The Android artwork | ||
| 57 | is instead rendered from the same Icon Composer SVG sources by `vp run icons:export:android`: | ||
| 58 | |||
| 59 | - `apps/mobile/assets/android-icon-foreground.png`: the shared transparent wordmark, sized to stay | ||
| 60 | inside the safe zone | ||
| 61 | - `apps/mobile/assets/android-icon-background-dev.png` and `-nightly.png`: full-bleed variant | ||
| 62 | artwork (blueprint grid and annotations; night sky and clouds). Production uses a solid color. | ||
| 63 | - `apps/mobile/assets/android-splash-icon-*.png`: the two layers composed into one 288dp image, so | ||
| 64 | the splash mask reproduces the launcher icon's framing. | ||
| 65 | |||
| 66 | Rerun the export after changing a layer SVG. `android-icon-mark.png` remains a flat silhouette for | ||
| 67 | Android's monochrome themed icon. |
assets/knife/android-foreground.png created| Binary files /dev/null and b/assets/knife/android-foreground.png differ | |||
assets/knife/android-splash.png created| Binary files /dev/null and b/assets/knife/android-splash.png differ | |||
assets/knife/app-icon.icon/Assets/knife.png created| Binary files /dev/null and b/assets/knife/app-icon.icon/Assets/knife.png differ | |||
assets/knife/app-icon.icon/icon.json created+36| ... | @@ -0,0 +1,36 @@ | ||
| 1 | { | ||
| 2 | "fill": { | ||
| 3 | "solid": "srgb:0.09020,0.07843,0.13333,1.00000" | ||
| 4 | }, | ||
| 5 | "groups": [ | ||
| 6 | { | ||
| 7 | "layers": [ | ||
| 8 | { | ||
| 9 | "image-name": "knife.png", | ||
| 10 | "name": "Knife", | ||
| 11 | "position": { | ||
| 12 | "scale": 0.65, | ||
| 13 | "translation-in-points": [ | ||
| 14 | 0, | ||
| 15 | 0 | ||
| 16 | ] | ||
| 17 | } | ||
| 18 | } | ||
| 19 | ], | ||
| 20 | "shadow": { | ||
| 21 | "kind": "neutral", | ||
| 22 | "opacity": 0.3 | ||
| 23 | }, | ||
| 24 | "translucency": { | ||
| 25 | "enabled": false, | ||
| 26 | "value": 0.0 | ||
| 27 | } | ||
| 28 | } | ||
| 29 | ], | ||
| 30 | "supported-platforms": { | ||
| 31 | "circles": [ | ||
| 32 | "watchOS" | ||
| 33 | ], | ||
| 34 | "squares": "shared" | ||
| 35 | } | ||
| 36 | } | ||
assets/knife/apple-touch-180.png created| Binary files /dev/null and b/assets/knife/apple-touch-180.png differ | |||
assets/knife/favicon-16x16.png created| Binary files /dev/null and b/assets/knife/favicon-16x16.png differ | |||
assets/knife/favicon-32x32.png created| Binary files /dev/null and b/assets/knife/favicon-32x32.png differ | |||
assets/knife/icon-1024.png created| Binary files /dev/null and b/assets/knife/icon-1024.png differ | |||
assets/knife/icon.ico created| Binary files /dev/null and b/assets/knife/icon.ico differ | |||
assets/knife/macos-1024.png created| Binary files /dev/null and b/assets/knife/macos-1024.png differ | |||
docs/operations/development.md+8| ... | @@ -206,3 +206,11 @@ passed as `--wsl-runtime`; see the | ... | @@ -206,3 +206,11 @@ passed as `--wsl-runtime`; see the |
| 206 | Add `--signed` after configuring the platform credentials in the | 206 | Add `--signed` after configuring the platform credentials in the |
| 207 | [release runbook](./release.md). macOS passkeys need a signed, provisioned app; follow the | 207 | [release runbook](./release.md). macOS passkeys need a signed, provisioned app; follow the |
| 208 | [Connect setup](./connect-setup.md#desktop-passkeys) for local signing and renderer HMR. | 208 | [Connect setup](./connect-setup.md#desktop-passkeys) for local signing and renderer HMR. |
| 209 | |||
| 210 | ### Private fork releases for Apple Silicon | ||
| 211 | |||
| 212 | Licensed Name Sans and Name Mono files live in the ignored | ||
| 213 | `apps/web/public/fonts/private/` directory. Copy that directory into any other | ||
| 214 | build workspace before building with these fonts. The web and desktop builds | ||
| 215 | bundle the local files automatically; source checkouts without them use system | ||
| 216 | fallback fonts. Keep the font binaries out of source history. |
native/browser-secret/main.c+1-1| ... | @@ -16,7 +16,7 @@ static const SecretSchema chromium_schema = { | ... | @@ -16,7 +16,7 @@ static const SecretSchema chromium_schema = { |
| 16 | 16 | ||
| 17 | int main(int argc, char **argv) { | 17 | int main(int argc, char **argv) { |
| 18 | if (argc != 2 || argv[1][0] == '\0') return 64; | 18 | if (argc != 2 || argv[1][0] == '\0') return 64; |
| 19 | g_set_application_name("T3 Code"); | 19 | g_set_application_name("The Knife"); |
| 20 | 20 | ||
| 21 | GHashTable *attributes = secret_attributes_build(&chromium_schema, "application", argv[1], NULL); | 21 | GHashTable *attributes = secret_attributes_build(&chromium_schema, "application", argv[1], NULL); |
| 22 | GError *error = NULL; | 22 | GError *error = NULL; |
native/hyprland-snap-shot/src/ipc.rs+3-3| ... | @@ -139,7 +139,7 @@ pub fn destination(windows: Vec<Window>, pid: u32, title: &str) -> Result<Option | ... | @@ -139,7 +139,7 @@ pub fn destination(windows: Vec<Window>, pid: u32, title: &str) -> Result<Option |
| 139 | .filter(|w| w.pid == pid && w.title == title && w.mapped && !w.hidden); | 139 | .filter(|w| w.pid == pid && w.title == title && w.mapped && !w.hidden); |
| 140 | let found = matches.next(); | 140 | let found = matches.next(); |
| 141 | if matches.next().is_some() { | 141 | if matches.next().is_some() { |
| 142 | return Err("More than one T3 Code window matches the capture destination.".into()); | 142 | return Err("More than one The Knife window matches the capture destination.".into()); |
| 143 | } | 143 | } |
| 144 | Ok(found) | 144 | Ok(found) |
| 145 | } | 145 | } |
| ... | @@ -157,14 +157,14 @@ pub fn activate(pid: u32, title: &str) -> Result<()> { | ... | @@ -157,14 +157,14 @@ pub fn activate(pid: u32, title: &str) -> Result<()> { |
| 157 | if request(&lua)?.trim() != "ok" | 157 | if request(&lua)?.trim() != "ok" |
| 158 | && request(&format!("/dispatch focuswindow address:0x{address:x}"))?.trim() != "ok" | 158 | && request(&format!("/dispatch focuswindow address:0x{address:x}"))?.trim() != "ok" |
| 159 | { | 159 | { |
| 160 | return Err("Hyprland could not focus T3 Code.".into()); | 160 | return Err("Hyprland could not focus The Knife.".into()); |
| 161 | } | 161 | } |
| 162 | return Ok(()); | 162 | return Ok(()); |
| 163 | } | 163 | } |
| 164 | events.get_ref().set_read_timeout(Some( | 164 | events.get_ref().set_read_timeout(Some( |
| 165 | deadline | 165 | deadline |
| 166 | .checked_duration_since(Instant::now()) | 166 | .checked_duration_since(Instant::now()) |
| 167 | .ok_or("T3 Code did not become visible.")?, | 167 | .ok_or("The Knife did not become visible.")?, |
| 168 | ))?; | 168 | ))?; |
| 169 | let mut event = Vec::new(); | 169 | let mut event = Vec::new(); |
| 170 | if events.by_ref().take(8193).read_until(b'\n', &mut event)? == 0 || event.len() > 8192 { | 170 | if events.by_ref().take(8193).read_until(b'\n', &mut event)? == 0 || event.len() > 8192 { |
native/kde-snap-shot/src/main.rs+1-1| ... | @@ -348,7 +348,7 @@ fn run() -> Result<()> { | ... | @@ -348,7 +348,7 @@ fn run() -> Result<()> { |
| 348 | ), | 348 | ), |
| 349 | )?; | 349 | )?; |
| 350 | if serde_json::from_str::<serde_json::Value>(&value)?["activated"] != true { | 350 | if serde_json::from_str::<serde_json::Value>(&value)?["activated"] != true { |
| 351 | return Err("KDE could not identify the T3 Code window to activate".into()); | 351 | return Err("KDE could not identify The Knife window to activate".into()); |
| 352 | } | 352 | } |
| 353 | println!("{{\"activated\":true}}"); | 353 | println!("{{\"activated\":true}}"); |
| 354 | } | 354 | } |
packages/client-runtime/src/connection/compatibility.ts+1-1| ... | @@ -21,7 +21,7 @@ export function orchestrationProtocolCompatibilityError( | ... | @@ -21,7 +21,7 @@ export function orchestrationProtocolCompatibilityError( |
| 21 | }) | 21 | }) |
| 22 | : new ConnectionBlockedError({ | 22 | : new ConnectionBlockedError({ |
| 23 | reason: "unsupported", | 23 | reason: "unsupported", |
| 24 | detail: `This client requires a newer server. Update T3 Code on ${descriptor.label} to connect.`, | 24 | detail: `This client requires a newer server. Update the server on ${descriptor.label} to connect.`, |
| 25 | ...(canSelfUpdate(descriptor) ? { serverUpdateRequired: true } : {}), | 25 | ...(canSelfUpdate(descriptor) ? { serverUpdateRequired: true } : {}), |
| 26 | }); | 26 | }); |
| 27 | } | 27 | } |
packages/client-runtime/src/connection/outdatedHostUpdate.ts+2-2| ... | @@ -77,7 +77,7 @@ export const updateOutdatedHost = Effect.fn("clientRuntime.connection.updateOutd | ... | @@ -77,7 +77,7 @@ export const updateOutdatedHost = Effect.fn("clientRuntime.connection.updateOutd |
| 77 | ) { | 77 | ) { |
| 78 | return yield* new OutdatedHostUpdateError({ | 78 | return yield* new OutdatedHostUpdateError({ |
| 79 | environmentId, | 79 | environmentId, |
| 80 | message: `Update T3 Code on ${descriptor.label} manually; it cannot update itself.`, | 80 | message: `Update the server on ${descriptor.label} manually; it cannot update itself.`, |
| 81 | }); | 81 | }); |
| 82 | } | 82 | } |
| 83 | 83 | ||
| ... | @@ -189,7 +189,7 @@ export const updateOutdatedHost = Effect.fn("clientRuntime.connection.updateOutd | ... | @@ -189,7 +189,7 @@ export const updateOutdatedHost = Effect.fn("clientRuntime.connection.updateOutd |
| 189 | if (Option.isNone(resumed)) { | 189 | if (Option.isNone(resumed)) { |
| 190 | return yield* new OutdatedHostUpdateError({ | 190 | return yield* new OutdatedHostUpdateError({ |
| 191 | environmentId, | 191 | environmentId, |
| 192 | message: `${descriptor.label} did not come back on a compatible T3 Code version.`, | 192 | message: `${descriptor.label} did not come back on a compatible server version.`, |
| 193 | }); | 193 | }); |
| 194 | } | 194 | } |
| 195 | 195 |
packages/client-runtime/src/connection/resolver.test.ts+1-1| ... | @@ -200,7 +200,7 @@ describe("ConnectionResolver", () => { | ... | @@ -200,7 +200,7 @@ describe("ConnectionResolver", () => { |
| 200 | const error = yield* Effect.flip(broker.prepare(catalogEntry(target))); | 200 | const error = yield* Effect.flip(broker.prepare(catalogEntry(target))); |
| 201 | 201 | ||
| 202 | expect(error).toMatchObject({ reason: "unsupported" }); | 202 | expect(error).toMatchObject({ reason: "unsupported" }); |
| 203 | expect(error.message).toContain("Update T3 Code on Compatible environment"); | 203 | expect(error.message).toContain("Update the server on Compatible environment"); |
| 204 | }), | 204 | }), |
| 205 | ); | 205 | ); |
| 206 | 206 |
packages/client-runtime/src/relay/errorPresentation.ts+1-1| ... | @@ -95,7 +95,7 @@ export function relayRequestFailureDetail(error: { | ... | @@ -95,7 +95,7 @@ export function relayRequestFailureDetail(error: { |
| 95 | // coming back, which clears this reason. While it is still reported, the host | 95 | // coming back, which clears this reason. While it is still reported, the host |
| 96 | // is either still off or running a build too old to do that. | 96 | // is either still off or running a build too old to do that. |
| 97 | export const RELAY_TUNNEL_RELEASED_MESSAGE = | 97 | export const RELAY_TUNNEL_RELEASED_MESSAGE = |
| 98 | "Offline for a while, so its T3 Connect tunnel was removed. Start T3 Code on that computer and update it to the latest version to reconnect."; | 98 | "Offline for a while, so its T3 Connect tunnel was removed. Start the server on that computer and update it to the latest version to reconnect."; |
| 99 | 99 | ||
| 100 | /** User-facing text for an offline status, or null when the relay gave no known reason. */ | 100 | /** User-facing text for an offline status, or null when the relay gave no known reason. */ |
| 101 | export function relayOfflineReasonMessage( | 101 | export function relayOfflineReasonMessage( |
packages/client-runtime/src/state/pullRequestDiffHttp.test.ts+1-1| ... | @@ -112,7 +112,7 @@ describe("fetchEnvironmentPullRequestDiff", () => { | ... | @@ -112,7 +112,7 @@ describe("fetchEnvironmentPullRequestDiff", () => { |
| 112 | traceId: "trace-auth-test", | 112 | traceId: "trace-auth-test", |
| 113 | }); | 113 | }); |
| 114 | expect(error.message).toBe( | 114 | expect(error.message).toBe( |
| 115 | "This environment session is no longer valid (invalid_credential). Refresh the page or quit and reopen T3 Code.", | 115 | "This environment session is no longer valid (invalid_credential). Refresh the page or quit and reopen The Knife.", |
| 116 | ); | 116 | ); |
| 117 | }), | 117 | }), |
| 118 | ); | 118 | ); |
packages/client-runtime/src/state/pullRequestDiffHttp.ts+1-1| ... | @@ -28,7 +28,7 @@ export class PullRequestDiffCredentialRejectedError extends Schema.TaggedError<P | ... | @@ -28,7 +28,7 @@ export class PullRequestDiffCredentialRejectedError extends Schema.TaggedError<P |
| 28 | }, | 28 | }, |
| 29 | ) { | 29 | ) { |
| 30 | override get message(): string { | 30 | override get message(): string { |
| 31 | return "This environment session is no longer valid (invalid_credential). Refresh the page or quit and reopen T3 Code."; | 31 | return "This environment session is no longer valid (invalid_credential). Refresh the page or quit and reopen The Knife."; |
| 32 | } | 32 | } |
| 33 | } | 33 | } |
| 34 | 34 |
scripts/build-desktop-artifact.test.ts+7-7| ... | @@ -273,8 +273,8 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { | ... | @@ -273,8 +273,8 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 273 | }); | 273 | }); |
| 274 | 274 | ||
| 275 | it("switches desktop packaging product names to nightly for nightly builds", () => { | 275 | it("switches desktop packaging product names to nightly for nightly builds", () => { |
| 276 | assert.equal(resolveDesktopProductName("0.0.17"), "T3 Code (Alpha)"); | 276 | assert.equal(resolveDesktopProductName("0.0.17"), "The Knife"); |
| 277 | assert.equal(resolveDesktopProductName("0.0.17-nightly.20260413.42"), "T3 Code (Nightly)"); | 277 | assert.equal(resolveDesktopProductName("0.0.17-nightly.20260413.42"), "The Knife (Nightly)"); |
| 278 | }); | 278 | }); |
| 279 | 279 | ||
| 280 | it("switches desktop packaging icons to the nightly artwork for nightly versions", () => { | 280 | it("switches desktop packaging icons to the nightly artwork for nightly versions", () => { |
| ... | @@ -702,7 +702,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { | ... | @@ -702,7 +702,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 702 | "**/*.map", | 702 | "**/*.map", |
| 703 | ]); | 703 | ]); |
| 704 | assert.deepStrictEqual(mac.dmg, { | 704 | assert.deepStrictEqual(mac.dmg, { |
| 705 | title: "T3 Code (Alpha) 1.2.3 Installer", | 705 | title: "The Knife 1.2.3 Installer", |
| 706 | background: "dmg/dmg-background-latest.png", | 706 | background: "dmg/dmg-background-latest.png", |
| 707 | window: { width: 640, height: 432 }, | 707 | window: { width: 640, height: 432 }, |
| 708 | contents: [ | 708 | contents: [ |
| ... | @@ -717,11 +717,11 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { | ... | @@ -717,11 +717,11 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 717 | // Linux must register the renderer schemes so the generated .desktop | 717 | // Linux must register the renderer schemes so the generated .desktop |
| 718 | // entry advertises MimeType=x-scheme-handler/t3code; for OAuth deep links. | 718 | // entry advertises MimeType=x-scheme-handler/t3code; for OAuth deep links. |
| 719 | assert.deepStrictEqual((linux.linux as Record<string, unknown>).protocols, [ | 719 | assert.deepStrictEqual((linux.linux as Record<string, unknown>).protocols, [ |
| 720 | { name: "T3 Code", schemes: ["t3code", "t3code-dev"] }, | 720 | { name: "The Knife", schemes: ["t3code", "t3code-dev"] }, |
| 721 | ]); | 721 | ]); |
| 722 | // macOS also offers itself as a web browser, so it can be chosen as the default. | 722 | // macOS also offers itself as a web browser, so it can be chosen as the default. |
| 723 | assert.deepStrictEqual((mac.mac as Record<string, unknown>).protocols, [ | 723 | assert.deepStrictEqual((mac.mac as Record<string, unknown>).protocols, [ |
| 724 | { name: "T3 Code", schemes: ["t3code", "t3code-dev"] }, | 724 | { name: "The Knife", schemes: ["t3code", "t3code-dev"] }, |
| 725 | { name: "Web site URL", schemes: ["http", "https"], role: "Viewer" }, | 725 | { name: "Web site URL", schemes: ["http", "https"], role: "Viewer" }, |
| 726 | ]); | 726 | ]); |
| 727 | // macOS lists a default browser only when it also opens web pages as documents. | 727 | // macOS lists a default browser only when it also opens web pages as documents. |
| ... | @@ -746,7 +746,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { | ... | @@ -746,7 +746,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 746 | assert.propertyVal( | 746 | assert.propertyVal( |
| 747 | (mac.mac as Record<string, unknown>).extendInfo as Record<string, unknown>, | 747 | (mac.mac as Record<string, unknown>).extendInfo as Record<string, unknown>, |
| 748 | "NSLocalNetworkUsageDescription", | 748 | "NSLocalNetworkUsageDescription", |
| 749 | "T3 Code connects to devices on your local network for remote environments and commands run by terminals and coding agents.", | 749 | "The Knife connects to devices on your local network for remote environments and commands run by terminals and coding agents.", |
| 750 | ); | 750 | ); |
| 751 | for (const config of [linux, win]) { | 751 | for (const config of [linux, win]) { |
| 752 | assert.deepStrictEqual(config.electronLanguages, DESKTOP_ELECTRON_LANGUAGES); | 752 | assert.deepStrictEqual(config.electronLanguages, DESKTOP_ELECTRON_LANGUAGES); |
| ... | @@ -2141,7 +2141,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { | ... | @@ -2141,7 +2141,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 2141 | assert.equal(mac.provisioningProfile, "/tmp/t3code.provisionprofile"); | 2141 | assert.equal(mac.provisioningProfile, "/tmp/t3code.provisionprofile"); |
| 2142 | assert.match(String(mac.sign), /[\\/]scripts[\\/]sign-macos\.ts$/); | 2142 | assert.match(String(mac.sign), /[\\/]scripts[\\/]sign-macos\.ts$/); |
| 2143 | assert.deepStrictEqual(mac.protocols, [ | 2143 | assert.deepStrictEqual(mac.protocols, [ |
| 2144 | { name: "T3 Code", schemes: ["t3code", "t3code-dev"] }, | 2144 | { name: "The Knife", schemes: ["t3code", "t3code-dev"] }, |
| 2145 | { name: "Web site URL", schemes: ["http", "https"], role: "Viewer" }, | 2145 | { name: "Web site URL", schemes: ["http", "https"], role: "Viewer" }, |
| 2146 | ]); | 2146 | ]); |
| 2147 | }).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))), | 2147 | }).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))), |
scripts/build-desktop-artifact.ts+11-11| ... | @@ -971,7 +971,7 @@ export const DESKTOP_FILE_EXCLUSIONS = [ | ... | @@ -971,7 +971,7 @@ export const DESKTOP_FILE_EXCLUSIONS = [ |
| 971 | "!**/node_modules/@cursor/sdk-*/**/*", | 971 | "!**/node_modules/@cursor/sdk-*/**/*", |
| 972 | "!apps/desktop/prod-resources/cursor-sdk", | 972 | "!apps/desktop/prod-resources/cursor-sdk", |
| 973 | "!apps/desktop/prod-resources/cursor-sdk/**/*", | 973 | "!apps/desktop/prod-resources/cursor-sdk/**/*", |
| 974 | // T3 Code always passes the user's installed Claude executable to the SDK, | 974 | // The Knife always passes the user's installed Claude executable to the SDK, |
| 975 | // so the SDK's optional platform packages (each a ~200MB bundled executable) | 975 | // so the SDK's optional platform packages (each a ~200MB bundled executable) |
| 976 | // are dead weight. The trailing dash keeps the SDK's own JS package. | 976 | // are dead weight. The trailing dash keeps the SDK's own JS package. |
| 977 | "!**/node_modules/@anthropic-ai/claude-agent-sdk-*/**/*", | 977 | "!**/node_modules/@anthropic-ai/claude-agent-sdk-*/**/*", |
| ... | @@ -2745,8 +2745,8 @@ export function resolvePackageManagerUserAgent(packageManager: string): string { | ... | @@ -2745,8 +2745,8 @@ export function resolvePackageManagerUserAgent(packageManager: string): string { |
| 2745 | 2745 | ||
| 2746 | export function resolveDesktopProductName(version: string): string { | 2746 | export function resolveDesktopProductName(version: string): string { |
| 2747 | return resolveDesktopUpdateChannel(version) === "nightly" | 2747 | return resolveDesktopUpdateChannel(version) === "nightly" |
| 2748 | ? "T3 Code (Nightly)" | 2748 | ? "The Knife (Nightly)" |
| 2749 | : (desktopPackageJson.productName ?? "T3 Code"); | 2749 | : (desktopPackageJson.productName ?? "The Knife"); |
| 2750 | } | 2750 | } |
| 2751 | 2751 | ||
| 2752 | export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( | 2752 | export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( |
| ... | @@ -2771,7 +2771,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( | ... | @@ -2771,7 +2771,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( |
| 2771 | const buildConfig: Record<string, unknown> = { | 2771 | const buildConfig: Record<string, unknown> = { |
| 2772 | appId: DESKTOP_APP_ID, | 2772 | appId: DESKTOP_APP_ID, |
| 2773 | productName: resolveDesktopProductName(version), | 2773 | productName: resolveDesktopProductName(version), |
| 2774 | artifactName: "T3-Code-${version}-${arch}.${ext}", | 2774 | artifactName: "The-Knife-${version}-${arch}.${ext}", |
| 2775 | electronLanguages: [...DESKTOP_ELECTRON_LANGUAGES], | 2775 | electronLanguages: [...DESKTOP_ELECTRON_LANGUAGES], |
| 2776 | files: [ | 2776 | files: [ |
| 2777 | ...DESKTOP_FILE_EXCLUSIONS, | 2777 | ...DESKTOP_FILE_EXCLUSIONS, |
| ... | @@ -2824,9 +2824,9 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( | ... | @@ -2824,9 +2824,9 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( |
| 2824 | // macOS 26 offers Messages/Mail codes in every text field unless opted out. | 2824 | // macOS 26 offers Messages/Mail codes in every text field unless opted out. |
| 2825 | NSAutoFillRequiresTextContentTypeForOneTimeCodeOnMac: true, | 2825 | NSAutoFillRequiresTextContentTypeForOneTimeCodeOnMac: true, |
| 2826 | NSLocalNetworkUsageDescription: | 2826 | NSLocalNetworkUsageDescription: |
| 2827 | "T3 Code connects to devices on your local network for remote environments and commands run by terminals and coding agents.", | 2827 | "The Knife connects to devices on your local network for remote environments and commands run by terminals and coding agents.", |
| 2828 | NSScreenCaptureUsageDescription: | 2828 | NSScreenCaptureUsageDescription: |
| 2829 | "T3 Code captures the active window when you use the window capture shortcut.", | 2829 | "The Knife captures the active window when you use the window capture shortcut.", |
| 2830 | // macOS lists an app under Default web browser only when it opens web | 2830 | // macOS lists an app under Default web browser only when it opens web |
| 2831 | // pages as documents as well as http and https links (see protocols). | 2831 | // pages as documents as well as http and https links (see protocols). |
| 2832 | CFBundleDocumentTypes: [ | 2832 | CFBundleDocumentTypes: [ |
| ... | @@ -2840,10 +2840,10 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( | ... | @@ -2840,10 +2840,10 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( |
| 2840 | }, | 2840 | }, |
| 2841 | protocols: [ | 2841 | protocols: [ |
| 2842 | { | 2842 | { |
| 2843 | name: "T3 Code", | 2843 | name: "The Knife", |
| 2844 | schemes: ["t3code", "t3code-dev"], | 2844 | schemes: ["t3code", "t3code-dev"], |
| 2845 | }, | 2845 | }, |
| 2846 | // Lets people choose T3 Code as their default web browser, which opens | 2846 | // Lets people choose The Knife as their default web browser, which opens |
| 2847 | // each link in a new thread's browser panel. | 2847 | // each link in a new thread's browser panel. |
| 2848 | { | 2848 | { |
| 2849 | name: "Web site URL", | 2849 | name: "Web site URL", |
| ... | @@ -2907,7 +2907,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( | ... | @@ -2907,7 +2907,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( |
| 2907 | // t3code:// OAuth callbacks to the app. | 2907 | // t3code:// OAuth callbacks to the app. |
| 2908 | protocols: [ | 2908 | protocols: [ |
| 2909 | { | 2909 | { |
| 2910 | name: "T3 Code", | 2910 | name: "The Knife", |
| 2911 | schemes: ["t3code", "t3code-dev"], | 2911 | schemes: ["t3code", "t3code-dev"], |
| 2912 | }, | 2912 | }, |
| 2913 | ], | 2913 | ], |
| ... | @@ -3869,7 +3869,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( | ... | @@ -3869,7 +3869,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 3869 | private: true, | 3869 | private: true, |
| 3870 | packageManager: rootPackageJson.packageManager, | 3870 | packageManager: rootPackageJson.packageManager, |
| 3871 | description: | 3871 | description: |
| 3872 | "T3 Code is an open-source desktop app for coding agents. Work with your existing agent subscriptions, review code changes, and run commands in your projects. Connect from desktop, web, or mobile to continue working remotely.", | 3872 | "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. Connect from desktop, web, or mobile to continue working remotely.", |
| 3873 | license: "MIT", | 3873 | license: "MIT", |
| 3874 | // Required by the .deb control file. | 3874 | // Required by the .deb control file. |
| 3875 | homepage: "https://t3.codes", | 3875 | homepage: "https://t3.codes", |
| ... | @@ -4158,7 +4158,7 @@ const buildDesktopArtifactCli = Command.make("build-desktop-artifact", { | ... | @@ -4158,7 +4158,7 @@ const buildDesktopArtifactCli = Command.make("build-desktop-artifact", { |
| 4158 | Flag.optional, | 4158 | Flag.optional, |
| 4159 | ), | 4159 | ), |
| 4160 | }).pipe( | 4160 | }).pipe( |
| 4161 | Command.withDescription("Build a desktop artifact for T3 Code."), | 4161 | Command.withDescription("Build a desktop artifact for The Knife."), |
| 4162 | Command.withHandler((input) => Effect.flatMap(resolveBuildOptions(input), buildDesktopArtifact)), | 4162 | Command.withHandler((input) => Effect.flatMap(resolveBuildOptions(input), buildDesktopArtifact)), |
| 4163 | ); | 4163 | ); |
| 4164 | 4164 |
scripts/export-android-icons.ts+45-207| ... | @@ -1,18 +1,5 @@ | ... | @@ -1,18 +1,5 @@ |
| 1 | #!/usr/bin/env node | 1 | #!/usr/bin/env node |
| 2 | 2 | ||
| 3 | // Renders the Android launcher and splash artwork from the Icon Composer SVG sources. | ||
| 4 | // | ||
| 5 | // Icon Composer exports already contain a rounded-square silhouette, and Android masks | ||
| 6 | // the central 72dp of a 108dp adaptive canvas, so exporting them as a foreground produces | ||
| 7 | // a double-framed icon with the letters cropped by the mask. Instead, each variant gets a | ||
| 8 | // full-bleed background layer (the artwork behind the wordmark) and a shared transparent | ||
| 9 | // foreground that keeps the wordmark inside the safe zone. | ||
| 10 | // | ||
| 11 | // The Android 12+ splash screen masks its icon to a circle covering the central two thirds | ||
| 12 | // of a 288dp canvas, which is the same proportion the launcher crops. Composing the two | ||
| 13 | // adaptive layers into one 288dp image therefore makes the splash frame the wordmark | ||
| 14 | // exactly like the launcher icon does. | ||
| 15 | |||
| 16 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 3 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| 17 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 18 | import * as Console from "effect/Console"; | 5 | import * as Console from "effect/Console"; |
| ... | @@ -22,211 +9,62 @@ import * as Path from "effect/Path"; | ... | @@ -22,211 +9,62 @@ import * as Path from "effect/Path"; |
| 22 | import * as Schema from "effect/Schema"; | 9 | import * as Schema from "effect/Schema"; |
| 23 | import sharp from "sharp"; | 10 | import sharp from "sharp"; |
| 24 | 11 | ||
| 25 | type IconVariant = "dev" | "nightly" | "prod"; | 12 | import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts"; |
| 26 | |||
| 27 | // 108dp at xxxhdpi. Expo's prebuild derives every launcher density bucket from this. | ||
| 28 | const ADAPTIVE_CANVAS = 432; | ||
| 29 | // 288dp at xxxhdpi: the full Android 12+ splash canvas, so the icon needs no upscaling. | ||
| 30 | const SPLASH_CANVAS = 1152; | ||
| 31 | // Icon Composer's layer sources use a 128pt viewBox; the wordmark path spans this box. | ||
| 32 | const TEXT = { x: 15.53, y: 37, width: 94.5, height: 57 }; | ||
| 33 | // Wordmark width as a fraction of the 108dp canvas. The visible area is 72dp (66dp | ||
| 34 | // guaranteed), so 0.48 leaves the letters at ~72% of the mask with room for the | ||
| 35 | // launcher's own zoom effects. | ||
| 36 | const WORDMARK_FRACTION = 0.48; | ||
| 37 | // Icon Composer positions layers on a 1024pt canvas, with translation relative to center. | ||
| 38 | const COMPOSER_CANVAS_PT = 1024; | ||
| 39 | const SVG_DENSITY = 300; | ||
| 40 | const OUTPUT_DIRECTORY = "apps/mobile/assets"; | ||
| 41 | // Production has no background artwork, so its splash composes onto the adaptive color. | ||
| 42 | const PRODUCTION_BACKGROUND_COLOR = "#000000"; | ||
| 43 | 13 | ||
| 44 | export class AndroidIconRenderError extends Schema.TaggedError<AndroidIconRenderError>()( | 14 | export class AndroidIconRenderError extends Schema.TaggedError<AndroidIconRenderError>()( |
| 45 | "AndroidIconRenderError", | 15 | "AndroidIconRenderError", |
| 46 | { layer: Schema.String, cause: Schema.Defect() }, | 16 | { cause: Schema.Defect() }, |
| 47 | ) {} | 17 | ) {} |
| 48 | 18 | ||
| 49 | const wordmarkTransform = (size: number) => { | 19 | const decodeProject = Schema.decodeEffect( |
| 50 | const scale = (size * WORDMARK_FRACTION) / TEXT.width; | 20 | Schema.fromJsonString(Schema.Struct({ fill: Schema.Struct({ solid: Schema.String }) })), |
| 51 | const tx = (size - TEXT.width * scale) / 2 - TEXT.x * scale; | 21 | ); |
| 52 | const ty = (size - TEXT.height * scale) / 2 - TEXT.y * scale; | 22 | const decodeColor = Schema.decodeUnknownEffect( |
| 53 | return `translate(${tx.toFixed(3)} ${ty.toFixed(3)}) scale(${scale.toFixed(4)})`; | 23 | Schema.Tuple([Schema.Number, Schema.Number, Schema.Number, Schema.Number]), |
| 54 | }; | ||
| 55 | |||
| 56 | const canvasSvg = (size: number, inner: string) => | ||
| 57 | `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" fill="none">${inner}</svg>`; | ||
| 58 | |||
| 59 | // The layer sources clip to a 10pt rounded rectangle for the iOS silhouette. Android | ||
| 60 | // applies its own mask, so the layer must bleed to the canvas edge. | ||
| 61 | const fullBleed = (svg: string) => | ||
| 62 | svg.replace(/<rect width="128" height="128" rx="10"\/>/, '<rect width="128" height="128"/>'); | ||
| 63 | |||
| 64 | const rasterize = (layer: string, svg: string, width: number, height = width) => | ||
| 65 | Effect.tryPromise({ | ||
| 66 | try: () => | ||
| 67 | sharp(Buffer.from(svg), { density: SVG_DENSITY }).resize(width, height).png().toBuffer(), | ||
| 68 | catch: (cause) => new AndroidIconRenderError({ layer, cause }), | ||
| 69 | }); | ||
| 70 | |||
| 71 | const composite = ( | ||
| 72 | layer: string, | ||
| 73 | base: Buffer, | ||
| 74 | overlays: ReadonlyArray<{ input: Buffer; left?: number; top?: number }>, | ||
| 75 | ) => | ||
| 76 | Effect.tryPromise({ | ||
| 77 | try: () => | ||
| 78 | sharp(base) | ||
| 79 | .composite([...overlays]) | ||
| 80 | .png() | ||
| 81 | .toBuffer(), | ||
| 82 | catch: (cause) => new AndroidIconRenderError({ layer, cause }), | ||
| 83 | }); | ||
| 84 | |||
| 85 | const solidCanvas = (layer: string, size: number, background: string) => | ||
| 86 | Effect.tryPromise({ | ||
| 87 | try: () => | ||
| 88 | sharp({ create: { width: size, height: size, channels: 4, background } }) | ||
| 89 | .png() | ||
| 90 | .toBuffer(), | ||
| 91 | catch: (cause) => new AndroidIconRenderError({ layer, cause }), | ||
| 92 | }); | ||
| 93 | |||
| 94 | const readLayerSource = Effect.fn("androidIcons.readLayerSource")(function* ( | ||
| 95 | repositoryRoot: string, | ||
| 96 | variant: IconVariant, | ||
| 97 | file: string, | ||
| 98 | ) { | ||
| 99 | const fs = yield* FileSystem.FileSystem; | ||
| 100 | const path = yield* Path.Path; | ||
| 101 | return yield* fs.readFileString( | ||
| 102 | path.join(repositoryRoot, "assets", variant, "app-icon.icon", "Assets", file), | ||
| 103 | ); | ||
| 104 | }); | ||
| 105 | |||
| 106 | const renderForeground = Effect.fn("androidIcons.renderForeground")(function* ( | ||
| 107 | repositoryRoot: string, | ||
| 108 | size: number, | ||
| 109 | ) { | ||
| 110 | const text = yield* readLayerSource(repositoryRoot, "prod", "text.svg"); | ||
| 111 | const paths = text.match(/<path[^>]*\/>/g) ?? []; | ||
| 112 | return yield* rasterize( | ||
| 113 | "foreground", | ||
| 114 | canvasSvg(size, `<g transform="${wordmarkTransform(size)}">${paths.join("")}</g>`), | ||
| 115 | size, | ||
| 116 | ); | ||
| 117 | }); | ||
| 118 | |||
| 119 | const renderDevelopmentBackground = Effect.fn("androidIcons.renderDevelopmentBackground")( | ||
| 120 | function* (repositoryRoot: string, size: number) { | ||
| 121 | // The annotation layer shares the wordmark's coordinate space, so it is scaled and | ||
| 122 | // centered the same way to keep the dimension lines around the letters. | ||
| 123 | const annotations = yield* readLayerSource(repositoryRoot, "dev", "annotations.svg"); | ||
| 124 | const defs = annotations.match(/<defs>[\s\S]*?<\/defs>/)?.[0] ?? ""; | ||
| 125 | const body = annotations.replace(/^[\s\S]*?<\/defs>/, "").replace(/<\/svg>\s*$/, ""); | ||
| 126 | const paper = yield* readLayerSource(repositoryRoot, "dev", "background.svg"); | ||
| 127 | const background = yield* rasterize("dev-background", fullBleed(paper), size); | ||
| 128 | const overlay = yield* rasterize( | ||
| 129 | "dev-annotations", | ||
| 130 | canvasSvg(size, `${defs}<g transform="${wordmarkTransform(size)}">${body}</g>`), | ||
| 131 | size, | ||
| 132 | ); | ||
| 133 | return yield* composite("dev-background", background, [{ input: overlay }]); | ||
| 134 | }, | ||
| 135 | ); | 24 | ); |
| 136 | |||
| 137 | const renderNightlyBackground = Effect.fn("androidIcons.renderNightlyBackground")(function* ( | ||
| 138 | repositoryRoot: string, | ||
| 139 | size: number, | ||
| 140 | ) { | ||
| 141 | // Positions mirror assets/nightly/app-icon.icon/icon.json. The SVG blur filter is | ||
| 142 | // dropped because Icon Composer ignores it and it smears at this raster size. | ||
| 143 | const clouds = [ | ||
| 144 | { file: "cloud-lower-left.svg", scale: 25, translation: [-309.6375, 268.66077693836917] }, | ||
| 145 | { | ||
| 146 | file: "cloud-upper-right.svg", | ||
| 147 | scale: 15, | ||
| 148 | translation: [387.9605131881942, -134.30064713259117], | ||
| 149 | }, | ||
| 150 | ] as const; | ||
| 151 | const k = size / COMPOSER_CANVAS_PT; | ||
| 152 | const overlays = yield* Effect.forEach(clouds, (cloud) => | ||
| 153 | Effect.gen(function* () { | ||
| 154 | const width = Math.round(64 * cloud.scale * k); | ||
| 155 | const height = Math.round(32 * cloud.scale * k); | ||
| 156 | const left = Math.round((COMPOSER_CANVAS_PT / 2 + cloud.translation[0]) * k - width / 2); | ||
| 157 | const top = Math.round((COMPOSER_CANVAS_PT / 2 + cloud.translation[1]) * k - height / 2); | ||
| 158 | const source = yield* readLayerSource(repositoryRoot, "nightly", cloud.file); | ||
| 159 | const png = yield* rasterize( | ||
| 160 | cloud.file, | ||
| 161 | source.replace(/ filter="url\(#soft\)"/, ""), | ||
| 162 | width, | ||
| 163 | height, | ||
| 164 | ); | ||
| 165 | const x0 = Math.max(0, left); | ||
| 166 | const y0 = Math.max(0, top); | ||
| 167 | const x1 = Math.min(size, left + width); | ||
| 168 | const y1 = Math.min(size, top + height); | ||
| 169 | const clipped = yield* Effect.tryPromise({ | ||
| 170 | try: () => | ||
| 171 | sharp(png) | ||
| 172 | .extract({ left: x0 - left, top: y0 - top, width: x1 - x0, height: y1 - y0 }) | ||
| 173 | .png() | ||
| 174 | .toBuffer(), | ||
| 175 | catch: (cause) => new AndroidIconRenderError({ layer: cloud.file, cause }), | ||
| 176 | }); | ||
| 177 | return { input: clipped, left: x0, top: y0 }; | ||
| 178 | }), | ||
| 179 | ); | ||
| 180 | const sky = yield* readLayerSource(repositoryRoot, "nightly", "background.svg"); | ||
| 181 | const background = yield* rasterize("nightly-background", fullBleed(sky), size); | ||
| 182 | return yield* composite("nightly-background", background, overlays); | ||
| 183 | }); | ||
| 184 | |||
| 185 | const renderBackground = Effect.fn("androidIcons.renderBackground")(function* ( | ||
| 186 | repositoryRoot: string, | ||
| 187 | variant: IconVariant, | ||
| 188 | size: number, | ||
| 189 | ) { | ||
| 190 | switch (variant) { | ||
| 191 | case "dev": | ||
| 192 | return yield* renderDevelopmentBackground(repositoryRoot, size); | ||
| 193 | case "nightly": | ||
| 194 | return yield* renderNightlyBackground(repositoryRoot, size); | ||
| 195 | case "prod": | ||
| 196 | return yield* solidCanvas("prod-background", size, PRODUCTION_BACKGROUND_COLOR); | ||
| 197 | } | ||
| 198 | }); | ||
| 199 | |||
| 200 | const renderSplashIcon = Effect.fn("androidIcons.renderSplashIcon")(function* ( | ||
| 201 | repositoryRoot: string, | ||
| 202 | variant: IconVariant, | ||
| 203 | ) { | ||
| 204 | const background = yield* renderBackground(repositoryRoot, variant, SPLASH_CANVAS); | ||
| 205 | const foreground = yield* renderForeground(repositoryRoot, SPLASH_CANVAS); | ||
| 206 | return yield* composite(`${variant}-splash`, background, [{ input: foreground }]); | ||
| 207 | }); | ||
| 208 | 25 | ||
| 209 | const exportAndroidIcons = Effect.gen(function* () { | 26 | const exportAndroidIcons = Effect.gen(function* () { |
| 210 | const fs = yield* FileSystem.FileSystem; | 27 | const fs = yield* FileSystem.FileSystem; |
| 211 | const path = yield* Path.Path; | 28 | const path = yield* Path.Path; |
| 212 | const repositoryRoot = path.resolve(import.meta.dirname, ".."); | 29 | const repositoryRoot = path.resolve(import.meta.dirname, ".."); |
| 213 | const outputs = [ | 30 | const projectRoot = path.join(repositoryRoot, BRAND_ASSET_PATHS.productionIconComposerProject); |
| 214 | ["android-icon-foreground.png", yield* renderForeground(repositoryRoot, ADAPTIVE_CANVAS)], | 31 | const project = yield* decodeProject( |
| 215 | [ | 32 | yield* fs.readFileString(path.join(projectRoot, "icon.json")), |
| 216 | "android-icon-background-dev.png", | 33 | ); |
| 217 | yield* renderDevelopmentBackground(repositoryRoot, ADAPTIVE_CANVAS), | 34 | const [r, g, b, alpha] = yield* decodeColor( |
| 218 | ], | 35 | project.fill.solid.slice("srgb:".length).split(",").map(Number), |
| 219 | [ | 36 | ); |
| 220 | "android-icon-background-nightly.png", | 37 | const background = { r: r * 255, g: g * 255, b: b * 255, alpha }; |
| 221 | yield* renderNightlyBackground(repositoryRoot, ADAPTIVE_CANVAS), | 38 | const source = yield* fs.readFile(path.join(projectRoot, "Assets", "knife.png")); |
| 222 | ], | 39 | |
| 223 | ["android-splash-icon-dev.png", yield* renderSplashIcon(repositoryRoot, "dev")], | 40 | for (const [name, size] of [ |
| 224 | ["android-splash-icon-nightly.png", yield* renderSplashIcon(repositoryRoot, "nightly")], | 41 | ["android-foreground.png", 432], |
| 225 | ["android-splash-icon-prod.png", yield* renderSplashIcon(repositoryRoot, "prod")], | 42 | ["android-splash.png", 1152], |
| 226 | ] as const; | 43 | ] as const) { |
| 227 | for (const [name, contents] of outputs) { | 44 | const png = yield* Effect.tryPromise({ |
| 228 | yield* fs.writeFile(path.join(repositoryRoot, OUTPUT_DIRECTORY, name), contents); | 45 | try: async () => { |
| 229 | yield* Console.log(`wrote ${OUTPUT_DIRECTORY}/${name}`); | 46 | // Android masks the central two thirds; the blade must stay inside that circle. |
| 47 | const foreground = await sharp(source) | ||
| 48 | .resize(Math.round(size * 0.57), Math.round(size * 0.57), { fit: "inside" }) | ||
| 49 | .png() | ||
| 50 | .toBuffer(); | ||
| 51 | return sharp({ | ||
| 52 | create: { | ||
| 53 | width: size, | ||
| 54 | height: size, | ||
| 55 | channels: 4, | ||
| 56 | background: name === "android-foreground.png" ? "transparent" : background, | ||
| 57 | }, | ||
| 58 | }) | ||
| 59 | .composite([{ input: foreground, gravity: "centre" }]) | ||
| 60 | .png() | ||
| 61 | .toBuffer(); | ||
| 62 | }, | ||
| 63 | catch: (cause) => new AndroidIconRenderError({ cause }), | ||
| 64 | }); | ||
| 65 | const output = path.join(repositoryRoot, "assets", "knife", name); | ||
| 66 | yield* fs.writeFile(output, png); | ||
| 67 | yield* Console.log(`wrote ${output}`); | ||
| 230 | } | 68 | } |
| 231 | }); | 69 | }); |
| 232 | 70 |
scripts/export-brand-icons.ts+35-63| ... | @@ -12,6 +12,7 @@ import * as Schema from "effect/Schema"; | ... | @@ -12,6 +12,7 @@ import * as Schema from "effect/Schema"; |
| 12 | import * as Stream from "effect/Stream"; | 12 | import * as Stream from "effect/Stream"; |
| 13 | import { Command, Flag } from "effect/cli"; | 13 | import { Command, Flag } from "effect/cli"; |
| 14 | import { ChildProcess, ChildProcessSpawner } from "effect/process"; | 14 | import { ChildProcess, ChildProcessSpawner } from "effect/process"; |
| 15 | import sharp from "sharp"; | ||
| 15 | 16 | ||
| 16 | import { BRAND_ASSET_PATHS, DEVELOPMENT_PUBLIC_ICON_OVERRIDES } from "./lib/brand-assets.ts"; | 17 | import { BRAND_ASSET_PATHS, DEVELOPMENT_PUBLIC_ICON_OVERRIDES } from "./lib/brand-assets.ts"; |
| 17 | import { encodePngIco, readPngDimensions, WINDOWS_ICON_SIZES } from "./lib/icon-export.ts"; | 18 | import { encodePngIco, readPngDimensions, WINDOWS_ICON_SIZES } from "./lib/icon-export.ts"; |
| ... | @@ -41,7 +42,7 @@ const decodeIconComposerVersion = Schema.decodeUnknownEffect( | ... | @@ -41,7 +42,7 @@ const decodeIconComposerVersion = Schema.decodeUnknownEffect( |
| 41 | Schema.fromJsonString(IconComposerVersion), | 42 | Schema.fromJsonString(IconComposerVersion), |
| 42 | ); | 43 | ); |
| 43 | 44 | ||
| 44 | type IconPlatform = "iOS"; | 45 | type IconPlatform = "iOS" | "macOS"; |
| 45 | 46 | ||
| 46 | interface VariantOutputs { | 47 | interface VariantOutputs { |
| 47 | readonly ios: string; | 48 | readonly ios: string; |
| ... | @@ -204,7 +205,7 @@ export class IconExportAssetsStaleError extends Schema.TaggedError<IconExportAss | ... | @@ -204,7 +205,7 @@ export class IconExportAssetsStaleError extends Schema.TaggedError<IconExportAss |
| 204 | 205 | ||
| 205 | const ICON_VARIANTS = [ | 206 | const ICON_VARIANTS = [ |
| 206 | { | 207 | { |
| 207 | label: "development", | 208 | label: "knife", |
| 208 | source: BRAND_ASSET_PATHS.developmentIconComposerProject, | 209 | source: BRAND_ASSET_PATHS.developmentIconComposerProject, |
| 209 | outputs: { | 210 | outputs: { |
| 210 | ios: BRAND_ASSET_PATHS.developmentIosIconPng, | 211 | ios: BRAND_ASSET_PATHS.developmentIosIconPng, |
| ... | @@ -217,44 +218,8 @@ const ICON_VARIANTS = [ | ... | @@ -217,44 +218,8 @@ const ICON_VARIANTS = [ |
| 217 | windowsIco: BRAND_ASSET_PATHS.developmentWindowsIconIco, | 218 | windowsIco: BRAND_ASSET_PATHS.developmentWindowsIconIco, |
| 218 | }, | 219 | }, |
| 219 | }, | 220 | }, |
| 220 | { | ||
| 221 | label: "preview", | ||
| 222 | source: BRAND_ASSET_PATHS.nightlyIconComposerProject, | ||
| 223 | outputs: { | ||
| 224 | ios: BRAND_ASSET_PATHS.nightlyIosIconPng, | ||
| 225 | macos: BRAND_ASSET_PATHS.nightlyMacIconPng, | ||
| 226 | universal: BRAND_ASSET_PATHS.nightlyLinuxIconPng, | ||
| 227 | appleTouch: BRAND_ASSET_PATHS.nightlyWebAppleTouchIconPng, | ||
| 228 | favicon16: BRAND_ASSET_PATHS.nightlyWebFavicon16Png, | ||
| 229 | favicon32: BRAND_ASSET_PATHS.nightlyWebFavicon32Png, | ||
| 230 | faviconIco: BRAND_ASSET_PATHS.nightlyWebFaviconIco, | ||
| 231 | windowsIco: BRAND_ASSET_PATHS.nightlyWindowsIconIco, | ||
| 232 | }, | ||
| 233 | }, | ||
| 234 | { | ||
| 235 | label: "production", | ||
| 236 | source: BRAND_ASSET_PATHS.productionIconComposerProject, | ||
| 237 | outputs: { | ||
| 238 | ios: BRAND_ASSET_PATHS.productionIosIconPng, | ||
| 239 | macos: BRAND_ASSET_PATHS.productionMacIconPng, | ||
| 240 | universal: BRAND_ASSET_PATHS.productionLinuxIconPng, | ||
| 241 | appleTouch: BRAND_ASSET_PATHS.productionWebAppleTouchIconPng, | ||
| 242 | favicon16: BRAND_ASSET_PATHS.productionWebFavicon16Png, | ||
| 243 | favicon32: BRAND_ASSET_PATHS.productionWebFavicon32Png, | ||
| 244 | faviconIco: BRAND_ASSET_PATHS.productionWebFaviconIco, | ||
| 245 | windowsIco: BRAND_ASSET_PATHS.productionWindowsIconIco, | ||
| 246 | }, | ||
| 247 | }, | ||
| 248 | ] as const satisfies ReadonlyArray<IconVariant>; | 221 | ] as const satisfies ReadonlyArray<IconVariant>; |
| 249 | 222 | ||
| 250 | const MACOS_EXPORT_CODEX_PROMPT = [ | ||
| 251 | "Use [@Computer](plugin://computer-use@openai-bundled) and the Icon Composer app to export the three macOS app icons in this repository.", | ||
| 252 | "For each project below, use Platform: macOS pre-Tahoe, Appearance: Default, Size: 1024pt, and Scale: 1×, then save the PNG to the exact destination:", | ||
| 253 | ...ICON_VARIANTS.map((variant) => `- ${variant.source} -> ${variant.outputs.macos}`), | ||
| 254 | "Do not resize, composite, or otherwise post-process the exported PNGs.", | ||
| 255 | "Verify every result is 1024×1024 and has the classic macOS safe area: an 824×824 opaque body inset 100px on every side, with only Icon Composer's native shadow extending beyond it.", | ||
| 256 | ]; | ||
| 257 | |||
| 258 | const RepositoryRoot = Effect.service(Path.Path).pipe( | 223 | const RepositoryRoot = Effect.service(Path.Path).pipe( |
| 259 | Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))), | 224 | Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))), |
| 260 | Effect.mapError( | 225 | Effect.mapError( |
| ... | @@ -586,6 +551,36 @@ const renderVariant = Effect.fn("iconExport.renderVariant")(function* ( | ... | @@ -586,6 +551,36 @@ const renderVariant = Effect.fn("iconExport.renderVariant")(function* ( |
| 586 | }); | 551 | }); |
| 587 | 552 | ||
| 588 | const ios = yield* render("iOS", 1024); | 553 | const ios = yield* render("iOS", 1024); |
| 554 | const macosFullBleed = yield* render("macOS", 1024); | ||
| 555 | const macos = yield* Effect.tryPromise({ | ||
| 556 | try: async () => { | ||
| 557 | const body = await sharp(macosFullBleed).resize(824, 824).png().toBuffer(); | ||
| 558 | const shadow = await sharp(body) | ||
| 559 | .ensureAlpha() | ||
| 560 | .linear([0, 0, 0, 0.2], [0, 0, 0, 0]) | ||
| 561 | .extend({ | ||
| 562 | top: 108, | ||
| 563 | bottom: 92, | ||
| 564 | left: 100, | ||
| 565 | right: 100, | ||
| 566 | background: "transparent", | ||
| 567 | }) | ||
| 568 | .blur(12) | ||
| 569 | .png() | ||
| 570 | .toBuffer(); | ||
| 571 | return sharp(shadow) | ||
| 572 | .composite([{ input: body, left: 100, top: 100 }]) | ||
| 573 | .png() | ||
| 574 | .toBuffer(); | ||
| 575 | }, | ||
| 576 | catch: (cause) => | ||
| 577 | new IconExportRenditionError({ | ||
| 578 | sourcePath, | ||
| 579 | outputPath: variant.outputs.macos, | ||
| 580 | expectedSize: 1024, | ||
| 581 | cause, | ||
| 582 | }), | ||
| 583 | }); | ||
| 589 | const icoRenditions = yield* Effect.forEach( | 584 | const icoRenditions = yield* Effect.forEach( |
| 590 | WINDOWS_ICON_SIZES, | 585 | WINDOWS_ICON_SIZES, |
| 591 | (size) => render("iOS", size).pipe(Effect.map((contents) => ({ size, contents }))), | 586 | (size) => render("iOS", size).pipe(Effect.map((contents) => ({ size, contents }))), |
| ... | @@ -598,6 +593,7 @@ const renderVariant = Effect.fn("iconExport.renderVariant")(function* ( | ... | @@ -598,6 +593,7 @@ const renderVariant = Effect.fn("iconExport.renderVariant")(function* ( |
| 598 | 593 | ||
| 599 | return new Map<string, Buffer>([ | 594 | return new Map<string, Buffer>([ |
| 600 | [variant.outputs.ios, ios], | 595 | [variant.outputs.ios, ios], |
| 596 | [variant.outputs.macos, macos], | ||
| 601 | [variant.outputs.universal, ios], | 597 | [variant.outputs.universal, ios], |
| 602 | [variant.outputs.appleTouch, yield* render("iOS", 180)], | 598 | [variant.outputs.appleTouch, yield* render("iOS", 180)], |
| 603 | [variant.outputs.favicon16, yield* render("iOS", 16)], | 599 | [variant.outputs.favicon16, yield* render("iOS", 16)], |
| ... | @@ -607,24 +603,6 @@ const renderVariant = Effect.fn("iconExport.renderVariant")(function* ( | ... | @@ -607,24 +603,6 @@ const renderVariant = Effect.fn("iconExport.renderVariant")(function* ( |
| 607 | ]); | 603 | ]); |
| 608 | }); | 604 | }); |
| 609 | 605 | ||
| 610 | const logManualMacOsExportInstructions = Effect.fn("iconExport.logManualMacOsExportInstructions")( | ||
| 611 | function* () { | ||
| 612 | yield* Console.warn( | ||
| 613 | [ | ||
| 614 | "macOS icons require Icon Composer's GUI-only pre-Tahoe preset and were not changed.", | ||
| 615 | "Export each source with Platform: macOS pre-Tahoe, Appearance: Default, Size: 1024pt, Scale: 1×:", | ||
| 616 | ...ICON_VARIANTS.map((variant) => `- ${variant.source} -> ${variant.outputs.macos}`), | ||
| 617 | "See assets/README.md for the complete workflow.", | ||
| 618 | "", | ||
| 619 | "Copy/paste this prompt into Codex to perform the native exports:", | ||
| 620 | "---", | ||
| 621 | ...MACOS_EXPORT_CODEX_PROMPT, | ||
| 622 | "---", | ||
| 623 | ].join("\n"), | ||
| 624 | ); | ||
| 625 | }, | ||
| 626 | ); | ||
| 627 | |||
| 628 | const writeAtomically = Effect.fn("iconExport.writeAtomically")(function* ( | 606 | const writeAtomically = Effect.fn("iconExport.writeAtomically")(function* ( |
| 629 | repositoryRoot: string, | 607 | repositoryRoot: string, |
| 630 | relativePath: string, | 608 | relativePath: string, |
| ... | @@ -774,7 +752,6 @@ export const exportBrandIcons = Effect.fn("exportBrandIcons")(function* (checkOn | ... | @@ -774,7 +752,6 @@ export const exportBrandIcons = Effect.fn("exportBrandIcons")(function* (checkOn |
| 774 | }); | 752 | }); |
| 775 | } | 753 | } |
| 776 | yield* Console.log(`All ${generated.size} generated icon assets are current.`); | 754 | yield* Console.log(`All ${generated.size} generated icon assets are current.`); |
| 777 | yield* logManualMacOsExportInstructions(); | ||
| 778 | return; | 755 | return; |
| 779 | } | 756 | } |
| 780 | 757 | ||
| ... | @@ -784,7 +761,6 @@ export const exportBrandIcons = Effect.fn("exportBrandIcons")(function* (checkOn | ... | @@ -784,7 +761,6 @@ export const exportBrandIcons = Effect.fn("exportBrandIcons")(function* (checkOn |
| 784 | { concurrency: 1, discard: true }, | 761 | { concurrency: 1, discard: true }, |
| 785 | ); | 762 | ); |
| 786 | yield* Console.log(`Updated ${generated.size} generated icon assets.`); | 763 | yield* Console.log(`Updated ${generated.size} generated icon assets.`); |
| 787 | yield* logManualMacOsExportInstructions(); | ||
| 788 | }); | 764 | }); |
| 789 | 765 | ||
| 790 | export const exportBrandIconsCommand = Command.make( | 766 | export const exportBrandIconsCommand = Command.make( |
| ... | @@ -796,11 +772,7 @@ export const exportBrandIconsCommand = Command.make( | ... | @@ -796,11 +772,7 @@ export const exportBrandIconsCommand = Command.make( |
| 796 | ), | 772 | ), |
| 797 | }, | 773 | }, |
| 798 | ({ check }) => exportBrandIcons(check).pipe(Effect.scoped), | 774 | ({ check }) => exportBrandIcons(check).pipe(Effect.scoped), |
| 799 | ).pipe( | 775 | ).pipe(Command.withDescription("Export The Knife assets from its Icon Composer project.")); |
| 800 | Command.withDescription( | ||
| 801 | "Export development, preview, and production assets from Icon Composer projects.", | ||
| 802 | ), | ||
| 803 | ); | ||
| 804 | 776 | ||
| 805 | if (import.meta.main) { | 777 | if (import.meta.main) { |
| 806 | Command.run(exportBrandIconsCommand, { version: "0.0.0" }).pipe( | 778 | Command.run(exportBrandIconsCommand, { version: "0.0.0" }).pipe( |
scripts/lib/brand-assets.test.ts-15| ... | @@ -83,19 +83,4 @@ describe("brand-assets", () => { | ... | @@ -83,19 +83,4 @@ describe("brand-assets", () => { |
| 83 | expect(resolveWebAssetBrandForPackageVersion("0.0.29-nightly.20260723.882")).toBe("nightly"); | 83 | expect(resolveWebAssetBrandForPackageVersion("0.0.29-nightly.20260723.882")).toBe("nightly"); |
| 84 | expect(resolveWebAssetBrandForPackageVersion("0.0.29-preview.20260723.882")).toBe("nightly"); | 84 | expect(resolveWebAssetBrandForPackageVersion("0.0.29-preview.20260723.882")).toBe("nightly"); |
| 85 | }); | 85 | }); |
| 86 | |||
| 87 | it("keeps development, nightly, and production icon families separate", () => { | ||
| 88 | expect([ | ||
| 89 | BRAND_ASSET_PATHS.developmentIconComposerProject, | ||
| 90 | BRAND_ASSET_PATHS.nightlyIconComposerProject, | ||
| 91 | BRAND_ASSET_PATHS.productionIconComposerProject, | ||
| 92 | ]).toEqual([ | ||
| 93 | "assets/dev/app-icon.icon", | ||
| 94 | "assets/nightly/app-icon.icon", | ||
| 95 | "assets/prod/app-icon.icon", | ||
| 96 | ]); | ||
| 97 | expect(BRAND_ASSET_PATHS.developmentDesktopIconPng).toMatch(/^assets\/dev\/blueprint-/); | ||
| 98 | expect(BRAND_ASSET_PATHS.nightlyMacIconPng).toMatch(/^assets\/nightly\/nightly-/); | ||
| 99 | expect(BRAND_ASSET_PATHS.productionMacIconPng).toMatch(/^assets\/prod\/black-/); | ||
| 100 | }); | ||
| 101 | }); | 86 | }); |
scripts/lib/brand-assets.ts+27-30| ... | @@ -1,34 +1,31 @@ | ... | @@ -1,34 +1,31 @@ |
| 1 | export const BRAND_ASSET_PATHS = { | 1 | export const BRAND_ASSET_PATHS = { |
| 2 | developmentIconComposerProject: "assets/dev/app-icon.icon", | 2 | developmentIconComposerProject: "assets/knife/app-icon.icon", |
| 3 | developmentIosIconPng: "assets/dev/blueprint-ios-1024.png", | 3 | developmentIosIconPng: "assets/knife/icon-1024.png", |
| 4 | developmentUniversalIconPng: "assets/dev/blueprint-universal-1024.png", | 4 | developmentUniversalIconPng: "assets/knife/icon-1024.png", |
| 5 | 5 | productionIconComposerProject: "assets/knife/app-icon.icon", | |
| 6 | productionIconComposerProject: "assets/prod/app-icon.icon", | 6 | productionIosIconPng: "assets/knife/icon-1024.png", |
| 7 | productionIosIconPng: "assets/prod/black-ios-1024.png", | 7 | productionMacIconPng: "assets/knife/macos-1024.png", |
| 8 | productionMacIconPng: "assets/prod/black-macos-1024.png", | 8 | productionLinuxIconPng: "assets/knife/icon-1024.png", |
| 9 | productionLinuxIconPng: "assets/prod/black-universal-1024.png", | 9 | productionWindowsIconIco: "assets/knife/icon.ico", |
| 10 | productionWindowsIconIco: "assets/prod/t3-black-windows.ico", | 10 | productionWebFaviconIco: "assets/knife/icon.ico", |
| 11 | productionWebFaviconIco: "assets/prod/t3-black-web-favicon.ico", | 11 | productionWebFavicon16Png: "assets/knife/favicon-16x16.png", |
| 12 | productionWebFavicon16Png: "assets/prod/t3-black-web-favicon-16x16.png", | 12 | productionWebFavicon32Png: "assets/knife/favicon-32x32.png", |
| 13 | productionWebFavicon32Png: "assets/prod/t3-black-web-favicon-32x32.png", | 13 | productionWebAppleTouchIconPng: "assets/knife/apple-touch-180.png", |
| 14 | productionWebAppleTouchIconPng: "assets/prod/t3-black-web-apple-touch-180.png", | 14 | nightlyIconComposerProject: "assets/knife/app-icon.icon", |
| 15 | 15 | nightlyIosIconPng: "assets/knife/icon-1024.png", | |
| 16 | nightlyIconComposerProject: "assets/nightly/app-icon.icon", | 16 | nightlyMacIconPng: "assets/knife/macos-1024.png", |
| 17 | nightlyIosIconPng: "assets/nightly/nightly-ios-1024.png", | 17 | nightlyLinuxIconPng: "assets/knife/icon-1024.png", |
| 18 | nightlyMacIconPng: "assets/nightly/nightly-macos-1024.png", | 18 | nightlyWindowsIconIco: "assets/knife/icon.ico", |
| 19 | nightlyLinuxIconPng: "assets/nightly/nightly-universal-1024.png", | 19 | nightlyWebFaviconIco: "assets/knife/icon.ico", |
| 20 | nightlyWindowsIconIco: "assets/nightly/nightly-windows.ico", | 20 | nightlyWebFavicon16Png: "assets/knife/favicon-16x16.png", |
| 21 | nightlyWebFaviconIco: "assets/nightly/nightly-web-favicon.ico", | 21 | nightlyWebFavicon32Png: "assets/knife/favicon-32x32.png", |
| 22 | nightlyWebFavicon16Png: "assets/nightly/nightly-web-favicon-16x16.png", | 22 | nightlyWebAppleTouchIconPng: "assets/knife/apple-touch-180.png", |
| 23 | nightlyWebFavicon32Png: "assets/nightly/nightly-web-favicon-32x32.png", | 23 | developmentDesktopIconPng: "assets/knife/macos-1024.png", |
| 24 | nightlyWebAppleTouchIconPng: "assets/nightly/nightly-web-apple-touch-180.png", | 24 | developmentWindowsIconIco: "assets/knife/icon.ico", |
| 25 | 25 | developmentWebFaviconIco: "assets/knife/icon.ico", | |
| 26 | developmentDesktopIconPng: "assets/dev/blueprint-macos-1024.png", | 26 | developmentWebFavicon16Png: "assets/knife/favicon-16x16.png", |
| 27 | developmentWindowsIconIco: "assets/dev/blueprint-windows.ico", | 27 | developmentWebFavicon32Png: "assets/knife/favicon-32x32.png", |
| 28 | developmentWebFaviconIco: "assets/dev/blueprint-web-favicon.ico", | 28 | developmentWebAppleTouchIconPng: "assets/knife/apple-touch-180.png", |
| 29 | developmentWebFavicon16Png: "assets/dev/blueprint-web-favicon-16x16.png", | ||
| 30 | developmentWebFavicon32Png: "assets/dev/blueprint-web-favicon-32x32.png", | ||
| 31 | developmentWebAppleTouchIconPng: "assets/dev/blueprint-web-apple-touch-180.png", | ||
| 32 | } as const; | 29 | } as const; |
| 33 | 30 | ||
| 34 | export type WebAssetBrand = "development" | "nightly" | "production"; | 31 | export type WebAssetBrand = "development" | "nightly" | "production"; |
scripts/mobile-native-client.ts+3-3| ... | @@ -348,9 +348,9 @@ const main = Command.make( | ... | @@ -348,9 +348,9 @@ const main = Command.make( |
| 348 | [ | 348 | [ |
| 349 | "xcodebuild", | 349 | "xcodebuild", |
| 350 | "-workspace", | 350 | "-workspace", |
| 351 | path.join(mobile, "ios/T3CodeDev.xcworkspace"), | 351 | path.join(mobile, "ios/TheKnifeDev.xcworkspace"), |
| 352 | "-scheme", | 352 | "-scheme", |
| 353 | "T3CodeDev", | 353 | "TheKnifeDev", |
| 354 | "-configuration", | 354 | "-configuration", |
| 355 | "Debug", | 355 | "Debug", |
| 356 | "-destination", | 356 | "-destination", |
| ... | @@ -367,7 +367,7 @@ const main = Command.make( | ... | @@ -367,7 +367,7 @@ const main = Command.make( |
| 367 | "simctl", | 367 | "simctl", |
| 368 | "install", | 368 | "install", |
| 369 | device, | 369 | device, |
| 370 | path.join(output, "Build/Products/Debug-iphonesimulator/T3CodeDev.app"), | 370 | path.join(output, "Build/Products/Debug-iphonesimulator/TheKnifeDev.app"), |
| 371 | ], | 371 | ], |
| 372 | true, | 372 | true, |
| 373 | ); | 373 | ); |
scripts/mobile-showcase.ts+3-3| ... | @@ -42,7 +42,7 @@ const SERVER_HOST = "0.0.0.0"; | ... | @@ -42,7 +42,7 @@ const SERVER_HOST = "0.0.0.0"; |
| 42 | const IOS_SIMULATOR_ARCH = NodeProcess.arch === "arm64" ? "arm64" : "x86_64"; | 42 | const IOS_SIMULATOR_ARCH = NodeProcess.arch === "arm64" ? "arm64" : "x86_64"; |
| 43 | const IOS_APP_PATH = NodePath.join( | 43 | const IOS_APP_PATH = NodePath.join( |
| 44 | MOBILE_ROOT, | 44 | MOBILE_ROOT, |
| 45 | ".showcase/ios-derived-data/Build/Products/Debug-iphonesimulator/T3Code.app", | 45 | ".showcase/ios-derived-data/Build/Products/Debug-iphonesimulator/TheKnife.app", |
| 46 | ); | 46 | ); |
| 47 | const ANDROID_APK_PATH = NodePath.join( | 47 | const ANDROID_APK_PATH = NodePath.join( |
| 48 | MOBILE_ROOT, | 48 | MOBILE_ROOT, |
| ... | @@ -707,9 +707,9 @@ async function buildIos(): Promise<string> { | ... | @@ -707,9 +707,9 @@ async function buildIos(): Promise<string> { |
| 707 | "xcodebuild", | 707 | "xcodebuild", |
| 708 | [ | 708 | [ |
| 709 | "-workspace", | 709 | "-workspace", |
| 710 | NodePath.join(MOBILE_ROOT, "ios/T3Code.xcworkspace"), | 710 | NodePath.join(MOBILE_ROOT, "ios/TheKnife.xcworkspace"), |
| 711 | "-scheme", | 711 | "-scheme", |
| 712 | "T3Code", | 712 | "TheKnife", |
| 713 | "-configuration", | 713 | "-configuration", |
| 714 | "Debug", | 714 | "Debug", |
| 715 | "-sdk", | 715 | "-sdk", |