Skip to content

[codex] rebuild ClayCode feature rollout#2464

Draft
cschubiner wants to merge 20 commits intopingdotgg:mainfrom
cschubiner:codex/rebuild-feature-rollout
Draft

[codex] rebuild ClayCode feature rollout#2464
cschubiner wants to merge 20 commits intopingdotgg:mainfrom
cschubiner:codex/rebuild-feature-rollout

Conversation

@cschubiner
Copy link
Copy Markdown

@cschubiner cschubiner commented May 2, 2026

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 with bun fmt, bun lint, bun typecheck, and git 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.interrupt in 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

  • Renames the product from 'T3 Code' to 'ClayCode' across all user-facing strings, branding constants, CLI help text, git commit metadata, and desktop package name.
  • Adds a queued follow-up turns system (queuedTurnStore) that persists per-thread draft turns, auto-dispatches them when the thread goes idle, and surfaces a QueuedFollowUpsPanel UI with send/reorder/delete/snippet-save actions.
  • Introduces composer snippet support: a composerSnippets module with built-in and saved snippets, a /snippet slash command, SnippetPickerDialog, and keyboard shortcuts (Tab to queue, Cmd+Enter to steer, Shift+Cmd+Enter to queue-front).
  • Adds a SkillPickerDialog that searches workspace SKILL.md files via a new skills.search RPC, and a SkillPickerStore for global open/focus state.
  • Adds quick thread search (QuickThreadSearchDialog), global thread search (GlobalThreadSearchDialog), and project folder search (ProjectFolderSearchDialog), each backed by a ranked search library and a zustand store.
  • Introduces an ImportFromCodexDialog (Cmd+Shift+I) that lists and imports local Codex rollout transcripts into durable threads via new codexImport RPC endpoints and a CodexImport server service.
  • Extends the sidebar with a grouped/recent mode toggle, PR pill rendering per thread row, cross-project keyboard traversal, browser-history navigation, and a thread rename shortcut (Cmd+Shift+R).
  • Exposes Tailscale tailnet info via a new desktop:get-tailnet-info IPC channel and surfaces a Tailnet URL in Connections Settings when the desktop is network-accessible and Tailscale is connected.
  • Adds default keybindings for all new commands and a check-toolchain script 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

cschubiner and others added 19 commits April 16, 2026 11:20
* 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.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d76520d2-e211-49b8-8a2d-56b0127c06d3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 2, 2026
Comment on lines +10 to +15
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;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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).

Comment on lines +194 to +203
export function upsertSavedComposerSnippet(
savedSnippets: ReadonlyArray<SavedComposerSnippetRecord>,
value: string,
nowIso = new Date().toISOString(),
): {
snippets: SavedComposerSnippetRecord[];
snippet: SavedComposerSnippetRecord;
deduped: boolean;
} {
const normalizedBody = normalizeComposerSnippetBody(value);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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)

Comment thread apps/server/src/skills.ts
const unquoted = trimmed.slice(1, -1);
if (trimmed.startsWith('"')) {
return (
unquoted.replace(/\\\\/g, "\\").replace(/\\"/g, '"').replace(/\\n/g, "\n").trim() || null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant