[codex] rebuild ClayCode feature rollout#2464
[codex] rebuild ClayCode feature rollout#2464cschubiner wants to merge 20 commits intopingdotgg:mainfrom
Conversation
* add toolchain guardrails * stabilize snippet picker browser test
Update cherry-picked tests to match upstream's current API shape after
replay onto upstream/main:
- CommandId/ThreadId use .make() rather than .makeUnsafe()
- probeCodexAccount was replaced by probeCodexDiscovery (returns
{ account, skills })
- ProviderRegistry cache-invalidation test now provides ServerConfig
- makeManagedServerProvider requires initialSnapshot and snapshots
include slashCommands/skills arrays
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sync upstream + replay Codex refresh features (2026-04-16)
- appBranding.ts: APP_BASE_NAME drives all helper-derived display names - electron-launcher.mjs: dev/alpha display names + comment - main.ts: refactor hardcoded "T3 Code failed to start" / update dialog to use desktopAppBranding.baseName so future renames stay centralized - package.json: productName for electron-builder artifacts - appBranding.test.ts: update fixtures Internal IDs (bundle id com.t3tools.t3code, USER_DATA_DIR_NAME t3code, LINUX_WM_CLASS, LEGACY_USER_DATA_DIR_NAME, --t3code-dev-root flag, WORKTREE_BRANCH_PREFIX) are intentionally NOT renamed to preserve upgrade paths for existing installs and avoid breaking deep links.
feat(desktop): rebrand T3 Code → ClayCode in user-visible surfaces
Adds two new keybinding commands: - sidebar.history.previous → window.history.back() - sidebar.history.next → window.history.forward() Default bindings: mod+[ / mod+] (when !terminalFocus). Distinct from the existing thread.previous/next (mod+shift+[/]) which walk the sidebar thread list — these instead step through router history (e.g. revisit threads in the order you navigated to them). Helpers added in apps/web/src/keybindings.ts following the existing isXShortcut pattern for parity with chat.new/editor.openFavorite/etc. Tests cover: macOS + Linux modifiers, terminal-focus opt-out, and non-conflict with mod+shift+[ thread traversal.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| function readMiseToolVersion(toolName) { | ||
| const misePath = join(import.meta.dirname, "..", ".mise.toml"); | ||
| const contents = readFileSync(misePath, "utf8"); | ||
| const match = contents.match(new RegExp(`^${toolName}\\s*=\\s*"([^"]+)"$`, "m")); | ||
| return match?.[1] ?? null; | ||
| } |
There was a problem hiding this comment.
🟢 Low scripts/check-toolchain.mjs:10
readMiseToolVersion throws when .mise.toml doesn't exist, crashing the script before the ?? FALLBACK_*_VERSION fallback can be used. The nullish coalescing only handles null return values, not thrown exceptions. Wrap the readFileSync call in a try-catch to return null when the file is missing.
+function readMiseToolVersion(toolName) {
+ try {
+ const misePath = join(import.meta.dirname, "..", ".mise.toml");
+ const contents = readFileSync(misePath, "utf8");
+ const match = contents.match(new RegExp(`^${toolName}\\s*=\\s*"([^"]+)"$`, "m"));
+ return match?.[1] ?? null;
+ } catch {
+ return null;
+ }
+}🤖 Copy this AI Prompt to have your agent fix this:
In file scripts/check-toolchain.mjs around lines 10-15:
`readMiseToolVersion` throws when `.mise.toml` doesn't exist, crashing the script before the `?? FALLBACK_*_VERSION` fallback can be used. The nullish coalescing only handles `null` return values, not thrown exceptions. Wrap the `readFileSync` call in a try-catch to return `null` when the file is missing.
Evidence trail:
scripts/check-toolchain.mjs lines 10-15: `readMiseToolVersion` uses `readFileSync` without try-catch. Lines 62-63: `??` fallback pattern. Lines 42-47: `readCurrentBunVersion` shows the correct try-catch pattern used elsewhere in the same file. .mise.toml exists in the repo root (39 bytes).
| export function upsertSavedComposerSnippet( | ||
| savedSnippets: ReadonlyArray<SavedComposerSnippetRecord>, | ||
| value: string, | ||
| nowIso = new Date().toISOString(), | ||
| ): { | ||
| snippets: SavedComposerSnippetRecord[]; | ||
| snippet: SavedComposerSnippetRecord; | ||
| deduped: boolean; | ||
| } { | ||
| const normalizedBody = normalizeComposerSnippetBody(value); |
There was a problem hiding this comment.
🟢 Low chat/composerSnippets.ts:194
When value is whitespace-only (e.g., " " or "\n\n"), normalizeComposerSnippetBody trims it to an empty string, and upsertSavedComposerSnippet creates a SavedComposerSnippetRecord with body: "". This violates the Schema.NonEmptyString constraint, so the record will fail schema validation when later decoded via SavedComposerSnippetList. Consider rejecting whitespace-only snippets early in upsertSavedComposerSnippet.
-export function upsertSavedComposerSnippet(
- savedSnippets: ReadonlyArray<SavedComposerSnippetRecord>,
- value: string,
- nowIso = new Date().toISOString(),
-): {
+export function upsertSavedComposerSnippet(
+ savedSnippets: ReadonlyArray<SavedComposerSnippetRecord>,
+ value: string,
+ nowIso = new Date().toISOString(),
+): {
+ snippets: SavedComposerSnippetRecord[];
+ snippet: SavedComposerSnippetRecord;
+ deduped: boolean;
+} | null {
+ const normalizedBody = normalizeComposerSnippetBody(value);
+ if (!normalizedBody) {
+ return null;
+ }
const normalizedBody = normalizeComposerSnippetBody(value);🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/chat/composerSnippets.ts around lines 194-203:
When `value` is whitespace-only (e.g., `" "` or `"\n\n"`), `normalizeComposerSnippetBody` trims it to an empty string, and `upsertSavedComposerSnippet` creates a `SavedComposerSnippetRecord` with `body: ""`. This violates the `Schema.NonEmptyString` constraint, so the record will fail schema validation when later decoded via `SavedComposerSnippetList`. Consider rejecting whitespace-only snippets early in `upsertSavedComposerSnippet`.
Evidence trail:
composerSnippets.ts:144-146 — `normalizeComposerSnippetBody` calls `.trim()` via `normalizeSnippetWhitespace` (whitespace → "")
composerSnippets.ts:125-130 — `SavedComposerSnippetRecord` schema has `body: Schema.NonEmptyString`
composerSnippets.ts:194-228 — `upsertSavedComposerSnippet` assigns `body: normalizedBody` with only `satisfies` (compile-time)
ChatView.tsx:2643-2653 — caller validates `normalizedBody.length === 0` before calling
ChatComposer.tsx:1599-1608 — second caller also validates `draftValue.length === 0`
useLocalStorage.ts:23-24 — encode/decode use `Schema.encodeSync`/`Schema.decodeSync` which validate at runtime
package.json:11 — Effect 4.0.0-beta.45 (NonEmptyString.Type is `string`, no brand, so `satisfies` doesn't catch it)
| const unquoted = trimmed.slice(1, -1); | ||
| if (trimmed.startsWith('"')) { | ||
| return ( | ||
| unquoted.replace(/\\\\/g, "\\").replace(/\\"/g, '"').replace(/\\n/g, "\n").trim() || null |
There was a problem hiding this comment.
🟢 Low src/skills.ts:148
In decodeYamlScalar, the escape sequence replacement order corrupts strings containing \\n (escaped backslash followed by 'n'). For YAML input like "hello\\\\nworld" (intended to produce hello\nworld), the code first replaces \\\\ with \\, producing hello\\nworld, then replaces \\n with a newline character. The result is hello<newline>world instead of the expected hello\nworld.
+ unquoted.replace(/\\n/g, "\n").replace(/\\\\/g, "\\").trim() || null🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/skills.ts around line 148:
In `decodeYamlScalar`, the escape sequence replacement order corrupts strings containing `\\n` (escaped backslash followed by 'n'). For YAML input like `"hello\\\\nworld"` (intended to produce `hello\nworld`), the code first replaces `\\\\` with `\\`, producing `hello\\nworld`, then replaces `\\n` with a newline character. The result is `hello<newline>world` instead of the expected `hello\nworld`.
Evidence trail:
apps/server/src/skills.ts lines 147-149 at REVIEWED_COMMIT — the chained `.replace(/\\\\/g, "\\").replace(/\\"/g, '"').replace(/\\n/g, "\n")` processes `\\` before `\n`, which creates false escape sequences from previously-escaped backslashes.
Summary
This PR rebuilds the feature rollout on top of the current baseline under the ClayCode rebrand. It restores the user-facing workflow improvements that existed in the earlier rebuild work while reimplementing them cleanly against the current app structure instead of replaying old code directly.
The main user impact is that ClayCode again feels like a practical multi-thread coding cockpit: users can queue follow-up work while an agent is busy, steer active turns without interrupting them, jump through sidebar history and search, preserve drafts across thread/project switches, import durable Codex threads, open snippet and skill pickers from the composer, see GitHub PR status pills, and configure remote/Tailscale access from desktop settings.
What Changed
This branch restores the ClayCode rebrand and adds the rebuilt workflow features across server, web, desktop, contracts, and shared packages. The largest UI changes are in the chat view, composer controls, sidebar, search dialogs, queued follow-up panel, snippet/skill picker dialogs, Codex import dialog, and connection/settings surfaces. Server and contracts changes add the supporting schemas, keybindings, skills discovery, Codex import service, orchestration command handling, websocket methods, and GitHub/remote status plumbing.
The Queue + Steer work is the marquee behavior in this branch. Follow-ups can be queued while a turn is running, remain scoped to their originating thread/project, preserve normal composer drafts, auto-dispatch when safe, and can be steered into an active turn through the explicit Steer path without being treated as ordinary queued work. The panel supports row focus/reordering hotkeys, edit/remove/save/send actions, and disabled states while a turn is still active.
Root Cause / Context
The previous feature work existed before the upstream refresh and could not simply be carried forward wholesale. The app had moved enough that old implementation details would have been brittle, especially around session lifecycle, websocket recovery, composer state, Codex app-server events, desktop readiness, and project/thread scoping. Rebuilding from the intended behavior let the branch align with the current architecture and add tests around the risky state transitions instead of preserving stale assumptions.
Validation
I ran the universal local checks repeatedly while rebuilding:
bun fmt,bun lint,bun typecheck,bun run test, targeted browser tests for the chat view and queued follow-up panel, and Electron production-build QA through Computer Use. The final doc-only QA update was also checked withbun fmt,bun lint,bun typecheck, andgit diff --check.The Electron QA covered fresh threads, same-thread follow-ups, thread switching mid-stream, project switching, hard reload/reconnect behavior, search, snippet/skill picker flows, Codex import, sidebar shortcuts, and deep Queue + Steer variations. The latest Queue + Steer pass specifically verified live steer plus immediate project/thread switch using keyboard-style input, pending queue persistence across Electron reload while the base turn was still running, no hidden
thread.turn.interruptin provider traces, and correct auto-dispatch after the base turn completed.QA artifacts are included under
.codex/artifacts/qa/, including the final Queue + Steer evidence in.codex/artifacts/qa/queue-steer-switching-2026-05-02.md.Notes For Reviewers
This is intentionally a large comparison branch for the rebuilt feature set. The highest-value review areas are queue/steer state transitions, composer draft preservation, websocket reconnect behavior, Codex import durability, and desktop remote/Tailscale settings. Lint currently reports 0 errors and 8 pre-existing warnings that were not introduced by the final QA documentation update.
Note
Rebuild ClayCode feature rollout with queued turns, search dialogs, snippet/skill pickers, and Codex import
queuedTurnStore) that persists per-thread draft turns, auto-dispatches them when the thread goes idle, and surfaces aQueuedFollowUpsPanelUI with send/reorder/delete/snippet-save actions.composerSnippetsmodule with built-in and saved snippets, a/snippetslash command,SnippetPickerDialog, and keyboard shortcuts (Tab to queue, Cmd+Enter to steer, Shift+Cmd+Enter to queue-front).SkillPickerDialogthat searches workspace SKILL.md files via a newskills.searchRPC, and aSkillPickerStorefor global open/focus state.QuickThreadSearchDialog), global thread search (GlobalThreadSearchDialog), and project folder search (ProjectFolderSearchDialog), each backed by a ranked search library and a zustand store.ImportFromCodexDialog(Cmd+Shift+I) that lists and imports local Codex rollout transcripts into durable threads via newcodexImportRPC endpoints and aCodexImportserver service.desktop:get-tailnet-infoIPC channel and surfaces a Tailnet URL in Connections Settings when the desktop is network-accessible and Tailscale is connected.check-toolchainscript that fails fast when Node/Bun versions are incompatible.📊 Macroscope summarized 8c44550. 61 files reviewed, 4 issues evaluated, 0 issues filtered, 3 comments posted
🗂️ Filtered Issues