Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .factory/init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
set -e

# 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
fi

echo "Environment ready. iOS app at apps/ios/"
104 changes: 43 additions & 61 deletions .factory/library/architecture.md
Original file line number Diff line number Diff line change
@@ -1,67 +1,49 @@
# Architecture

Architectural decisions, patterns discovered, and design principles.
Architectural decisions, patterns discovered, and conventions.

**What belongs here:** Architectural patterns, data flow, component organization, design decisions.
**What belongs here:** Design patterns, module boundaries, data flow, naming conventions.

---

## iOS App Architecture

### Pattern: MVVM-like with Shared Service
- `SyncService` is the shared `@MainActor ObservableObject` injected via `.environmentObject()`
- Views own local `@State` for UI concerns
- Views call into `SyncService` for remote operations and data fetching
- Data flow: `SyncService` → `DatabaseService` (SQLite) → `localStateRevision` increment → SwiftUI reactivity via `.task(id: syncService.localStateRevision)`

### File Structure
```
ADE/
├── App/
│ ├── ADEApp.swift # App entry point, UIKit theme config
│ └── ContentView.swift # Root TabView, Settings tab, design system components
├── Views/
│ ├── LanesTabView.swift # ~3,706 lines - complete
│ ├── FilesTabView.swift # ~500 lines - baseline
│ ├── WorkTabView.swift # ~300 lines - baseline
│ └── PRsTabView.swift # ~500 lines - baseline
├── Models/
│ └── RemoteModels.swift # ~700 lines - all domain models
├── Services/
│ ├── Database.swift # ~1,949 lines - SQLite + cr-sqlite sync
│ ├── KeychainService.swift # ~50 lines - token persistence
│ └── SyncService.swift # ~1,781 lines - WebSocket + Bonjour + RPC
└── Resources/
└── DatabaseBootstrap.sql # ~2,260 lines - full schema
```

### Database
- Direct SQLite3 C API (no ORM)
- cr-sqlite change tracking with custom triggers (insert/update/delete)
- Bidirectional changeset sync via WebSocket
- Site ID management (persistent 128-bit random)
- Full bootstrap SQL schema (~2,260 lines) mirroring desktop

### Networking
- Raw `URLSessionWebSocketTask` — no third-party dependencies
- JSON envelopes with optional gzip compression (>4KB)
- Heartbeat ping/pong protocol
- Auto-reconnect with exponential backoff
- Bonjour (`NetServiceBrowser`) for LAN discovery
- Connection-scoped async work in `SyncService` must be tied to the active socket/session: store long-lived tasks so `disconnect()` and host switching can cancel them, and ignore stale send/receive callbacks unless they still belong to the current `socket`

### Command Routing
- State-only operations: write locally → cr-sqlite syncs to host
- Execution operations: send command via WebSocket → host executes → state syncs back
- Offline command queue: persisted to UserDefaults, flushed on reconnect

### Key Model Types (RemoteModels.swift)
- `RemoteLane`, `RemoteLaneDetail`, `LaneStateSnapshot`
- `RemoteTerminalSession`, `SessionHistoryEntry`
- `PullRequestRow`, `PullRequestSnapshot`, `PRDetailPayload`
- `RemoteFileNode`, `RemoteSearchResult`
- `ChatMessage`, `ToolCallResult`

### Adding New Swift Files
New .swift files MUST be added to the Xcode project by editing `ADE.xcodeproj/project.pbxproj`.
Both `PBXFileReference` and `PBXSourcesBuildPhase` sections need entries.
## iOS App Structure
- Entry: `ADEApp.swift` → creates SyncService → passes to ContentView
- ContentView: TabView with 5 tabs (Lanes, Files, Work, PRs, Settings)
- SyncService: `@MainActor` `ObservableObject`, Bonjour discovery + pairing + `ws://` socket transport to the desktop host, CRDT sync via cr-sqlite
- Database: SQLite with cr-sqlite for local caching
- Models: RemoteModels.swift (Codable structs for all API types)
- Host discovery: Bonjour browser maintains discovered LAN hosts, while Settings also supports manual host/port entry and QR pairing payloads with candidate addresses
- Authentication: pairing exchanges a short-lived host code for a shared secret, stores that secret in Keychain, and persists host identity/device metadata in `HostConnectionProfile`
- Transport security: the current implementation uses plain `ws://` on the trusted local network or saved address set; host trust is enforced by the pairing secret plus host-identity checks, not TLS or certificate pinning

## Data Flow
1. `SyncService` discovers or reuses the host address, opens a `ws://` socket, and sends `hello` with either bootstrap auth or paired-device auth
2. Commands sent (for example `lanes.refreshSnapshots`) are decoded into local models, while `changeset_batch` payloads are applied into the SQLite cache
3. Data is cached in SQLite tables including `lane_list_snapshots` and `lane_detail_snapshots`, and cached rows remain readable when the host is offline
4. Views observe `syncService.localStateRevision` via `.task(id:)`; when live refresh fails they keep the last cached state visible and surface a user-facing error banner or reconnect CTA instead of blanking the screen
5. Pull-to-refresh triggers `reload(refreshRemote: true)`: the remote refresh runs first, then the view reloads from SQLite; on failure the view keeps cached rows, records the localized `SyncUserFacingError`, and offers retry/reconnect UI
6. Disconnects and send/receive failures tear down the active socket, fail pending requests, and schedule automatic reconnect with exponential backoff (1s, 2s, 4s, 8s, 16s; immediate retry for heartbeat close code `4001`)
7. Session lifecycle is stateful: a successful `hello` refreshes the saved host profile and starts relay/hydration tasks, `auth_failed` invalidates the saved pairing, and manual disconnect stops reconnect attempts until the user reconnects or pairs again

## Design System (ADEDesignSystem.swift)
- Colors: ADEColor (pageBackground, surfaceBackground, accent, success, warning, danger, etc.)
- Motion: ADEMotion (standard, quick, emphasis, pulse - all respect reduceMotion)
- Components: ADENoticeCard, ADEStatusPill, ADEEmptyStateView, ADESkeletonView, ADECardSkeleton
- Modifiers: .adeGlassCard(), .adeScreenBackground(), .adeNavigationGlass(), .adeInsetField(), .adeListCard()
- Image caching: ADEImageCache with memory + disk cache

## Coding Conventions
- Private views as nested structs within the parent file
- @EnvironmentObject for SyncService access
- @State for local view state
- Computed properties for filtered/derived data
- .task(id:) for reactive data loading
- .sensoryFeedback for haptics
- accessibilityLabel on all interactive elements
- ADEMotion helpers for all animations (respects reduceMotion)

## Lane Types
- `LaneListSnapshot`: List item (lane + runtime + rebaseSuggestion + autoRebase + conflict + stateSnapshot + adoptable)
- `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"
25 changes: 25 additions & 0 deletions .factory/library/environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Environment

Environment variables, external dependencies, and setup notes.

**What belongs here:** Required env vars, external API keys/services, dependency quirks, platform-specific notes.
**What does NOT belong here:** Service ports/commands (use `.factory/services.yaml`).

---

## Platform
- iOS 26.0+ deployment target
- Swift 5.0
- Xcode 26.2+
- No external package dependencies (no SPM, CocoaPods, Carthage)
- Uses SQLite3 via system framework import (cr-sqlite for CRDT sync)

## Simulators
- Required: iOS 26.3.1 simulators
- Recommended: iPhone 17 Pro
- iOS 18.x simulators are older than the minimum deployment target (26.0), so they are incompatible.

## Build 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)
26 changes: 26 additions & 0 deletions .factory/library/user-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# User Testing

Testing surface, tools, setup steps, isolation notes, known quirks.

**What belongs here:** How to manually test the app, what surfaces to check, tools available.

---

## Testing Surface
- iOS Simulator (iPhone 17 Pro, iOS 26.3.1)
- Build + run in simulator via xcodebuild or Xcode
- App requires pairing with a desktop ADE host for full runtime testing (WebSocket connection)

## Limitations
- Cannot do full runtime/integration testing without a paired desktop host
- Validation focuses on: build success, unit tests, code review
- SwiftUI previews may be available for individual views but are not set up in the current codebase

## Testing Commands
- Build: `xcodebuild build -project apps/ios/ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.3.1' -quiet`
- Test: `xcodebuild test -project apps/ios/ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.3.1' -quiet`
- Line count check: `find apps/ios/ADE -name '*.swift' -exec wc -l {} + | sort -rn | head -20`

## Test Coverage
- Unit tests in ADETests.swift cover: sync protocol, database CRDT, lane hydration, PR workflows, syntax highlighting, work tab, utilities
- Tests use @testable import ADE
12 changes: 8 additions & 4 deletions .factory/services.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
commands:
build: cd apps/ios && xcodebuild -project ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,id=2107A402-C2A7-4323-AF26-74A0AC406C44' -derivedDataPath /tmp/ade-build build
test: cd apps/ios && xcodebuild -project ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,id=2107A402-C2A7-4323-AF26-74A0AC406C44' -derivedDataPath /tmp/ade-build test
typecheck: cd apps/ios && xcodebuild -project ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,id=2107A402-C2A7-4323-AF26-74A0AC406C44' -derivedDataPath /tmp/ade-build build
lint: cd apps/ios && xcodebuild -project ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,id=2107A402-C2A7-4323-AF26-74A0AC406C44' -derivedDataPath /tmp/ade-build analyze
build: xcodebuild build -project apps/ios/ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.3.1' -quiet
test: xcodebuild test -project apps/ios/ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.3.1' -quiet
build-for-testing: xcodebuild build-for-testing -project apps/ios/ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.3.1' -quiet
typecheck: cd apps/ios && xcodebuild -project ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.3.1' -derivedDataPath /tmp/ade-build build
lint: cd apps/ios && xcodebuild -project ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.3.1' -derivedDataPath /tmp/ade-build analyze
line-count: find apps/ios/ADE -name '*.swift' -exec wc -l {} + | sort -rn | head -20

services: {}
145 changes: 145 additions & 0 deletions .factory/skills/ios-worker/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
name: ios-worker
description: Implements and tests SwiftUI views and components for the iOS app
---

# iOS Worker

NOTE: Startup and cleanup are handled by `worker-base`. This skill defines the WORK PROCEDURE.

## When to Use This Skill

Use for any feature that involves creating or modifying Swift/SwiftUI code in the iOS app at `apps/ios/ADE/`. This includes view extraction, UI redesign, component creation, and performance optimization.

## Work Procedure

### 1. Understand the Feature

Read the feature description, preconditions, expectedBehavior, and verificationSteps carefully. Then:

- Read `mission.md` for overall mission context
- Read `AGENTS.md` for boundaries and conventions
- Read `.factory/library/architecture.md` for app architecture and patterns
- Read the existing code files you'll be modifying
- If the feature modifies or replaces existing views, read the FULL original `LanesTabView.swift` to understand the complete existing implementation before making changes

### 2. Plan the Changes

Before writing any code:
- List every file you'll create or modify
- Identify which existing code to preserve vs. rewrite
- Note which SyncService methods the views need to call
- Check that your plan doesn't violate any boundary (no changes to SyncService, RemoteModels, Database)

### 3. Write Tests First (Red)

For any testable logic (filters, computed properties, data transformations, directory grouping):
- Add test cases to `ADETests.swift` (the single test file)
- Tests should fail initially (red phase)
- Use `@testable import ADE` and XCTest patterns matching existing tests
- Focus on logic tests, not UI rendering tests

### 4. Implement (Green)

Write the SwiftUI code:
- **File organization**: Each major view gets its own file under `apps/ios/ADE/Views/Lanes/`. Keep files under 500 lines.
- **Design system**: Use `ADEColor.*` for colors, `ADEMotion.*` for animations, `.adeGlassCard()` for card surfaces, `.adeInsetField()` for inputs, `ADENoticeCard` for notices, `ADEStatusPill` for badges, `ADEEmptyStateView` for empty states, `ADESkeletonView`/`ADECardSkeleton` for loading.
- **Accessibility**: Add `.accessibilityLabel` to all interactive elements. Use `ADEMotion` which respects `reduceMotion`. Preserve `.sensoryFeedback` on appropriate interactions.
- **Data access**: Use `@EnvironmentObject var syncService: SyncService`. Call existing SyncService methods — never add new ones.
- **State management**: Use `@State` for local view state. Use `.task(id: syncService.localStateRevision)` for reactive data loading.
- **Lazy loading**: Use `LazyVStack` for lists with many items (file changes, commits, lane list).
- **No changes** to SyncService.swift, RemoteModels.swift, Database.swift, or DatabaseBootstrap.sql.

### 5. Update Xcode Project

When creating new Swift files, you MUST add them to the Xcode project:
- Edit `apps/ios/ADE.xcodeproj/project.pbxproj` to add file references and build phase entries
- Follow the existing pattern in the pbxproj for file references (PBXFileReference, PBXBuildFile, PBXGroup children)
- Alternatively, if the project uses folder references, ensure files are in the correct directory

### 6. Verify

Run these commands and fix any issues:

```bash
# Pick an available simulator pair on the current machine first.
xcrun simctl list runtimes
xcrun simctl list devices available
DESTINATION="platform=iOS Simulator,name=<available iPhone>,OS=<available iOS runtime>"

# Build
xcodebuild build -project apps/ios/ADE.xcodeproj -scheme ADE -destination "$DESTINATION" -quiet

# Test
xcodebuild test -project apps/ios/ADE.xcodeproj -scheme ADE -destination "$DESTINATION" -quiet

# Check file sizes (no file should exceed 500 lines)
find apps/ios/ADE -name '*.swift' -exec wc -l {} + | sort -rn | head -20
```

All must pass. If build fails, fix immediately. If tests fail, fix immediately. If any file exceeds 500 lines, split it.

### 7. Manual Verification

Since the app requires a paired desktop host for runtime testing:
- Review your code for correctness by tracing data flow from SyncService through to the view
- Verify all SyncService method calls match the existing API exactly (check method signatures)
- Verify all RemoteModels properties are accessed correctly (check property names and types)
- Ensure no unused imports, dead code, or TODO placeholders remain
- Confirm view hierarchy is correct (NavigationStack, sheets, navigation links)

### 8. Commit

Commit with a clear message describing what was implemented.

## Example Handoff

```json
{
"salientSummary": "Extracted LaneDetailScreen, LaneCreateSheet, LaneAttachSheet, LaneDiffScreen, LaneBatchManageSheet, LaneStackGraphSheet, and shared components from the monolithic LanesTabView.swift into 12 separate files under Views/Lanes/. Created LaneTypes.swift for shared enums/structs and LaneHelpers.swift for utility functions. All 50 existing tests pass. No file exceeds 500 lines. Build succeeds.",
"whatWasImplemented": "Split LanesTabView.swift (3,749 lines) into 12 files: LanesTabView.swift (thin coordinator, ~300 lines), LaneDetailScreen.swift (~450 lines), LaneCreateSheet.swift (~180 lines), LaneAttachSheet.swift (~90 lines), LaneDiffScreen.swift (~200 lines), LaneBatchManageSheet.swift (~150 lines), LaneStackGraphSheet.swift (~80 lines), LaneChatLaunchSheet.swift (~120 lines), LaneSessionTranscriptView.swift (~100 lines), LaneChatSessionView.swift (~100 lines), LaneComponents.swift (~350 lines, shared small components), LaneTypes.swift (~80 lines, enums and model structs), LaneHelpers.swift (~120 lines, search/format helpers). Updated project.pbxproj with all new file references.",
"whatWasLeftUndone": "",
"verification": {
"commandsRun": [
{
"command": "xcodebuild build -project apps/ios/ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.3.1' -quiet",
"exitCode": 0,
"observation": "Build succeeded with no errors or warnings"
},
{
"command": "xcodebuild test -project apps/ios/ADE.xcodeproj -scheme ADE -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.3.1' -quiet",
"exitCode": 0,
"observation": "All 50 tests passed"
},
{
"command": "find apps/ios/ADE -name '*.swift' -exec wc -l {} + | sort -rn | head -20",
"exitCode": 0,
"observation": "Largest file is LaneDetailScreen.swift at 448 lines. All files under 500 line limit."
}
],
"interactiveChecks": [
{
"action": "Traced data flow from SyncService.fetchLaneListSnapshots through LanesTabView to LaneListRow",
"observed": "All property accesses match RemoteModels.LaneListSnapshot fields. No missing or renamed properties."
},
{
"action": "Verified all SyncService method calls in extracted views match original signatures",
"observed": "All 35 service calls preserved with correct parameter names and types."
}
]
},
"tests": {
"added": []
},
"discoveredIssues": []
}
```

## When to Return to Orchestrator

- SyncService method signatures don't match what the view expects (API contract issue)
- RemoteModels properties are missing or have different types than expected
- Xcode project file is corrupted or in an unrecoverable state
- Build fails due to issues outside the lanes tab code
- A feature requires changes to SyncService, RemoteModels, or Database (boundary violation)
- File exceeds 500 lines and cannot be reasonably split without changing the feature boundary
Loading
Loading