Skip to content

Apply upstream roadmap improvements#49

Merged
Jay1 merged 31 commits into
mainfrom
jcode/markdown-link-robustness
Jun 2, 2026
Merged

Apply upstream roadmap improvements#49
Jay1 merged 31 commits into
mainfrom
jcode/markdown-link-robustness

Conversation

@Jay1

@Jay1 Jay1 commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • apply accumulated upstream-inspired JCode slices across markdown robustness, terminal/session polish, plan review UX, local-first remote boundaries, schema/API cleanup, Codex settings, keybinding migration, and shutdown handling
  • preserve JCode-native/local-first constraints while integrating the verified tracked work from this branch
  • leave private .brainstorm roadmap/triage files ignored and out of the PR

Verification

  • bun run fmt:check
  • bun run --cwd apps/server test src/auth/utils.test.ts src/effectServer.test.ts src/git/Layers/GitCore.test.ts src/keybindings.test.ts src/provider/providerStatusCache.test.ts src/voiceTranscription.test.ts
  • bun run --cwd apps/web test src/appSettings.test.ts src/components/ChatView.logic.test.ts src/components/chat/ChangedFilesTree.uiState.test.ts src/markdown-links.test.ts src/planReview.test.ts src/wsNativeApi.test.ts src/terminalStateStore.test.ts
  • bun run --cwd apps/desktop test src/desktopServerExposure.test.ts
  • bun run --cwd apps/server typecheck
  • bun run --cwd apps/web typecheck
  • bun run --cwd apps/web test:browser -- src/components/ChatView.browser.tsx
  • git diff --check
  • LSP diagnostics: no errors in apps/server, apps/web, apps/desktop

Summary by CodeRabbit

  • New Features

    • Plan review panel with inline annotations and composer export
    • Terminal/group inline rename (double‑click) with persisted title overrides
    • Codex launch-args exposed in settings and passed to provider start options
    • Provider-scoped discovery for plugins/skills/models via provider options
    • Chat view improved markdown rendering (headings, code, links)
  • Bug Fixes

    • Improved voice transcription auth-expiry handling and surfaced error details
    • Better desktop bootstrap repair detection
    • More permissive markdown file-link resolution
  • Refactor

    • Per-turn directory expansion state persisted for changed-files view

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b8572a1b-3ab3-4d3a-b8b5-7c3ab21b711d

📥 Commits

Reviewing files that changed from the base of the PR and between 6e22492 and 82b73f9.

📒 Files selected for processing (4)
  • apps/web/src/lib/gitReactQuery.ts
  • apps/web/src/lib/providerDiscoveryReactQuery.ts
  • apps/web/src/lib/providerOptions.test.ts
  • apps/web/src/lib/providerOptions.ts

📝 Walkthrough

Walkthrough

This pull request centralizes auth HTTP route metadata, adds Codex launch-args and provider-scoped discovery, introduces typed voice-transcription auth-expired errors and propagation, implements a plan-review annotation UI and hook, adds terminal/group inline rename and state for title overrides, persists per-turn changed-files expansion state, and ships multiple supporting infra and tests (git Duration types, keybinding migration, socket shutdown cleanup, schema encoders, theme/platform logic, markdown link regex updates).

Changes

HTTP Route Centralization

Layer / File(s) Summary
Auth HTTP route contract definitions
packages/contracts/src/auth.ts, packages/contracts/src/auth.test.ts
AuthHttpRoutes constant exports endpoint metadata (method, pathname, request/response schemas) for authentication operations.
Server route matching refactoring
apps/server/src/auth/http.ts
Route condition checks refactored to use AuthHttpRoutes constants for session, bootstrap, bearer, WebSocket token, pairing, client, and revocation endpoints.
Web client auth API refactoring
apps/web/src/connection/remoteAuthApi.ts, apps/web/src/wsNativeApi.ts, apps/web/src/routes/pair.tsx
Remote auth API functions and bootstrap calls updated to use AuthHttpRoutes properties instead of hardcoded paths and methods.

Codex Launch Arguments

Layer / File(s) Summary
Launch arguments contract definitions
packages/contracts/src/orchestration.ts, packages/contracts/src/settings.ts
CodexProviderStartOptions and CodexServerProviderSettings schemas extended with optional launchArgs field.
Server launch argument parsing and integration
apps/server/src/codexAppServerManager.ts, apps/server/src/codexAppServerManager.test.ts
parseCodexLaunchArgs and buildCodexAppServerArgs implement shell-style argument parsing; startSession, forkThread, and discovery session wiring forward parsed launch arguments and providerOptions.
Web settings and display for launch arguments
apps/web/src/appSettings.ts, apps/web/src/appSettings.test.ts, apps/web/src/routes/_chat.settings.tsx, apps/web/src/wsNativeApi.test.ts
codexLaunchArgs added to app settings, mapped to/from server settings and exposed in settings UI; tests updated for defaults and patching.

Voice Transcription Error Handling

Layer / File(s) Summary
Voice transcription error contracts
packages/contracts/src/server.ts, packages/contracts/src/rpc.ts, packages/contracts/src/rpc.test.ts
New ServerVoiceTranscriptionErrorCode and ServerVoiceTranscriptionErrorDetail types; WsRpcError extended with optional detail.
Server voice error implementation
apps/server/src/voiceTranscription.ts, apps/server/src/voiceTranscription.test.ts, apps/server/src/wsRpc.ts, apps/server/src/codexAppServerManager.ts
VoiceTranscriptionAuthExpiredError added; readVoiceTranscriptionErrorDetail extracts typed detail from error chains; RPC mapping preserves detail for voice transcription failures.
Web client voice error detection
apps/web/src/components/ChatView.logic.ts, apps/web/src/components/ChatView.logic.test.ts, apps/web/src/components/chat/useComposerVoiceController.ts
isVoiceAuthExpiredError helper added; voice controller uses both detail-based and message-based detection for auth-expired flows.

Plan Review and Annotations

Layer / File(s) Summary
Plan annotation domain model and utilities
apps/web/src/planReview.ts, apps/web/src/planReview.test.ts
PlanAnnotation/PlanReviewState and helpers for create/update/format/render composer markdown added and tested.
Plan review React hook
apps/web/src/hooks/usePlanReview.ts
usePlanReview hook manages annotations and exposes CRUD and composer markdown building.
Plan review UI components
apps/web/src/components/PlanReviewPanel.tsx, apps/web/src/components/PlanSidebar.tsx, apps/web/src/components/chat/ProposedPlanActions.tsx, apps/web/src/components/ChatView.tsx
PlanReviewPanel provides review UI with selection-anchored annotations; ChatView and sidebar wire review actions and composer integration.

Terminal and Group Title Customization

Layer / File(s) Summary
Terminal state store group title overrides
apps/web/src/terminalStateStore.ts, apps/web/src/terminalStateStore.test.ts
ThreadTerminalState extended with groupTitleOverridesById; normalization, setter, persistence, and cleanup logic added.
Terminal tab inline rename components
apps/web/src/components/terminal/TerminalChrome.tsx, apps/web/src/components/terminal/TerminalViewportPane.tsx, apps/web/src/components/terminal/InlineRenameField.tsx, apps/web/src/components/terminal/InlineRenameField.browser.tsx
InlineRenameField and tab title components enable inline rename UX with commit/cancel semantics; tests cover interactions.
Terminal rename integration in ChatView
apps/web/src/components/ChatView.tsx, apps/web/src/components/ThreadTerminalDrawer.tsx, apps/web/src/components/WorkspaceView.tsx
Drawer and workspace wiring pass overrides and rename callbacks through component tree and persist via store APIs.

Changed Files Tree Directory Expansion Persistence

Layer / File(s) Summary
Directory expansion state persistence module
apps/web/src/components/chat/ChangedFilesTree.uiState.ts, apps/web/src/components/chat/ChangedFilesTree.uiState.test.ts
New localStorage-backed per-turn expansion state helpers with SSR guards and sanitization.
ChangedFilesTree persistence integration
apps/web/src/components/chat/ChangedFilesTree.tsx, apps/web/src/components/chat/ChangedFilesTree.browser.tsx
Component initializes expansion from persisted state, persists changes per turn, and browser tests validate isolation by turnId.

Supporting Infrastructure and Improvements

Layer / File(s) Summary
Git timeout Duration type updates
apps/server/src/git/Services/GitCore.ts, apps/server/src/git/Layers/GitCore.ts, apps/server/src/git/Layers/GitCore.test.ts
Timeout fields accept Duration.Input to avoid manual ms conversions.
Legacy keybinding command migration
apps/server/src/keybindings.ts, apps/server/src/keybindings.test.ts
LEGACY_KEYBINDING_COMMAND_ALIASES and normalizeLegacyKeybindingEntry migrate thread.next/previous to chat.visible.* and persist migrations when detected.
WebSocket socket cleanup on server shutdown
apps/server/src/effectServer.ts, apps/server/src/effectServer.test.ts
trackUpgradedSocketsForShutdown tracks and destroys upgraded sockets before server close; tests added.
Theme platform detection and rendering
apps/web/src/theme/theme.logic.ts, apps/web/src/theme/theme.logic.test.ts, apps/web/src/hooks/useTheme.ts
buildThemeCssVariables adds isMac option; translucent material now requires Electron + macOS + non-opaque setting.
Desktop project bootstrap repair check
apps/web/src/lib/desktopProjectRecovery.ts, apps/web/src/lib/desktopProjectRecovery.test.ts, apps/web/src/routes/__root.tsx
shouldRepairDesktopProjectBootstrapSnapshot centralizes repair decision logic.
Markdown link pattern improvements
apps/web/src/markdown-links.ts, apps/web/src/markdown-links.test.ts
Regex broadened to accept spaces and additional punctuation in relative file links; tests added.
Provider status cache encoding
apps/server/src/provider/providerStatusCache.ts, apps/server/src/provider/providerStatusCache.test.ts
Schema-based encoding introduced; transient updateState removed before persistence.
Server runtime state schema-based encoding
apps/server/src/serverRuntimeState.ts
Uses Effect Schema encoder for persisted runtime state instead of manual JSON.stringify.
Contract and discovery provider options
packages/contracts/src/providerDiscovery.ts, apps/web/src/lib/providerDiscoveryReactQuery.ts, apps/web/src/lib/providerDiscoveryReactQuery.test.ts, apps/web/src/lib/gitReactQuery.ts, apps/web/src/lib/gitReactQuery.test.ts, apps/server/src/codexAppServerManager.ts, apps/server/src/codexAppServerManager.test.ts, apps/server/src/provider/Layers/CodexAdapter.ts
Adds per-provider ProviderStartOptions, threads providerOptions into discovery/react-query keys (secret-safe), and scopes discovery sessions and caches by providerOptions where appropriate.
Test coverage expansions
many test files across apps/web, apps/server, apps/desktop, packages/contracts, packages/shared
Adds tests for new behaviors and edge cases (markdown rendering, voice auth expiry, loopback fallback, OS detection, mouse reporting reset, changed-files persistence, inline rename behavior, plan review helpers, and more).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Jay1/jcode#6: Related changes touching web theme application logic and testability.
  • Jay1/jcode#2: Overlaps in provider discovery/react-query logic and related tests.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

@github-actions github-actions Bot added size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Jun 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (3)
apps/web/src/appSettings.ts (1)

297-304: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Coalesce optional launchArgs at the server-settings boundary.

settings.providers.codex.launchArgs is optional in the shared contract, but AppSettings.codexLaunchArgs is a required string. Passing it through verbatim can leave this field undefined, which makes the install row look dirty and can flip the input between controlled and uncontrolled when the server omits the property.

Suggested fix
 export function serverSettingsToAppSettings(settings: ServerSettings): Partial<AppSettings> {
   return {
     addProjectBaseDirectory: settings.addProjectBaseDirectory,
     claudeBinaryPath: settings.providers.claudeAgent.binaryPath,
     codexBinaryPath: settings.providers.codex.binaryPath,
     codexHomePath: settings.providers.codex.homePath,
-    codexLaunchArgs: settings.providers.codex.launchArgs,
+    codexLaunchArgs: settings.providers.codex.launchArgs ?? "",
     cursorApiEndpoint: settings.providers.cursor.apiEndpoint,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/appSettings.ts` around lines 297 - 304,
serverSettingsToAppSettings currently copies settings.providers.codex.launchArgs
(which is optional) directly into the required AppSettings.codexLaunchArgs,
risking undefined and uncontrolled inputs; update serverSettingsToAppSettings
(the function) to coalesce settings.providers.codex.launchArgs to a safe default
(e.g., empty string) when undefined before returning codexLaunchArgs so the
AppSettings field is always a string.
apps/web/src/appSettings.test.ts (1)

265-306: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add the omitted-field regression for codex.launchArgs.

This suite exercises the populated case, but the shared contract also allows providers.codex.launchArgs to be absent. Please add a case that omits the property entirely and asserts the mapper normalizes it to codexLaunchArgs: "", so the settings UI stays clean when the server leaves it out.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/appSettings.test.ts` around lines 265 - 306, The test currently
only covers the populated case for providers.codex.launchArgs; add a new unit
test that omits providers.codex.launchArgs from the input to
serverSettingsToAppSettings and assert the mapper returns codexLaunchArgs: ""
(i.e., the mapper normalizes a missing providers.codex.launchArgs to an empty
string). Locate the existing describe("server-backed app settings") block and
add a new it(...) case that constructs server settings without the
codex.launchArgs property and uses
expect(serverSettingsToAppSettings(...)).toMatchObject({ codexLaunchArgs: "" });
to validate the regression is fixed.
apps/web/src/components/WorkspaceView.tsx (1)

317-393: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wire rename callbacks and fix missing dependency in WorkspaceView.

Two issues:

  1. Missing rename callbacks: The component passes groupTitleOverridesById (line 331) but doesn't provide onRenameTerminal or onRenameGroup callbacks. Users can see override titles but cannot rename terminals or groups via double-click in WorkspaceView. Follow the pattern from ChatView to add:

    • onRenameTerminal callback using setTerminalTitleOverride
    • onRenameGroup callback using setGroupTitleOverride
  2. Missing useMemo dependency: terminalState.groupTitleOverridesById is used in the memoized object (line 331) but not listed in the dependencies array (lines 366-393). Add it to prevent stale values.

Suggested fix
+  const setTerminalTitleOverride = useTerminalStateStore((state) => state.setTerminalTitleOverride);
+  const setGroupTitleOverride = useTerminalStateStore((state) => state.setGroupTitleOverride);

   const terminalDrawerProps = useMemo(
     () => ({
       // ... existing props
       groupTitleOverridesById: terminalState.groupTitleOverridesById,
+      onRenameTerminal: (terminalId: string, name: string) => {
+        setTerminalTitleOverride(threadId, terminalId, name);
+      },
+      onRenameGroup: (groupId: string, name: string) => {
+        setGroupTitleOverride(threadId, groupId, name);
+      },
       // ... rest of props
     }),
     [
       // ... existing dependencies
+      setTerminalTitleOverride,
+      setGroupTitleOverride,
+      terminalState.groupTitleOverridesById,
       threadId,
     ],
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/WorkspaceView.tsx` around lines 317 - 393, The
terminal drawer props are missing rename callbacks and a useMemo dependency:
inside WorkspaceView where terminalDrawerProps is built, add onRenameTerminal
that calls setTerminalTitleOverride(threadId, terminalId, title) and
onRenameGroup that calls setGroupTitleOverride(threadId, groupId, title) (follow
ChatView pattern), and add terminalState.groupTitleOverridesById to the useMemo
dependency array so the memo updates when group title overrides change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/src/codexAppServerManager.ts`:
- Around line 964-970: getOrCreateDiscoverySession currently hardcodes
spawn("codex", ["app-server"]) and therefore ignores configured codexBinaryPath,
codexHomePath and codexLaunchArgs; update getOrCreateDiscoverySession to call
spawn with codexBinaryPath and buildCodexAppServerArgs(codexLaunchArgs) and pass
the same spawn options used when creating normal sessions (cwd/resolvedCwd,
include homePath when present, and any env/launch arg handling). Ensure you
reference and reuse the same helpers/values (codexBinaryPath, codexHomePath,
codexLaunchArgs, buildCodexAppServerArgs) so discovery sessions run against the
configured Codex runtime.
- Around line 402-405: The parser currently unconditionally treats '\' as an
escape prefix (setting escaping = true) and discards it; update the launch-arg
parsing logic in codexAppServerManager.ts (the block using the escaping variable
where it checks if char === "\\") so that a backslash is only consumed as an
escape when it is followed by a valid escapable character (e.g., backslash,
quote, n, r, t, b, f, v, 0); otherwise append the literal '\' to the current
token and continue parsing normally. Concretely, look at the code that sets
escaping = true and change it to peek the next char: if nextChar is in the
escapables list then consume the backslash and set escaping mode, else push a
'\' into the current buffer and do not set escaping. Ensure the rest of the
parser handles escaped sequences as before.

In `@apps/web/src/components/chat/ChangedFilesTree.tsx`:
- Around line 46-68: The initializer currently hardcodes
buildDirectoryExpansionState(directoryPaths, true) so persisted/collapsed state
is never restored; change that call to use the allDirectoriesExpanded flag
(buildDirectoryExpansionState(directoryPaths, allDirectoriesExpanded)) and keep
the loop that sets state[path] = true only for paths in persistedSet so
persisted expanded paths are applied when the default is collapsed. Also update
the useEffect merge logic that builds next from allDirectoryExpansionState so it
does not blindly preserve current true values that are already represented in
allDirectoryExpansionState: set next = { ...allDirectoryExpansionState } and
only copy current true entries into next when the path is not present in
allDirectoryExpansionState (i.e., if current[path] && !(path in next) then
next[path] = true) to allow collapsing when the global default changes.
- Around line 70-88: The setExpandedDirectories updater in toggleDirectory is
impure because it calls readChangedFilesUiState() and
persistChangedFilesUiState(...) inside the functional updater; move all
localStorage persistence out of the updater so the updater only computes and
returns next state. Concretely: have toggleDirectory compute next via
setExpandedDirectories(current => next) (only computing the new map and
returning it), then after state is set persist expandedPaths by deriving them
from the newest expandedDirectories (or use a useEffect keyed on
[expandedDirectories, turnId]) and call
setExpandedDirectoryPathsForTurn/readChangedFilesUiState/persistChangedFilesUiState
there; reference functions: toggleDirectory, setExpandedDirectories,
readChangedFilesUiState, persistChangedFilesUiState,
setExpandedDirectoryPathsForTurn.

In `@apps/web/src/components/chat/ProposedPlanActions.tsx`:
- Line 23: In ProposedPlanActions.tsx remove the redundant "| undefined" from
the optional prop type declarations (e.g., change "onReview?: (() => void) |
undefined" to "onReview?: () => void") and apply the same cleanup for the other
occurrences noted (around the other prop declarations at the locations
referenced, including the block spanning lines ~157-166); update the component's
Prop type/interface and any similar optional prop typings so optional properties
use the "?:" form without "| undefined".

In `@apps/web/src/components/ChatView.tsx`:
- Line 3367: terminalDrawerProps uses terminalState.groupTitleOverridesById but
the memo dependency array omitted it, causing ThreadTerminalDrawer to show stale
titles after onRenameGroup updates the store; include
terminalState.groupTitleOverridesById in the useMemo (or equivalent memo hook)
dependency array wherever terminalDrawerProps is computed (references around
terminalDrawerProps, ThreadTerminalDrawer, and onRenameGroup) so the memo
invalidates and re-renders when groupTitleOverridesById changes.
- Around line 964-965: The plan-review state (planReviewOpen, setPlanReviewOpen,
and usePlanReview()) must be keyed to the active plan so it can’t bleed across
thread/plan switches; update ChatView so the plan review hook and open flag are
tied to the current sidebarProposedPlan (for example by deriving/resetting state
when sidebarProposedPlan.id changes or by storing a per-plan map keyed by plan
id) and ensure you reset/close the panel and re-init the usePlanReview()
instance whenever sidebarProposedPlan changes so annotations and exported review
content cannot persist into a different plan.

In `@apps/web/src/components/PlanSidebar.tsx`:
- Line 50: Remove the redundant "| undefined" from the optional prop type
annotations in the PlanSidebar props interface/props declarations (e.g., change
"onReview?: (() => void) | undefined" and the other occurrences at the same file
to simply "onReview?: () => void" and likewise for the other mentioned optional
props); update the type annotations in the PlanSidebar component (and any
related prop interfaces) so optional props use ?: without the explicit "|
undefined".

In `@apps/web/src/components/terminal/TerminalViewportPane.tsx`:
- Around line 83-126: Extract the duplicated InlineRenameField component into a
single shared module (e.g., InlineRenameField.tsx) and replace the local
definitions in both TerminalViewportPane and TerminalChrome with imports;
preserve the component signature (props: initialValue, onCommit, onCancel,
className), hooks (useState, useRef, useEffect to select), event handlers
(handleKeyDown, handleBlur) and JSX (input attributes including autoFocus and cn
className usage), export it (named or default) from the new module, update both
files to import that exported InlineRenameField, and remove the duplicate local
component definitions so both files use the shared component.

In `@apps/web/src/hooks/usePlanReview.ts`:
- Around line 20-23: Replace the ad-hoc ID generation in addAnnotation with a
UUID from the Web Crypto API: use crypto.randomUUID() to create the id passed
into createPlanAnnotation and setAnnotations; update the addAnnotation callback
(the function named addAnnotation that calls createPlanAnnotation and
setAnnotations) to use crypto.randomUUID() (optionally add a small fallback if
your target runtimes lack crypto.randomUUID).

In `@apps/web/src/planReview.ts`:
- Around line 13-26: The createPlanAnnotation function currently accepts any
string for comment; add a validation that comment.trim().length > 0 and throw a
clear Error (e.g., "comment must be non-empty") if it fails so callers cannot
create annotations with empty comments; keep the existing return shape (id,
createdAt, updatedAt, quote, comment) and reuse the existing quote trimming
logic, and update any tests/usages of createPlanAnnotation accordingly.
- Around line 28-43: The updatePlanAnnotation function must validate provided
comment updates just like createPlanAnnotation does: if updates.comment is
provided, trim it and if the result is an empty string throw a validation error
(e.g., "Comment cannot be empty") instead of accepting it; otherwise use the
trimmed value. Keep existing behavior for undefined fields (leave
annotation.comment untouched), preserve the handling of updates.quote and
updatedAt, and apply this validation inside updatePlanAnnotation to mirror
createPlanAnnotation's logic.

In `@apps/web/src/terminalStateStore.test.ts`:
- Around line 365-401: Add parallel tests for group title overrides mirroring
the terminal tests: use useTerminalStateStore.getState() and call
setGroupTitleOverride(THREAD_ID, groupId, "Title") then assert
selectThreadTerminalState(...).groupTitleOverridesById contains the value; test
removal by setting an empty string and assert the groupTitleOverridesById is
empty; test clearing by creating terminals in a group with
newTerminal(THREAD_ID, terminalId) tied to a group, setGroupTitleOverride, then
close all terminals in that group with closeTerminal(...) and assert
groupTitleOverridesById is cleared; finally add a test for
normalizeGroupTitleOverrides by seeding stale overrides, calling
normalizeGroupTitleOverrides(THREAD_ID) (or triggering the normalization flow)
and asserting only existing group IDs remain. Ensure tests reference
setGroupTitleOverride, normalizeGroupTitleOverrides, newTerminal, closeTerminal,
selectThreadTerminalState, and useTerminalStateStore to locate code.

In `@packages/contracts/src/auth.test.ts`:
- Around line 82-97: The test currently asserts only 4 route entries; extend the
assertion to include all remaining AuthHttpRoutes entries (pairingToken,
pairingLinks, revokePairingLink, clients, revokeClient, revokeOtherClients) by
adding them to the expected object alongside the existing keys, using each
route’s .method and .pathname (e.g., AuthHttpRoutes.pairingToken.method,
AuthHttpRoutes.pairingToken.pathname) so the compare covers all 10 routes and
ensures shared metadata completeness.

---

Outside diff comments:
In `@apps/web/src/appSettings.test.ts`:
- Around line 265-306: The test currently only covers the populated case for
providers.codex.launchArgs; add a new unit test that omits
providers.codex.launchArgs from the input to serverSettingsToAppSettings and
assert the mapper returns codexLaunchArgs: "" (i.e., the mapper normalizes a
missing providers.codex.launchArgs to an empty string). Locate the existing
describe("server-backed app settings") block and add a new it(...) case that
constructs server settings without the codex.launchArgs property and uses
expect(serverSettingsToAppSettings(...)).toMatchObject({ codexLaunchArgs: "" });
to validate the regression is fixed.

In `@apps/web/src/appSettings.ts`:
- Around line 297-304: serverSettingsToAppSettings currently copies
settings.providers.codex.launchArgs (which is optional) directly into the
required AppSettings.codexLaunchArgs, risking undefined and uncontrolled inputs;
update serverSettingsToAppSettings (the function) to coalesce
settings.providers.codex.launchArgs to a safe default (e.g., empty string) when
undefined before returning codexLaunchArgs so the AppSettings field is always a
string.

In `@apps/web/src/components/WorkspaceView.tsx`:
- Around line 317-393: The terminal drawer props are missing rename callbacks
and a useMemo dependency: inside WorkspaceView where terminalDrawerProps is
built, add onRenameTerminal that calls setTerminalTitleOverride(threadId,
terminalId, title) and onRenameGroup that calls setGroupTitleOverride(threadId,
groupId, title) (follow ChatView pattern), and add
terminalState.groupTitleOverridesById to the useMemo dependency array so the
memo updates when group title overrides change.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 920d4b59-c1a1-44d8-b9e7-7aebe0da730e

📥 Commits

Reviewing files that changed from the base of the PR and between f379e18 and 7cc2251.

📒 Files selected for processing (64)
  • apps/desktop/src/desktopServerExposure.test.ts
  • apps/server/src/auth/http.ts
  • apps/server/src/auth/utils.test.ts
  • apps/server/src/auth/utils.ts
  • apps/server/src/codexAppServerManager.test.ts
  • apps/server/src/codexAppServerManager.ts
  • apps/server/src/effectServer.test.ts
  • apps/server/src/effectServer.ts
  • apps/server/src/git/Layers/GitCore.test.ts
  • apps/server/src/git/Layers/GitCore.ts
  • apps/server/src/git/Services/GitCore.ts
  • apps/server/src/keybindings.test.ts
  • apps/server/src/keybindings.ts
  • apps/server/src/provider/providerStatusCache.test.ts
  • apps/server/src/provider/providerStatusCache.ts
  • apps/server/src/serverRuntimeState.ts
  • apps/server/src/voiceTranscription.test.ts
  • apps/server/src/voiceTranscription.ts
  • apps/server/src/wsRpc.ts
  • apps/web/src/appSettings.test.ts
  • apps/web/src/appSettings.ts
  • apps/web/src/components/ChatView.browser.tsx
  • apps/web/src/components/ChatView.logic.test.ts
  • apps/web/src/components/ChatView.logic.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/PlanReviewPanel.tsx
  • apps/web/src/components/PlanSidebar.tsx
  • apps/web/src/components/ThreadTerminalDrawer.tsx
  • apps/web/src/components/WorkspaceView.tsx
  • apps/web/src/components/chat/ChangedFilesTree.tsx
  • apps/web/src/components/chat/ChangedFilesTree.uiState.test.ts
  • apps/web/src/components/chat/ChangedFilesTree.uiState.ts
  • apps/web/src/components/chat/ProposedPlanActions.tsx
  • apps/web/src/components/chat/useComposerVoiceController.ts
  • apps/web/src/components/terminal/TerminalChrome.tsx
  • apps/web/src/components/terminal/TerminalViewportPane.tsx
  • apps/web/src/connection/remoteAuthApi.ts
  • apps/web/src/hooks/usePlanReview.ts
  • apps/web/src/hooks/useTheme.ts
  • apps/web/src/lib/desktopProjectRecovery.test.ts
  • apps/web/src/lib/desktopProjectRecovery.ts
  • apps/web/src/markdown-links.test.ts
  • apps/web/src/markdown-links.ts
  • apps/web/src/planReview.test.ts
  • apps/web/src/planReview.ts
  • apps/web/src/routes/-_chat.settings.install.test.ts
  • apps/web/src/routes/__root.tsx
  • apps/web/src/routes/_chat.settings.tsx
  • apps/web/src/routes/pair.tsx
  • apps/web/src/terminalStateStore.test.ts
  • apps/web/src/terminalStateStore.ts
  • apps/web/src/theme/theme.logic.test.ts
  • apps/web/src/theme/theme.logic.ts
  • apps/web/src/wsNativeApi.test.ts
  • apps/web/src/wsNativeApi.ts
  • packages/contracts/src/auth.test.ts
  • packages/contracts/src/auth.ts
  • packages/contracts/src/orchestration.test.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/rpc.test.ts
  • packages/contracts/src/rpc.ts
  • packages/contracts/src/server.ts
  • packages/contracts/src/settings.ts
  • packages/shared/src/terminalThreads.test.ts

Comment thread apps/server/src/codexAppServerManager.ts
Comment thread apps/server/src/codexAppServerManager.ts
Comment thread apps/web/src/components/chat/ChangedFilesTree.tsx Outdated
Comment thread apps/web/src/components/chat/ChangedFilesTree.tsx
Comment thread apps/web/src/components/chat/ProposedPlanActions.tsx Outdated
Comment thread apps/web/src/hooks/usePlanReview.ts
Comment thread apps/web/src/planReview.ts
Comment thread apps/web/src/planReview.ts
Comment thread apps/web/src/terminalStateStore.test.ts
Comment thread packages/contracts/src/auth.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
apps/web/src/components/terminal/TerminalChrome.tsx (1)

162-171: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider adding autoFocus to InlineRenameField for consistent rename UX.

TerminalViewportPane passes autoFocus={true} to InlineRenameField when renaming terminal tabs (line 99 in TerminalViewportPane.tsx), but this component doesn't pass autoFocus when renaming group tabs. Since InlineRenameField always selects the text on mount (via useEffect), omitting autoFocus means the text is selected but the input isn't focused—the user must click before typing. Adding autoFocus would let users immediately type after double-clicking, matching the terminal tab rename experience.

Suggested enhancement
             <InlineRenameField
               initialValue={displayTitle}
               onCommit={(value) => {
                 props.onRenameGroup?.(terminalGroup.id, value);
                 setEditingGroupId(null);
               }}
               onCancel={() => setEditingGroupId(null)}
               className="min-w-0 flex-1"
+              autoFocus
             />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/terminal/TerminalChrome.tsx` around lines 162 - 171,
When entering group-rename mode in TerminalChrome, InlineRenameField is rendered
without passing autoFocus so the input text is selected but not focused; update
the JSX that renders InlineRenameField (inside the isEditing &&
props.onRenameGroup branch) to forward autoFocus={true} along with the existing
props (initialValue, onCommit calling props.onRenameGroup(terminalGroup.id,
value) and setEditingGroupId(null), onCancel calling setEditingGroupId(null),
className) so the field receives focus immediately and matches the rename
behavior from TerminalViewportPane.
apps/server/src/codexAppServerManager.ts (1)

2377-2381: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Delete discovery sessions by their composite cache key.

Lines 2377-2381 still remove discovery sessions with context.session.cwd, but getOrCreateDiscoverySession() now stores them under buildCodexDiscoverySessionKey(...). After an unexpected Codex exit, the dead discovery context stays cached and can be reused for later discovery calls with the same provider options until the idle timer fires.

Suggested fix
       this.emitLifecycleEvent(context, "session/exited", message);
       if (context.discovery) {
-        const discoveryKey = context.session.cwd ?? "";
-        if (discoveryKey) {
-          this.discoverySessions.delete(discoveryKey);
+        for (const [discoveryKey, session] of this.discoverySessions.entries()) {
+          if (session === context) {
+            this.discoverySessions.delete(discoveryKey);
+            break;
+          }
         }
       } else {
         this.sessions.delete(context.session.threadId);
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/server/src/codexAppServerManager.ts` around lines 2377 - 2381, The code
deletes discovery sessions using context.session.cwd but
getOrCreateDiscoverySession() caches sessions under a composite key from
buildCodexDiscoverySessionKey(...), so change the deletion to compute that same
key and remove by it; specifically, when context.discovery is truthy, call
buildCodexDiscoverySessionKey(context) (or the same key-construction used in
getOrCreateDiscoverySession) and pass that to this.discoverySessions.delete(...)
instead of using context.session.cwd so dead/disconnected Codex contexts aren't
left cached.
♻️ Duplicate comments (2)
apps/web/src/lib/providerDiscoveryReactQuery.ts (1)

48-61: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Duplicate helper – see comment in gitReactQuery.ts.

This function is identical to codexProviderOptionsKey in apps/web/src/lib/gitReactQuery.ts. Refactor both to use a shared utility as noted in that file's review.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/providerDiscoveryReactQuery.ts` around lines 48 - 61, The
two identical serializers codexDiscoveryProviderOptionsKey and
codexProviderOptionsKey should be consolidated into a single shared utility
(e.g., serializeCodexProviderOptions or codexProviderOptionsKey in a new/shared
util module) and both places should import and call that utility; ensure the
shared function preserves current behavior (accepts ProviderStartOptions | null
| undefined, returns string | null, reads codex, and JSON.stringify({
binaryPath: codex.binaryPath ?? null, homePath: codex.homePath ?? null,
launchArgs: codex.launchArgs ?? null })). Replace the local implementations in
the modules that define codexDiscoveryProviderOptionsKey and
codexProviderOptionsKey with imports to this shared function.
apps/web/src/components/ChatView.tsx (1)

1922-1930: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Also reset the review panel open state on plan/thread changes.

This clears annotations, but planReviewOpen still survives thread switches. If the user changes threads while the review panel is open and the next thread also has a plan, PlanReviewPanel stays open against the new plan unexpectedly.

♻️ Proposed fix
-  const planReviewPlanIdRef = useRef<string | null>(null);
+  const planReviewPlanKeyRef = useRef<string | null>(null);
   useEffect(() => {
-    const planId = sidebarProposedPlan?.id ?? null;
-    if (planReviewPlanIdRef.current === planId) {
+    const planKey =
+      activeThread?.id && sidebarProposedPlan?.id
+        ? `${activeThread.id}:${sidebarProposedPlan.id}`
+        : null;
+    if (planReviewPlanKeyRef.current === planKey) {
       return;
     }
-    planReviewPlanIdRef.current = planId;
+    planReviewPlanKeyRef.current = planKey;
+    setPlanReviewOpen(false);
     planReview.resetAnnotations();
-  }, [planReview.resetAnnotations, sidebarProposedPlan?.id]);
+  }, [activeThread?.id, planReview.resetAnnotations, sidebarProposedPlan?.id]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/ChatView.tsx` around lines 1922 - 1930, When the
tracked plan id changes you currently reset annotations but not the panel open
state, so keep planReviewPlanIdRef logic but also reset the panel visibility:
detect plan id change (same place using planReviewPlanIdRef and
sidebarProposedPlan?.id) and call the routine that closes the review panel (e.g.
setPlanReviewOpen(false) or the PlanReviewPanel close method) immediately after
resetting annotations so PlanReviewPanel does not remain open for the new
thread/plan.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/lib/gitReactQuery.ts`:
- Around line 17-30: Extract the duplicated JSON-serialization logic into a
single shared helper (e.g., buildCodexProviderOptionsKey) and replace both
codexProviderOptionsKey and codexDiscoveryProviderOptionsKey to call that
helper; specifically, move the logic that reads providerOptions?.codex and
returns JSON.stringify({ binaryPath: codexOptions.binaryPath ?? null, homePath:
codexOptions.homePath ?? null, launchArgs: codexOptions.launchArgs ?? null })
into the new buildCodexProviderOptionsKey(providerOptions) function, export it
from a common module, then import and use it from the modules that currently
define codexProviderOptionsKey and codexDiscoveryProviderOptionsKey so the
duplicate implementations are removed.

---

Outside diff comments:
In `@apps/server/src/codexAppServerManager.ts`:
- Around line 2377-2381: The code deletes discovery sessions using
context.session.cwd but getOrCreateDiscoverySession() caches sessions under a
composite key from buildCodexDiscoverySessionKey(...), so change the deletion to
compute that same key and remove by it; specifically, when context.discovery is
truthy, call buildCodexDiscoverySessionKey(context) (or the same
key-construction used in getOrCreateDiscoverySession) and pass that to
this.discoverySessions.delete(...) instead of using context.session.cwd so
dead/disconnected Codex contexts aren't left cached.

In `@apps/web/src/components/terminal/TerminalChrome.tsx`:
- Around line 162-171: When entering group-rename mode in TerminalChrome,
InlineRenameField is rendered without passing autoFocus so the input text is
selected but not focused; update the JSX that renders InlineRenameField (inside
the isEditing && props.onRenameGroup branch) to forward autoFocus={true} along
with the existing props (initialValue, onCommit calling
props.onRenameGroup(terminalGroup.id, value) and setEditingGroupId(null),
onCancel calling setEditingGroupId(null), className) so the field receives focus
immediately and matches the rename behavior from TerminalViewportPane.

---

Duplicate comments:
In `@apps/web/src/components/ChatView.tsx`:
- Around line 1922-1930: When the tracked plan id changes you currently reset
annotations but not the panel open state, so keep planReviewPlanIdRef logic but
also reset the panel visibility: detect plan id change (same place using
planReviewPlanIdRef and sidebarProposedPlan?.id) and call the routine that
closes the review panel (e.g. setPlanReviewOpen(false) or the PlanReviewPanel
close method) immediately after resetting annotations so PlanReviewPanel does
not remain open for the new thread/plan.

In `@apps/web/src/lib/providerDiscoveryReactQuery.ts`:
- Around line 48-61: The two identical serializers
codexDiscoveryProviderOptionsKey and codexProviderOptionsKey should be
consolidated into a single shared utility (e.g., serializeCodexProviderOptions
or codexProviderOptionsKey in a new/shared util module) and both places should
import and call that utility; ensure the shared function preserves current
behavior (accepts ProviderStartOptions | null | undefined, returns string |
null, reads codex, and JSON.stringify({ binaryPath: codex.binaryPath ?? null,
homePath: codex.homePath ?? null, launchArgs: codex.launchArgs ?? null })).
Replace the local implementations in the modules that define
codexDiscoveryProviderOptionsKey and codexProviderOptionsKey with imports to
this shared function.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b9e2f4b7-b58d-44e9-84dd-01c502d66256

📥 Commits

Reviewing files that changed from the base of the PR and between 7cc2251 and e788479.

📒 Files selected for processing (30)
  • apps/server/src/codexAppServerManager.test.ts
  • apps/server/src/codexAppServerManager.ts
  • apps/server/src/provider/Layers/CodexAdapter.ts
  • apps/web/src/appSettings.test.ts
  • apps/web/src/appSettings.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/PlanSidebar.tsx
  • apps/web/src/components/PluginLibrary.tsx
  • apps/web/src/components/SkillLibrarySettingsPanel.tsx
  • apps/web/src/components/WorkspaceView.tsx
  • apps/web/src/components/chat/ChangedFilesTree.browser.tsx
  • apps/web/src/components/chat/ChangedFilesTree.tsx
  • apps/web/src/components/chat/ProposedPlanActions.tsx
  • apps/web/src/components/terminal/InlineRenameField.browser.tsx
  • apps/web/src/components/terminal/InlineRenameField.tsx
  • apps/web/src/components/terminal/TerminalChrome.tsx
  • apps/web/src/components/terminal/TerminalViewportPane.tsx
  • apps/web/src/hooks/usePlanReview.ts
  • apps/web/src/lib/gitReactQuery.test.ts
  • apps/web/src/lib/gitReactQuery.ts
  • apps/web/src/lib/providerDiscoveryReactQuery.test.ts
  • apps/web/src/lib/providerDiscoveryReactQuery.ts
  • apps/web/src/planReview.test.ts
  • apps/web/src/planReview.ts
  • apps/web/src/terminalStateStore.test.ts
  • packages/contracts/src/auth.test.ts
  • packages/contracts/src/git.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/provider.ts
  • packages/contracts/src/providerDiscovery.ts

Comment thread apps/web/src/lib/gitReactQuery.ts Outdated
Comment on lines +17 to +30
function codexProviderOptionsKey(
providerOptions: ProviderStartOptions | null | undefined,
): string | null {
const codexOptions = providerOptions?.codex;
if (!codexOptions) {
return null;
}

return JSON.stringify({
binaryPath: codexOptions.binaryPath ?? null,
homePath: codexOptions.homePath ?? null,
launchArgs: codexOptions.launchArgs ?? null,
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Consider extracting the duplicated helper to a shared module.

This helper function is identical to codexDiscoveryProviderOptionsKey in providerDiscoveryReactQuery.ts. Both extract and serialize the same three codex fields. Extracting to a shared utility (e.g., apps/web/src/lib/providerOptions.ts) would eliminate duplication and provide a single source of truth for codex options keying.

♻️ Suggested refactor

Create a shared helper module:

// apps/web/src/lib/providerOptions.ts
import type { ProviderStartOptions } from "`@jcode/contracts`";

export function buildCodexProviderOptionsKey(
  providerOptions: ProviderStartOptions | null | undefined,
): string | null {
  const codexOptions = providerOptions?.codex;
  if (!codexOptions) {
    return null;
  }

  return JSON.stringify({
    binaryPath: codexOptions.binaryPath ?? null,
    homePath: codexOptions.homePath ?? null,
    launchArgs: codexOptions.launchArgs ?? null,
  });
}

Then import and use in both files:

+import { buildCodexProviderOptionsKey } from "./providerOptions";
+
-function codexProviderOptionsKey(
-  providerOptions: ProviderStartOptions | null | undefined,
-): string | null {
-  const codexOptions = providerOptions?.codex;
-  if (!codexOptions) {
-    return null;
-  }
-
-  return JSON.stringify({
-    binaryPath: codexOptions.binaryPath ?? null,
-    homePath: codexOptions.homePath ?? null,
-    launchArgs: codexOptions.launchArgs ?? null,
-  });
-}

 export function gitSummarizeDiffQueryOptions(input: {
   // ...
 }) {
   const normalizedPatch = input.patch?.trim() ?? null;
   const patchKey = /*...*/;

-  const providerOptionsKey = codexProviderOptionsKey(input.providerOptions);
+  const providerOptionsKey = buildCodexProviderOptionsKey(input.providerOptions);

   return queryOptions({
     // ...
   });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/gitReactQuery.ts` around lines 17 - 30, Extract the
duplicated JSON-serialization logic into a single shared helper (e.g.,
buildCodexProviderOptionsKey) and replace both codexProviderOptionsKey and
codexDiscoveryProviderOptionsKey to call that helper; specifically, move the
logic that reads providerOptions?.codex and returns JSON.stringify({ binaryPath:
codexOptions.binaryPath ?? null, homePath: codexOptions.homePath ?? null,
launchArgs: codexOptions.launchArgs ?? null }) into the new
buildCodexProviderOptionsKey(providerOptions) function, export it from a common
module, then import and use it from the modules that currently define
codexProviderOptionsKey and codexDiscoveryProviderOptionsKey so the duplicate
implementations are removed.

@Jay1 Jay1 merged commit 6b82365 into main Jun 2, 2026
8 checks passed
@Jay1 Jay1 deleted the jcode/markdown-link-robustness branch June 2, 2026 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 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.

1 participant