Files
platform/PHONE_APP.md
T
2026-02-20 12:35:47 +00:00

48 KiB

Officer Mobile App Plan

This document is written by Claude for Claude. It contains the full plan for native mobile apps (Kotlin/Android + Swift/iOS) for the Officer platform, based on a thorough study of the monorepo. When resuming this work, read this document first, then explain the plan to the user before starting implementation.


Table of Contents

  1. Current Web App Summary
  2. What to Include vs Exclude
  3. Architecture Overview
  4. Authentication
  5. Site Map
  6. Screen-by-Screen Breakdown
  7. Tablet Adaptations
  8. Technical Challenges
  9. API Integration Notes
  10. Shared Design Language
  11. Implementation Phases

1. Current Web App Summary

Officer is an AI-assisted personal backend for life management. The web app (officer-web) is a React 19 SPA talking to a Hono API server (port 5000). Key features:

Feature Description
Chat Multi-provider AI chat (Claude, OpenCode, Pi-Mono) via WebSockets. Sessions persisted as JSON files. Supports images, file attachments, model selection, working directory context.
File Browser Full file manager with upload/download, rename, copy/move, search, archive extraction, git clone, yt-dlp download, OCR, TTS, transcription, audio extraction, video transcoding. Multiple "roots" (home, ~, officer.dev).
Workspaces Multi-panel layouts combining chat, files, terminal, code editor. Panels can be split horizontally/vertically. State persisted per workspace.
Terminal Sandboxed Docker terminals and host terminals (Super Admin). WebSocket-based PTY.
Code Editor Text editor with syntax highlighting (PrismJS).
Dashboard Widget panel (clock, weather, pomodoro, daily goals, quick notes) + workspace launcher + chat launcher.
Automation Builder for workflows, pipelines, processes, services, cron jobs, tasks, skills.
Tasks File-based task definitions (TASK.md) that can be triggered on files/directories. Native/global/user scopes.
Skills AI skill library (SKILL.md files) injected into chat system prompts.
Settings User profile, system settings, server resources, AI harness configuration.
Auth Email/password + passkey (WebAuthn). JWT tokens. Role-based access (Member, Admin, Owner, Super Admin).

The API is at /api/* with JWT auth on all protected routes. WebSockets for chat (/api/harness/*/ws) and terminal (/api/terminal/ws).


2. What to Include vs Exclude

Include (core mobile experience)

Feature Rationale
Chat The #1 use case. Having an AI assistant in your pocket is the killer feature. Voice input makes it even better on mobile.
File Browser Essential for viewing/managing files. Mobile adds camera upload, share sheet integration. Simplified but fully functional.
Dashboard / Widgets Perfect for mobile - quick glance at clock, weather, goals, notes. Widget-style info is native to mobile platforms.
Notifications Mobile-exclusive feature. Push notifications for chat completions, task results, long downloads finishing.
Settings Profile, preferences. Subset of web settings.
Session History Browse and resume past chat sessions.
Quick Actions Share sheet integration (share URL/text/image to Officer for chat or file upload).

Include (reduced scope)

Feature What changes
File Viewer View files (text, images, PDFs, audio, video) but no full code editor. Read-only text with syntax highlighting.
Tasks View and trigger tasks, see logs. No task creation/editing (too complex for mobile).
Skills Browse skill library. No editing.
Workspaces View workspace list, open a workspace (but as a single-panel experience, not multi-panel).

Exclude (not suitable for phone)

Feature Rationale
Terminal Typing shell commands on a phone keyboard is miserable. The sandboxed Docker terminal is a power-user web feature.
Code Editor (write mode) Writing code on a phone is impractical. Read-only viewing with syntax highlighting is fine.
Multi-panel workspaces Phone screens can't meaningfully split into panels. Tablet gets a simplified version (see section 7).
Automation Builder Complex drag-and-drop/form-heavy workflow builder. Keep this web-only.
Server/Resource Management Admin infrastructure management (installing databases, configuring services). Web-only.
System Settings Server configuration, AI harness setup, plugin management. Web-only.

Mobile-Exclusive Features (not on web)

Feature Description
Push Notifications FCM (Android) / APNs (iOS) for chat completions, task results, downloads.
Voice Input Native speech-to-text for chat messages. Much better than web speech API.
Camera Integration Take photo and send to chat or upload to file browser directly.
Share Sheet Receive shared content (URLs, text, images) from other apps.
Biometric Auth Fingerprint/Face ID as alternative to passkey/password.
Home Screen Widgets Android widgets / iOS widgets for quick notes, pomodoro, weather.
Offline Queue Queue chat messages when offline, send when reconnected.
Background Downloads File downloads and yt-dlp downloads continue in background.

3. Architecture Overview

Platform Strategy

Two fully native apps sharing no code, but sharing identical:

  • API contract (same REST + WebSocket endpoints)
  • UX flows and information architecture
  • Design language (adapted to each platform's conventions)
┌──────────────┐     ┌──────────────┐
│  Android App │     │   iOS App    │
│   (Kotlin)   │     │   (Swift)    │
│              │     │              │
│  Jetpack     │     │  SwiftUI     │
│  Compose     │     │              │
│  Material 3  │     │  Native iOS  │
│              │     │  Components  │
└──────┬───────┘     └──────┬───────┘
       │                    │
       └────────┬───────────┘
                │
         ┌──────┴──────┐
         │  Officer API │
         │  (Hono)     │
         │  port 5000  │
         └─────────────┘

Android Stack

Layer Technology
UI Jetpack Compose + Material 3
Navigation Compose Navigation
Networking Ktor Client (HTTP + WebSocket)
State ViewModel + StateFlow
DI Hilt
Storage DataStore (preferences), Room (offline cache)
Images Coil
Push Firebase Cloud Messaging
Auth BiometricPrompt API
Background WorkManager

iOS Stack

Layer Technology
UI SwiftUI
Navigation NavigationStack (iOS 16+)
Networking URLSession + native WebSocket (URLSessionWebSocketTask)
State @Observable (iOS 17+) / ObservableObject
DI Swift native (Environment, protocol-based)
Storage UserDefaults, SwiftData (offline cache)
Images AsyncImage + custom cache
Push APNs
Auth LocalAuthentication (Face ID / Touch ID)
Background BGTaskScheduler

Minimum OS Versions

  • Android: API 26 (Android 8.0) - covers 95%+ of devices, gives us WorkManager, BiometricPrompt, etc.
  • iOS: iOS 16 - gives us NavigationStack, modern SwiftUI features. iOS 17 for @Observable if we want it.

4. Authentication

Flow

App Launch
    │
    ├─ Has stored JWT? ──Yes──► Validate token (GET /api/auth/me)
    │                              │
    │                          Valid? ──Yes──► Home Screen
    │                              │
    │                          No ──► Login Screen
    │
    └─ No ──► Login Screen
                  │
                  ├─ Email/Password
                  │     POST /api/auth/signin
                  │     ► Store JWT securely
                  │     ► Home Screen
                  │
                  ├─ Passkey (WebAuthn)
                  │     ► Platform passkey flow
                  │     ► Store JWT securely
                  │
                  └─ Biometric Unlock (returning user)
                        ► Decrypt stored JWT
                        ► Validate & proceed

Token Storage

  • Android: EncryptedSharedPreferences (AndroidX Security)
  • iOS: Keychain Services

Biometric Integration

On first successful login, offer to enable biometric unlock:

  1. User logs in with email/password or passkey
  2. App prompts: "Enable Face ID / Fingerprint for quick access?"
  3. If yes: encrypt JWT with biometric-bound key, store in Keychain/Keystore
  4. On next launch: biometric prompt → decrypt JWT → validate → proceed

Server URL Configuration

The mobile app needs to know where the Officer API lives. First-launch flow:

  1. "Enter your Officer server URL" input
  2. Validate by hitting GET /api/server-settings/plugins (public endpoint)
  3. Store URL in app preferences
  4. Proceed to login

This is important because Officer is self-hosted - there's no central server.


5. Site Map

App
├── Login
│   ├── Server URL Setup (first launch)
│   ├── Email/Password
│   ├── Passkey
│   └── Biometric Unlock
│
├── Home (Tab 1)
│   ├── Widget Dashboard
│   │   ├── Clock
│   │   ├── Weather
│   │   ├── Pomodoro Timer
│   │   ├── Daily Goals
│   │   └── Quick Notes
│   └── Workspace Quick Access
│
├── Chat (Tab 2)
│   ├── Session List
│   │   ├── Active Sessions
│   │   └── Archived Sessions
│   ├── New Chat
│   └── Chat Session
│       ├── Message Thread
│       ├── Model Selector
│       ├── Image Attachment
│       ├── Voice Input
│       └── Tool Use Display
│
├── Files (Tab 3)
│   ├── Directory Browser
│   │   ├── Grid/List View
│   │   ├── Search
│   │   ├── Sort/Filter
│   │   └── Context Actions
│   │       ├── Open/Preview
│   │       ├── Share
│   │       ├── Rename
│   │       ├── Copy/Move
│   │       ├── Delete
│   │       ├── Download to Device
│   │       └── Upload (camera, gallery, files)
│   └── File Viewer
│       ├── Text (syntax highlighted, read-only)
│       ├── Image
│       ├── PDF
│       ├── Audio Player
│       ├── Video Player
│       └── AI Actions (OCR, TTS, Transcribe)
│
├── Activity (Tab 4)
│   ├── Task List
│   │   ├── Native Tasks
│   │   ├── Global Tasks
│   │   └── User Tasks
│   ├── Task Logs
│   └── Running Processes
│
└── Settings (Tab 5 / Profile)
    ├── Profile
    │   ├── Name, Username, Avatar
    │   ├── Password Change
    │   └── Passkey Management
    ├── App Preferences
    │   ├── Theme (Light/Dark/System)
    │   ├── Notification Settings
    │   ├── Default Chat Model
    │   ├── Biometric Lock
    │   └── Server URL
    └── About / Logout

Navigation Pattern

Bottom Tab Bar with 4 primary tabs + profile access:

┌─────────────────────────────────────┐
│                                     │
│          [Screen Content]           │
│                                     │
├─────┬─────┬─────┬─────┬────────────┤
│ Home│ Chat│Files│ Act.│  Settings  │
│  🏠 │ 💬  │ 📁  │ ⚡  │    ⚙️     │
└─────┴─────┴─────┴─────┴────────────┘
  • Home: Dashboard with widgets
  • Chat: Session list → chat view
  • Files: File browser → file viewer
  • Activity: Tasks, logs, processes
  • Settings: Profile and preferences

On Android: Material 3 NavigationBar. On iOS: UITabBarController / SwiftUI TabView.


6. Screen-by-Screen Breakdown

6.1 Login Screen

┌─────────────────────────┐
│                         │
│      [Officer Logo]     │
│                         │
│  ┌───────────────────┐  │
│  │ Server URL        │  │ ← Only on first launch or
│  └───────────────────┘  │   tap "Change server"
│                         │
│  ┌───────────────────┐  │
│  │ Email             │  │
│  └───────────────────┘  │
│  ┌───────────────────┐  │
│  │ Password          │  │
│  └───────────────────┘  │
│                         │
│  [      Sign In       ] │
│                         │
│  ── or ──               │
│                         │
│  [  Sign in with Passkey ] │
│                         │
│  Forgot password?       │
│                         │
└─────────────────────────┘

Behavior:

  • Server URL field shown on first launch or when tapped
  • Email/password fields with proper keyboard types
  • "Sign in with Passkey" triggers platform WebAuthn flow
  • On success: store JWT, navigate to Home
  • On returning user with biometric enabled: show biometric prompt immediately, with "Use password instead" fallback

6.2 Home / Dashboard

┌─────────────────────────┐
│ Officer          [Avatar]│
├─────────────────────────┤
│                         │
│  ┌──────┐  ┌──────────┐│
│  │ 14:32│  │ Weather  ││
│  │ Fri  │  │ 22° ☀️   ││
│  └──────┘  └──────────┘│
│                         │
│  ┌──────────────────┐   │
│  │ 🍅 Pomodoro      │   │
│  │ 18:42 remaining  │   │
│  │ [Start] [Reset]  │   │
│  └──────────────────┘   │
│                         │
│  ┌──────────────────┐   │
│  │ Daily Goals      │   │
│  │ ☑ Exercise       │   │
│  │ ☐ Read 30min     │   │
│  │ ☐ Journal        │   │
│  │ [+ Add goal]     │   │
│  └──────────────────┘   │
│                         │
│  ┌──────────────────┐   │
│  │ Quick Notes      │   │
│  │ Tap to add...    │   │
│  └──────────────────┘   │
│                         │
│  Workspaces             │
│  ┌────┐ ┌────┐ ┌────┐  │
│  │ WS1│ │ WS2│ │ WS3│  │
│  └────┘ └────┘ └────┘  │
│                         │
└─────────────────────────┘

Behavior:

  • Scrollable vertical layout
  • Widgets are cards that can be reordered (long-press drag)
  • Widget data syncs with web app via user settings API
  • Workspace cards show name + icon, tap opens workspace (single-panel mode on phone)
  • Pull-to-refresh updates widget data
  • Avatar tap opens Settings

Widget Implementation:

  • Clock: Local device time (no API needed)
  • Weather: Can use device location + weather API, or sync from web app settings
  • Pomodoro: Local timer with state synced to server (so it persists across devices)
  • Daily Goals: Stored in user settings, synced
  • Quick Notes: Stored in user settings, synced

6.3 Chat - Session List

┌─────────────────────────┐
│ Chat             [+ New]│
├─────────────────────────┤
│ [🔍 Search sessions...] │
├─────────────────────────┤
│                         │
│  Today                  │
│  ┌──────────────────┐   │
│  │ 🤖 Debug login   │   │
│  │ Claude · 2h ago  │   │
│  │ Last: "The issue  │   │
│  │ was in the..."   │   │
│  └──────────────────┘   │
│  ┌──────────────────┐   │
│  │ 🤖 Refactor API  │   │
│  │ Pi-Mono · 5h ago │   │
│  │ Last: "I've upda- │   │
│  │ ted the router..." │  │
│  └──────────────────┘   │
│                         │
│  Yesterday              │
│  ┌──────────────────┐   │
│  │ 🤖 Recipe ideas  │   │
│  │ Claude · 1d ago  │   │
│  └──────────────────┘   │
│                         │
│  Archived ▸             │
│                         │
└─────────────────────────┘

Behavior:

  • Sessions fetched from GET /api/sessions?provider=all
  • Grouped by date (Today, Yesterday, This Week, Older)
  • Each card shows: session name, provider icon, time, last message preview
  • Swipe left to delete, swipe right to archive
  • Long-press for context menu (rename, archive, delete)
  • "Archived" section expandable
  • Search filters sessions by name
  • FAB or top-right button for new chat

Data Source:

  • GET /api/sessions?provider=claude (and opencode, pi-mono)
  • Sessions are JSON files on the server, API returns metadata
  • Message preview from last assistant message

6.4 Chat - Conversation

┌─────────────────────────┐
│ ← Debug login    [···]  │
│    Claude 3.5 Sonnet ▾  │
├─────────────────────────┤
│                         │
│  ┌─ You ────────────┐   │
│  │ The login page    │   │
│  │ throws a 401...   │   │
│  └──────────────────┘   │
│                         │
│  ┌─ Claude ─────────┐   │
│  │ Looking at the    │   │
│  │ auth middleware... │   │
│  │                   │   │
│  │ ┌─ Tool Use ────┐ │   │
│  │ │ 📄 Read file  │ │   │
│  │ │ auth/jwt.ts   │ │   │
│  │ └──────────────┘ │   │
│  │                   │   │
│  │ The issue is in   │   │
│  │ the token verify  │   │
│  │ ```ts             │   │
│  │ if (!jti) ...     │   │
│  │ ```               │   │
│  └──────────────────┘   │
│                         │
├─────────────────────────┤
│ ┌───────────────┐ 📎 🎤│
│ │ Message...    │ [Send]│
│ └───────────────┘       │
└─────────────────────────┘

Behavior:

  • WebSocket connection to /api/harness/{provider}/ws
  • Messages stream in real-time (assistant:partial → assistant:text)
  • Tool use blocks shown as collapsible cards (collapsed by default)
  • Code blocks with syntax highlighting and copy button
  • Markdown rendering for assistant messages
  • Model selector dropdown in the header (or a bottom sheet)
  • Attachment button (📎) opens picker: Camera, Photo Library, Files
  • Microphone button (🎤) for voice input (speech-to-text → send as text)
  • Send button disabled while assistant is generating
  • "Stop" button appears during generation
  • Pull-down to load older messages (if session has many)
  • [···] menu: Rename session, View in files, Delete, Share

WebSocket Message Protocol:

Client sends:
  { type: 'chat', prompt, sessionId, model, images?, attachmentIds? }
  { type: 'resume', sessionId }
  { type: 'stop' }

Server sends:
  { type: 'session:init', sessionId, name }
  { type: 'assistant:partial', text }
  { type: 'assistant:text', text }
  { type: 'tool:use', name, input }
  { type: 'tool:result', result }
  { type: 'result', cost?, duration?, turns? }
  { type: 'error', message }
  { type: 'stopped' }

Offline Behavior:

  • If offline when sending, queue message locally
  • Show "Queued - will send when online" indicator
  • On reconnect, send queued messages in order
  • Previously loaded messages remain visible offline (cached in local DB)

Voice Input:

  • Tap and hold microphone for continuous recording
  • Or tap once to start, tap again to stop
  • Platform native speech recognition
  • Transcribed text placed in input field for review before sending
  • User can edit before sending

6.5 Files - Directory Browser

┌─────────────────────────┐
│ ← Files          [···]  │
│ / > Documents > Work    │
├─────────────────────────┤
│ [🔍 Search files...]    │
├─────────────────────────┤
│ Sort: Name ▾   [≡] [⊞] │
├─────────────────────────┤
│                         │
│  📁 Projects         →  │
│  📁 Reports          →  │
│  📄 notes.md      3KB   │
│  🖼️ photo.jpg    1.2MB  │
│  🎵 song.mp3     4.5MB  │
│  📹 demo.mp4    45.2MB  │
│  📦 backup.zip  12.0MB  │
│                         │
│         [+ Upload]      │
│                         │
└─────────────────────────┘

Behavior:

  • Breadcrumb navigation at top (scrollable horizontal)
  • Tap folder to navigate in, tap file to open viewer
  • Long-press for context menu:
    • Open / Preview
    • Share (native share sheet)
    • Rename
    • Copy / Move (enter selection mode)
    • Delete
    • Download to device
    • AI actions (OCR, TTS, Transcribe - based on file type)
  • Pull-to-refresh
  • FAB or bottom button for upload (Camera, Photo Library, Files picker)
  • Multi-select mode (long-press first item, then tap others)
  • Swipe actions: left = delete, right = share
  • Grid/List view toggle
  • Sort by name/date/size
  • Search with debounce (calls /api/file-browser/search)

Root Selection:

  • Dropdown or segmented control at top for root selection
  • Only show non-home roots for Super Admin users
  • Default to "home"

Upload Sources:

  • Device file picker
  • Camera (take photo)
  • Photo library
  • Share sheet (from other apps)
  • Drag & drop (tablet only)

6.6 Files - File Viewer

┌─────────────────────────┐
│ ← notes.md    [↗] [···]│
├─────────────────────────┤
│                         │
│  # Meeting Notes        │
│                         │
│  ## Action Items        │
│  - Fix login bug        │
│  - Update docs          │
│  - Review PR #42        │
│                         │
│  ## Discussion          │
│  We talked about the    │
│  new mobile app and...  │
│                         │
│                         │
│                         │
│                         │
└─────────────────────────┘

Viewer Types:

File Type Viewer Notes
.md Rendered Markdown With proper styling
.txt, .json, .ts, .js, etc. Syntax-highlighted text Read-only, with line numbers
.jpg, .png, .gif, .svg, .webp Image viewer Pinch to zoom, pan
.pdf PDF viewer Native PDF rendering
.mp3, .wav, .flac, .ogg Audio player Play/pause, seek, speed control
.mp4, .webm, .mov Video player Native player, fullscreen support
.mkv, .avi Video player (transcoded) Uses /api/file-browser/transcode endpoint
Other Download prompt "This file type can't be previewed. Download?"

Header Actions:

  • [↗] Share via native share sheet
  • [···] Menu: Download to device, AI actions (contextual), Open in chat, Delete

AI Actions (contextual):

  • Images: OCR (extract text)
  • Text files: TTS (read aloud)
  • Audio/Video: Transcribe
  • Video: Extract audio

6.7 Activity

┌─────────────────────────┐
│ Activity                │
├─────────────────────────┤
│ [Tasks] [Logs] [Procs]  │
├─────────────────────────┤
│                         │
│  Native Tasks           │
│  ┌──────────────────┐   │
│  │ 📋 Format Code   │   │
│  │ Formats source   │   │
│  │ files with...    │   │
│  │         [Run ▶]  │   │
│  └──────────────────┘   │
│  ┌──────────────────┐   │
│  │ 📋 Compress Imgs │   │
│  │ Optimizes image  │   │
│  │ files in...      │   │
│  │         [Run ▶]  │   │
│  └──────────────────┘   │
│                         │
│  User Tasks             │
│  ┌──────────────────┐   │
│  │ 📋 Backup DB     │   │
│  │ Runs pg_dump...  │   │
│  │         [Run ▶]  │   │
│  └──────────────────┘   │
│                         │
└─────────────────────────┘

Tabs:

  1. Tasks: List of available tasks (native/global/user). Each shows name, description, trigger types. "Run" button opens a sheet to select target file/directory.
  2. Logs: Recent task execution logs. Each shows task name, status (success/failure), timestamp, duration. Tap to see full log output.
  3. Processes: Currently running background processes. Show name, status, uptime. No start/stop controls (too dangerous on mobile - manage via web).

Task Runner:

  • When "Run" is tapped, show a bottom sheet:
    • Select target file/directory (mini file browser)
    • Confirm and run
    • Show real-time output in a scrollable log view
    • Toast on completion

6.8 Settings

┌─────────────────────────┐
│ Settings                │
├─────────────────────────┤
│                         │
│  ┌──────────────────┐   │
│  │ 👤 John Doe      │   │
│  │ john@example.com │   │
│  │ Super Admin      │   │
│  └──────────────────┘   │
│                         │
│  Profile                │
│  Name, Username, Avatar │
│  Change Password      → │
│  Passkeys             → │
│                         │
│  ─────────────────────  │
│                         │
│  App                    │
│  Theme           System │
│  Notifications       → │
│  Default Model  Claude  │
│  Biometric Lock    [🔘] │
│                         │
│  ─────────────────────  │
│                         │
│  Connection             │
│  Server  192.168.1.5 → │
│  Status     Connected 🟢│
│                         │
│  ─────────────────────  │
│                         │
│  [      Sign Out      ] │
│                         │
│  Officer v1.0.0         │
│                         │
└─────────────────────────┘

Sections:

  1. Profile card: Avatar, name, email, role. Tap to edit.
  2. Profile: Name/username editing, avatar upload (camera or gallery), password change, passkey management.
  3. App: Theme (light/dark/system), notification preferences, default chat model/provider, biometric lock toggle.
  4. Connection: Current server URL (editable), connection status indicator, last sync time.
  5. Sign Out: Clears JWT, biometric keys, navigates to login.

7. Tablet Adaptations

Tablets (iPad, Android tablets/foldables) get an enhanced layout while using the same app binary. Detection via screen width:

  • Phone: < 600dp (Android) / Compact (iOS)
  • Tablet: >= 600dp (Android) / Regular (iOS)

7.1 Navigation

Phone: Bottom tab bar (5 tabs) Tablet: Side navigation rail (Android) / Sidebar (iOS)

Tablet Layout:
┌────┬──────────────────────────────┐
│    │                              │
│ 🏠 │                              │
│ 💬 │       [Main Content]         │
│ 📁 │                              │
│ ⚡ │                              │
│    │                              │
│    │                              │
│ ⚙️ │                              │
└────┴──────────────────────────────┘

7.2 Chat on Tablet

Split view: Session list on left (1/3), conversation on right (2/3).

┌────┬──────────┬───────────────────┐
│    │Sessions  │ Debug login       │
│ 🏠 │──────────│ Claude 3.5 Sonnet │
│ 💬 │ Debug..  │───────────────────│
│ 📁 │ Refact.. │                   │
│ ⚡ │ Recipe.. │ [Messages...]     │
│    │          │                   │
│    │          │                   │
│    │          ├───────────────────│
│ ⚙️ │          │ [Input] [Send]    │
└────┴──────────┴───────────────────┘

7.3 Files on Tablet

Split view: Directory browser on left, file viewer on right. Tap a file to preview it in the right pane without navigating away.

┌────┬──────────┬───────────────────┐
│    │ / > Docs │ notes.md          │
│ 🏠 │──────────│───────────────────│
│ 💬 │ 📁 Proj  │ # Meeting Notes   │
│ 📁 │ 📁 Reps  │                   │
│ ⚡ │ 📄 note  │ ## Action Items   │
│    │ 🖼️ phot  │ - Fix login bug   │
│    │ 🎵 song  │ - Update docs     │
│    │          │                   │
│ ⚙️ │          │                   │
└────┴──────────┴───────────────────┘

7.4 Dashboard on Tablet

Widgets in a 2-3 column grid instead of single column. More widgets visible at once.

┌────┬──────────────────────────────┐
│    │ ┌──────┐ ┌──────┐ ┌──────┐  │
│ 🏠 │ │Clock │ │Weath.│ │Pomo  │  │
│ 💬 │ └──────┘ └──────┘ └──────┘  │
│ 📁 │ ┌─────────────┐ ┌──────────┐│
│ ⚡ │ │ Daily Goals  │ │ Quick    ││
│    │ │ ☑ Exercise   │ │ Notes    ││
│    │ │ ☐ Read 30min │ │ ...      ││
│    │ └─────────────┘ └──────────┘│
│ ⚙️ │ Workspaces                   │
│    │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
└────┴──────────────────────────────┘

7.5 Tablet-Only Features

Feature Description
Drag & Drop Drag files between panes, drag text into chat
Keyboard Shortcuts External keyboard support with shortcuts (Cmd+N for new chat, etc.)
Picture-in-Picture Video playback continues in PiP when navigating away
Multi-Window Android: Split screen support. iPad: Stage Manager support.

8. Technical Challenges

8.1 WebSocket Lifecycle on Mobile

Problem: Mobile OSes aggressively kill background connections. WebSocket to chat/terminal will disconnect when app is backgrounded.

Solution:

  • Detect app going to background → gracefully close WebSocket
  • On foreground → reconnect WebSocket, resume session
  • Use { type: 'resume', sessionId } to restore chat state
  • Buffer any pending assistant output server-side (the server already does this - messages are saved to JSON files)
  • Show "Reconnecting..." indicator when connection drops
  • Implement exponential backoff for reconnection attempts

Android specifics:

  • Use ProcessLifecycleOwner to detect app lifecycle
  • Consider foreground service for active chat sessions (shows notification "Chat in progress")

iOS specifics:

  • Use UIApplication.willResignActiveNotification / didBecomeActiveNotification
  • Background URL session for critical requests
  • Use BGAppRefreshTask for periodic sync

8.2 Push Notifications

Problem: The Officer API server currently has no push notification infrastructure.

Required Backend Changes:

  1. New endpoint: POST /api/devices/register — stores FCM/APNs token per user
  2. New database table: DeviceTokens (userId, token, platform, createdAt)
  3. Server-side notification triggers:
    • Chat: When assistant finishes responding and user's WebSocket is disconnected
    • Tasks: When a long-running task completes
    • Downloads: When yt-dlp or git clone finishes
  4. Firebase Admin SDK (Android) + APNs library (iOS) on the server

Notification Types:

Type Title Body Action
Chat complete "Claude responded" First 100 chars of response Open chat session
Task complete "Task finished" "Format Code completed successfully" Open task log
Download complete "Download ready" "video.mp4 downloaded" Open file browser
Error "Task failed" "Backup DB failed: connection refused" Open task log

8.3 File Upload from Mobile

Problem: Mobile file uploads have different sources than web (camera, photo library, share sheet).

Solution:

  • Use platform file pickers that support multiple sources
  • Camera capture: Take photo → upload via POST /api/file-browser/upload
  • Compress images before upload (configurable quality setting)
  • Show upload progress (the existing XHR-based upload in useFiles.ts supports progress)
  • Background upload for large files using WorkManager (Android) / BGTaskScheduler (iOS)
  • Resume interrupted uploads if possible

8.4 Offline Support

Problem: Users may open the app with no connectivity.

Strategy: Read-heavy offline, write-queued.

Feature Offline Behavior
Dashboard Cached widget data, local timer for pomodoro
Chat history Cached session list and recent messages
New chat Queue message, show "Will send when online"
File browser Show last cached directory listing
File viewer Show cached/downloaded files only
Settings Show cached profile, edits queued

Local Database Schema (Room / SwiftData):

ChatSessions: id, name, provider, lastMessagePreview, updatedAt, isArchived
ChatMessages: id, sessionId, role, content, timestamp (cache recent N per session)
DirectoryCache: path, root, entriesJson, cachedAt
FileCache: path, root, localPath, size, cachedAt (for downloaded/viewed files)
PendingActions: id, type, payload, createdAt, status

8.5 Deep Linking & Share Sheet

URL Scheme: officer:// Universal Links: https://officer.dev/app/... (if applicable)

Deep Link Routes:

  • officer://chat/{sessionId} — open specific chat
  • officer://files?path=/Documents — open file browser at path
  • officer://chat/new?prompt=... — start new chat with prefilled text

Share Sheet Integration (receiving):

  • Register app as share target for: text, URLs, images, files
  • When content is shared to Officer:
    1. Show a sheet: "Send to Chat" or "Upload to Files"
    2. If Chat: open new chat with shared content as first message
    3. If Files: open file browser with upload in current directory

8.6 Image Handling in Chat

Problem: The web app sends images as base64 in the WebSocket message. Large images on mobile could cause memory issues.

Solution:

  1. Before sending, resize images to max 2048px on longest edge
  2. Compress to JPEG at 85% quality (unless PNG is needed for screenshots)
  3. Convert to base64 and include in chat message
  4. Show thumbnail in the message thread, tap to view full-size
  5. Consider using the upload endpoint first, then sending attachment IDs instead of base64 (check if API supports this path — it does via attachmentIds in the chat message)

8.7 Markdown & Code Rendering

Problem: Rendering rich markdown with code blocks, tables, and images in native views.

Android Solution:

  • Use a Markdown library (Markwon or compose-markdown)
  • Code blocks: custom span with monospace font + syntax highlighting
  • Consider a lightweight WebView-based renderer for complex markdown as fallback

iOS Solution:

  • Use AttributedString with custom markdown parsing
  • Or use a SwiftUI markdown library (MarkdownUI)
  • Code blocks: custom view with monospace font + syntax highlighting

Code Syntax Highlighting:

  • Android: Highlight.js via WebView, or a native library like CodeView
  • iOS: Splash or Highlightr library

8.8 Streaming Text Display

Problem: Chat responses stream token-by-token via WebSocket. Need smooth text rendering without layout thrashing.

Solution:

  • Buffer incoming tokens (e.g., collect for 50ms before rendering)
  • Use a single mutable text state that appends
  • Avoid re-rendering the entire message list on each token
  • Only the last (active) message bubble needs updating
  • Auto-scroll to bottom during streaming, unless user has scrolled up

8.9 Security

Concerns:

  • JWT stored on device must be encrypted
  • Biometric key binding for stored credentials
  • Certificate pinning for API connections (optional but recommended for self-hosted)
  • No sensitive data in logs
  • Clear all data on sign out
  • Jailbreak/root detection (optional)

Android:

  • EncryptedSharedPreferences for tokens
  • Android Keystore for biometric-bound keys
  • ProGuard/R8 for release builds

iOS:

  • Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly
  • Secure Enclave for biometric keys
  • App Transport Security (ATS) enforced

8.10 Self-Hosted Server Discovery

Problem: Officer is self-hosted. Users need to point the app at their server. The server might be on a local network, behind a VPN, or on the public internet.

Solution:

  • First-launch: manual URL entry with validation
  • Local network: mDNS/Bonjour discovery (optional nice-to-have)
  • QR code scanning from web app settings page (generate QR with server URL + optional auth token)
  • Store multiple server profiles (for users who have dev/prod setups)
  • Connection status indicator in settings

9. API Integration Notes

Endpoints the Mobile App Will Use

Auth:

  • POST /api/auth/signin — login
  • POST /api/auth/signup — register (if enabled)
  • POST /api/auth/signout — logout
  • GET /api/auth/me — validate token, get user info
  • POST /api/auth/change-password — password change
  • */passkeys/* — passkey registration/auth

Chat:

  • GET /api/sessions?provider={provider} — list sessions
  • DELETE /api/sessions/{provider}/{id} — delete session
  • POST /api/sessions/{provider}/{id}/rename — rename session
  • POST /api/sessions/{provider}/{id}/archive — archive session
  • WebSocket /api/harness/{provider}/ws — chat communication

Files:

  • GET /api/file-browser/ls?path={path}&root={root} — list directory
  • GET /api/file-browser/read?path={path} — read text file
  • GET /api/file-browser/raw?path={path} — raw file (binary, supports Range)
  • GET /api/file-browser/search?q={query} — search files
  • POST /api/file-browser/mkdir — create directory
  • POST /api/file-browser/write — write file
  • POST /api/file-browser/upload — upload files (multipart)
  • POST /api/file-browser/rename — rename
  • POST /api/file-browser/copy — copy
  • POST /api/file-browser/move — move
  • DELETE /api/file-browser/rm — delete
  • GET /api/file-browser/download?path={path} — download file
  • POST /api/file-browser/download — download multiple as zip
  • GET /api/file-browser/transcode?path={path} — transcode video
  • POST /api/file-browser/tts — text-to-speech
  • POST /api/file-browser/ocr — OCR image
  • POST /api/file-browser/transcribe — transcribe audio
  • POST /api/file-browser/extract-audio — extract audio from video
  • POST /api/file-browser/extract — extract archive
  • POST /api/file-browser/git-clone — clone git repo
  • POST /api/file-browser/download-video — yt-dlp download

Tasks:

  • GET /api/tasks — list tasks
  • GET /api/tasks/{name} — task details
  • POST /api/tasks/{name}/run — run task

Task Logs:

  • GET /api/task-logs — list logs
  • GET /api/task-logs/{id} — log details

Processes:

  • GET /api/processes — list processes

Settings:

  • GET /api/user/settings — user settings
  • PUT /api/user/settings — update settings
  • PUT /api/auth/users — update profile

Upload:

  • POST /api/upload — upload attachments (for chat)

Scrape:

  • POST /api/scrape — scrape URL (for sharing URLs to chat)

New Endpoints Needed

Endpoint Purpose
POST /api/devices/register Register push notification token
DELETE /api/devices/{token} Unregister device on logout
GET /api/sessions/{provider}/{id}/messages Get messages for a session (currently read from files, need API endpoint)

Authentication Header

All requests include: Authorization: Bearer {jwt_token}

WebSocket connections pass token as query parameter: ?token={jwt_token}


10. Shared Design Language

While each platform follows its native design guidelines (Material 3 for Android, Human Interface Guidelines for iOS), some design elements should be consistent:

Color Palette

From the web app's Tailwind theme:

  • Primary (Teal): duck-teal — used for primary actions, active states
  • Accent (Yellow): duck-yellow — used for text on primary, highlights
  • Dark: duck-dark — text, icons
  • Background: Light/dark mode adaptive

Both apps should use these brand colors while adapting to platform conventions:

  • Android: Material 3 dynamic color with Officer palette as seed
  • iOS: Officer palette with system-adaptive backgrounds

Typography

  • Android: Roboto (system default), with monospace for code
  • iOS: SF Pro (system default), with SF Mono for code
  • Both: Similar size scale, consistent heading hierarchy

Iconography

  • Android: Material Symbols (outlined)
  • iOS: SF Symbols
  • Map lucide-react icons used in web to platform equivalents

Animations

  • Android: Material Motion (shared element transitions, container transforms)
  • iOS: Spring animations, matched geometry effects
  • Both: Smooth transitions between screens, subtle feedback on interactions

11. Implementation Phases

Phase 1: Foundation (MVP)

Goal: Core functionality that makes the app useful enough to ship.

Feature Details
Auth Email/password login, token storage, biometric unlock
Server setup URL configuration, validation
Chat New chat, session list, real-time conversation with streaming
Chat basics Text messages, model selection, session management
Settings Profile view, theme, logout

Estimated screens: Login, Home (simple), Chat List, Chat Conversation, Settings

Phase 2: Files & Polish

Goal: File management and mobile-specific enhancements.

Feature Details
File browser Directory listing, navigation, search
File viewer Text, images, PDF, audio, video
File actions Upload (camera, files), download, rename, delete
Chat attachments Image attachment, voice input
Push notifications Basic chat completion notifications

Estimated screens: File Browser, File Viewer, enhanced Chat

Phase 3: Productivity

Goal: Dashboard, tasks, and deeper integration.

Feature Details
Dashboard Widget system (clock, weather, pomodoro, goals, notes)
Tasks Task list, run tasks, view logs
Workspace quick access Open workspaces (single-panel)
Share sheet Receive content from other apps
Offline support Cached data, queued actions

Estimated screens: Dashboard, Task List, Task Logs, enhanced Home

Phase 4: Tablet & Advanced

Goal: Tablet optimization and advanced features.

Feature Details
Tablet layouts Split views for chat, files
Tablet navigation Side rail / sidebar
Home screen widgets Android widgets, iOS widgets
AI file actions OCR, TTS, Transcribe from mobile
Multi-select Batch file operations
Deep linking URL scheme, universal links

Phase 5: Nice-to-Haves

Feature Details
QR code server setup Scan from web to configure
Local network discovery mDNS/Bonjour
Picture-in-Picture Video PiP on tablet
Passkey auth Full WebAuthn support
Keyboard shortcuts External keyboard on tablet
Skills browser View and search skills library
Background downloads yt-dlp and git clone in background

Appendix: Decision Log

Decision Rationale
Native (Kotlin + Swift) over cross-platform User preference. Also gives best performance, platform-native UX, and full access to platform APIs (biometrics, push, background tasks, share sheet).
Exclude terminal Phone keyboards make shell commands impractical. The sandboxed Docker setup is inherently desktop-oriented.
Exclude code editor (write) Writing code on a phone is poor UX. Read-only with syntax highlighting is sufficient for review.
Exclude automation builder Complex form-heavy UI that doesn't translate to small screens.
Include chat as primary feature AI assistant in your pocket is the strongest mobile use case. Voice input makes it even more compelling on mobile.
Bottom tabs (phone) / Side rail (tablet) Standard navigation patterns for each form factor. 5 top-level destinations fits within guidelines.
Offline-first for reads, queue for writes Mobile connectivity is unreliable. Showing cached data is better than empty screens.
Phase 1 = Auth + Chat Chat is the killer feature. Get it right first, everything else is complementary.