Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
92279de
Add sticky commit bar to lane detail
arul28 Apr 14, 2026
f9e610c
WIP: lanes tab, sync pairing, and settings refactor
arul28 Apr 15, 2026
9dd4c0f
mobile work tab
arul28 Mar 30, 2026
2f73c1e
fixing review agent comments
arul28 Mar 30, 2026
aa83a63
Sync active lane presence across devices
arul28 Apr 16, 2026
7a9d34a
Restore the green iOS baseline validation path
arul28 Apr 16, 2026
3e72ea0
Promote shared iOS glass primitives and cache code highlighting
arul28 Apr 16, 2026
8cc19f4
Polish cached Lanes state and gating
arul28 Apr 16, 2026
9f3c04b
Restore cached lane diff inspection offline
arul28 Apr 16, 2026
e4184fc
Synthesize foundation scrutiny rerun
arul28 Apr 16, 2026
f059932
Extend Work sync parity metadata
arul28 Apr 16, 2026
f9d2f12
Prioritize mobile Work session triage and chat creation
arul28 Apr 16, 2026
4e8b956
Split mobile Work session detail views
arul28 Apr 16, 2026
f8ffe45
Synthesize work scrutiny findings
arul28 Apr 16, 2026
2966c3e
Fix Work follow-up parity gaps
arul28 Apr 16, 2026
baa1752
Synthesize work scrutiny rerun
arul28 Apr 16, 2026
bf8d709
Add read-only Files mobile cache contracts
arul28 Apr 16, 2026
37a7914
Rebuild the iOS Files mobile browser
arul28 Apr 16, 2026
efb42f8
Fix Work crash on duplicate transcript merge keys
arul28 Apr 16, 2026
b6078a5
Rework Files detail around content-first layout
arul28 Apr 16, 2026
5a3aa65
Reshape Settings into a mobile shell with grouped sections
arul28 Apr 16, 2026
adebf83
Cache Work activity transcripts and flatten filter chips
arul28 Apr 16, 2026
ad17c74
Add PR mobile snapshot sync contract for iOS parity
arul28 Apr 16, 2026
e1b3ec6
Harden Dictionary merges and drop duplicate matched-geometry sources
arul28 Apr 16, 2026
c8bd5a3
Split PRsTabView.swift into per-file view modules
arul28 Apr 16, 2026
4c47cd4
Wire PrMobileSnapshot into PRs root screen and unified workflow cards
arul28 Apr 16, 2026
5e5c2f5
Gate PR detail actions and Create wizard by host capabilities
arul28 Apr 16, 2026
00e0843
Forward createCapabilities from root into Create PR wizard
arul28 Apr 16, 2026
16c8422
Polish PR stack sheet, workflow cards, and create wizard
arul28 Apr 16, 2026
24db48b
Add chat activity indicator pill + per-turn provider badge
arul28 Apr 16, 2026
db7cf42
Add reasoning card and smart-autoscroll jump-to-latest pill to Work chat
arul28 Apr 16, 2026
204b502
Upgrade queued-steer strip with collapsible rows and haptic cancel
arul28 Apr 16, 2026
145c468
Rebuild Work sidebar to match desktop SessionListPane architecture
arul28 Apr 16, 2026
31e38e7
Wire WorkActivityIndicator + provider env into the chat session view
arul28 Apr 16, 2026
f5887ec
Chat polish: streaming shimmer, tool result truncation, compact divider
arul28 Apr 16, 2026
6d0501d
Use branded provider logos + fix Work sidebar row and chat header layout
arul28 Apr 16, 2026
f09b744
Polish Work logo avatar container + row chip strip + ended-session co…
arul28 Apr 16, 2026
b217287
Drop the Turn finished card that dumped raw usage JSON into the trans…
arul28 Apr 16, 2026
01eb8c0
Add inline model picker to the chat composer
arul28 Apr 16, 2026
e64a392
Replace New Chat modal with a pushed welcome screen
arul28 Apr 16, 2026
81d437f
Streamline Work chat composer, picker, and chrome
arul28 Apr 16, 2026
d650845
Collapse Work composer into a single desktop-style container
arul28 Apr 16, 2026
b452c41
Flow Work chat transcript like a document
arul28 Apr 16, 2026
51929ed
Tune composer typing, markdown pills, and model catalog
arul28 Apr 16, 2026
9283289
Ship a properly defined composer card
arul28 Apr 16, 2026
a89e454
Give composer desktop-shaped Send pill and full model name
arul28 Apr 16, 2026
0f1cec0
Drop outer composer inset bubble
arul28 Apr 16, 2026
ba0320a
Move composer feedback above the composer card
arul28 Apr 16, 2026
6fdd9cf
Match desktop model picker organization and new chat composer
arul28 Apr 16, 2026
958d874
WIP: in-progress mobile droid work before merging main
arul28 Apr 16, 2026
05081b6
Merge origin/main into ade/mobile-droid-attempt-bbdcd095
arul28 Apr 16, 2026
b2ac39c
WIP: iOS chat redesign + unified per-provider chat accent
arul28 Apr 17, 2026
cf03e74
Finalize pass: simplify sync/chat services, align iOS schema, docs
arul28 Apr 17, 2026
0a20945
Fix PR review and CI issues
arul28 Apr 17, 2026
b48fc02
Retry buffered sync updates on transient failures
arul28 Apr 17, 2026
4a56d7f
Include project root in PTY broadcast tests
arul28 Apr 17, 2026
2ab0156
Preserve local Claude commands and orchestrator compaction
arul28 Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"env": {
"CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
}
}
25 changes: 18 additions & 7 deletions .factory/init.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
#!/bin/bash
set -e
set -euo pipefail

# No dependencies to install - iOS app uses only system frameworks (no SPM/CocoaPods/Carthage)
# Verify xcodebuild is available
if ! command -v xcodebuild &> /dev/null; then
echo "ERROR: xcodebuild not found. Xcode must be installed." >&2
exit 1
if ! command -v xcodebuild >/dev/null 2>&1; then
echo "ERROR: xcodebuild not found. Xcode must be installed." >&2
exit 1
fi

echo "Environment ready. iOS app at apps/ios/"
if ! command -v npm >/dev/null 2>&1; then
echo "ERROR: npm not found. Node.js/npm must be installed for desktop parity work." >&2
exit 1
fi

if ! xcrun simctl list devices available | grep -q "iPhone 17 Pro"; then
echo "ERROR: iPhone 17 Pro simulator not available. Install the iOS 26.3 simulator runtime." >&2
exit 1
fi

mkdir -p /tmp/ade-build /tmp/ade-ios-dryrun

echo "Environment ready for ADE iOS parity mission."
echo "Simulator target: iPhone 17 Pro / iOS 26.3.1"
32 changes: 32 additions & 0 deletions .factory/library/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,35 @@ Architectural decisions, patterns discovered, and conventions.
- `LaneDetailPayload`: Full detail (lane + runtime + stack + children + state + suggestions + conflicts + commits + changes + stashes + sessions)
- Lane types: "primary", "worktree", "attached"
- Runtime buckets: "running", "awaiting-input", "ended", "none"

## Mission parity architecture

This mission is about bringing the existing iOS tabs to mobile-first parity with desktop behavior for the surfaces that already exist on iOS.

### Scope boundaries
- In scope tabs: `Lanes`, `Work`, `Files`, `PRs`, `Settings`
- Out of scope product surfaces: dedicated `CTO`, dedicated `Missions`
- `Files` is read-only mobile parity, not Monaco editing parity
- `Settings` is core mobile settings only, not full desktop settings parity

### Worker boundary split
- `ios-worker`: SwiftUI/UI-only work inside `apps/ios/ADE/`
- `sync-parity-worker`: desktop sync-host/shared payload work plus iOS `SyncService` / `RemoteModels` / `Database` changes when parity requires it

### Current tab state snapshot
- `Lanes` is the benchmark tab and should be used as the quality reference for structure, state handling, and shared component patterns
- `Work` is the weakest major tab: feature-rich but extremely monolithic and currently the biggest performance/UX problem
- `Files` is functionally deep but still monolithic and must end as a read-only mobile browsing/search/diff surface
- `PRs` is functionally broad but monolithic; mobile list/detail/workflow adaptation is required
- `Settings` currently centers on sync/pairing + appearance and needs to become a coherent mobile settings shell

### Shared parity pressure points
- `ContentView.swift` owns root tab selection and cross-tab navigation requests (`settingsPresented`, `requestedFilesNavigation`, `requestedLaneNavigation`, `requestedPrNavigation`)
- Many parity gaps are contract gaps, not just renderer gaps; backend expansion is allowed when it is needed to support the existing iOS tabs
- Preserve cached/offline readability and explicit live-action gating when extending contracts

### Quality bar for this mission
- Prefer shared `Views/Components/` primitives over tab-local duplication
- Keep large SwiftUI files split into focused files under per-tab folders
- Preserve source-tab context when navigating away and back
- Avoid expensive derived work in SwiftUI `body`; cache or precompute where possible
11 changes: 11 additions & 0 deletions .factory/library/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,14 @@ Environment variables, external dependencies, and setup notes.
- Development team: configured in Xcode project settings
- Code signing disabled for tests (CODE_SIGNING_ALLOWED = NO)
- Asset catalog warning: BrandMark.imageset references missing logo.png (cosmetic only)

## Mission-specific environment notes
- Mission simulator target: `platform=iOS Simulator,name=iPhone 17 Pro,OS=26.3.1`
- Desktop parity work may also touch `apps/desktop/`; `npm` must be available in addition to `xcodebuild`
- Live synced-behavior validation may launch a local ADE desktop host and pair the simulator against it
- When ADE desktop is running locally for validation, the observed/default sync host port is `8787`

## Validation baseline
- The build path is executable locally
- The iOS test path is executable locally
- The mission starts with 2 baseline failing iOS tests and must fix them in the foundation milestone before treating the suite as green
7 changes: 7 additions & 0 deletions .factory/library/ios-design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ DetailView(item: item)
- Use `matchedTransitionSource(id:in:)` on the list/grid source and `.navigationTransition(.zoom(sourceID:in:))` on the pushed destination for iOS 26 list-to-detail continuity.
- When a flow also supports Reduce Motion, keep a simpler fallback path available instead of forcing spring-heavy motion on every transition.

## Shared ADE mobile primitives

- Reuse `apps/ios/ADE/Views/Components/ADEMobilePrimitives.swift` before adding tab-local glass wrappers.
- `ADEGlassSection` is the shared section shell for grouped mobile content with consistent spacing and glass treatment.
- `ADEGlassChip` and `ADEGlassActionButton` are the standard compact status/action controls for list, header, and sheet surfaces.
- `ADEMatchedTransitionScope` is the shared namespace wrapper for new list-to-detail zoom transitions; existing screens may still use the older helper modifiers while adoption catches up.

### Tab Bar Minimization
```swift
TabView { ... }.tabBarMinimizeBehavior(.onScrollDown)
Expand Down
87 changes: 87 additions & 0 deletions .factory/library/mobile-chat-port-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Mobile chat port plan

Reference extract of what makes the desktop Work/chat surface feel strong, mapped against today's mobile `WorkChatSessionView`, with a ranked port list. Desktop source: `apps/desktop/src/renderer/components/chat/`. Mobile source: `apps/ios/ADE/Views/Work/WorkChat*.swift`.

## 1. Desktop strengths inventory

1. **Live streaming shimmer + glow on the active assistant bubble** while a turn is running (`AgentChatMessageList.tsx:1219-1232` — `ade-glow-pulse` and `ade-streaming-shimmer` applied only when `options.turnActive`). Makes "it's doing something right now" unmissable.
2. **Brain lottie + "Thinking…" pill inside a collapsible Reasoning card** that auto-opens while live, auto-closes with a duration stamp afterward (`AgentChatMessageList.tsx:1704-1756`, `BrainLottie`, `ThinkingDots`).
3. **Per-turn model badge** inline in the assistant bubble header (model/label chip, `AgentChatMessageList.tsx:1237-1241`, `deriveTurnModelState` at 934). Users always know which model wrote which turn.
4. **Streaming activity indicator** separate from reasoning: a discreet ping-bead line at the bottom of the transcript ("Label: detail…") driven by `deriveLatestActivity` (`AgentChatMessageList.tsx:2500-2509`, `ActivityIndicator` at 785).
5. **Collapsible tool call card** with status pill, tool icon, secondary label, preview text, and expand-on-failure default (`AgentChatMessageList.tsx:1763-1823` `tool_invocation`, 1825-1879 `tool_call`, and `ToolResultCard` at 804).
6. **Tool result truncation with "show all (N chars)" toggle** (`TOOL_RESULT_TRUNCATE_LIMIT = 500`, `AgentChatMessageList.tsx:802-881`).
7. **Command card with status glyph + stream-aware preview** via `CommandEventCard` (`AgentChatMessageList.tsx:1047`) and the full `ChatWorkLogBlock` surface (`ChatWorkLogBlock.tsx` 616 lines).
8. **File-change card with per-file status icon, +/− counts, dirname in muted treatment**, and collapsible diff preview via `DiffPreview` that opens automatically on failure (`AgentChatMessageList.tsx:1097-1133`, `DiffPreview` at 721).
9. **Plan / Todo update inline rows** with per-step status icons, strike-through on completed, active item expanded-by-default (`AgentChatMessageList.tsx:1264-1359`). Explanations sit below a hairline separator.
10. **Approval request cards with inline question cards, recommended option highlighting, freeform fallback, and an "approval responding" busy state** (`AgentChatMessageList.tsx:1886-2214`, plus `AgentQuestionModal.tsx`).
11. **Structured-question chips that dispatch through `onApproval` with a value** — answer without losing chat position (`AgentChatMessageList.tsx:1572-1602`).
12. **Queued-input strip above the composer** for messages submitted during an active turn (`PendingSteerItem` in `AgentChatComposer.tsx:203-312`, `pendingSteers` prop). Edit/cancel per pending item.
13. **Attachment tray with inline chip UI** (`ChatAttachmentTray.tsx`) and attachment picker with file search + recent results (`AgentChatComposer.tsx:453-513`).
14. **@-mention + /-slash command menu** with live filter, caret-anchored popover, family-specific defaults (`ChatCommandMenu.tsx`, `buildSlashCommands` at `AgentChatComposer.tsx:71`).
15. **Scroll-anchor `stickToBottom`** that remembers user scroll intent and re-anchors only when already at bottom; a `MutationObserver` scoped to the live turn keeps streaming content pinned without fighting user scroll (`AgentChatMessageList.tsx:2780-2930`).
16. **Virtualized row window** with `calculateVirtualWindow` / `reconcileMeasuredScrollTop` and measured row heights, kicking in above `VIRTUALIZATION_THRESHOLD = 60` (`AgentChatMessageList.tsx:2653-2748`).
17. **Per-turn summary card ("Turn summary")** aggregating files touched, tasks completed, token usage (`TurnSummaryCard` at 2343, `deriveTurnSummary` at 2240).
18. **Context-compact divider** — a horizontal rule with "Context compacted · ~N tokens freed · auto/manual" chip so users know context was truncated (`AgentChatMessageList.tsx:1626-1656`).
19. **Subagent strip with started / progress / result cards**, each with its own status color, last-tool chip, and usage rollup (`AgentChatMessageList.tsx:1469-1570`, `ChatSubagentStrip.tsx`).
20. **Navigation suggestions embedded in tool results** — clickable pills that route to `files/lanes/work/prs` surfaces when a tool returns navigation hints (`AgentChatMessageList.tsx:68-97`, rendered at 853-866).

## 2. Mobile gaps

- **No streaming shimmer / glow** on the active assistant card. `WorkChatMessageBubble` (`WorkChatHeaderAndMessageViews.swift:98`) is a flat glass rounded-rect with no live-state visual.
- **No Reasoning card** at all. Mobile timeline has no `reasoning` event rendering path.
- **No per-turn model badge**. The message bubble shows only "Assistant" / "You" labels, dropping which model wrote the turn.
- **No activity indicator**. Mobile surfaces a spinner in `streamingStatusSection` (`WorkChatSessionView.swift:268`) but no "Running tool X" / "Editing file Y" granular text.
- **Tool cards exist but lack truncation controls and preview** (`WorkToolCardView` in `WorkChatRichCardViews.swift:5`). No "show all" affordance, no result preview in the header.
- **No collapsible disclosure pattern** — rich cards are always-open. Desktop relies heavily on `CollapsibleCard` / `InlineDisclosureRow` to keep the transcript scannable.
- **Plan / Todo: we have `WorkEventCardView` (326) but no per-step status icon + strike-through treatment** comparable to desktop.
- **No queued-steer UI strip above composer.** `WorkQueuedSteerStrip` (`WorkChatComposerAndInputViews.swift:207`) exists but is sparse and lacks edit controls present on desktop.
- **Approval cards lack structured-question support beyond a single bucket.** `WorkApprovalRequestCard` / `WorkStructuredQuestionCard` are basic compared to desktop's multi-question card set with recommended tags.
- **No slash/@-mention menu in composer.** Attachments are supported only via sheet; slash commands and file-mentions are missing.
- **Autoscroll always snaps to bottom on timeline growth** (`WorkChatSessionView.swift:454-457`) — no "user scrolled up → pause autoscroll → show jump-to-latest pill" pattern.
- **No virtualization.** The mobile transcript is a plain `LazyVStack` inside `ScrollViewReader`. Fine at small counts; will hurt during long sessions.
- **No per-turn summary card, no context-compact divider, no subagent strip.**
- **No navigation-suggestion pills** baked into tool results.
- **Copy affordance** is a context-menu-only path on the bubble (`WorkChatHeaderAndMessageViews.swift:139`). Desktop exposes a hover copy button and also copies from inside tool result cards.

## 3. Prioritized port list (top 10 by value)

| # | Title | Why | Effort | Affected files |
|---|---|---|---|---|
| 1 | Add Reasoning / "Thinking…" card with live→collapse transition | Biggest single "this feels alive" signal. Users on desktop trust the agent partly because they see it think. | medium | `Views/Work/WorkChatRichCardViews.swift`, new `WorkReasoningCard.swift`, `WorkTimelineHelpers.swift` (add reasoning timeline entry), `WorkChatSessionView+Timeline.swift` |
| 2 | Activity indicator pill ("Running bash: ls -la", "Editing src/foo.ts") | Fills the silent-gap problem when streaming slows; huge perceived-responsiveness win. | small | `WorkChatSessionView.swift` (`streamingStatusSection`), new `WorkActivityIndicator.swift`, port of `deriveLatestActivity` |
| 3 | Per-turn model badge inline in assistant bubble | Critical when users juggle Claude/Codex/Cursor. Invisible cost, high clarity. | small | `WorkChatHeaderAndMessageViews.swift`, `WorkTimelineHelpers.swift` (derive per-turn model) |
| 4 | Collapsible tool / file-change / plan cards with default-open-on-failure | Keeps long transcripts scannable, matches desktop density cadence. | medium | `Views/Components/ADECollapsibleCard.swift` (new shared primitive), `WorkChatRichCardViews.swift` refactor |
| 5 | Smart autoscroll: pause when user scrolls up, "Jump to latest" pill to resume | Prevents the "I'm reading older output but it keeps yanking me back" frustration on long turns. | medium | `WorkChatSessionView.swift` (replace naive `onChange(of: timeline.count)`), new `WorkJumpToLatestPill.swift` |
| 6 | Streaming shimmer/glow on active assistant bubble | Mirrors #1 but on the bubble itself; the two together make live turns feel unmissable. | small | `WorkChatHeaderAndMessageViews.swift` (+ reduce-motion fallback via `ADEMotion`) |
| 7 | Queued-steer strip above composer with edit/cancel per item | Users will queue follow-ups; mobile drops them into a void today. | medium | `WorkChatComposerAndInputViews.swift` (flesh out `WorkQueuedSteerStrip` + `WorkQueuedSteerRow`) |
| 8 | Tool-result truncation with "show all" toggle and structured preview line | The thing that keeps tool transcripts usable at all. | small | `WorkChatRichCardViews.swift` (`WorkToolCardView`) |
| 9 | Slash-command menu on `/` trigger, anchored to composer | Huge ergonomic win; users memorize `/plan`, `/clear`, family defaults. | large | `WorkChatComposerAndInputViews.swift`, new `WorkSlashCommandMenu.swift`, slash registry port |
| 10 | Context-compact divider line ("~N tokens freed · auto") | Explains sudden memory loss; small but confidence-building. | small | `WorkChatRichCardViews.swift` (new `WorkContextCompactDivider`) |

## 4. Non-goals

- **Desktop information density.** Keep generous touch targets; don't port the 8-11px type stack literally. Mobile cards stay at 12-13pt body.
- **Virtualization (item 16 on desktop) in V1.** Our typical mobile session is short and our `LazyVStack` pagination already lazy-loads. Revisit only if a long-session regression appears.
- **@-mentions over file tree.** Desktop has a full file picker anchored at caret; mobile keyboards + Files tab already cover this. Ship `/slash` first.
- **Keyboard shortcuts** (Cmd-Enter to send, etc.) — iOS already exposes hardware-keyboard chords through `.keyboardShortcut`, but designing around them would mislead thumb-only users.
- **Per-provider permission mode pickers** (Claude thinking/plan/edit, Codex sandbox presets). Leave these on desktop; mobile Work runs against whatever the desktop session is configured as.
- **Computer-use / Proof panels.** Out of scope for mobile Work.
- **Subagent strip V1.** Useful but specialized; queue for a V2 once the core bubble feels right.

## 5. Design-system additions needed

Land these in `apps/ios/ADE/Views/Components/` so Lanes, Files, PRs can reuse as those tabs grow:

- **`ADECollapsibleCard`** — the workhorse: rounded-rect header (summary view + caret), tap-to-expand body, `defaultOpen` + `forceOpen` bindings. Must animate through `ADEMotion.quick/standard` and short-circuit under reduce motion. Mirrors desktop `CollapsibleCard` (`AgentChatMessageList.tsx:659`).
- **`ADEInlineDisclosureRow`** — denser variant (single-line summary + optional inline body) for plan steps, todo, tool-summary. Mirrors desktop `InlineDisclosureRow` (445).
- **`ADEActivityPill`** — dotted/pulsing bead + single-line status text. Respects reduce motion (static dot fallback).
- **`ADEStreamingShimmer` view modifier** — applies a one-shot gradient sweep to an overlay; guarded by `ADEMotion.allowsMatchedGeometry(reduceMotion:)`.
- **`ADEStatusGlyph`** — unified `working | completed | failed | interrupted` icon with size/tint input, replacing ad-hoc `Image(systemName: ...)` picks scattered across `WorkChatRichCardViews.swift`.
- **`ADEJumpToLatestPill`** — floating pill with up-arrow and unread-turn count; toggles visibility from the transcript container.
- **`ADEMarkdownRenderer` consolidation** — today `WorkMarkdownRenderer` and `PrMarkdownRenderer` (`Views/PRs/CreatePrWizardView.swift:255`) each re-implement `AttributedString(markdown:)`. Collapse into one `ADEMarkdown` component to avoid drift.

## Implementation notes

- Keep `.adeGlassCard` as the single wrapper — no nested `ADEGlassSection` inside it.
- Gate shimmer/glow behind `!reduceMotion` via `ADEMotion`; smart-autoscroll should persist scroll position per `sessionId` across tab switches.
- Extend `WorkTimelineEntry.Payload` cases rather than introducing parallel state.
34 changes: 34 additions & 0 deletions .factory/library/performance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Performance hotspots

Mission-specific performance notes for ADE iOS parity work.

**What belongs here:** known lag sources, hot-path rendering issues, and the kinds of fixes workers should prioritize while preserving behavior.

---

## Work tab
- Large monolith (`Views/WorkTabView.swift`) with repeated full-transcript derivation and sorting across many computed properties
- Timeline/tool/event/file-change/message models are rebuilt repeatedly from the same transcript data
- Loose JSON parsing, regex extraction, markdown parsing, ANSI parsing, and string concatenation happen in hot paths
- Read-only terminal and artifact rendering should avoid repeated expensive conversions in `body`

## Files tab
- Syntax highlighting and diff generation should not rerun needlessly on every render
- File metadata/history loading and diff building should avoid repeated expensive recomputation from `localStateRevision` churn
- Keep the locked read-only scope simple; do not carry write/edit state machinery that no longer serves the mobile product

## PRs tab
- Avoid eager rendering of large diff/detail content for every PR file row at once
- Keep list/detail/workflow slices split into smaller files and lazy containers where possible
- Preserve cached detail readability when offline instead of forcing repeated failed reloads

## Settings / shared shell
- Avoid over-animating decorative backgrounds or high-frequency pulse effects when they do not improve task clarity
- Shared tab-shell navigation and cross-tab request handling should not trigger duplicate pushes or repeated refresh loops

## Global guidance
- Prefer lazy containers for large lists and card stacks
- Move expensive sorting/parsing/merging work out of SwiftUI `body`
- Cache or memoize derived models when the source data has not changed
- Coalesce `localStateRevision`-driven refreshes instead of reloading multiple times per update burst
- Validate that back navigation preserves context instead of resetting and rebuilding the entire screen unnecessarily
Loading
Loading