Skip to content

sync: merge 10 new upstream commits (v0.0.20)#74

Merged
aaditagrawal merged 14 commits intomainfrom
sync/upstream-2026-04-20
Apr 22, 2026
Merged

sync: merge 10 new upstream commits (v0.0.20)#74
aaditagrawal merged 14 commits intomainfrom
sync/upstream-2026-04-20

Conversation

@aaditagrawal
Copy link
Copy Markdown
Owner

@aaditagrawal aaditagrawal commented Apr 20, 2026

Summary

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

Upstream additions:

Fork adaptations:

  • Took upstream's redesigned model picker (favorites + search)
  • Extended `PROVIDER_ICON_BY_PROVIDER` for all 8 fork providers (Copilot, Cursor, Amp, GeminiCli, Kilo, OpenCode)
  • Removed deleted `codexAppServerManager` (replaced by effect-codex-app-server)
  • Stubbed `fetchCodexUsage` — manager-based readout no longer available
  • Updated `SettingsPanels` imports for new picker module structure
  • Inline `ClaudeSlashCommand` type (not yet re-exported from SDK entry)
  • Extended `modelOptionsByProvider` test fixtures for all 8 providers
  • Preserved fork's CI customizations (ubuntu-24.04 runners, not Blacksmith)
  • Proper ancestry merge: two-parent commit so `git log main..upstream/main` → 0 commits after merge

Test plan

  • `bun typecheck` — 0 errors across all 8 packages
  • `bun run build` — 0 errors across all 7 build targets
  • `bun run fmt` — clean
  • CI passes

Summary by CodeRabbit

  • New Features

    • Model picker: toggle shortcut (Mod+Shift+M), number-key jumps, sidebar, favorites, persistent favorites
    • Fuzzy multi-token model search and keyboard jump labels
    • Home-directory (~) path expansion for user paths
  • UI Improvements

    • Updated model-picker/sidebar visuals: provider icons, “NEW” badges, slimmer scrollbar, improved composer integration
    • Safer global shortcut/modifier handling and more predictable hint behavior
  • Bug Fixes

    • Stability and cleanup fixes around model picker focus, shortcuts, and keybinding handling

EfeDurmaz16 and others added 11 commits April 19, 2026 10:58
…gdotgg#2210)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: = <=>
pingdotgg#2192)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: codex <codex@users.noreply.github.com>
…#2218)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…gdotgg#2224)

Co-authored-by: Julius Marminge <julius0216@outlook.com>
Upstream additions:
- fix(web): restore manual sort drag and keep per-group expand state (pingdotgg#2221)
- fix: Change right panel sheet to be below title bar / action bar (pingdotgg#2224)
- Refactor OpenCode lifecycle and structured output handling (pingdotgg#2218)
- effect-codex-app-server (pingdotgg#1942)
- Redesign model picker with favorites and search (pingdotgg#2153)
- fix(server): prevent probeClaudeCapabilities from wasting API requests (pingdotgg#2192)
- fix(server): handle OpenCode text response format in commit message gen (pingdotgg#2202)
- Devcontainer / IDE updates (pingdotgg#2208)
- Expand leading ~ in Codex home paths before exporting CODEX_HOME (pingdotgg#2210)
- fix(release): use v<semver> tag format for nightly releases (pingdotgg#2186)

Fork adaptations:
- Took upstream's redesigned model picker with favorites and search
- Removed deleted codexAppServerManager (replaced by effect-codex-app-server)
- Stubbed fetchCodexUsage (manager-based readout no longer available)
- Extended PROVIDER_ICON_BY_PROVIDER for all 8 fork providers
- Extended modelOptionsByProvider test fixtures for all 8 providers
- Inline ClaudeSlashCommand type (not yet re-exported from SDK)
- Updated SettingsPanels imports for new picker module structure
- Preserved fork's CI customizations (ubuntu-24.04 not Blacksmith)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Warning

Rate limit exceeded

@aaditagrawal has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 57 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 6 minutes and 57 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: 7b28c2f4-e5e1-4a7d-bf32-04ce19492dc1

📥 Commits

Reviewing files that changed from the base of the PR and between 68bffc1 and 1879cc9.

⛔ Files ignored due to path filters (1)
  • .claude/scheduled_tasks.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • .claude/settings.json
  • .github/workflows/release.yml
  • apps/server/src/provider/Layers/CodexAdapter.test.ts
  • apps/server/src/provider/Layers/CodexAdapter.ts
  • apps/server/src/provider/Layers/CodexProvider.ts
  • apps/server/src/provider/Layers/EventNdjsonLogger.test.ts
  • apps/server/src/provider/Layers/EventNdjsonLogger.ts
  • apps/server/src/provider/Layers/ProviderRegistry.test.ts
  • apps/web/src/components/Icons.tsx
  • apps/web/src/components/chat/ModelPickerContent.tsx
  • apps/web/src/components/chat/ModelPickerSidebar.tsx
  • apps/web/src/components/chat/ProviderModelPicker.browser.tsx
  • apps/web/src/hooks/useSettings.ts
  • packages/contracts/src/providerRuntime.ts
📝 Walkthrough

Walkthrough

Replaces legacy Codex manager/CLI probing with per-session Codex runtimes, introduces an Effect-based OpenCode runtime service, reworks provider adapters/registry, adds model-picker sidebar/search UI with new keybindings and modifier-state store, and introduces path/JSON utilities plus many test updates and CI/devcontainer tweaks.

Changes

Cohort / File(s) Summary
Codex removal & migration
apps/server/src/codexAppServerManager.*, apps/server/src/provider/codexAppServer.ts, apps/server/src/provider/codexAccount.ts, apps/server/src/provider/codexCliVersion.ts
Deleted legacy Codex manager and CLI probing/account/version helpers and their exports; removed many related tests.
Codex runtime & adapter
apps/server/src/provider/Layers/CodexSessionRuntime.ts, apps/server/src/provider/Layers/CodexAdapter.*, apps/server/src/provider/Layers/CodexProvider.ts, apps/server/src/provider/CodexDeveloperInstructions.ts, apps/server/src/provider/Layers/CodexSessionRuntime.test.ts
Added typed per-session CodexSessionRuntime, new developer-instructions constants, refactored adapter/provider to use runtime factories, updated exports and tests; simplified fetchCodexUsage.
OpenCode runtime & consumers
apps/server/src/provider/opencodeRuntime.ts, apps/server/src/provider/Layers/OpenCodeProvider.*, apps/server/src/provider/Layers/OpenCodeAdapter.*, apps/server/src/git/Layers/OpenCodeTextGeneration.*, apps/server/src/provider/opencodeRuntime.test.ts, apps/server/src/provider/Layers/OpenCodeProvider.test.ts, apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
Replaced imperative OpenCode helpers with an Effect-based OpenCodeRuntime service/layer, changed process lifecycle to scope-managed effects, rewired providers/adapters to use runtime, improved error typing and inventory→model inference, and updated tests to use injected layer doubles.
Shared utilities
apps/server/src/pathExpansion.ts, apps/server/src/git/Utils.ts, apps/server/src/pathExpansion.test.ts
Added expandHomePath() and extractJsonObject() utilities with tests; callers updated to use them (e.g., CODEX_HOME expansion, Cursor JSON extraction).
Git / text-generation tweaks
apps/server/src/git/Layers/CodexTextGeneration.ts, apps/server/src/git/Layers/CursorTextGeneration.ts
Removed model-capability normalization in Codex text generation, adjusted fastMode/service_tier logic, and moved Cursor JSON extraction to shared util.
Provider registry & Claude probe
apps/server/src/provider/Layers/ProviderRegistry.ts, apps/server/src/provider/Layers/ClaudeProvider.ts
Added OpenCodeRuntimeLive to ProviderRegistry composition; changed Claude probe to use a never-yielding prompt generator with abort handling and tightened slash-command typing.
Model picker UI & search
apps/web/src/components/chat/ModelPickerContent.tsx, .../ModelPickerSidebar.tsx, .../ModelListRow.tsx, .../ProviderModelPicker.tsx, .../modelPickerSearch.ts, .../modelPickerModelHighlights.ts, many related tests (ProviderModelPicker.browser.tsx, etc.)
Replaced menu-based model picker with a sidebar/search-based picker: favorites, fuzzy search scoring, jump-key navigation, new components and tests; removed old model menu variant and adjusted composer integration.
Keybindings & modifier state
apps/web/src/keybindings.ts, apps/web/src/shortcutModifierState.ts, apps/web/src/modelPickerOpenState.ts, related tests
Added model-picker keybindings and jump helpers, introduced a shortcut-modifier Zustand store and sync helpers, and added global model-picker open state.
Chat/composer & shortcuts integration
apps/web/src/components/chat/ChatComposer.tsx, apps/web/src/components/ChatView.*, apps/web/src/components/ComposerCommandMenu.tsx, apps/web/src/components/AppSidebarLayout.tsx, apps/web/src/components/chat/ModelPicker*
Extended ChatComposer handle and props for model picker control, wired chat view and composer to new keybindings/context, changed /model trigger behavior to open picker (no inline model selection).
Sidebar, project keys & UI persistence
apps/web/src/components/Sidebar.tsx, apps/web/src/logicalProject.ts, apps/web/src/uiStateStore.ts, apps/web/src/hooks/useHandleNewThread.ts, tests
Switched to logical project ordering keys, persisted collapsed project cwds, exported hydrate/persist helpers, and updated sync/order logic and tests.
OpenCode/Codex adapter tests & harness updates
apps/server/src/provider/Layers/*.test.ts (multiple)
Updated many tests to replace manager-based mocks with runtime-layer test doubles and Scope/finalizer-driven lifecycle assertions.
Frontend UI tweaks
apps/web/src/components/.../Icons.tsx, .../combobox.tsx, .../scroll-area.tsx, apps/web/src/index.css, apps/web/src/rightPanelLayout.ts
Icon theming/className changes, added GithubCopilotIcon, combobox/scroll-area class tweaks, model-picker scrollbar CSS, and right-panel sheet class adjustments.
Server wiring & misc
apps/server/src/server.ts, apps/server/package.json
Added NetService.layer to server runtime dependencies; added effect-codex-app-server as a devDependency.
Devcontainer, editor, CI
.devcontainer/devcontainer.json, .oxfmtrc.json, .vscode/settings.json, .github/workflows/release.yml
Pinned devcontainer features (git, bun pinned, node pinned), changed Python version and feature install order, added trailing-comma override, renamed TypeScript SDK setting, and added scheduled release check_changes job gating preflight.
Small tests & fixtures
apps/desktop/src/clientPersistence.test.ts, apps/web/src/localApi.test.ts, apps/web/src/keybindings.test.ts, etc.
Updated fixtures to include favorites: [], added/adjusted keybinding tests, and broadened/assertion updates across many tests.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

"I hopped through runtimes, nose a-gleam,
swapped managers for scoped Effects in a dream.
A sidebar of models, favorites in tow,
shortcuts that rattle, and search set to go.
🐰✨"

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

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ effective changed lines (test files excluded in mixed PRs). labels Apr 20, 2026
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: 14

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/provider/Layers/ClaudeProvider.ts (1)

688-719: ⚠️ Potential issue | 🟡 Minor

Normalize the Claude binary path before SDK probes.

runClaudeCommand normalizes blank settings with .trim() || "claude" at line 561, but the SDK probe calls at lines 690 and 718 pass the raw claudeSettings.binaryPath. With default blank or whitespace-only settings, slash-command and subscription probing will receive an invalid path, causing silent failures while the CLI health check succeeds.

Suggested fix
+  const claudeBinaryPath = claudeSettings.binaryPath.trim() || "claude";
+
   const slashCommands =
     (resolveSlashCommands
-      ? yield* resolveSlashCommands(claudeSettings.binaryPath).pipe(
+      ? yield* resolveSlashCommands(claudeBinaryPath).pipe(
           Effect.orElseSucceed(() => undefined),
         )
       : undefined) ?? [];
@@
   if (!subscriptionType && resolveSubscriptionType) {
-    subscriptionType = yield* resolveSubscriptionType(claudeSettings.binaryPath);
+    subscriptionType = yield* resolveSubscriptionType(claudeBinaryPath);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/ClaudeProvider.ts` around lines 688 - 719,
The slash-command and SDK probe calls use the raw claudeSettings.binaryPath
which can be blank/whitespace and cause silent probe failures; normalize the
path first (e.g., const normalizedBinary = claudeSettings.binaryPath.trim() ||
"claude") and use normalizedBinary when calling resolveSlashCommands,
runClaudeCommand (or any helper that probes the CLI), and
resolveSubscriptionType so all probes receive the same valid binary path (update
references around resolveSlashCommands, slashCommands,
authProbe/runClaudeCommand, and resolveSubscriptionType).
🧹 Nitpick comments (5)
apps/web/src/components/CommandPalette.logic.ts (1)

154-173: Prefer object spread over Object.assign for readability.

The refactor is functionally correct, but mixing Object.assign with as const is harder to read than a plain object literal with conditional spreads, and it yields a less precise intersection type instead of a single inferred object type. The empty-string defaults on Lines 158 and delimiter on Line 160 also use backticks with no interpolation — regular string literals would be clearer (and your formatter may eventually rewrite them).

♻️ Suggested refactor
-    return Object.assign(
-      {
-        kind: "action" as const,
-        value: `thread:${thread.id}`,
-        searchTerms: [thread.title, projectTitle ?? ``, thread.branch ?? ``],
-        title: thread.title,
-        description: descriptionParts.join(` · `),
-        timestamp: formatRelativeTimeLabel(
-          thread.latestUserMessageAt ?? thread.updatedAt ?? thread.createdAt,
-        ),
-        icon: input.icon,
-      },
-      leadingContent ? { titleLeadingContent: leadingContent } : {},
-      trailingContent ? { titleTrailingContent: trailingContent } : {},
-      {
-        run: async () => {
-          await input.runThread(thread);
-        },
-      },
-    );
+    return {
+      kind: "action" as const,
+      value: `thread:${thread.id}`,
+      searchTerms: [thread.title, projectTitle ?? "", thread.branch ?? ""],
+      title: thread.title,
+      description: descriptionParts.join(" · "),
+      timestamp: formatRelativeTimeLabel(
+        thread.latestUserMessageAt ?? thread.updatedAt ?? thread.createdAt,
+      ),
+      icon: input.icon,
+      ...(leadingContent ? { titleLeadingContent: leadingContent } : {}),
+      ...(trailingContent ? { titleTrailingContent: trailingContent } : {}),
+      run: async () => {
+        await input.runThread(thread);
+      },
+    };
🤖 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 154 - 173,
Replace the Object.assign return with a single object literal using spread
syntax and conditional spreads (e.g., {...(leadingContent && {
titleLeadingContent: leadingContent })}) to improve readability and preserve a
single inferred object type; keep the "kind: 'action' as const" assertion,
compute searchTerms using normal string literals for defaults ('' instead of ``)
and use a normal string for the delimiter in descriptionParts.join(' · ');
ensure the timestamp still calls
formatRelativeTimeLabel(thread.latestUserMessageAt ?? thread.updatedAt ??
thread.createdAt) and the run function still calls await input.runThread(thread)
and include input.icon and other fields unchanged.
apps/server/src/keybindings.test.ts (1)

195-197: Consider asserting the when gating too.

The new assertions verify the key for modelPicker.* defaults but not that modelPicker.jump.* are gated by when: "modelPickerOpen" (and modelPicker.toggle by !terminalFocus). Since modelPicker.jump.1 and thread.jump.1 share mod+1, the when clause is the actual disambiguator, and a regression on it would silently break the UX without failing this test.

♻️ Proposed extension
+      const defaultsByCommandFull = new Map(
+        DEFAULT_KEYBINDINGS.map((binding) => [binding.command, binding] as const),
+      );
+      assert.equal(defaultsByCommandFull.get("modelPicker.toggle")?.when, "!terminalFocus");
+      assert.equal(defaultsByCommandFull.get("modelPicker.jump.1")?.when, "modelPickerOpen");
+      assert.equal(defaultsByCommandFull.get("modelPicker.jump.9")?.when, "modelPickerOpen");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/keybindings.test.ts` around lines 195 - 197, The test
currently asserts defaultsByCommand.get("modelPicker.toggle"),
"modelPicker.jump.1", and "modelPicker.jump.9" only for their key values; add
assertions that verify the corresponding `when` gating as well — e.g. assert
that the entry for "modelPicker.toggle" has when === "!terminalFocus" and that
entries "modelPicker.jump.1" through "modelPicker.jump.9" have when ===
"modelPickerOpen" — so that defaultsByCommand (and any lookups used in the test)
validate the `when` field and prevent regressions/conflicts with similar
bindings like "thread.jump.1".
apps/web/src/shortcutModifierState.test.ts (1)

23-146: Reset modifier state at the suite boundary.

These tests mutate module-level state. A beforeEach/afterEach reset makes the suite order-independent and protects future tests from leaked modifier state.

Suggested isolation hook
-import { describe, expect, it } from "vitest";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
@@
 describe("shortcutModifierState", () => {
+  beforeEach(() => {
+    clearShortcutModifierState();
+  });
+
+  afterEach(() => {
+    clearShortcutModifierState();
+  });
+
   it("compares modifier states by value", () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/shortcutModifierState.test.ts` around lines 23 - 146, Tests
mutate module-level shortcut modifier state and lack isolation; add suite-level
hooks to reset state before and/or after each test by calling
clearShortcutModifierState inside a beforeEach (and optionally afterEach) within
the describe("shortcutModifierState") block so each it() starts with a clean
state; locate where tests reference readShortcutModifierState,
setShortcutModifierState, syncShortcutModifierStateFromKeyboardEvent and insert
the clearShortcutModifierState calls in beforeEach/afterEach to make the suite
order-independent.
apps/web/src/keybindings.test.ts (1)

410-422: Cover the inverse hint condition for shared jump shortcuts.

Since thread.jump.* and modelPicker.jump.* share the same shortcuts, also assert thread hints stay hidden while modelPickerOpen is active.

Suggested assertion
     assert.isTrue(
       shouldShowModelPickerJumpHints(event({ metaKey: true }), DEFAULT_BINDINGS, {
         platform: "MacIntel",
         context: { modelPickerOpen: true },
       }),
     );
+    assert.isFalse(
+      shouldShowThreadJumpHints(event({ metaKey: true }), DEFAULT_BINDINGS, {
+        platform: "MacIntel",
+        context: { modelPickerOpen: true },
+      }),
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/keybindings.test.ts` around lines 410 - 422, Add an assertion to
cover the inverse condition: when modelPickerOpen is true, verify that thread
jump hints remain hidden. In the same test that calls
shouldShowModelPickerJumpHints, also call shouldShowThreadJumpHints (the thread
hint visibility helper) with the same event(DEFAULT_BINDINGS) and context {
platform: "MacIntel", context: { modelPickerOpen: true } } and assert it returns
false so shared shortcuts do not show thread hints while the model picker is
active.
apps/web/src/components/ChatView.browser.tsx (1)

5574-5577: Assert the composer draft is cleared after selecting /model.

This currently checks the picker button text, but a regression could leave /mod in the composer and still pass. Add a direct draft/editor assertion so the slash-command replacement behavior is covered.

Suggested test tightening
       await vi.waitFor(() => {
         expect(document.querySelector(".model-picker-list")).not.toBeNull();
-        expect(findComposerProviderModelPicker()?.textContent).not.toContain("/model");
+        const providerModelPicker = findComposerProviderModelPicker();
+        expect(providerModelPicker).not.toBeNull();
+        expect(providerModelPicker!.textContent).not.toContain("/model");
+        expect(useComposerDraftStore.getState().draftsByThreadKey[THREAD_KEY]?.prompt ?? "").toBe(
+          "",
+        );
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/ChatView.browser.tsx` around lines 5574 - 5577, The
test currently verifies the model picker UI via
document.querySelector(".model-picker-list") and
findComposerProviderModelPicker(), but doesn't assert the composer draft itself;
update the test inside the vi.waitFor callback to also locate the composer text
input/editor (e.g., the composer input element used elsewhere in tests) and
assert its value/textContent no longer contains "/model" and is cleared (or
equals the expected post-replacement string). Reference the existing helpers
used in this file (findComposerProviderModelPicker() and the composer
input/editor selector used by other tests) to locate the composer element and
add the direct assertion that the draft is cleared after selecting "/model".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 8-9: The scheduled workflow uses GITHUB_REF_NAME (branch) to
derive raw and then validates semver, but scheduled runs set the event to
"schedule" and have a branch like "main", causing semver validation to fail; fix
by detecting scheduled runs and injecting an explicit version branch (e.g., set
raw = "nightly" or "scheduled") before the semver validation step: update the
workflow expression that sets raw (the variable named raw derived from
GITHUB_REF_NAME/GITHUB_REF_NAME) to check github.event_name == 'schedule' (or
compare to the schedule trigger) and assign a stable string like "nightly" for
scheduled runs so the subsequent semver validation logic (the validation step
that currently rejects non-semver raw values) accepts scheduled runs.

In @.vscode/settings.json:
- Line 8: Replace the invalid VS Code setting key "js/ts.tsdk.path" with the
correct key "typescript.tsdk" while keeping the value
"node_modules/typescript/lib"; locate the entry that currently reads
"js/ts.tsdk.path" and rename the key to "typescript.tsdk" so VS Code will use
the workspace TypeScript SDK.

In `@apps/server/src/provider/Layers/CodexAdapter.test.ts`:
- Around line 1030-1033: The test in CodexAdapter.test.ts currently asserts the
native log prefix mistakenly as "NTIVE"; update the assertion to validate the
payload message directly instead of the log prefix: after reading threadLogPath
into contents (variable contents), replace the regex /NTIVE: .*"message":"native
flush test"/ with a regex that only matches the JSON payload message such as
/"message":"native flush test"/ so the test verifies flush behavior rather than
the log prefix.

In `@apps/server/src/provider/Layers/CodexAdapter.ts`:
- Around line 1401-1416: The event consumer fiber (eventFiber) is being forked
into the temporary scope created by startSession (via Effect.scoped) using
Effect.forkChild, so when startSession's scope closes the fiber is interrupted
and runtime.events are dropped; change the fork target to the persistent session
scope (sessionScope) instead of forking as a child of the temporary scope—i.e.,
when creating the fiber from Stream.runForEach(runtime.events,
...)/.pipe(Effect.forkChild) ensure you fork into sessionScope (the session's
persistent scope) so the event pump lives for the full session lifetime rather
than being tied to startSession's ephemeral scope.

In `@apps/server/src/provider/Layers/CodexProvider.ts`:
- Around line 389-394: The probe call in CodexProvider is using process.cwd()
which ignores the configured server workspace; update CodexProviderLive to read
ServerConfig (serverConfig.cwd) and pass that cwd into the probe call (used when
building the options for checkCodexProviderStatus / probe), replacing
process.cwd() with the injected serverConfig.cwd so the probe (function probe
and the checkCodexProviderStatus flow that produces probeResult) runs against
the configured workspace while preserving the existing timeout
(PROVIDER_PROBE_TIMEOUT_MS) and other parameters (binaryPath, homePath,
customModels).
- Around line 218-239: The initialize call is using a hardcoded version
("0.1.0") instead of the helper; replace the inline clientInfo object passed to
client.request("initialize", ...) with the values produced by
buildCodexInitializeParams() (or merge its returned params) so the
clientInfo.version comes from packageJson via buildCodexInitializeParams; locate
the initialization block around the clientContext/CodexClient.layerCommand and
update the initialize variable creation to use buildCodexInitializeParams()
output rather than the hardcoded literal.

In `@apps/server/src/provider/Layers/CodexSessionRuntime.ts`:
- Around line 313-340: The code in buildTurnStartParams is dropping label-only
user-input options (it rejects options when description is missing), causing
valid Codex user-input requests to be suppressed; update the logic that builds
turnInput (inside buildTurnStartParams) to include option objects even when
description is absent—ensure V2TurnStartParams__UserInput entries preserve
label-only options by setting description to undefined or omitting it rather
than filtering the option out, and keep the existing handling for attachments
and text so label-only options are pushed into turnInput the same way other
inputs are.

In `@apps/server/src/provider/Layers/OpenCodeAdapter.test.ts`:
- Around line 79-99: The test double connectToOpenCodeServer currently always
registers a finalizer that mutates runtimeMock.state (closeCalls/closeError),
which makes configured-server sessions (when serverUrl is provided) incorrectly
exercise finalizer failure paths; change connectToOpenCodeServer to only add the
finalizer when serverUrl is not provided (i.e., local/test server path) so
configured external connections return without owning lifecycle, and move any
test that simulates finalizer throws (stopAll close-error coverage) into the
layer or test that calls connectToOpenCodeServer with no serverUrl; reference
connectToOpenCodeServer, runtimeMock.state.closeCalls, and
runtimeMock.state.closeError when making this conditional change.

In `@apps/server/src/provider/opencodeRuntime.ts`:
- Around line 434-440: The code currently masks failures by converting
child.exitCode failures into 0 via Effect.orElseSucceed(() => 0); update the
return so callers can distinguish real success from errors: either remove the
orElseSucceed wrapper and make exitCode an errorful Effect<number,
OpenCodeRuntimeError> (update OpenCodeServerProcess/OpenCodeServerConnection
types accordingly) so failures surface, or, if keeping a successful-effect
signature, replace the mask with a non-colliding sentinel (e.g., -1) and ensure
failures are logged/annotated; locate the return that references child.exitCode
and Effect.orElseSucceed and adjust the behavior and types consistently.

In `@apps/web/src/components/chat/ModelPickerContent.tsx`:
- Around line 330-337: The handler is incorrectly using
targetModelKey.split(":"), which breaks slugs that contain colons; update the
parsing of targetModelKey (used where modelJumpModelKeys and jumpIndex are
referenced and in other handlers around lines noted) to split only on the first
":"—e.g., find the first colon index and take the substring before as provider
and the remainder as slug—then call handleModelSelect(slug, provider);
alternatively centralize encode/decode logic for keys (provider:slug) to a
single helper used by these handlers to avoid truncation bugs (refer to
ProviderKind, targetModelKey, modelJumpModelKeys, jumpIndex, and
handleModelSelect).

In `@apps/web/src/components/chat/ModelPickerSidebar.tsx`:
- Around line 156-207: Remove the hardcoded "coming soon" Gemini and Github
Copilot UI blocks in ModelPickerSidebar.tsx: delete the two
Tooltip/TooltipTrigger blocks that render buttons with
data-model-picker-provider="gemini-coming-soon" and "github-copilot-coming-soon"
(the blocks that include Gemini, GithubCopilotIcon, SOON_BADGE_CLASS and
Clock3Icon and corresponding TooltipPopup using PICKER_TOOLTIP_SIDE and
PICKER_TOOLTIP_CLASS). This eliminates duplicate entries since
AVAILABLE_PROVIDER_OPTIONS now includes copilot and geminiCli and those
providers are rendered elsewhere.

In `@apps/web/src/components/Icons.tsx`:
- Around line 22-27: The CursorIcon component is forwarding a non-SVG prop
`monochrome` to the <svg> because Icon props aren't destructured; update
CursorIcon (and other icons that follow this pattern) to strip `monochrome` from
the props before spreading to the svg—e.g., destructure { className, monochrome,
...props } in the CursorIcon function signature or remove monochrome from the
object prior to {...props} so the svg doesn't receive a non-standard DOM
attribute (follow the same approach used by ClaudeAI).

In `@apps/web/src/environments/runtime/service.ts`:
- Around line 473-480: syncProjectUiFromStore builds logicalKey using
getClientSettings(), so UI keys go stale when grouping settings change; add a
settings change listener that calls syncProjectUiFromStore whenever
sidebarProjectGroupingMode or sidebarProjectGroupingOverrides change (or re-run
the same sync logic) to re-compute logical IDs and call
useUiStateStore.getState().syncProjects again; locate this near the runtime
service initialization where selectProjectsAcrossEnvironments,
getClientSettings, deriveLogicalProjectKeyFromSettings, and
syncProjectUiFromStore are defined and wire the listener to the settings store
or event emitter so changes immediately trigger re-sync.

In `@apps/web/src/hooks/useSettings.ts`:
- Around line 134-140: getClientSettings() currently returns the in-memory
snapshot (getClientSettingsSnapshot()) and can return DEFAULT_CLIENT_SETTINGS if
hydration hasn't started; change the API so there are two clear accessors: keep
getClientSettingsSnapshot() as the synchronous snapshot-only accessor, and make
getClientSettings() an async/hydrating accessor that ensures persisted settings
are loaded before returning (implement an ensureClientSettingsHydrated() or
call/await subscribeClientSettings() startup/hydration flow inside
getClientSettings()), and update callers: runtime init code should await
getClientSettings(), while synchronous callers that truly need a snapshot should
call getClientSettingsSnapshot(); update useSettings() subscribers to continue
starting hydration as before.

---

Outside diff comments:
In `@apps/server/src/provider/Layers/ClaudeProvider.ts`:
- Around line 688-719: The slash-command and SDK probe calls use the raw
claudeSettings.binaryPath which can be blank/whitespace and cause silent probe
failures; normalize the path first (e.g., const normalizedBinary =
claudeSettings.binaryPath.trim() || "claude") and use normalizedBinary when
calling resolveSlashCommands, runClaudeCommand (or any helper that probes the
CLI), and resolveSubscriptionType so all probes receive the same valid binary
path (update references around resolveSlashCommands, slashCommands,
authProbe/runClaudeCommand, and resolveSubscriptionType).

---

Nitpick comments:
In `@apps/server/src/keybindings.test.ts`:
- Around line 195-197: The test currently asserts
defaultsByCommand.get("modelPicker.toggle"), "modelPicker.jump.1", and
"modelPicker.jump.9" only for their key values; add assertions that verify the
corresponding `when` gating as well — e.g. assert that the entry for
"modelPicker.toggle" has when === "!terminalFocus" and that entries
"modelPicker.jump.1" through "modelPicker.jump.9" have when ===
"modelPickerOpen" — so that defaultsByCommand (and any lookups used in the test)
validate the `when` field and prevent regressions/conflicts with similar
bindings like "thread.jump.1".

In `@apps/web/src/components/ChatView.browser.tsx`:
- Around line 5574-5577: The test currently verifies the model picker UI via
document.querySelector(".model-picker-list") and
findComposerProviderModelPicker(), but doesn't assert the composer draft itself;
update the test inside the vi.waitFor callback to also locate the composer text
input/editor (e.g., the composer input element used elsewhere in tests) and
assert its value/textContent no longer contains "/model" and is cleared (or
equals the expected post-replacement string). Reference the existing helpers
used in this file (findComposerProviderModelPicker() and the composer
input/editor selector used by other tests) to locate the composer element and
add the direct assertion that the draft is cleared after selecting "/model".

In `@apps/web/src/components/CommandPalette.logic.ts`:
- Around line 154-173: Replace the Object.assign return with a single object
literal using spread syntax and conditional spreads (e.g., {...(leadingContent
&& { titleLeadingContent: leadingContent })}) to improve readability and
preserve a single inferred object type; keep the "kind: 'action' as const"
assertion, compute searchTerms using normal string literals for defaults (''
instead of ``) and use a normal string for the delimiter in
descriptionParts.join(' · '); ensure the timestamp still calls
formatRelativeTimeLabel(thread.latestUserMessageAt ?? thread.updatedAt ??
thread.createdAt) and the run function still calls await input.runThread(thread)
and include input.icon and other fields unchanged.

In `@apps/web/src/keybindings.test.ts`:
- Around line 410-422: Add an assertion to cover the inverse condition: when
modelPickerOpen is true, verify that thread jump hints remain hidden. In the
same test that calls shouldShowModelPickerJumpHints, also call
shouldShowThreadJumpHints (the thread hint visibility helper) with the same
event(DEFAULT_BINDINGS) and context { platform: "MacIntel", context: {
modelPickerOpen: true } } and assert it returns false so shared shortcuts do not
show thread hints while the model picker is active.

In `@apps/web/src/shortcutModifierState.test.ts`:
- Around line 23-146: Tests mutate module-level shortcut modifier state and lack
isolation; add suite-level hooks to reset state before and/or after each test by
calling clearShortcutModifierState inside a beforeEach (and optionally
afterEach) within the describe("shortcutModifierState") block so each it()
starts with a clean state; locate where tests reference
readShortcutModifierState, setShortcutModifierState,
syncShortcutModifierStateFromKeyboardEvent and insert the
clearShortcutModifierState calls in beforeEach/afterEach to make the suite
order-independent.
🪄 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: e5da1bb1-77e4-45e2-acc9-99b2d1e8ef41

📥 Commits

Reviewing files that changed from the base of the PR and between 26ca6ec and 78e42ab.

⛔ Files ignored due to path filters (5)
  • .claude/scheduled_tasks.lock is excluded by !**/*.lock
  • bun.lock is excluded by !**/*.lock
  • packages/effect-codex-app-server/src/_generated/meta.gen.ts is excluded by !**/_generated/**
  • packages/effect-codex-app-server/src/_generated/namespaces.gen.ts is excluded by !**/_generated/**
  • packages/effect-codex-app-server/src/_generated/schema.gen.ts is excluded by !**/_generated/**
📒 Files selected for processing (105)
  • .devcontainer/devcontainer.json
  • .github/workflows/release.yml
  • .oxfmtrc.json
  • .vscode/settings.json
  • apps/desktop/src/clientPersistence.test.ts
  • apps/server/package.json
  • apps/server/src/codexAppServerManager.test.ts
  • apps/server/src/codexAppServerManager.ts
  • apps/server/src/git/Layers/CodexTextGeneration.ts
  • apps/server/src/git/Layers/CursorTextGeneration.ts
  • apps/server/src/git/Layers/OpenCodeTextGeneration.test.ts
  • apps/server/src/git/Layers/OpenCodeTextGeneration.ts
  • apps/server/src/git/Utils.ts
  • apps/server/src/keybindings.test.ts
  • apps/server/src/keybindings.ts
  • apps/server/src/pathExpansion.test.ts
  • apps/server/src/pathExpansion.ts
  • apps/server/src/provider/CodexDeveloperInstructions.ts
  • apps/server/src/provider/Layers/ClaudeProvider.ts
  • apps/server/src/provider/Layers/CodexAdapter.test.ts
  • apps/server/src/provider/Layers/CodexAdapter.ts
  • apps/server/src/provider/Layers/CodexProvider.ts
  • apps/server/src/provider/Layers/CodexSessionRuntime.test.ts
  • apps/server/src/provider/Layers/CodexSessionRuntime.ts
  • apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
  • apps/server/src/provider/Layers/OpenCodeAdapter.ts
  • apps/server/src/provider/Layers/OpenCodeProvider.test.ts
  • apps/server/src/provider/Layers/OpenCodeProvider.ts
  • apps/server/src/provider/Layers/ProviderAdapterConformance.test.ts
  • apps/server/src/provider/Layers/ProviderRegistry.test.ts
  • apps/server/src/provider/Layers/ProviderRegistry.ts
  • apps/server/src/provider/codexAccount.ts
  • apps/server/src/provider/codexAppServer.ts
  • apps/server/src/provider/codexCliVersion.ts
  • apps/server/src/provider/opencodeRuntime.test.ts
  • apps/server/src/provider/opencodeRuntime.ts
  • apps/server/src/server.test.ts
  • apps/server/src/server.ts
  • apps/web/src/components/AppSidebarLayout.tsx
  • apps/web/src/components/ChatView.browser.tsx
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/CommandPalette.logic.ts
  • apps/web/src/components/DiffPanelShell.tsx
  • apps/web/src/components/Icons.tsx
  • apps/web/src/components/Sidebar.logic.test.ts
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/chat/ChatComposer.tsx
  • apps/web/src/components/chat/ComposerCommandMenu.tsx
  • apps/web/src/components/chat/ModelListRow.tsx
  • apps/web/src/components/chat/ModelPickerContent.tsx
  • apps/web/src/components/chat/ModelPickerSidebar.tsx
  • apps/web/src/components/chat/ProviderModelPicker.browser.tsx
  • apps/web/src/components/chat/ProviderModelPicker.tsx
  • apps/web/src/components/chat/TraitsPicker.browser.tsx
  • apps/web/src/components/chat/TraitsPicker.tsx
  • apps/web/src/components/chat/modelPickerModelHighlights.ts
  • apps/web/src/components/chat/modelPickerSearch.test.ts
  • apps/web/src/components/chat/modelPickerSearch.ts
  • apps/web/src/components/chat/providerIconUtils.ts
  • apps/web/src/components/settings/SettingsPanels.tsx
  • apps/web/src/components/ui/autocomplete.tsx
  • apps/web/src/components/ui/combobox.tsx
  • apps/web/src/components/ui/scroll-area.tsx
  • apps/web/src/composer-logic.test.ts
  • apps/web/src/composer-logic.ts
  • apps/web/src/environments/runtime/service.ts
  • apps/web/src/hooks/useHandleNewThread.ts
  • apps/web/src/hooks/useSettings.ts
  • apps/web/src/index.css
  • apps/web/src/keybindings.test.ts
  • apps/web/src/keybindings.ts
  • apps/web/src/localApi.test.ts
  • apps/web/src/logicalProject.ts
  • apps/web/src/modelPickerOpenState.ts
  • apps/web/src/rightPanelLayout.ts
  • apps/web/src/session-logic.test.ts
  • apps/web/src/session-logic.ts
  • apps/web/src/shortcutModifierState.test.ts
  • apps/web/src/shortcutModifierState.ts
  • apps/web/src/uiStateStore.test.ts
  • apps/web/src/uiStateStore.ts
  • packages/contracts/src/keybindings.test.ts
  • packages/contracts/src/keybindings.ts
  • packages/contracts/src/server.ts
  • packages/contracts/src/settings.ts
  • packages/effect-acp/src/_internal/stdio.ts
  • packages/effect-codex-app-server/package.json
  • packages/effect-codex-app-server/scripts/generate.ts
  • packages/effect-codex-app-server/src/_internal/shared.ts
  • packages/effect-codex-app-server/src/_internal/stdio.ts
  • packages/effect-codex-app-server/src/client.test.ts
  • packages/effect-codex-app-server/src/client.ts
  • packages/effect-codex-app-server/src/errors.ts
  • packages/effect-codex-app-server/src/protocol.test.ts
  • packages/effect-codex-app-server/src/protocol.ts
  • packages/effect-codex-app-server/src/rpc.ts
  • packages/effect-codex-app-server/src/schema.ts
  • packages/effect-codex-app-server/test/examples/codex-app-server-probe.ts
  • packages/effect-codex-app-server/test/fixtures/codex-app-server-mock-peer.ts
  • packages/effect-codex-app-server/tsconfig.json
  • packages/shared/src/model.ts
  • scripts/release-smoke.ts
  • scripts/resolve-nightly-release.test.ts
  • scripts/resolve-nightly-release.ts
  • scripts/resolve-previous-release-tag.ts
💤 Files with no reviewable changes (6)
  • apps/server/src/provider/codexCliVersion.ts
  • apps/server/src/codexAppServerManager.test.ts
  • apps/server/src/provider/opencodeRuntime.test.ts
  • apps/server/src/provider/codexAppServer.ts
  • apps/server/src/provider/codexAccount.ts
  • apps/server/src/codexAppServerManager.ts

Comment thread .github/workflows/release.yml
Comment thread .vscode/settings.json Outdated
Comment on lines +1030 to +1033
const threadLogPath = path.join(tempDir, "thread-logger.log");
assert.equal(fs.existsSync(threadLogPath), true);
const contents = fs.readFileSync(threadLogPath, "utf8");
assert.match(contents, /NTIVE: .*"message":"native flush test"/);
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

Fix the native log assertion typo.

Line 1033 matches NTIVE, which looks like a missing A from the native log prefix. Prefer asserting the payload message directly so this test only validates the flush behavior.

Proposed fix
-      assert.match(contents, /NTIVE: .*"message":"native flush test"/);
+      assert.match(contents, /"message":"native flush test"/);
📝 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
const threadLogPath = path.join(tempDir, "thread-logger.log");
assert.equal(fs.existsSync(threadLogPath), true);
const contents = fs.readFileSync(threadLogPath, "utf8");
assert.match(contents, /NTIVE: .*"message":"native flush test"/);
const threadLogPath = path.join(tempDir, "thread-logger.log");
assert.equal(fs.existsSync(threadLogPath), true);
const contents = fs.readFileSync(threadLogPath, "utf8");
assert.match(contents, /"message":"native flush test"/);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/CodexAdapter.test.ts` around lines 1030 -
1033, The test in CodexAdapter.test.ts currently asserts the native log prefix
mistakenly as "NTIVE"; update the assertion to validate the payload message
directly instead of the log prefix: after reading threadLogPath into contents
(variable contents), replace the regex /NTIVE: .*"message":"native flush test"/
with a regex that only matches the JSON payload message such as
/"message":"native flush test"/ so the test verifies flush behavior rather than
the log prefix.

Comment thread apps/server/src/provider/Layers/CodexAdapter.ts Outdated
Comment on lines +218 to +239
const clientContext = yield* Layer.build(
CodexClient.layerCommand({
command: input.binaryPath,
args: ["app-server"],
cwd: input.cwd,
...(input.homePath ? { env: { CODEX_HOME: input.homePath } } : {}),
}),
);
const client = yield* Effect.service(CodexClient.CodexAppServerClient).pipe(
Effect.provide(clientContext),
);
const configPath = path.join(codexHome, "config.toml");

const content = yield* fileSystem
.readFileString(configPath)
.pipe(Effect.orElseSucceed(() => undefined));
if (content === undefined) {
return undefined;
}
const initialize = yield* client.request("initialize", {
clientInfo: {
name: "t3code_desktop",
title: "T3 Code Desktop",
version: "0.1.0",
},
capabilities: {
experimentalApi: true,
},
});
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:

# First, let's look at the file structure and find the relevant code
cd /repo && find . -type f -name "CodexProvider.ts" | head -5

Repository: aaditagrawal/t3code

Length of output: 118


🏁 Script executed:

# Search for CodexClient.layerCommand definition
rg "layerCommand" --type ts -A 10 | head -50

Repository: aaditagrawal/t3code

Length of output: 3874


🏁 Script executed:

# Search for buildCodexInitializeParams definition
rg "buildCodexInitializeParams" --type ts -A 15 | head -80

Repository: aaditagrawal/t3code

Length of output: 4253


🏁 Script executed:

# Examine the specific CodexProvider.ts file around lines 218-239
cat -n apps/server/src/provider/Layers/CodexProvider.ts | sed -n '210,250p'

Repository: aaditagrawal/t3code

Length of output: 1663


🏁 Script executed:

# Get CodexClient.layerCommand implementation
cat -n packages/effect-codex-app-server/src/client.ts | sed -n '1,150p'

Repository: aaditagrawal/t3code

Length of output: 7182


🏁 Script executed:

# Search for the full layerCommand function
rg "export const layerCommand" --type ts -A 30 packages/effect-codex-app-server/src/client.ts

Repository: aaditagrawal/t3code

Length of output: 1204


🏁 Script executed:

# Look for ChildProcessSpawner and env handling
rg "env" --type ts -B 3 -A 3 packages/effect-codex-app-server/src/client.ts | head -60

Repository: aaditagrawal/t3code

Length of output: 638


Replace hardcoded initialize version with helper function.

Lines 230–239 hardcode the initialization version as "0.1.0", but buildCodexInitializeParams() already exists and uses the correct packageJson.version. Use the helper instead to stay consistent with the session runtime (which correctly imports and uses it).

Proposed fix
-  const initialize = yield* client.request("initialize", {
-    clientInfo: {
-      name: "t3code_desktop",
-      title: "T3 Code Desktop",
-      version: "0.1.0",
-    },
-    capabilities: {
-      experimentalApi: true,
-    },
-  });
+  const initialize = yield* client.request("initialize", buildCodexInitializeParams());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/provider/Layers/CodexProvider.ts` around lines 218 - 239, The
initialize call is using a hardcoded version ("0.1.0") instead of the helper;
replace the inline clientInfo object passed to client.request("initialize", ...)
with the values produced by buildCodexInitializeParams() (or merge its returned
params) so the clientInfo.version comes from packageJson via
buildCodexInitializeParams; locate the initialization block around the
clientContext/CodexClient.layerCommand and update the initialize variable
creation to use buildCodexInitializeParams() output rather than the hardcoded
literal.

Comment thread apps/web/src/components/chat/ModelPickerContent.tsx
Comment thread apps/web/src/components/chat/ModelPickerSidebar.tsx Outdated
Comment thread apps/web/src/components/Icons.tsx Outdated
Comment on lines 473 to 480
function syncProjectUiFromStore() {
const projects = selectProjectsAcrossEnvironments(useStore.getState());
const clientSettings = getClientSettings();
useUiStateStore.getState().syncProjects(
projects.map((project) => ({
key: derivePhysicalProjectKey(project),
logicalKey: deriveLogicalProjectKeyFromSettings(project, clientSettings),
cwd: project.cwd,
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:

#!/bin/bash
# Verify whether settings changes trigger project UI resync after logical keys became settings-derived.
# Expected: a settings update path calls useUiStateStore.getState().syncProjects(...) or otherwise invokes the project sync path.

rg -n -C3 '\bsyncProjects\s*\('
rg -n -C3 'sidebarProjectGroupingMode|sidebarProjectGroupingOverrides'
rg -n -C3 'deriveLogicalProjectKeyFromSettings|getClientSettings'

Repository: aaditagrawal/t3code

Length of output: 50375


🏁 Script executed:

#!/bin/bash
# Find all calls to syncProjectUiFromStore to understand when project UI sync occurs
rg -n 'syncProjectUiFromStore'

Repository: aaditagrawal/t3code

Length of output: 305


🏁 Script executed:

#!/bin/bash
# Check useUpdateSettings hook to see if it triggers any project UI sync
rg -n -A10 'useUpdateSettings|persistClientSettings' apps/web/src/hooks/useSettings.ts

Repository: aaditagrawal/t3code

Length of output: 2129


🏁 Script executed:

#!/bin/bash
# Search for subscriptions or listeners on settings that might trigger project UI sync
rg -n -C5 'subscribeClientSettings|clientSettingsSnapshot' apps/web/src/

Repository: aaditagrawal/t3code

Length of output: 3495


🏁 Script executed:

#!/bin/bash
# Check context around line 499 where syncProjectUiFromStore is called
sed -n '490,510p' apps/web/src/environments/runtime/service.ts

Repository: aaditagrawal/t3code

Length of output: 770


🏁 Script executed:

#!/bin/bash
# Check context around line 617 where syncProjectUiFromStore is called
sed -n '605,625p' apps/web/src/environments/runtime/service.ts

Repository: aaditagrawal/t3code

Length of output: 865


🏁 Script executed:

#!/bin/bash
# Search for any subscriptions to settings changes in the runtime service
rg -n 'clientSettingsListeners|subscribeClientSettings|addEventListener.*settings' apps/web/src/environments/runtime/service.ts

Repository: aaditagrawal/t3code

Length of output: 45


Resync project UI when grouping settings change.

logicalKey now depends on getClientSettings() (lines 475, 550), but these paths only run on project events or snapshot reconciliation. When sidebarProjectGroupingMode or sidebarProjectGroupingOverrides change independently, expansion/collapse state remains keyed by stale logical IDs until the next project event.

Consider adding a settings change listener in the runtime service to re-run project UI sync, or confirm the app design accepts stale grouping until the next sync trigger.

Also applies to: 549-554

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

In `@apps/web/src/environments/runtime/service.ts` around lines 473 - 480,
syncProjectUiFromStore builds logicalKey using getClientSettings(), so UI keys
go stale when grouping settings change; add a settings change listener that
calls syncProjectUiFromStore whenever sidebarProjectGroupingMode or
sidebarProjectGroupingOverrides change (or re-run the same sync logic) to
re-compute logical IDs and call useUiStateStore.getState().syncProjects again;
locate this near the runtime service initialization where
selectProjectsAcrossEnvironments, getClientSettings,
deriveLogicalProjectKeyFromSettings, and syncProjectUiFromStore are defined and
wire the listener to the settings store or event emitter so changes immediately
trigger re-sync.

Comment on lines +134 to +140
/**
* Non-hook accessor for the current merged client settings snapshot.
* Used by non-React code paths (e.g. runtime services) that need the latest
* settings without subscribing.
*/
export function getClientSettings(): ClientSettings {
return getClientSettingsSnapshot();
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

Hydrate before serving non-hook settings.

Line 140 returns the in-memory snapshot, but hydration is only started by subscribeClientSettings(). A runtime service that calls getClientSettings() before any useSettings() subscriber mounts will see DEFAULT_CLIENT_SETTINGS, not the persisted client settings.

Proposed direction
-export function getClientSettings(): ClientSettings {
+export async function getClientSettings(): Promise<ClientSettings> {
+  await hydrateClientSettings();
   return getClientSettingsSnapshot();
 }

Callers that need a synchronous snapshot should use a clearly named snapshot-only accessor, while runtime initialization paths should await the hydrated accessor.

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

In `@apps/web/src/hooks/useSettings.ts` around lines 134 - 140,
getClientSettings() currently returns the in-memory snapshot
(getClientSettingsSnapshot()) and can return DEFAULT_CLIENT_SETTINGS if
hydration hasn't started; change the API so there are two clear accessors: keep
getClientSettingsSnapshot() as the synchronous snapshot-only accessor, and make
getClientSettings() an async/hydrating accessor that ensures persisted settings
are loaded before returning (implement an ensureClientSettingsHydrated() or
call/await subscribeClientSettings() startup/hydration flow inside
getClientSettings()), and update callers: runtime init code should await
getClientSettings(), while synchronous callers that truly need a snapshot should
call getClientSettingsSnapshot(); update useSettings() subscribers to continue
starting hydration as before.

Skipped 6 tests from upstream's new model picker (pingdotgg#2153) that assert
specific model names or provider orderings (codex/claudeAgent/cursor/
opencode only) that don't match our fork's extended 8-provider setup.
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.

🧹 Nitpick comments (3)
apps/web/src/components/chat/ProviderModelPicker.browser.tsx (3)

191-204: buildOpenCodeProvider is only referenced from it.skip blocks.

Every call site (lines 475–518, 669–720, 722–782) is currently skipped per the fork adaptation notes. Until those tests are re-enabled this helper is effectively dead weight and will show up as unused under stricter lint settings. Either unskip at least one consumer, move the helper next to the first test you plan to restore, or drop it for now and reintroduce when the corresponding test is revived.

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

In `@apps/web/src/components/chat/ProviderModelPicker.browser.tsx` around lines
191 - 204, The helper buildOpenCodeProvider is only referenced from skipped
tests and is effectively unused; either delete the function to remove dead code,
or move it next to the first test you intend to restore (so it won’t be flagged
as unused) or re-enable at least one of the it.skip consumers so the helper is
actually used; update or remove the function declaration accordingly (function
name: buildOpenCodeProvider) and adjust imports/usages in the nearby test blocks
to match the chosen approach.

384-424: Tests are coupled to the production MODEL_OPTIONS_BY_PROVIDER list.

TEST_PROVIDERS only declares Claude Opus 4.6, Sonnet 4.6, and Haiku 4.5, yet this test expects getVisibleModelNames() to also include "Claude Opus 4.7" and "Claude Opus 4.5". Those extra entries must be coming from the static model list read by getCustomModelOptionsByProvider(DEFAULT_UNIFIED_SETTINGS, ...), not from the fixture. The same implicit coupling exists in the arrow-key navigation test (lines 550–585) which asserts "Claude Opus 4.7" as the first highlighted item.

Consequence: any future fork or upstream change that adds/removes/renames a claudeAgent model in the production list silently breaks these picker tests even though the picker behavior didn't change. Consider either:

  • Driving getCustomModelOptionsByProvider (or an equivalent) from a fully synthetic provider set the test controls, or
  • Deriving the expected list from the same source (e.g., snapshot modelOptionsByProvider.claudeAgent.map(m => m.name) and assert ordering/favorite placement on that), so the test validates picker ordering semantics without hard-coding model names.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/chat/ProviderModelPicker.browser.tsx` around lines
384 - 424, The test is implicitly coupled to the production
MODEL_OPTIONS_BY_PROVIDER; instead of hard-coding "Claude Opus 4.7"/"4.5" update
the test to derive expectations from the same source the picker uses (e.g., call
getCustomModelOptionsByProvider(DEFAULT_UNIFIED_SETTINGS, ...) or read
modelOptionsByProvider.claudeAgent.map(m => m.name)) and then assert
ordering/favorite placement against that derived list (use
TEST_PROVIDERS/favorites to mark the favorite first and then compare the
remaining items in production order); locate references to getVisibleModelNames,
mountPicker, TEST_PROVIDERS, getCustomModelOptionsByProvider and change the
assertion block to build expectedNames from the shared model-options helper
rather than literal model strings.

299-321: Sidebar-order assertion hard-codes copilot but it isn't in TEST_PROVIDERS.

The expected slice ["favorites","codex","copilot","claudeAgent"] implies the sidebar is sourcing providers (or at least their order) from something other than the providers prop passed in (likely default client settings / favorites defaults). That makes the assertion fragile: adding or reordering providers in the fork's defaults (you now have 8 providers) will break this test unrelated to any real picker regression.

Prefer asserting a relation that matches the test's intent ("favorites" appears before the active provider, and the active provider appears before its non-favorite siblings) rather than a fixed 4-element prefix. At minimum, add a short comment pointing at the exact default-settings source that injects copilot so the next maintainer doesn't have to reverse-engineer it.

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

In `@apps/web/src/components/chat/ProviderModelPicker.browser.tsx` around lines
299 - 321, The test hard-codes a 4-element sidebar order including "copilot",
which is brittle; change the assertion to check ordering relations instead of a
fixed prefix: use getSidebarProviderOrder() to assert that "favorites" is at
index 0 and that the active provider "claudeAgent" appears after "favorites"
(e.g., indexOf("favorites") < indexOf("claudeAgent")), and optionally assert
that "claudeAgent" appears before any non-favorite sibling by comparing its
index against other providers returned by getSidebarProviderOrder(); update the
test inside the it("shows favorites first...") block (where mountPicker and
getSidebarProviderOrder are used) and add a one-line comment pointing to the
defaults source that injects "copilot" (the default client/favorites settings)
so future maintainers know why extra providers may appear.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/web/src/components/chat/ProviderModelPicker.browser.tsx`:
- Around line 191-204: The helper buildOpenCodeProvider is only referenced from
skipped tests and is effectively unused; either delete the function to remove
dead code, or move it next to the first test you intend to restore (so it won’t
be flagged as unused) or re-enable at least one of the it.skip consumers so the
helper is actually used; update or remove the function declaration accordingly
(function name: buildOpenCodeProvider) and adjust imports/usages in the nearby
test blocks to match the chosen approach.
- Around line 384-424: The test is implicitly coupled to the production
MODEL_OPTIONS_BY_PROVIDER; instead of hard-coding "Claude Opus 4.7"/"4.5" update
the test to derive expectations from the same source the picker uses (e.g., call
getCustomModelOptionsByProvider(DEFAULT_UNIFIED_SETTINGS, ...) or read
modelOptionsByProvider.claudeAgent.map(m => m.name)) and then assert
ordering/favorite placement against that derived list (use
TEST_PROVIDERS/favorites to mark the favorite first and then compare the
remaining items in production order); locate references to getVisibleModelNames,
mountPicker, TEST_PROVIDERS, getCustomModelOptionsByProvider and change the
assertion block to build expectedNames from the shared model-options helper
rather than literal model strings.
- Around line 299-321: The test hard-codes a 4-element sidebar order including
"copilot", which is brittle; change the assertion to check ordering relations
instead of a fixed prefix: use getSidebarProviderOrder() to assert that
"favorites" is at index 0 and that the active provider "claudeAgent" appears
after "favorites" (e.g., indexOf("favorites") < indexOf("claudeAgent")), and
optionally assert that "claudeAgent" appears before any non-favorite sibling by
comparing its index against other providers returned by
getSidebarProviderOrder(); update the test inside the it("shows favorites
first...") block (where mountPicker and getSidebarProviderOrder are used) and
add a one-line comment pointing to the defaults source that injects "copilot"
(the default client/favorites settings) so future maintainers know why extra
providers may appear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13103a9b-eacb-49f6-a2cb-3779a912ee3d

📥 Commits

Reviewing files that changed from the base of the PR and between 468953d and 68bffc1.

📒 Files selected for processing (1)
  • apps/web/src/components/chat/ProviderModelPicker.browser.tsx

CodeRabbit fixes:
- release.yml: derive nightly version for scheduled runs (avoid semver fail)
- CodexAdapter: fork event pump in session scope (prevents leak past Effect.scoped)
- ModelPickerSidebar: remove 'coming soon' for Gemini/Copilot (we have them)
- CodexProvider: use ServerConfig.cwd instead of process.cwd() for probing
- CodexAdapter + contracts: keep label-only user-input options (don't drop)
- useSettings: hydrate before serving non-hook settings
- EventNdjsonLogger: NTIVE → NATIVE typo
- Icons: strip monochrome before spreading SVG props
- ModelPickerContent: parse model keys without truncating colon-containing slugs
- .vscode/settings.json: js/ts.tsdk.path → typescript.tsdk

Browser tests:
- Un-skipped 5 of 6 ProviderModelPicker tests, adapted to fork's 8-provider set
- Used existing models from MODEL_OPTIONS_BY_PROVIDER (GPT-5.3 Codex, etc.)
- Kept 1 test skipped: codex spark visibility requires picker to merge server
  models (fork uses static MODEL_OPTIONS_BY_PROVIDER)
@aaditagrawal aaditagrawal merged commit 0c5b59f into main Apr 22, 2026
7 of 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.

10 participants