Skip to content

sync: merge 6 new upstream commits + ancestry#62

Merged
aaditagrawal merged 7 commits intomainfrom
sync/upstream-main-2026-04-13
Apr 12, 2026
Merged

sync: merge 6 new upstream commits + ancestry#62
aaditagrawal merged 7 commits intomainfrom
sync/upstream-main-2026-04-13

Conversation

@aaditagrawal
Copy link
Copy Markdown
Owner

@aaditagrawal aaditagrawal commented Apr 12, 2026

Summary

Merges 6 new upstream commits from pingdotgg/t3code with proper ancestry recording.

Upstream additions:

Fork adaptations:

  • Took upstream's extensible command palette (fork's version replaced)
  • Took upstream's extracted thread sort logic (Sidebar.logic.tslib/threadSort.ts)
  • Inline ModelUsage/NonNullableUsage types (not yet re-exported from SDK entry)
  • Updated @anthropic-ai/claude-agent-sdk to 0.2.104
  • Made Thread.archivedAt required (matches upstream)
  • Proper ancestry merge: git log main..upstream/main → 0 commits after merge

Test plan

  • bun typecheck — 0 errors
  • bun run build — 0 errors
  • bun run fmt — clean
  • CI passes

Summary by CodeRabbit

  • New Features

    • Global command palette with Mod+K (context-aware) plus project/thread search, filtering, and submenu navigation.
  • Improvements

    • Better token-usage handling and clamping for AI responses.
    • Improved thread sorting and sidebar search integration.
    • Windows startup/launch behavior refined.
    • UI refinements: command backdrop, input wrapper support, and higher-priority toast stacking.
  • Documentation

    • Keybindings docs updated to include the command-palette shortcut.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

Warning

Rate limit exceeded

@aaditagrawal has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 48 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 14 minutes and 48 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c0bde144-15cc-45f6-8624-76dba27f9de7

📥 Commits

Reviewing files that changed from the base of the PR and between 0123393 and b7f2969.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (33)
  • KEYBINDINGS.md
  • apps/desktop/src/main.ts
  • apps/server/src/git/Layers/GitCore.test.ts
  • apps/server/src/git/Layers/GitCore.ts
  • apps/server/src/keybindings.ts
  • apps/server/src/open.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.test.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.ts
  • apps/web/src/commandPaletteStore.ts
  • apps/web/src/components/ChatView.browser.tsx
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/CommandPalette.logic.test.ts
  • apps/web/src/components/CommandPalette.logic.ts
  • apps/web/src/components/CommandPalette.tsx
  • apps/web/src/components/CommandPaletteResults.tsx
  • apps/web/src/components/Sidebar.logic.test.ts
  • apps/web/src/components/Sidebar.logic.ts
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/ui/command.tsx
  • apps/web/src/components/ui/toast.tsx
  • apps/web/src/composerHandleContext.ts
  • apps/web/src/keybindings.test.ts
  • apps/web/src/lib/chatThreadActions.test.ts
  • apps/web/src/lib/chatThreadActions.ts
  • apps/web/src/lib/threadSort.test.ts
  • apps/web/src/lib/threadSort.ts
  • apps/web/src/routes/__root.tsx
  • apps/web/src/routes/_chat.tsx
  • apps/web/src/types.ts
  • apps/web/vitest.browser.config.ts
  • package.json
  • packages/contracts/src/keybindings.test.ts
  • packages/contracts/src/keybindings.ts
📝 Walkthrough

Walkthrough

Adds a global command palette (toggleable via Mod+K when terminal unfocused), implements palette UI, search/filter/sort logic, state store, sidebar trigger, thread-sorting utilities, command execution plumbing, tests, and related server/adapter and platform adjustments.

Changes

Cohort / File(s) Summary
Keybindings & Contracts
KEYBINDINGS.md, apps/server/src/keybindings.ts, apps/web/src/keybindings.test.ts, packages/contracts/src/keybindings.ts, packages/contracts/src/keybindings.test.ts
Introduced commandPalette.toggle binding (mod+k when !terminalFocus) and added literal to keybinding command set; tests updated to assert new binding and resolution behavior.
Command Palette UI & Logic
apps/web/src/components/CommandPalette.tsx, apps/web/src/components/CommandPaletteResults.tsx, apps/web/src/components/CommandPalette.logic.ts, apps/web/src/components/CommandPalette.logic.test.ts
New command-palette implementation: view-stack/submenu model, item/group builders, ranked filtering, rendering component and results list; comprehensive unit tests added.
State & Context
apps/web/src/commandPaletteStore.ts, apps/web/src/composerHandleContext.ts
Added Zustand store for palette open state and a composer handle React context/hook.
Integration & Chat/Sidebar Changes
apps/web/src/components/ChatView.tsx, apps/web/src/components/ChatView.browser.tsx, apps/web/src/components/Sidebar.tsx, apps/web/src/components/Sidebar.logic.ts, apps/web/src/components/Sidebar.logic.test.ts, apps/web/src/routes/__root.tsx, apps/web/src/routes/_chat.tsx
Integrated palette into chat/sidebar flows: gating keydown handling when palette open, added sidebar trigger with shortcut label, refactored thread-creation to shared action helpers, updated tests and removed legacy sidebar-sorting tests.
Thread Sorting Library & Tests
apps/web/src/lib/threadSort.ts, apps/web/src/lib/threadSort.test.ts
Extracted and added shared thread sorting utilities (sortOrders, stable tie-breaks, latest-user-message logic) with tests.
Chat Thread Action Helpers & Tests
apps/web/src/lib/chatThreadActions.ts, apps/web/src/lib/chatThreadActions.test.ts
New helpers to resolve project refs and start threads from contextual state; tests added.
Command Palette Styling / UI tweaks
apps/web/src/components/ui/command.tsx, apps/web/src/components/ui/toast.tsx
Adjusted backdrop styling and input wrapper prop; increased toast z-index.
Command Palette Store Consumption
apps/web/src/components/CommandPaletteResults.tsx, apps/web/src/components/CommandPalette.tsx
Result rows compute shortcut labels from resolved keybindings; item execution calls item.run() and reports errors to toast.
Claude Adapter & Tests
apps/server/src/provider/Layers/ClaudeAdapter.ts, apps/server/src/provider/Layers/ClaudeAdapter.test.ts
Refined token-usage typing and normalization, clamped usage to model context windows, updated accumulation logic, and added tests for oversized token snapshots.
Git Core Refresh Changes & Tests
apps/server/src/git/Layers/GitCore.ts, apps/server/src/git/Layers/GitCore.test.ts
Switched refresh cache keys to remote-based (StatusRemoteRefreshCacheKey), replaced upstream-ref fetch with fetchRemoteForStatus, and strengthened test assertions for fetch coalescing and failure cooldowns.
Platform & Spawn Behavior
apps/desktop/src/main.ts, apps/server/src/open.ts
Windows APP_USER_MODEL_ID now varies by dev/prod; detached process spawn now computes Win32 handling and quotes arguments on Windows.
Types, Config & Dependencies
apps/web/src/types.ts, apps/web/vitest.browser.config.ts, package.json
Made Thread.archivedAt required (nullable), relaxed Vitest strictPort settings, and added @anthropic-ai/claude-agent-sdk dependency.

Sequence Diagram

sequenceDiagram
    actor User
    participant ChatView as ChatView
    participant Store as CommandPaletteStore
    participant Palette as CommandPalette
    participant Logic as CommandPalette.logic
    participant Handler as ThreadActionHandler

    User->>ChatView: Press Mod+K
    ChatView->>Store: getState().open
    alt Closed
        ChatView->>Store: toggleOpen()
        Store->>Palette: open=true
    else Open
        ChatView->>Store: toggleOpen()
        Store->>Palette: open=false
    end

    Palette->>Logic: buildRootGroups(...) / filterCommandPaletteGroups(query)
    Logic->>Palette: groups/items (ranked)
    User->>Palette: Select item
    Palette->>Handler: item.run()
    Handler->>ChatView: handleNewThread(...)
    Handler->>Store: setOpen(false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 In the meadow of code I hop and play,

Mod+K summons the palette’s ray.
Search and run, threads neatly align,
A rabbit’s cheer for shortcuts divine! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: merging upstream commits while recording proper ancestry.
Description check ✅ Passed The description comprehensively documents what changed, why (upstream merge with fork adaptations), and includes a test plan with verification results.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/upstream-main-2026-04-13

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 size:XXL 1,000+ effective changed lines (test files excluded in mixed PRs). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Apr 12, 2026
@aaditagrawal aaditagrawal force-pushed the sync/upstream-main-2026-04-13 branch from 2d2cce0 to 0123393 Compare April 12, 2026 21:15
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git/Layers/GitCore.ts (1)

921-935: ⚠️ Potential issue | 🟠 Major

Remote-only fetch can leave some tracked upstream refs stale.

Switching this refresh from an explicit upstream ref fetch to plain git fetch <remote> changes behavior: it now only updates refs covered by remote.<name>.fetch. Branches tracking a narrowed/custom upstream ref can keep stale ahead/behind data, and the remote-level cache will then suppress another refresh for ~15s across sibling worktrees.

Please keep the cache key remote-based if you want the coalescing, but preserve the old explicit upstream refspec when the caller has a resolved upstream.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/git/Layers/GitCore.ts` around lines 921 - 935,
fetchRemoteForStatus currently runs git fetch <remote> which only updates refs
covered by remote.<name>.fetch and can leave tracked upstream refs stale; keep
the remote-based cache/coalescing but when the caller has a resolved upstream,
call git fetch with the explicit upstream refspec instead of the plain remote to
ensure that specific upstream ref is refreshed. Update fetchRemoteForStatus (and
its callers) to accept an optional upstream refspec (or detect a resolved
upstream) and, when present, pass ["--git-dir", gitCommonDir, "fetch",
"--quiet", "--no-tags", remoteName, "<refspec>"] (or replace <remote> with
"<remote> <refspec>" as a single fetch target) to executeGit; otherwise keep the
existing remote-only fetch and preserve the timeout and allowNonZeroExit
options.
🧹 Nitpick comments (4)
apps/desktop/src/main.ts (1)

107-107: Centralize desktop app identity to avoid runtime/build drift.

Line 107 correctly splits APP_USER_MODEL_ID by environment, but build config still appears hardcoded to com.t3tools.t3code (scripts/build-desktop-artifact.ts, Lines 493-501 in provided context). Consider using one shared source for app identity across launcher, main process, and build script.

♻️ Proposed follow-up alignment (build config)
-const buildConfig: Record<string, unknown> = {
-  appId: "com.t3tools.t3code",
+const APP_BUNDLE_ID = isDevelopment ? "com.t3tools.t3code.dev" : "com.t3tools.t3code";
+const buildConfig: Record<string, unknown> = {
+  appId: APP_BUNDLE_ID,
   productName,
   artifactName: "T3-Code-${version}-${arch}.${ext}",
   asarUnpack: ["node_modules/@github/copilot*/**/*"],
   directories: {
     buildResources: "apps/desktop/resources",
   },
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main.ts` at line 107, APP_USER_MODEL_ID is
environment-dependent in the main process but the build script still uses the
hardcoded string "com.t3tools.t3code", causing drift; fix by extracting a single
exported constant (e.g., APP_USER_MODEL_ID or APP_ID) into a shared config
module and import that constant wherever the app identity is needed (replace the
hardcoded literal in the build-script logic and replace the inline ternary in
main.ts with the shared constant), ensuring both the launcher/main process and
build tooling read the same value.
package.json (1)

59-61: Update the stale SDK version reference in ClaudeProvider.ts.

The dependency was upgraded to ^0.2.104 but the inline comment at apps/server/src/provider/Layers/ClaudeProvider.ts:441 still references SDK 0.2.77. Update it to reflect the current version to prevent misleading maintenance context.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 59 - 61, The inline version comment in
apps/server/src/provider/Layers/ClaudeProvider.ts that mentions SDK version
"0.2.77" is now stale; update that comment to reference the current dependency
version ^0.2.104 to match package.json. Locate the comment inside the
ClaudeProvider file (near the ClaudeProvider class / relevant provider setup
block) and replace the old version string with "^0.2.104" so maintenance notes
reflect the actual installed SDK.
packages/contracts/src/keybindings.test.ts (1)

50-54: Remove the duplicate commandPalette.toggle assertion.

This case is already covered at Lines 26-30, so the extra block adds noise without increasing coverage.

Proposed cleanup
-    const parsedCommandPalette = yield* decode(KeybindingRule, {
-      key: "mod+k",
-      command: "commandPalette.toggle",
-    });
-    assert.strictEqual(parsedCommandPalette.command, "commandPalette.toggle");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/contracts/src/keybindings.test.ts` around lines 50 - 54, The test
contains a duplicate assertion for the command "commandPalette.toggle" in the
block that decodes KeybindingRule into parsedCommandPalette; remove the
redundant assert.strictEqual(parsedCommandPalette.command,
"commandPalette.toggle") (the decode call and variable parsedCommandPalette can
remain if other assertions exist) because the same assertion is already covered
earlier; locate the decode(KeybindingRule, {...}) call and delete only the
duplicate assertion line referencing parsedCommandPalette.command.
packages/contracts/src/keybindings.ts (1)

30-38: Drop the duplicated "commandPalette.toggle" literal.

It does not change the schema, but it makes the canonical command list harder to scan and easier to let drift.

♻️ Suggested cleanup
 const STATIC_KEYBINDING_COMMANDS = [
   "commandPalette.toggle",
   "terminal.toggle",
   "terminal.split",
   "terminal.new",
   "terminal.close",
   "diff.toggle",
-  "commandPalette.toggle",
   "chat.new",
   "chat.newLocal",
   "editor.openFavorite",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/contracts/src/keybindings.ts` around lines 30 - 38,
STATIC_KEYBINDING_COMMANDS currently contains a duplicate
"commandPalette.toggle" entry; remove the duplicate from the
STATIC_KEYBINDING_COMMANDS array in packages/contracts/src/keybindings.ts so the
list contains each command literal only once (leave the other entries and
ordering intact).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/keybindings.ts`:
- Line 65: Remove the duplicate default keybinding entry for mod+k in the
DEFAULT_KEYBINDINGS array so the same { key: "mod+k", command:
"commandPalette.toggle", when: "!terminalFocus" } isn't declared twice; locate
the duplicate object (the entry shown in keybindings.ts) and delete it (or
deduplicate DEFAULT_KEYBINDINGS) so syncDefaultKeybindingsOnStartup() doesn't
persist a redundant binding.

In `@apps/server/src/provider/Layers/ClaudeAdapter.ts`:
- Around line 1427-1431: The comparison/guard currently uses
lastGoodUsage.usedTokens as the floor which can regress totalProcessedTokens;
change the check to compare accumulatedTotalProcessedTokens against the previous
total (lastGoodUsage.totalProcessedTokens ?? lastGoodUsage.usedTokens) and only
set totalProcessedTokens when accumulatedTotalProcessedTokens is a finite number
greater than that previousTotal; update the conditional around
accumulatedTotalProcessedTokens accordingly in the block that builds the result
(referencing accumulatedTotalProcessedTokens and
lastGoodUsage.totalProcessedTokens/usedTokens).
- Around line 350-358: In maxClaudeContextWindowFromModelUsage, guard the
extracted contextWindow values before passing to Math.max: iterate over
Object.values(modelUsage), coerce or validate that value.contextWindow is a
finite number (e.g., Number.isFinite), skip entries that are missing or
non-finite, and only call Math.max with valid numbers; if no valid contextWindow
is found return undefined so you don't propagate NaN into
context.lastKnownContextWindow (which completeTurn later relies on from the
SDK-cast payload).

In `@apps/web/src/components/Sidebar.tsx`:
- Around line 2146-2165: The global window keydown/keyup handlers that implement
thread traversal and jump hints in this component should be disabled while the
command palette is open: update the handlers (the functions registered on window
in this file that manage thread traversal/jump hints) to early-return when
useCommandPaletteStore.getState().open is true, or wrap their registration so
they are not active while the CommandDialogTrigger opens the palette; reference
CommandDialogTrigger and useCommandPaletteStore.getState().open to locate where
to add the guard.

In `@apps/web/src/keybindings.test.ts`:
- Around line 102-106: The test fixture still includes an unconditional
DEFAULT_BINDINGS entry mapping mod+k to "commandPalette.toggle", which overrides
the new conditional rule; remove or modify the unconditional shortcut object so
there is no plain { shortcut: modShortcut("k"), command: "commandPalette.toggle"
} entry (or replace it with the conditional rule using whenAst:
whenNot(whenIdentifier("terminalFocus"))), ensuring the only binding for mod+k
is the conditional one so resolveShortcutCommand(..., { context: {
terminalFocus: true } }) no longer resolves to "commandPalette.toggle".

In `@apps/web/src/lib/chatThreadActions.ts`:
- Around line 63-67: buildDefaultThreadOptions currently returns envMode:
context.defaultThreadEnvMode so calls like startNewLocalThreadFromContext (and
the similar path at 88-97) can still create worktree drafts; change the code so
the local-thread path forces a true local envMode: set envMode to "local"
(either by hardcoding in buildDefaultThreadOptions when used for local creation
or by adding a new buildLocalThreadOptions helper) and ensure callers
startNewLocalThreadFromContext and the other similar function(s) use this
local-only options object so no worktree metadata is inherited.
- Around line 52-74: startNewThreadInProjectFromContext currently always passes
buildContextualThreadOptions(context) which copies branch/worktree/env from the
active thread/draft even when projectRef is different; change it to detect
whether projectRef matches context.activeThread?.projectRef or
context.activeDraftThread?.projectRef and only use
buildContextualThreadOptions(context) in that case, otherwise call
buildDefaultThreadOptions(context) (ensuring branch/worktree are cleared and
envMode falls back to context.defaultThreadEnvMode) before invoking
context.handleNewThread; update references in startNewThreadInProjectFromContext
to use activeThread, activeDraftThread, buildContextualThreadOptions,
buildDefaultThreadOptions, defaultThreadEnvMode, and handleNewThread.

---

Outside diff comments:
In `@apps/server/src/git/Layers/GitCore.ts`:
- Around line 921-935: fetchRemoteForStatus currently runs git fetch <remote>
which only updates refs covered by remote.<name>.fetch and can leave tracked
upstream refs stale; keep the remote-based cache/coalescing but when the caller
has a resolved upstream, call git fetch with the explicit upstream refspec
instead of the plain remote to ensure that specific upstream ref is refreshed.
Update fetchRemoteForStatus (and its callers) to accept an optional upstream
refspec (or detect a resolved upstream) and, when present, pass ["--git-dir",
gitCommonDir, "fetch", "--quiet", "--no-tags", remoteName, "<refspec>"] (or
replace <remote> with "<remote> <refspec>" as a single fetch target) to
executeGit; otherwise keep the existing remote-only fetch and preserve the
timeout and allowNonZeroExit options.

---

Nitpick comments:
In `@apps/desktop/src/main.ts`:
- Line 107: APP_USER_MODEL_ID is environment-dependent in the main process but
the build script still uses the hardcoded string "com.t3tools.t3code", causing
drift; fix by extracting a single exported constant (e.g., APP_USER_MODEL_ID or
APP_ID) into a shared config module and import that constant wherever the app
identity is needed (replace the hardcoded literal in the build-script logic and
replace the inline ternary in main.ts with the shared constant), ensuring both
the launcher/main process and build tooling read the same value.

In `@package.json`:
- Around line 59-61: The inline version comment in
apps/server/src/provider/Layers/ClaudeProvider.ts that mentions SDK version
"0.2.77" is now stale; update that comment to reference the current dependency
version ^0.2.104 to match package.json. Locate the comment inside the
ClaudeProvider file (near the ClaudeProvider class / relevant provider setup
block) and replace the old version string with "^0.2.104" so maintenance notes
reflect the actual installed SDK.

In `@packages/contracts/src/keybindings.test.ts`:
- Around line 50-54: The test contains a duplicate assertion for the command
"commandPalette.toggle" in the block that decodes KeybindingRule into
parsedCommandPalette; remove the redundant
assert.strictEqual(parsedCommandPalette.command, "commandPalette.toggle") (the
decode call and variable parsedCommandPalette can remain if other assertions
exist) because the same assertion is already covered earlier; locate the
decode(KeybindingRule, {...}) call and delete only the duplicate assertion line
referencing parsedCommandPalette.command.

In `@packages/contracts/src/keybindings.ts`:
- Around line 30-38: STATIC_KEYBINDING_COMMANDS currently contains a duplicate
"commandPalette.toggle" entry; remove the duplicate from the
STATIC_KEYBINDING_COMMANDS array in packages/contracts/src/keybindings.ts so the
list contains each command literal only once (leave the other entries and
ordering intact).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 05a0dc15-0731-467f-b08f-05237f2adb46

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1917b and 2d2cce0.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (33)
  • KEYBINDINGS.md
  • apps/desktop/src/main.ts
  • apps/server/src/git/Layers/GitCore.test.ts
  • apps/server/src/git/Layers/GitCore.ts
  • apps/server/src/keybindings.ts
  • apps/server/src/open.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.test.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.ts
  • apps/web/src/commandPaletteStore.ts
  • apps/web/src/components/ChatView.browser.tsx
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/CommandPalette.logic.test.ts
  • apps/web/src/components/CommandPalette.logic.ts
  • apps/web/src/components/CommandPalette.tsx
  • apps/web/src/components/CommandPaletteResults.tsx
  • apps/web/src/components/Sidebar.logic.test.ts
  • apps/web/src/components/Sidebar.logic.ts
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/ui/command.tsx
  • apps/web/src/components/ui/toast.tsx
  • apps/web/src/composerHandleContext.ts
  • apps/web/src/keybindings.test.ts
  • apps/web/src/lib/chatThreadActions.test.ts
  • apps/web/src/lib/chatThreadActions.ts
  • apps/web/src/lib/threadSort.test.ts
  • apps/web/src/lib/threadSort.ts
  • apps/web/src/routes/__root.tsx
  • apps/web/src/routes/_chat.tsx
  • apps/web/src/types.ts
  • apps/web/vitest.browser.config.ts
  • package.json
  • packages/contracts/src/keybindings.test.ts
  • packages/contracts/src/keybindings.ts
💤 Files with no reviewable changes (1)
  • apps/web/src/components/Sidebar.logic.test.ts

Comment on lines +350 to 358
function maxClaudeContextWindowFromModelUsage(
modelUsage: Record<string, ModelUsage> | undefined,
): number | undefined {
if (!modelUsage) return undefined;

let maxContextWindow: number | undefined;
for (const value of Object.values(modelUsage as Record<string, unknown>)) {
if (!value || typeof value !== "object") {
continue;
}
const contextWindow = (value as { contextWindow?: unknown }).contextWindow;
if (
typeof contextWindow !== "number" ||
!Number.isFinite(contextWindow) ||
contextWindow <= 0
) {
continue;
}
for (const value of Object.values(modelUsage)) {
const contextWindow = value.contextWindow;
maxContextWindow = Math.max(maxContextWindow ?? 0, contextWindow);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

wc -l apps/server/src/provider/Layers/ClaudeAdapter.ts

Repository: aaditagrawal/t3code

Length of output: 116


🏁 Script executed:

# First, let's see the specific lines mentioned in the review
sed -n '340,370p' apps/server/src/provider/Layers/ClaudeAdapter.ts

Repository: aaditagrawal/t3code

Length of output: 920


🏁 Script executed:

# Also check lines around 1401-1431 for the monotonicity issue mentioned in scratchpad
sed -n '1395,1435p' apps/server/src/provider/Layers/ClaudeAdapter.ts

Repository: aaditagrawal/t3code

Length of output: 1930


🏁 Script executed:

# Look at the complete function to understand context better
sed -n '350,380p' apps/server/src/provider/Layers/ClaudeAdapter.ts

Repository: aaditagrawal/t3code

Length of output: 1055


🏁 Script executed:

# Find the ModelUsage type definition
rg -n "type ModelUsage\|interface ModelUsage" apps/server/src/provider/Layers/ClaudeAdapter.ts

Repository: aaditagrawal/t3code

Length of output: 45


🏁 Script executed:

# Check imports to understand where ModelUsage comes from
head -50 apps/server/src/provider/Layers/ClaudeAdapter.ts

Repository: aaditagrawal/t3code

Length of output: 1359


🏁 Script executed:

# Verify if there are any other calls to maxClaudeContextWindowFromModelUsage
rg -n "maxClaudeContextWindowFromModelUsage" apps/server/src/provider/Layers/ClaudeAdapter.ts

Repository: aaditagrawal/t3code

Length of output: 188


🏁 Script executed:

# Check how resultContextWindow is used after being set
sed -n '1401,1415p' apps/server/src/provider/Layers/ClaudeAdapter.ts

Repository: aaditagrawal/t3code

Length of output: 838


Guard contextWindow before feeding it into Math.max.

completeTurn() force-casts the SDK payload at line 1402, so this helper is effectively parsing untrusted runtime data. If any entry is missing or has a non-finite contextWindow, Math.max returns NaN, and since NaN !== undefined, the result gets stored in context.lastKnownContextWindow at line 1404, poisoning downstream token usage snapshots.

🛡️ Proposed fix
 function maxClaudeContextWindowFromModelUsage(
   modelUsage: Record<string, ModelUsage> | undefined,
 ): number | undefined {
   if (!modelUsage) return undefined;
 
   let maxContextWindow: number | undefined;
   for (const value of Object.values(modelUsage)) {
-    const contextWindow = value.contextWindow;
+    const contextWindow =
+      typeof value?.contextWindow === "number" && Number.isFinite(value.contextWindow)
+        ? value.contextWindow
+        : undefined;
+    if (contextWindow === undefined || contextWindow <= 0) {
+      continue;
+    }
     maxContextWindow = Math.max(maxContextWindow ?? 0, contextWindow);
   }
 
   return maxContextWindow;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ClaudeAdapter.ts` around lines 350 - 358, In
maxClaudeContextWindowFromModelUsage, guard the extracted contextWindow values
before passing to Math.max: iterate over Object.values(modelUsage), coerce or
validate that value.contextWindow is a finite number (e.g., Number.isFinite),
skip entries that are missing or non-finite, and only call Math.max with valid
numbers; if no valid contextWindow is found return undefined so you don't
propagate NaN into context.lastKnownContextWindow (which completeTurn later
relies on from the SDK-cast payload).

Comment on lines +1427 to 1431
...(typeof accumulatedTotalProcessedTokens === "number" &&
Number.isFinite(accumulatedTotalProcessedTokens) &&
accumulatedTotalProcessedTokens > lastGoodUsage.usedTokens
? { totalProcessedTokens: accumulatedTotalProcessedTokens }
: {}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Compare against the previous total, not the clamped usedTokens.

Line 1429 can regress totalProcessedTokens. If lastGoodUsage is already { usedTokens: 200000, totalProcessedTokens: 400000 } and the result later normalizes to 250000, this branch overwrites 400000 with 250000 just because 250000 > 200000. Since task_progress and result come from different SDK messages, the safer floor here is lastGoodUsage.totalProcessedTokens ?? lastGoodUsage.usedTokens.

🔧 Proposed fix
+    const previousTotalProcessedTokens =
+      lastGoodUsage?.totalProcessedTokens ?? lastGoodUsage?.usedTokens;
     const usageSnapshot: ThreadTokenUsageSnapshot | undefined = lastGoodUsage
       ? {
           ...lastGoodUsage,
           ...(typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0
             ? { maxTokens }
             : {}),
           ...(typeof accumulatedTotalProcessedTokens === "number" &&
           Number.isFinite(accumulatedTotalProcessedTokens) &&
-          accumulatedTotalProcessedTokens > lastGoodUsage.usedTokens
+          accumulatedTotalProcessedTokens > (previousTotalProcessedTokens ?? 0)
             ? { totalProcessedTokens: accumulatedTotalProcessedTokens }
             : {}),
         }
       : accumulatedSnapshot;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ClaudeAdapter.ts` around lines 1427 - 1431,
The comparison/guard currently uses lastGoodUsage.usedTokens as the floor which
can regress totalProcessedTokens; change the check to compare
accumulatedTotalProcessedTokens against the previous total
(lastGoodUsage.totalProcessedTokens ?? lastGoodUsage.usedTokens) and only set
totalProcessedTokens when accumulatedTotalProcessedTokens is a finite number
greater than that previousTotal; update the conditional around
accumulatedTotalProcessedTokens accordingly in the block that builds the result
(referencing accumulatedTotalProcessedTokens and
lastGoodUsage.totalProcessedTokens/usedTokens).

Comment on lines +52 to +74
function buildContextualThreadOptions(context: ChatThreadActionContext): NewThreadOptions {
return {
branch: context.activeThread?.branch ?? context.activeDraftThread?.branch ?? null,
worktreePath:
context.activeThread?.worktreePath ?? context.activeDraftThread?.worktreePath ?? null,
envMode:
context.activeDraftThread?.envMode ??
(context.activeThread?.worktreePath ? "worktree" : "local"),
};
}

function buildDefaultThreadOptions(context: ChatThreadActionContext): NewThreadOptions {
return {
envMode: context.defaultThreadEnvMode,
};
}

export async function startNewThreadInProjectFromContext(
context: ChatThreadActionContext,
projectRef: ScopedProjectRef,
): Promise<void> {
await context.handleNewThread(projectRef, buildContextualThreadOptions(context));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't reuse thread context when the target project is different.

startNewThreadInProjectFromContext() always forwards buildContextualThreadOptions(context), so choosing project B from the palette still copies branch/worktree/env mode from the active thread or draft in project A. That seeds the new draft with the wrong repo context and bypasses the target project's default env-mode fallback.

Only apply buildContextualThreadOptions() when the explicit projectRef matches the active thread/draft project; otherwise start from default options and clear branch/worktree metadata.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/chatThreadActions.ts` around lines 52 - 74,
startNewThreadInProjectFromContext currently always passes
buildContextualThreadOptions(context) which copies branch/worktree/env from the
active thread/draft even when projectRef is different; change it to detect
whether projectRef matches context.activeThread?.projectRef or
context.activeDraftThread?.projectRef and only use
buildContextualThreadOptions(context) in that case, otherwise call
buildDefaultThreadOptions(context) (ensuring branch/worktree are cleared and
envMode falls back to context.defaultThreadEnvMode) before invoking
context.handleNewThread; update references in startNewThreadInProjectFromContext
to use activeThread, activeDraftThread, buildContextualThreadOptions,
buildDefaultThreadOptions, defaultThreadEnvMode, and handleNewThread.

Comment on lines +63 to +67
function buildDefaultThreadOptions(context: ChatThreadActionContext): NewThreadOptions {
return {
envMode: context.defaultThreadEnvMode,
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

startNewLocalThreadFromContext() does not actually force local mode.

This path delegates to buildDefaultThreadOptions(), which uses context.defaultThreadEnvMode. If the default is "worktree", the "local" helper will still create a worktree draft.

Either hardcode envMode: "local" here (and avoid inheriting worktree metadata), or rename the API so callers do not assume local-thread semantics.

Also applies to: 88-97

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/chatThreadActions.ts` around lines 63 - 67,
buildDefaultThreadOptions currently returns envMode:
context.defaultThreadEnvMode so calls like startNewLocalThreadFromContext (and
the similar path at 88-97) can still create worktree drafts; change the code so
the local-thread path forces a true local envMode: set envMode to "local"
(either by hardcoding in buildDefaultThreadOptions when used for local creation
or by adding a new buildLocalThreadOptions helper) and ensure callers
startNewLocalThreadFromContext and the other similar function(s) use this
local-only options object so no worktree metadata is inherited.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git/Layers/GitCore.ts (1)

921-935: ⚠️ Potential issue | 🟠 Major

Do not treat non-zero git fetch exits as a successful refresh.

With allowNonZeroExit: true, a fetch that exits non-zero still resolves successfully here, and Effect.asVoid makes the cache entry use the success TTL. That breaks the intended failure cooldown path and can leave behind/ahead counts stale for longer after transient fetch failures.

Suggested fix
   return executeGit(
       "GitCore.fetchRemoteForStatus",
       fetchCwd,
       ["--git-dir", gitCommonDir, "fetch", "--quiet", "--no-tags", remoteName],
       {
-        allowNonZeroExit: true,
         timeoutMs: Duration.toMillis(STATUS_UPSTREAM_REFRESH_TIMEOUT),
+        fallbackErrorMessage: `git fetch ${remoteName} failed`,
       },
     ).pipe(Effect.asVoid);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/git/Layers/GitCore.ts` around lines 921 - 935,
fetchRemoteForStatus currently calls executeGit with allowNonZeroExit: true
which treats non-zero git fetch exits as success; change that option so non-zero
exits surface as GitCommandError (e.g., remove allowNonZeroExit or set it to
false) so failures propagate instead of being converted to success by
Effect.asVoid, leaving the cache to use the failure cooldown; keep the same
timeout (STATUS_UPSTREAM_REFRESH_TIMEOUT) and otherwise return the Effect as
before so successful fetches still map to void.
♻️ Duplicate comments (5)
apps/web/src/lib/chatThreadActions.ts (2)

63-67: ⚠️ Potential issue | 🟠 Major

newLocal path should force local mode.

Line 65 uses defaultThreadEnvMode, so startNewLocalThreadFromContext (Line 96) can still create non-local drafts when default is "worktree".

Suggested fix
 function buildDefaultThreadOptions(context: ChatThreadActionContext): NewThreadOptions {
   return {
+    branch: null,
+    worktreePath: null,
     envMode: context.defaultThreadEnvMode,
   };
 }
+
+function buildLocalThreadOptions(): NewThreadOptions {
+  return {
+    branch: null,
+    worktreePath: null,
+    envMode: "local",
+  };
+}
@@
 export async function startNewLocalThreadFromContext(
   context: ChatThreadActionContext,
 ): Promise<boolean> {
@@
-  await context.handleNewThread(projectRef, buildDefaultThreadOptions(context));
+  await context.handleNewThread(projectRef, buildLocalThreadOptions());
   return true;
 }

Also applies to: 88-97

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/chatThreadActions.ts` around lines 63 - 67, The default
builder buildDefaultThreadOptions uses context.defaultThreadEnvMode which allows
non-local drafts; ensure the "newLocal" path forces local mode by overriding
envMode to "local" when creating local threads—either add a dedicated builder
(e.g., buildLocalThreadOptions) or change startNewLocalThreadFromContext to call
buildDefaultThreadOptions but then set options.envMode = "local" (or pass an
explicit envMode override) so newLocal always creates a local draft; update the
analogous creation path referenced around lines 88–97 as well.

69-74: ⚠️ Potential issue | 🟠 Major

Avoid leaking thread context across projects.

Line 73 always applies buildContextualThreadOptions(context). If projectRef points to a different project, this can carry branch/worktree/env from the wrong project into the new draft.

Suggested fix
 export async function startNewThreadInProjectFromContext(
   context: ChatThreadActionContext,
   projectRef: ScopedProjectRef,
 ): Promise<void> {
-  await context.handleNewThread(projectRef, buildContextualThreadOptions(context));
+  const activeProjectRef = context.activeThread
+    ? scopeProjectRef(context.activeThread.environmentId, context.activeThread.projectId)
+    : context.activeDraftThread
+      ? scopeProjectRef(
+          context.activeDraftThread.environmentId,
+          context.activeDraftThread.projectId,
+        )
+      : null;
+
+  const options =
+    activeProjectRef === projectRef
+      ? buildContextualThreadOptions(context)
+      : buildDefaultThreadOptions(context);
+
+  await context.handleNewThread(projectRef, options);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/chatThreadActions.ts` around lines 69 - 74, The function
startNewThreadInProjectFromContext currently always passes
buildContextualThreadOptions(context) which can leak branch/worktree/env from
the current context into a different project; change it to only use the
contextual options when the context refers to the same project (compare
context.projectRef or equivalent) or construct/clean a new ThreadOptions for the
target project that resets project-scoped fields (branch, worktree, env) before
calling context.handleNewThread; update references to
buildContextualThreadOptions, startNewThreadInProjectFromContext, and
handleNewThread to implement this conditional/cleaning behavior.
apps/server/src/provider/Layers/ClaudeAdapter.ts (2)

350-358: ⚠️ Potential issue | 🟡 Minor

Filter invalid contextWindow values before calling Math.max.

This helper still trusts a force-cast SDK payload. A single missing or non-finite contextWindow turns the accumulator into NaN, and Line 1405 then persists that bad value into context.lastKnownContextWindow.

Suggested fix
 function maxClaudeContextWindowFromModelUsage(
   modelUsage: Record<string, ModelUsage> | undefined,
 ): number | undefined {
   if (!modelUsage) return undefined;
 
   let maxContextWindow: number | undefined;
   for (const value of Object.values(modelUsage)) {
-    const contextWindow = value.contextWindow;
+    const contextWindow =
+      typeof value?.contextWindow === "number" && Number.isFinite(value.contextWindow)
+        ? value.contextWindow
+        : undefined;
+    if (contextWindow === undefined || contextWindow <= 0) {
+      continue;
+    }
     maxContextWindow = Math.max(maxContextWindow ?? 0, contextWindow);
   }
 
   return maxContextWindow;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ClaudeAdapter.ts` around lines 350 - 358, In
maxClaudeContextWindowFromModelUsage, guard against invalid contextWindow values
before using Math.max: skip any value.contextWindow that is undefined, null or
not a finite number (use Number.isFinite or equivalent) and only feed valid
finite numbers into Math.max so maxContextWindow cannot become NaN; keep
references to the existing variables/function names
(maxClaudeContextWindowFromModelUsage, maxContextWindow, contextWindow,
ModelUsage) and initialize/compare against 0 when computing the maximum.

1417-1430: ⚠️ Potential issue | 🟡 Minor

Compare against the previous total, not usedTokens.

This merge can still regress totalProcessedTokens. If lastGoodUsage already has a larger totalProcessedTokens than usedTokens, the current guard lets a smaller accumulated total overwrite it.

Suggested fix
+    const previousTotalProcessedTokens =
+      lastGoodUsage?.totalProcessedTokens ?? lastGoodUsage?.usedTokens;
     const usageSnapshot: ThreadTokenUsageSnapshot | undefined = lastGoodUsage
       ? {
           ...lastGoodUsage,
           ...(typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0
             ? { maxTokens }
             : {}),
           ...(typeof accumulatedTotalProcessedTokens === "number" &&
           Number.isFinite(accumulatedTotalProcessedTokens) &&
-          accumulatedTotalProcessedTokens > lastGoodUsage.usedTokens
+          accumulatedTotalProcessedTokens > (previousTotalProcessedTokens ?? 0)
             ? { totalProcessedTokens: accumulatedTotalProcessedTokens }
             : {}),
         }
       : accumulatedSnapshot;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ClaudeAdapter.ts` around lines 1417 - 1430,
The merge incorrectly allows accumulatedTotalProcessedTokens to overwrite a
larger existing totalProcessedTokens because it only compares against
lastGoodUsage.usedTokens; update the conditional that builds usageSnapshot so
that accumulatedTotalProcessedTokens is compared against
lastGoodUsage.totalProcessedTokens when present (fallback to
lastGoodUsage.usedTokens only if totalProcessedTokens is undefined), i.e., in
ClaudeAdapter.ts where accumulatedTotalProcessedTokens, lastGoodUsage
(context.lastKnownTokenUsage) and usageSnapshot are constructed, guard setting
totalProcessedTokens by ensuring accumulatedTotalProcessedTokens >
(lastGoodUsage.totalProcessedTokens ?? lastGoodUsage.usedTokens) before
assigning.
apps/web/src/components/Sidebar.tsx (1)

2887-2914: ⚠️ Potential issue | 🟠 Major

Still gate sidebar window shortcuts while command palette is open.

onWindowKeyDown / onWindowKeyUp still run traversal and jump-hint logic when the palette is open, so navigation can fire behind the dialog. Add an early return guard in both handlers. (Line 2887 and Line 2964 paths.)

🛡️ Suggested patch
+import { useCommandPaletteStore } from "../commandPaletteStore";
@@
     const onWindowKeyDown = (event: globalThis.KeyboardEvent) => {
+      if (useCommandPaletteStore.getState().open) {
+        return;
+      }
       if (shouldIgnoreThreadJumpHintUpdate(event)) {
         return;
       }
@@
     const onWindowKeyUp = (event: globalThis.KeyboardEvent) => {
+      if (useCommandPaletteStore.getState().open) {
+        return;
+      }
       if (shouldIgnoreThreadJumpHintUpdate(event)) {
         return;
       }

Also applies to: 2964-2987

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/Sidebar.tsx` around lines 2887 - 2914, Add an
early-return guard at the top of both onWindowKeyDown and onWindowKeyUp so none
of the traversal or jump-hint logic runs while the command palette is open: call
getCurrentSidebarShortcutContext() (or the existing context getter used in these
handlers) and if its commandPaletteOpen (or equivalent flag) is true, return
immediately; do this before calling shouldIgnoreThreadJumpHintUpdate,
shouldShowThreadJumpHints, setThreadJumpLabelByKey, clearThreadJumpHints,
updateThreadJumpHintsVisibility, etc., so the handlers do nothing while the
palette is open.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/components/ChatView.browser.tsx`:
- Around line 569-579: createProjectlessSnapshot() doesn't fully remove
bootstrap context because mountChatView() still calls buildFixture() which sets
welcome.bootstrapProjectId/bootstrapThreadId to PROJECT_ID/THREAD_ID; update the
snapshot or build helper so the bootstrap fields are cleared. Specifically,
change createProjectlessSnapshot (or modify buildFixture) to override
welcome.bootstrapProjectId and welcome.bootstrapThreadId to undefined/null (or
remove them) in the returned OrchestrationReadModel, and ensure mountChatView()
uses this snapshot path (or accept an overrides parameter in buildFixture and
pass through) so tests run with no bootstrap project/thread context.

In `@apps/web/src/components/CommandPalette.logic.ts`:
- Around line 73-123: buildThreadActionItems currently uses bare projectId and
thread.id which can collide across environments; change lookups and item
identities to be environment-scoped by incorporating thread.environmentId into
keys. Specifically, update logic that reads from projectTitleById (and any
map/population code elsewhere) to use a composite key that includes
environmentId (e.g., `${environmentId}:${projectId}`) when calling
projectTitleById.get, and change the action item value from
`thread:${thread.id}` to include environment (e.g.,
`thread:${thread.environmentId}:${thread.id}`) so items and labels are unique
per environment; adjust searchTerms/title/description handling only if they rely
on the bare id. Use the function and symbol names buildThreadActionItems,
projectTitleById, thread.environmentId, and value to locate and apply the
changes.

In `@apps/web/src/lib/threadSort.ts`:
- Around line 16-18: The function getLatestUserMessageTimestamp currently
returns Number.NEGATIVE_INFINITY whenever thread.latestUserMessageAt is present
but toSortableTimestamp(thread.latestUserMessageAt) fails to parse; change the
logic so you only short-circuit and return when toSortableTimestamp returns a
valid numeric timestamp (non-null/undefined), otherwise fall through to the
existing fallbacks (e.g., examine thread.messages or thread.updatedAt). Update
the branch that references latestUserMessageAt to call toSortableTimestamp once,
test the result for success, and only return that value when valid; if parsing
fails, continue with the rest of the function instead of returning -Infinity.

---

Outside diff comments:
In `@apps/server/src/git/Layers/GitCore.ts`:
- Around line 921-935: fetchRemoteForStatus currently calls executeGit with
allowNonZeroExit: true which treats non-zero git fetch exits as success; change
that option so non-zero exits surface as GitCommandError (e.g., remove
allowNonZeroExit or set it to false) so failures propagate instead of being
converted to success by Effect.asVoid, leaving the cache to use the failure
cooldown; keep the same timeout (STATUS_UPSTREAM_REFRESH_TIMEOUT) and otherwise
return the Effect as before so successful fetches still map to void.

---

Duplicate comments:
In `@apps/server/src/provider/Layers/ClaudeAdapter.ts`:
- Around line 350-358: In maxClaudeContextWindowFromModelUsage, guard against
invalid contextWindow values before using Math.max: skip any value.contextWindow
that is undefined, null or not a finite number (use Number.isFinite or
equivalent) and only feed valid finite numbers into Math.max so maxContextWindow
cannot become NaN; keep references to the existing variables/function names
(maxClaudeContextWindowFromModelUsage, maxContextWindow, contextWindow,
ModelUsage) and initialize/compare against 0 when computing the maximum.
- Around line 1417-1430: The merge incorrectly allows
accumulatedTotalProcessedTokens to overwrite a larger existing
totalProcessedTokens because it only compares against lastGoodUsage.usedTokens;
update the conditional that builds usageSnapshot so that
accumulatedTotalProcessedTokens is compared against
lastGoodUsage.totalProcessedTokens when present (fallback to
lastGoodUsage.usedTokens only if totalProcessedTokens is undefined), i.e., in
ClaudeAdapter.ts where accumulatedTotalProcessedTokens, lastGoodUsage
(context.lastKnownTokenUsage) and usageSnapshot are constructed, guard setting
totalProcessedTokens by ensuring accumulatedTotalProcessedTokens >
(lastGoodUsage.totalProcessedTokens ?? lastGoodUsage.usedTokens) before
assigning.

In `@apps/web/src/components/Sidebar.tsx`:
- Around line 2887-2914: Add an early-return guard at the top of both
onWindowKeyDown and onWindowKeyUp so none of the traversal or jump-hint logic
runs while the command palette is open: call getCurrentSidebarShortcutContext()
(or the existing context getter used in these handlers) and if its
commandPaletteOpen (or equivalent flag) is true, return immediately; do this
before calling shouldIgnoreThreadJumpHintUpdate, shouldShowThreadJumpHints,
setThreadJumpLabelByKey, clearThreadJumpHints, updateThreadJumpHintsVisibility,
etc., so the handlers do nothing while the palette is open.

In `@apps/web/src/lib/chatThreadActions.ts`:
- Around line 63-67: The default builder buildDefaultThreadOptions uses
context.defaultThreadEnvMode which allows non-local drafts; ensure the
"newLocal" path forces local mode by overriding envMode to "local" when creating
local threads—either add a dedicated builder (e.g., buildLocalThreadOptions) or
change startNewLocalThreadFromContext to call buildDefaultThreadOptions but then
set options.envMode = "local" (or pass an explicit envMode override) so newLocal
always creates a local draft; update the analogous creation path referenced
around lines 88–97 as well.
- Around line 69-74: The function startNewThreadInProjectFromContext currently
always passes buildContextualThreadOptions(context) which can leak
branch/worktree/env from the current context into a different project; change it
to only use the contextual options when the context refers to the same project
(compare context.projectRef or equivalent) or construct/clean a new
ThreadOptions for the target project that resets project-scoped fields (branch,
worktree, env) before calling context.handleNewThread; update references to
buildContextualThreadOptions, startNewThreadInProjectFromContext, and
handleNewThread to implement this conditional/cleaning behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bcb3968e-bb7b-444e-bc85-bcbe88be7b44

📥 Commits

Reviewing files that changed from the base of the PR and between 2d2cce0 and 0123393.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (33)
  • KEYBINDINGS.md
  • apps/desktop/src/main.ts
  • apps/server/src/git/Layers/GitCore.test.ts
  • apps/server/src/git/Layers/GitCore.ts
  • apps/server/src/keybindings.ts
  • apps/server/src/open.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.test.ts
  • apps/server/src/provider/Layers/ClaudeAdapter.ts
  • apps/web/src/commandPaletteStore.ts
  • apps/web/src/components/ChatView.browser.tsx
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/CommandPalette.logic.test.ts
  • apps/web/src/components/CommandPalette.logic.ts
  • apps/web/src/components/CommandPalette.tsx
  • apps/web/src/components/CommandPaletteResults.tsx
  • apps/web/src/components/Sidebar.logic.test.ts
  • apps/web/src/components/Sidebar.logic.ts
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/ui/command.tsx
  • apps/web/src/components/ui/toast.tsx
  • apps/web/src/composerHandleContext.ts
  • apps/web/src/keybindings.test.ts
  • apps/web/src/lib/chatThreadActions.test.ts
  • apps/web/src/lib/chatThreadActions.ts
  • apps/web/src/lib/threadSort.test.ts
  • apps/web/src/lib/threadSort.ts
  • apps/web/src/routes/__root.tsx
  • apps/web/src/routes/_chat.tsx
  • apps/web/src/types.ts
  • apps/web/vitest.browser.config.ts
  • package.json
  • packages/contracts/src/keybindings.test.ts
  • packages/contracts/src/keybindings.ts
💤 Files with no reviewable changes (1)
  • apps/web/src/components/Sidebar.logic.test.ts
✅ Files skipped from review due to trivial changes (9)
  • KEYBINDINGS.md
  • apps/web/src/routes/__root.tsx
  • apps/web/src/components/ui/toast.tsx
  • packages/contracts/src/keybindings.test.ts
  • package.json
  • apps/web/src/composerHandleContext.ts
  • apps/web/src/lib/chatThreadActions.test.ts
  • apps/web/src/lib/threadSort.test.ts
  • apps/web/src/commandPaletteStore.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/contracts/src/keybindings.ts
  • apps/server/src/keybindings.ts
  • apps/web/src/keybindings.test.ts
  • apps/web/vitest.browser.config.ts
  • apps/web/src/components/ui/command.tsx
  • apps/web/src/routes/_chat.tsx
  • apps/server/src/provider/Layers/ClaudeAdapter.test.ts
  • apps/web/src/components/CommandPalette.logic.test.ts

Comment on lines +73 to +123
export function buildThreadActionItems(input: {
threads: ReadonlyArray<
Pick<
SidebarThreadSummary,
"archivedAt" | "branch" | "createdAt" | "environmentId" | "id" | "projectId" | "title"
> & {
updatedAt?: string | undefined;
latestUserMessageAt?: string | null;
}
>;
activeThreadId?: Thread["id"];
projectTitleById: ReadonlyMap<Project["id"], string>;
sortOrder: SidebarThreadSortOrder;
icon: ReactNode;
runThread: (thread: Pick<SidebarThreadSummary, "environmentId" | "id">) => Promise<void>;
limit?: number;
}): CommandPaletteActionItem[] {
const sortedThreads = sortThreads(
input.threads.filter((thread) => thread.archivedAt === null),
input.sortOrder,
);
const visibleThreads =
input.limit === undefined ? sortedThreads : sortedThreads.slice(0, input.limit);

return visibleThreads.map((thread) => {
const projectTitle = input.projectTitleById.get(thread.projectId);
const descriptionParts: string[] = [];

if (projectTitle) {
descriptionParts.push(projectTitle);
}
if (thread.branch) {
descriptionParts.push(`#${thread.branch}`);
}
if (thread.id === input.activeThreadId) {
descriptionParts.push("Current thread");
}

return {
kind: "action",
value: `thread:${thread.id}`,
searchTerms: [thread.title, projectTitle ?? "", thread.branch ?? ""],
title: thread.title,
description: descriptionParts.join(" · "),
timestamp: formatRelativeTimeLabel(thread.updatedAt ?? thread.createdAt),
icon: input.icon,
run: async () => {
await input.runThread(thread);
},
};
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use environment-scoped identity for thread search items.

This builder keys project titles by bare projectId and gives each thread item the bare thread.id in value, even though the palette is populated across environments. If two environments reuse ids, project labels can be overwritten and command rows can collide in the list. Please carry an environment-scoped key for both the project-title lookup and the thread item identity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/CommandPalette.logic.ts` around lines 73 - 123,
buildThreadActionItems currently uses bare projectId and thread.id which can
collide across environments; change lookups and item identities to be
environment-scoped by incorporating thread.environmentId into keys.
Specifically, update logic that reads from projectTitleById (and any
map/population code elsewhere) to use a composite key that includes
environmentId (e.g., `${environmentId}:${projectId}`) when calling
projectTitleById.get, and change the action item value from
`thread:${thread.id}` to include environment (e.g.,
`thread:${thread.environmentId}:${thread.id}`) so items and labels are unique
per environment; adjust searchTerms/title/description handling only if they rely
on the bare id. Use the function and symbol names buildThreadActionItems,
projectTitleById, thread.environmentId, and value to locate and apply the
changes.

Comment on lines +16 to +18
function getLatestUserMessageTimestamp(thread: ThreadSortInput): number {
if (thread.latestUserMessageAt) {
return toSortableTimestamp(thread.latestUserMessageAt) ?? Number.NEGATIVE_INFINITY;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fall back when latestUserMessageAt is present but invalid.

A truthy but unparsable latestUserMessageAt currently returns -Infinity, which pushes the thread to the end even if messages or updatedAt are valid. Only short-circuit when parsing succeeds.

Suggested fix
 function getLatestUserMessageTimestamp(thread: ThreadSortInput): number {
   if (thread.latestUserMessageAt) {
-    return toSortableTimestamp(thread.latestUserMessageAt) ?? Number.NEGATIVE_INFINITY;
+    const latestUserMessageTimestamp = toSortableTimestamp(thread.latestUserMessageAt);
+    if (latestUserMessageTimestamp !== null) {
+      return latestUserMessageTimestamp;
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getLatestUserMessageTimestamp(thread: ThreadSortInput): number {
if (thread.latestUserMessageAt) {
return toSortableTimestamp(thread.latestUserMessageAt) ?? Number.NEGATIVE_INFINITY;
function getLatestUserMessageTimestamp(thread: ThreadSortInput): number {
if (thread.latestUserMessageAt) {
const latestUserMessageTimestamp = toSortableTimestamp(thread.latestUserMessageAt);
if (latestUserMessageTimestamp !== null) {
return latestUserMessageTimestamp;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/lib/threadSort.ts` around lines 16 - 18, The function
getLatestUserMessageTimestamp currently returns Number.NEGATIVE_INFINITY
whenever thread.latestUserMessageAt is present but
toSortableTimestamp(thread.latestUserMessageAt) fails to parse; change the logic
so you only short-circuit and return when toSortableTimestamp returns a valid
numeric timestamp (non-null/undefined), otherwise fall through to the existing
fallbacks (e.g., examine thread.messages or thread.updatedAt). Update the branch
that references latestUserMessageAt to call toSortableTimestamp once, test the
result for success, and only return that value when valid; if parsing fails,
continue with the rest of the function instead of returning -Infinity.

Upstream additions:
- fix: Align token usage metrics for both Claude and Codex (pingdotgg#1943)
- fix(web): allow concurrent browser tests to retry ports (pingdotgg#1951)
- fix: quote editor launch args on Windows for paths with spaces (pingdotgg#1805)
- Coalesce status refreshes by remote (pingdotgg#1940)
- chore(desktop): separate dev AppUserModelID on Windows (pingdotgg#1934)
- feat(web): add extensible command palette (pingdotgg#1103)

Fork adaptations:
- Took upstream's extensible command palette (replaces fork's simpler version)
- Took upstream's extracted thread sort logic (Sidebar.logic.ts → lib/threadSort.ts)
- Inline ModelUsage/NonNullableUsage types (not yet re-exported from SDK)
- Updated claude-agent-sdk to 0.2.104
- Made Thread.archivedAt required (matches upstream)
- Removed duplicate commandPalette.toggle keybinding
- Gate sidebar shortcuts when command palette is open (CodeRabbit review)
@aaditagrawal aaditagrawal force-pushed the sync/upstream-main-2026-04-13 branch from 0123393 to b7f2969 Compare April 12, 2026 21:23
@aaditagrawal aaditagrawal merged commit b3bd5c3 into main Apr 12, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ effective changed lines (test files excluded in mixed PRs). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants