Skip to content

Add Codex chat integration to desktop app#6

Closed
arul28 wants to merge 1 commit intomainfrom
codex/add-codex-chat-integration
Closed

Add Codex chat integration to desktop app#6
arul28 wants to merge 1 commit intomainfrom
codex/add-codex-chat-integration

Conversation

@arul28
Copy link
Copy Markdown
Owner

@arul28 arul28 commented Feb 19, 2026

Summary

  • add the new CodexChatPage, its supporting state slice, renderer UI elements, and startup path wiring so the desktop app can host the Codex chat surface
  • introduce shared IPC/types and preload hooks plus a main-process codex service stack (JSON-RPC parser, lane thread store, codex app server service) that materializes threads and exposes the required endpoints
  • cover the new services/state with targeted unit tests so the integration logic stays verifiable as it evolves

Testing

  • Not run (not requested)

Summary by CodeRabbit

  • New Features

    • Integrated Codex chat with thread management, turn-based conversations, and model/reasoning effort selection.
    • Added ChatGPT and API key authentication for Codex accounts.
    • Added approval workflow UI for command execution and file change requests.
    • Lane-thread binding persistence for chat continuity.
  • UI/UX Enhancements

    • New Codex tab in main navigation.
    • Codex Chat button in terminal view for inline and full-page chat modes.
    • Lane picker dialog to select lanes for inline chat.
    • Connection state management and retry controls.
  • Performance

    • Optimized Vite caching and development configuration.
    • Improved startup page initialization with staged data loading.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive Codex app-server integration into the desktop application. It adds a new backend service that spawns and manages the Codex process via JSON-RPC over stdio, implements lane/thread lifecycle management with persistent storage, exposes IPC handlers for all Codex operations, creates a new CodexChatPage UI component for chat interactions, and integrates Codex chat capabilities into existing views.

Changes

Cohort / File(s) Summary
Core Codex Service
apps/desktop/src/main/services/codex/codexAppServerService.ts, codexAppServerService.test.ts
Implements complete Codex app-server orchestration with process spawning, JSON-RPC over stdio protocol, request/response tracking, approval workflows, lane/thread management, and lifecycle controls; comprehensive test suite covers request/response correlation, approval flows, turn operations, retry logic, and error handling.
JSON-RPC Protocol
apps/desktop/src/main/services/codex/jsonRpcLineParser.ts, jsonRpcLineParser.test.ts
Defines JSON-RPC message types and line-based parser for newline-delimited JSON communication; handles buffering, partial messages, and error resilience with unit tests for multi-chunk parsing and invalid JSON handling.
Lane/Thread Persistence
apps/desktop/src/main/services/codex/laneThreadStore.ts, laneThreadStore.test.ts
Implements per-lane thread binding storage with recent thread history, default thread tracking, and reverse lookup; includes sanitization and persistent backing via AdeDb.
Main Process Integration
apps/desktop/src/main/main.ts
Wires codexAppServerService into AppContext, initializes with db/logger/laneService/clientVersion, exposes via IPC, and ensures proper disposal on context close.
IPC & Bridge
apps/desktop/src/main/services/ipc/registerIpc.ts, apps/desktop/src/preload/preload.ts, apps/desktop/src/preload/global.d.ts
Adds 18 new IPC handlers for Codex operations (connection, threads, turns, accounts, models, approvals); exposes codex API surface on window.ade via preload; includes type imports for all Codex types.
Shared IPC Channels
apps/desktop/src/shared/ipc.ts
Adds 19 IPC channel constants under ade.codex.* namespace for all Codex operations.
Type Definitions
apps/desktop/src/shared/types.ts
Defines comprehensive TypeScript types for Codex concepts: connection state, accounts, rate limits, models, threads, turns, approvals, authentication, and event payloads; all exported for type safety across main/renderer.
Chat UI Component
apps/desktop/src/renderer/components/codex/CodexChatPage.tsx
Introduces full-featured Codex chat UI with state management, thread lifecycle handling, model/effort selection, prompt submission, turn rendering, and approval workflows; supports both embedded and full-page modes.
Chat State Management
apps/desktop/src/renderer/state/codexChatState.ts, codexChatState.test.ts
Implements reducer-based state container for chat with hydration from threads, turn/item management, streaming delta handling, and approval tracking; includes type definitions and selector utilities.
UI Integration
apps/desktop/src/renderer/components/app/App.tsx, TabNav.tsx, LaneWorkPane.tsx, TerminalsPage.tsx, StartupAuthPage.tsx
Registers /codex route, adds Codex tab navigation, integrates CodexChatPage into lane work pane with embedded mode, adds inline Codex chat to terminals view with lane picker dialog, refactors startup flow for cached snapshot handling.
Build Configuration
apps/desktop/package.json, apps/desktop/vite.config.ts
Updates dev script with --force flag for Vite; adds stable cache directory and optimizeDeps configuration to exclude UI/motion libraries from pre-bundling.

Sequence Diagram(s)

sequenceDiagram
    participant Renderer as Renderer Process
    participant IPC as IPC Bridge
    participant Service as CodexAppServerService
    participant ChildProcess as Codex App Server
    
    Renderer->>IPC: codexThreadStart({ laneId, model })
    IPC->>Service: threadStart(args)
    Service->>ChildProcess: thread/start (JSON-RPC)
    ChildProcess-->>Service: thread/start response
    Service->>Service: Store lane-thread binding
    Service-->>IPC: Return CodexThread
    IPC-->>Renderer: Promise<CodexThread>
    
    Renderer->>IPC: codexTurnStart({ threadId, prompt, effort })
    IPC->>Service: turnStart(args)
    Service->>ChildProcess: turn/start (JSON-RPC)
    ChildProcess-->>Service: turn/start response
    ChildProcess-->>Service: item/commandExecution (notification)
    Service-->>Renderer: codexEvent (via IPC)
    Renderer->>Renderer: Update chat UI with turn
    
    ChildProcess-->>Service: item/fileChange (approval request)
    Service->>Service: Store pending approval
    Service-->>Renderer: codexEvent (approval request)
    Renderer->>Renderer: Render approval card
    
    Renderer->>IPC: codexRespondApproval({ requestId, decision })
    IPC->>Service: respondToApprovalRequest(requestId, decision)
    Service->>ChildProcess: approval/response (JSON-RPC)
    ChildProcess-->>Service: acknowledgement
    Service->>Service: Clear pending approval
Loading
sequenceDiagram
    participant User as User
    participant CodexChatUI as CodexChatPage Component
    participant ChatState as Chat State (Reducer)
    participant API as Codex API (IPC)
    
    User->>CodexChatUI: Select lane, write prompt
    CodexChatUI->>API: getLaneBinding(laneId)
    API-->>CodexChatUI: CodexLaneThreadBinding
    alt Thread exists
        CodexChatUI->>API: threadResume({ laneId, threadId })
    else No thread
        CodexChatUI->>API: threadStart({ laneId, model })
    end
    API-->>CodexChatUI: CodexThread
    
    CodexChatUI->>ChatState: hydrate-thread action
    ChatState-->>CodexChatUI: Updated state with turns
    CodexChatUI->>CodexChatUI: Render thread turns
    
    User->>CodexChatUI: Submit prompt with model/effort
    CodexChatUI->>API: turnStart({ threadId, prompt, model, effort })
    CodexChatUI->>API: onEvent(callback)
    API-->>CodexChatUI: Turn initiated
    
    API-->>CodexChatUI: codexEvent (turn-started, item notifications, completion)
    CodexChatUI->>ChatState: notification action (streaming deltas)
    ChatState-->>CodexChatUI: Updated with turn items
    CodexChatUI->>CodexChatUI: Re-render active turn
    
    alt Item requires approval
        API-->>CodexChatUI: codexEvent (approval request)
        CodexChatUI->>CodexChatUI: Render ApprovalCard
        User->>CodexChatUI: Accept/decline
        CodexChatUI->>API: codexRespondApproval({ requestId, decision })
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

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

@arul28 arul28 closed this Feb 23, 2026
@arul28 arul28 deleted the codex/add-codex-chat-integration branch March 14, 2026 05:00
arul28 added a commit that referenced this pull request Apr 16, 2026
Ports items #6, #8, and #10 from .factory/library/mobile-chat-port-plan.md.

#6 — Streaming shimmer on the active assistant bubble:
- New ADEStreamingShimmer view modifier (Views/Components/): gentle
  one-direction gradient sweep + accent glow + stroke, gated on isActive
  and respecting Reduce Motion (becomes a no-op).
- WorkChatMessageBubble gains an `isLive: Bool = false` prop; the timeline
  call site passes `isLatestAssistantMessageLive(message)` so only the
  newest assistant bubble of an active session shimmers.
- Liveness derivation lives in WorkChatSessionView+MessageLiveness.swift
  (scans visibleTimeline tail for the most recent assistant message id).

#8 — Tool-result truncation, preview, and show-all toggle:
- WorkToolCardView now shows a first-line preview in the collapsed header.
- Long results (> 500 chars) render truncated with a "Show all (N chars)"
  toggle; copy affordance is already provided by WorkOutputBlockHeader.
- Helpers: workToolResultPreview, workToolResultTruncate,
  workToolResultByteLabel.

#10 — Context-compact divider:
- WorkEventCardView special-cases kind == "contextCompact" and renders
  the new WorkContextCompactDivider: a horizontal hairline flanking a
  warning-tinted chip with the rectangle.compress.vertical icon, tokens
  freed (when parseable) and an AUTO/MANUAL trigger tag.
- Parsing handled by WorkContextCompactSummary.

Tests (appended to the end of ADETests.swift per coordination rule):
8 new unit tests covering the truncation boundary, preview extraction,
byte-label formatting, and context-compact summary parsing.
arul28 added a commit that referenced this pull request Apr 17, 2026
* Add sticky commit bar to lane detail

* WIP: lanes tab, sync pairing, and settings refactor

Staging in-progress work on mobile lanes tab, desktop sync pairing
(PIN store), iOS design system/haptics, and connection settings
screen before merging in work tab branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* mobile work tab

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fixing review agent comments

* Sync active lane presence across devices

* Restore the green iOS baseline validation path

Make PR list hydration tolerate older/local test schemas, restore lane list ordering, and fix the manifest desktop test command so baseline validation can run again.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Promote shared iOS glass primitives and cache code highlighting

* Polish cached Lanes state and gating

Keep cached lane context visible while making offline, hydrating, and syncing states explicit so live git actions never fail silently on iPhone.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Restore cached lane diff inspection offline

* Synthesize foundation scrutiny rerun

Record the passing foundation scrutiny rerun after restoring the iOS-only hard gate and verifying the offline lane diff regression fix.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Extend Work sync parity metadata

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Prioritize mobile Work session triage and chat creation

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Split mobile Work session detail views

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Synthesize work scrutiny findings

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Fix Work follow-up parity gaps

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Synthesize work scrutiny rerun

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Add read-only Files mobile cache contracts

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Rebuild the iOS Files mobile browser

Split the Files tab into focused browser/detail slices so workspace switching, breadcrumbs, root-state messaging, and search flows stay readable and explicit on iPhone.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Fix Work crash on duplicate transcript merge keys

The cached base transcript can occasionally hold two envelopes with the
same merge key (hosts replay activity events during resume), and the
previous Dictionary(uniqueKeysWithValues:) init fatal-errors on that.
Replace it with an in-place dedupe that keeps the later envelope and
harden laneById against duplicate lane ids with uniquingKeysWith.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Rework Files detail around content-first layout

Replaces the five-stacked-glass-cards surface with a pinned thin header
(icon + name + language/size/read-only chips), an inline mode and diff
picker, and a content-hero region that lets the code/diff/image fill
the screen instead of competing with metadata chrome. Metadata and
history move into a Details sheet (info.circle in the toolbar) so they
stay one tap away without dominating the read flow.

Compact banners replace ADENoticeCard for disconnected / load-failure
states, and the binary and image-pending fallbacks share a single
centered FilesContentFallback. Transition IDs (files-container /
-icon / -title) stay stable so the zoom-push from FilesDirectoryScreen
keeps working. All read-only framing is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Reshape Settings into a mobile shell with grouped sections

Split the 1156-line ConnectionSettingsView.swift into focused sub-files
under Views/Settings/ (all < 500 lines), and reorganize the screen into
a proper settings shell: connection status header with inline reconnect/
disconnect quick action, pairing section (Discover/Scan/Manual), theme
tiles, and a diagnostics/about section with app version and paired-host
details. Preserves Bonjour discovery, QR scanning, manual entry, and
PIN pairing flows end-to-end via the existing SyncService API — no new
service methods.

- ConnectionSettingsView.swift: top-level shell + aurora background
- SettingsSupportTypes.swift: PinPreset, PairSheetRoute, status tint helpers
- SettingsConnectionHeader.swift: live indicator + host + Reconnect action
- SettingsPairingSection.swift: pair rows + Discover/QR/Manual sheets
- SettingsPinSheet.swift: PIN entry + digit box + keypad
- SettingsAppearanceSection.swift: theme tiles
- SettingsDiagnosticsSection.swift: version, paired host, last sync

* Cache Work activity transcripts and flatten filter chips

- Memoize parseWorkChatTranscript per session + buffer fingerprint so
  activityFeed stops re-parsing every localStateRevision tick
- Split WorkChatSessionView timeline switch into @ViewBuilder helpers
  in a new WorkChatSessionView+Timeline.swift (keeps parent under 500
  lines and lets the compiler skip card branches that haven't changed)
- Replace LaneMicroChip glass chips inside the filter card with a flat
  WorkFlatCountChip to avoid glass-on-glass nesting

* Add PR mobile snapshot sync contract for iOS parity

Introduce prs.getMobileSnapshot: a single viewer-allowed sync command
that returns stack metadata, create-PR eligibility, workflow cards
(queue/integration/rebase), and per-PR capability gates in one payload
so the iOS PRs rebuild can render its list/detail/workflow surfaces
without fanning out across several commands. Contract is additive —
existing desktop consumers of the PR service and sync registry are
untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Harden Dictionary merges and drop duplicate matched-geometry sources

Part A — replace Dictionary(uniqueKeysWithValues:) with coalescing
merges so sync reconciliation overlaps cannot fatal the app:
- Database.swift lane-row keying
- LaneTreeView.swift lane-by-id memo

Part B — remove the destination-side matchedGeometryEffect emissions
in LaneDetailHeaderCard and WorkSessionHeader. The container's
navigationTransition(.zoom(sourceID:)) interpolates child layouts
during the push, so having the detail header ALSO emit isSource=true
for lane-icon/title/status and work-icon/title/status groups is what
SwiftUI warns about ("Multiple inserted views ... have isSource: true,
results are undefined"). The list rows remain the sole source. Init
signatures are preserved for call-site compatibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Split PRsTabView.swift into per-file view modules

Pure mechanical file split of the 2480-line monolith into 14 focused
files under apps/ios/ADE/Views/PRs/ so the PRs mobile rebuild can
operate on clean per-file surfaces. No behavior, type, data-access, or
UI changes. Visibility narrowed from private to internal where types
are shared across the new files.

New files (all under 500 lines):
- PrModels.swift                — shared structs/enums
- PrHelpers.swift               — free functions + ISO date parsing
- PrListRowModifier.swift       — .prListRow() modifier
- PrFiltersCard.swift           — filters card + signal chip
- PrRowCard.swift               — list row card
- PrsRootScreen.swift           — PRsTabView root
- PrDetailScreen.swift          — PrDetailView root
- PrDetailOverviewTab.swift     — overview tab, header, section card, chip wrap, cleanup banner
- PrDetailFilesTab.swift        — files tab, file diff card, unified diff view
- PrDetailChecksTab.swift       — checks tab, check row
- PrDetailActivityTab.swift     — activity tab, timeline row
- PrWorkflowCards.swift         — integration/queue/rebase workflow cards
- PrStackSheet.swift            — stack members sheet
- CreatePrWizardView.swift      — wizard + step indicator + markdown renderer

PRsTabView.swift is emptied to a forwarding comment to preserve the
pbxproj reference without further edits.

* Wire PrMobileSnapshot into PRs root screen and unified workflow cards

Adopt the new prs.getMobileSnapshot contract (commit ad17c74) on the
root PRs surface. The snapshot replaces the three-fan-out fetch for
queue/integration/rebase state with a single unified `workflowCards`
array, and its per-PR capability map drives the row swipe actions so
Close/Reopen respect the host's actual gating instead of guessing from
the stored PR state. Create PR is now disabled when createCapabilities
reports canCreateAny=false, and the previously dead status notice for
disconnected / hydrating / failed phases finally renders at the top of
the list.

Adds a new PrMobileWorkflowCardView that dispatches on card.kind
(queue | integration | rebase) so one ForEach covers all three. Legacy
per-kind fetches remain as a fallback so the list still renders when a
paired host predates the mobile-snapshot command.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Gate PR detail actions and Create wizard by host capabilities

Thread PrActionCapabilities through PrDetailScreen → PrOverviewTab so
merge/close/reopen/request-reviewers/rerun-checks/comment gates come
from the host snapshot when available, and fall back to the legacy
supportsRemoteAction probe + PrActionAvailability when the mobile
snapshot hasn't arrived (offline / pre-contract host). Surface
mergeBlockedReason under the merge button so users see the specific
blocker (draft, failing checks, closed) instead of a silent disabled
state.

Accept optional PrCreateCapabilities in CreatePrWizardView. When
present, the lane picker shows only eligible lanes, the target branch
defaults from the host, and blocked lanes are listed separately with
their blockedReason. Nil fallback keeps the existing LaneSummary flow
intact so offline create still works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Forward createCapabilities from root into Create PR wizard

The wizard's new createCapabilities prop was landed with a nil default
so the root-screen wire-up could follow. Pass mobileSnapshot?.create
Capabilities through so the wizard actually filters to canCreate lanes,
fills the default base branch from host metadata, and surfaces each
lane's blockedReason. With a nil snapshot the wizard still falls back
to the raw lanes list unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Polish PR stack sheet, workflow cards, and create wizard

Stack sheet: join PrGroupMemberSummary with snapshot.stacks data so the
chain renders with role badges (BASE/BODY/HEAD), depth indentation
(capped at 4 levels to stay readable on iPhone), dirty-worktree
warnings per lane, and live PR state pills. Each member with a PR is
now a tap target that pushes PrDetailView inside the sheet's own
NavigationStack — users no longer have to dismiss + re-navigate from
the root to open a stack member. Falls back to position-derived depth
when the snapshot is unavailable so offline stacks still render.

Workflow cards:
- queue: position chip (3/5) now renders next to the title instead of
  at the bottom, so progress is visible before the action buttons
- integration: "Open linked PR" upgraded from ghost glass to prominent
  glass with a full-width label + icon so the escape hatch reads as a
  primary action
- rebase: the CONFLICT badge is a new PrConflictBadge with a solid red
  background and warning icon so a predicted conflict can't be glanced
  past alongside the other tinted status pills

Create wizard:
- PrStepIndicator: active step title shown beside the counter, segment
  labels align to segments and highlight up-to-current-step so users
  see where they are at a glance
- Blocked-lane list got a lock icon header, per-row minus-circle
  icons, and a subtle warning-tinted background so "not eligible"
  reads as a deliberate state, not greyed-out content

Tests: two new cases for buildStackRows covering snapshot-joined and
fallback paths. Build + targeted tests green on iPhone 17 Pro.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add chat activity indicator pill + per-turn provider badge

Ports items #2 and #3 from .factory/library/mobile-chat-port-plan.md.

- WorkActivityIndicator.swift: new view that scans the transcript tail
  for the most recent running command / tool call / file change / named
  activity / web search / subagent event and renders a one-line pill
  ("Running: ls -la", "Editing .../foo.ts", "Searching", etc.) with a
  pulsing dot. Falls back to "Thinking" when nothing specific is
  streaming. Respects Reduce Motion. Drop-in for
  WorkChatSessionView.streamingStatusSection.

- WorkChatHeaderAndMessageViews.swift: WorkChatMessageBubble now picks
  up the active session's provider from a new `workChatProvider`
  environment value and renders a compact provider chip (icon + label
  tinted by providerTint) next to the "Assistant" role label. No change
  to call sites — ancestor views opt in via
  .environment(\.workChatProvider, chatSummary?.provider).

* Add reasoning card and smart-autoscroll jump-to-latest pill to Work chat

Ports items #1 and #5 from the desktop chat port plan. A new
WorkReasoningCard replaces the generic event-card rendering for
reasoning entries: brain icon pulses while a turn is streaming, the
body stays open, three staggered dots signal live thinking, and once
the turn settles the card auto-collapses behind a "Reasoning" label
so it stops competing with the final assistant message.

Smart autoscroll now tracks an unreadBelowCount instead of always
yanking the view: when the user scrolls up and new entries arrive, the
count accumulates and a floating WorkJumpToLatestPill appears at the
bottom-trailing edge showing "N new" with the accent tint. Tap it or
naturally scroll back to the end to clear the count. Reduce-motion is
honored throughout via ADEMotion + static-dot fallbacks.

Also lands .factory/library/mobile-chat-port-plan.md so future work
on the other eight port-plan items has the same inventory to work
from.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Upgrade queued-steer strip with collapsible rows and haptic cancel

Chat composer plan item #7 — queued-steer strip. The mobile strip now
mirrors desktop's PendingSteerItem treatment:

- Header collapses by default to a compact "N queued · preview" row
  with a chevron; taps toggle the list. Strip auto-expands whenever a
  row enters edit mode so edits aren't hidden behind a collapse tap.
- Each row ribbon starts with a discreet "Queued" pill (accent tint)
  and a relative timestamp so the queued state reads at a glance
  without competing with the body text.
- Body text now clips at 2 lines with tail truncation. Long messages
  stay reachable through the Edit button's inline TextEditor.
- Cancel drives a light-weight .sensoryFeedback(.impact) so the strip
  confirms destructive actions through haptics, not just visuals.

File stays under the 500-line ceiling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Rebuild Work sidebar to match desktop SessionListPane architecture

Replaces the phone-only filter card + hard-coded section stack with the
same information architecture the desktop uses: a compact toolbar
(Search + New Chat accent button + Filter funnel toggle), an
expandable Group/Lane filter panel, and a grouped session list driven
by the user's chosen organization — byLane / byStatus / byTime —
with collapsible section headers showing icon, label, and count
badge. Organization and per-section collapse state persist via
@AppStorage so the sidebar reopens the way it was left.

Iconography matches desktop: magnifyingglass for search,
line.3.horizontal.decrease.circle for the funnel, plus for New Chat,
arrow.triangle.branch for lane grouping, and colored status dots for
the byStatus sections. Removes the always-on status chip strip and
the standalone "0 live" chip in favor of the filter-panel-scoped
live/waiting count chips that only appear when the panel is open.

Session row logic extracted into a reusable WorkSessionListRow so the
new grouped loop can render any organization with consistent swipe
and context-menu actions. New WorkSessionGrouping.swift holds the
pure grouping helpers (byStatus / byLane / byTime bucketers plus the
AppStorage serialization for collapsed-section ids).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Wire WorkActivityIndicator + provider env into the chat session view

Drops the spinner-only streamingStatusSection in favor of the activity
indicator settings-worker shipped at 24db48b, so the tail of the
transcript always surfaces the current tool/command/file edit as a
"Label · detail" pill when a turn is streaming. Also injects
\.workChatProvider down the transcript ForEach so every assistant
bubble can render the per-turn provider chip without each row having
to resolve it independently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Chat polish: streaming shimmer, tool result truncation, compact divider

Ports items #6, #8, and #10 from .factory/library/mobile-chat-port-plan.md.

#6 — Streaming shimmer on the active assistant bubble:
- New ADEStreamingShimmer view modifier (Views/Components/): gentle
  one-direction gradient sweep + accent glow + stroke, gated on isActive
  and respecting Reduce Motion (becomes a no-op).
- WorkChatMessageBubble gains an `isLive: Bool = false` prop; the timeline
  call site passes `isLatestAssistantMessageLive(message)` so only the
  newest assistant bubble of an active session shimmers.
- Liveness derivation lives in WorkChatSessionView+MessageLiveness.swift
  (scans visibleTimeline tail for the most recent assistant message id).

#8 — Tool-result truncation, preview, and show-all toggle:
- WorkToolCardView now shows a first-line preview in the collapsed header.
- Long results (> 500 chars) render truncated with a "Show all (N chars)"
  toggle; copy affordance is already provided by WorkOutputBlockHeader.
- Helpers: workToolResultPreview, workToolResultTruncate,
  workToolResultByteLabel.

#10 — Context-compact divider:
- WorkEventCardView special-cases kind == "contextCompact" and renders
  the new WorkContextCompactDivider: a horizontal hairline flanking a
  warning-tinted chip with the rectangle.compress.vertical icon, tokens
  freed (when parseable) and an AUTO/MANUAL trigger tag.
- Parsing handled by WorkContextCompactSummary.

Tests (appended to the end of ADETests.swift per coordination rule):
8 new unit tests covering the truncation boundary, preview extraction,
byte-label formatting, and context-compact summary parsing.

* Use branded provider logos + fix Work sidebar row and chat header layout

User flagged three concrete problems in the mobile Work surface:
the session rows all shared a generic grey brain icon instead of the
branded model logos the desktop uses, timestamps leaked as raw ISO
strings (`2026-04-16T07:40:08.095Z`) because the parser did not
accept fractional seconds, and the chat detail header wrapped its
chip row off-screen while showing a useless "—" duration and a
truncated "Summa..." column on mobile.

Bundle the LobeHub static SVGs (claude-color, codex-color, cursor,
opencode + anthropic / openai family marks) into Assets.xcassets as
Provider* imagesets with preserves-vector-representation. A new
WorkProviderLogo view renders the branded asset when one exists and
falls back to the tinted SF Symbol we already had.

Rewrite WorkSessionRow around the desktop SessionListPane cadence:
one line for status-dot + title + relative time, an optional preview
line, and a horizontal-scrolling chip strip (status pill, lane, model,
device presence). Drop the standalone duration column that was only
ever rendering "—" on ended sessions.

Rewrite WorkSessionHeader to a single collapsed meta line
("3h ago · sonnet"), a horizontal chip ScrollView so "Claude /
sonnet / Primary" never clips past the edge again, and a summary
line that only renders when there is a real summary — no more empty
"Duration —" placeholder. Session starts now parse cleanly thanks to
workDateFormatterFractional, so "Started" reads as relative time
everywhere else too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Polish Work logo avatar container + row chip strip + ended-session control bar

Wrap the branded provider SVG in a tinted rounded-card container so the
mobile mark reads the way desktop's Claude.Avatar / Codex.Avatar do —
logo centered on a tinted squircle with a hairline border — instead of
a raw glyph floating on the background.

Drop the redundant ENDED / closed status pill from session rows whose
status matches the section they already live in. Needs-input, running,
idle, and archived rows keep their pill because those differ from the
group header.

Rewrite WorkSessionControlBar around the actual session states. On an
ENDED session the only sensible action is Resume, so the old "Close
session" filled button (closing an already-closed session) is gone and
Resume chat takes the whole row as the prominent CTA. On active turns
Interrupt is the prominent action; End is the small escape hatch. Idle
and awaiting-input get a Resume + End pair.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Drop the Turn finished card that dumped raw usage JSON into the transcript

The Turn status card already marks completion and the session usage
summary card at the top of the chat aggregates token spend. Rendering
a second "Turn finished" event card for every .done envelope just
dumped the host's raw summary string — usually a JSON usage blob —
into the transcript alongside the real messages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add inline model picker to the chat composer

Composer chip strip now surfaces Model / Access / Profile as three
labeled pills with the branded provider logo on the model chip, and
tapping the model chip opens a WorkModelPickerSheet that mirrors the
desktop ProviderModelSelector mobile variant — grouped by provider,
branded logos inline, tier badge, one-line tagline, and a checkmark
for the currently applied model. Committing a selection calls
updateChatSession on the paired host and marks the change with a
light haptic.

Unknown models (e.g. a freshly released id the mobile catalog doesn't
know about yet) still render as the active entry so the picker never
hides the live host model.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Replace New Chat modal with a pushed welcome screen

Desktop's "+ New Chat" flows straight into a dedicated welcome page:
big ADE word-mark, tagline, workspace pill, and a prominent composer
at the bottom. Typing and sending turns the page into the live chat
without bouncing back to a sidebar. Mobile currently opens a six-step
slide-up sheet for the same intent, which the user called out as
wrong for this flow.

Add WorkNewChatScreen and a WorkNewChatRoute hashable value. The "+"
toolbar button, the inline "New Chat" button in the sidebar toolbar,
and the empty-state CTA now push the route onto the NavigationStack
path instead of opening WorkNewChatSheet. Inside the screen the
composer defaults to claude-sonnet-4-6 but the same
WorkModelPickerSheet the chat composer uses is one tap away, so
users can still switch provider/model before sending. Submitting
creates the host chat session and replaces the path with the live
session route — Back goes to the sidebar, not to an empty form.

The old WorkNewChatSheet stays in the codebase for now so the sync
surfaces it still references keep compiling; it can be pulled once
nothing else presents it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Streamline Work chat composer, picker, and chrome

Drop the bulky two-line composer chips for a single-row strip of compact
desktop-style pills (access dot + runtime label, model logo + short name).
The access pill flips runtime modes through an inline menu instead of
routing users through a separate Chat Settings sheet, which is removed
from the UI entirely. Redesign the model picker to match the desktop
ProviderModelSelector — "Select Model" title, search field, provider
tab strip with count badges, and grouped rows with active/Ready chips.
Hide the tab bar once a session is pushed so the transcript and
composer get the full screen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Collapse Work composer into a single desktop-style container

Fold the text field, pill strip, and send button into one rounded
container so the composer reads as one unit like the desktop
"Type to vibecode…" box. Drop the redundant ENDED/RUNNING status pill
row with its inline play/stop button — the session control bar above
the transcript already owns the Resume/Interrupt CTA and the
"Resume this chat before sending another message." feedback line below
communicates status in prose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Flow Work chat transcript like a document

Drop the heavy card chrome the timeline used on every message, turn
status, and system notice. User messages stay right-aligned with a
small tinted bubble; assistant messages shed the box entirely and
render as provider-chipped prose. Low-value event kinds
(status / activity / notice / todo / autoApproval / webSearch / promptSuggestion
/ toolUseSummary / pendingInputResolved) collapse into a single-line
ribbon instead of a full card, matching the desktop's document-style
feel. The session header drops its adeListCard chrome for a compact
status dot · lane · meta line, and the reasoning card now defaults to
collapsed with a slimmer background so it stops competing with the
final assistant turn for attention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Tune composer typing, markdown pills, and model catalog

- Composer now accepts keystrokes while the session is ended so the
  user can draft before hitting Resume. The feedback line below still
  explains why send is blocked, and Send itself stays disabled via a
  new canSend gate.
- Give inline markdown code runs the desktop pill treatment — tinted
  accent background, monospaced font, accent foreground — so
  identifiers, branch names, and file paths pop out of prose like they
  do on desktop.
- Expand the mobile model catalog to mirror
  apps/desktop/src/shared/modelRegistry.ts: Claude Opus 4.6 / Opus 4.6
  1M / Sonnet 4.6 / Haiku 4.5; the seven shipping Codex CLI tiers;
  Cursor Auto/Sonnet-thinking/Sonnet/GPT-5/Codex; and a broad OpenCode
  row set (Anthropic / OpenAI / Google / xAI / DeepSeek / LM Studio /
  Ollama). Ids match the sync-contract shape the host accepts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Ship a properly defined composer card

Rewrite composerInset so the composer reads as one bold rounded
container rather than a faint tinted rectangle. The new card gets a
deeper recessedBackground fill, a 22pt radius, a visible border
stroke, and a subtle drop shadow so it sits cleanly over the chat
surface. The send button shows a visible disabled state (accent ring
outline + secondary glyph) instead of fading into the background, and
takes on an accent fill with a soft purple halo the moment typing
begins, mirroring the desktop "Send" affordance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Give composer desktop-shaped Send pill and full model name

The composer now renders the model pill with the desktop display
name ("Claude Sonnet 4.6" instead of just "Sonnet"), and replaces
the icon-only circle with a desktop-shaped Send pill — paperplane
glyph plus a "Send" label in a capsule that fills with accent and
halos purple the moment the draft has content. Wrap the card in
ultra-thin material over a darker tint so the composer sits cleanly
against the transcript background and reads as one container.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Drop outer composer inset bubble

The composerInset used to paint a faint glass rectangle around the
whole bottom area (composer card + feedback text), which read as a
second "bubble" overlapping the composer card itself. Since the
composerCard now owns its own clearly-defined rounded surface we can
drop the outer background + glassEffect and let the feedback text sit
plainly below the card.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Move composer feedback above the composer card

The "Resume this chat before sending another message." (and similar
status-blocked messages) now render above the composer card instead
of below, and the bottom padding is dropped to zero so the card sits
flush with the safe-area edge. Feedback text is also centered to
read as a banner hint rather than a footnote.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Match desktop model picker organization and new chat composer

- Model picker now uses the desktop ModelCatalogPanel's 2-level
  hierarchy: CLAUDE / CODEX / CURSOR / OPENCODE group tab strip,
  then a provider badge row inside the active group. OpenCode
  exposes its full provider ladder (Anthropic, OpenAI, Google,
  xAI, DeepSeek, LM Studio, Ollama) as horizontal badges just like
  the desktop panel, with count chips on each. Search mode flattens
  results and renders them grouped by group header, mirroring
  desktop behavior.
- New chat welcome page composer rebuilt to share the same rounded
  glass card, model pill logic (full "Claude Sonnet 4.6" name), and
  desktop-shaped Send capsule as the in-session composer, dropping
  the prior ad-hoc chip + round-button layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* WIP: in-progress mobile droid work before merging main

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* WIP: iOS chat redesign + unified per-provider chat accent

iOS:
- New chatSurfaceAccent with per-provider table (Claude amber, Codex warm-white, etc.)
  mirroring the shared desktop mapping so each chat reads the same on both surfaces.
- Per-model reasoningTiers lookup matching MODEL_REGISTRY — effort pill only renders
  for models that advertise tiers (Opus: low/med/high/max, Sonnet: low/med/high,
  Haiku hides entirely, Codex tiers: low/med/high/xhigh, mini: med/high).
- Compact "…" nav toolbar menu (lane name + Go to lane) replacing the inline
  status row under the chat title.
- Stop button folded into the composer Send slot while the assistant streams;
  removed the full-width yellow WorkSessionControlBar.
- Turn-separator pill between user turns, assistant message card surface, dropped
  redundant model-badge chip above each response and inside the USAGE row, compact
  Thought pill, k/M-abbreviated usage numbers with separate Cache / New cache.
- Retired unused LaneChatSessionView and its pbxproj references.

Desktop:
- PROVIDER_CHAT_ACCENTS + providerChatAccent() in chatSurfaceTheme.ts; AgentChatPane's
  draftAccent routes through provider first so Claude/Codex chats share a single tone
  across model variants.
- resolveModelAlias now falls back to bySdkModelId so provider-model-id forms like
  "claude-sonnet-4-6" or "gpt-5.4-codex" resolve — fixes mid-turn model switches
  from iOS which sends that form.

Known build issue (pre-existing, not mine): apps/ios PRs area has uncommitted WIP
referencing PrSingleLineEditSheet / PrMultilineEditSheet / PrSubmitReviewSheet
(files don't exist) and ADEColor.surface (field doesn't exist — should be
surfaceBackground). Chat work compiles fine in isolation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Finalize pass: simplify sync/chat services, align iOS schema, docs

- Drop dead code across sync host/remote-command/pairing services, chat
  message builder, agent chat service, registerIpc, and PackedSessionGrid
- Mirror iOS files-cache schema (files_workspaces + 4 snapshot tables) into
  desktop kvDb migrations so generate-ios-bootstrap-sql stays in sync
- Stabilize CommandPalette test by making getDetail's default resolved value
  survive mockClear between tests
- Refresh internal docs for chat slash-command discovery, provider accents,
  PackedSessionGrid resize model, lane device presence, Linear dispatch
  replay, and sync pairing/pin stores

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix PR review and CI issues

* Retry buffered sync updates on transient failures

* Include project root in PTY broadcast tests

* Preserve local Claude commands and orchestrator compaction

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant