You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SkyTwin runs locally on the user's desktop. Approval requests need timely responses — but users aren't always at their computer. Today there's no way to approve actions from a phone. For daily use and the HN launch, phone access on the same WiFi network is essential: scan a QR code, open the dashboard, approve from your pocket.
Claude Code estimate: ~3-4h
Current State (verified 2026-04-04)
Web dashboard: static SPA served by Express, works in any browser
Mobile CSS: basic responsive fixes shipped in v0.1 (mobile nav backdrop, sidebar collapse) but not systematically tested
Authentication: no session tokens — dashboard talks directly to API on localhost
mDNS/Bonjour: not implemented
QR codes: not implemented
HTTPS: not implemented (HTTP only)
Proposed Change
1. mDNS service advertisement
Register Bonjour service on API start via `bonjour-service`. Phone finds desktop as `skytwin.local`.
2. QR code pairing
Desktop generates session token (crypto.randomUUID + HMAC), stores hash in DB, encodes URL as QR via `qrcode` npm package. Phone scans, stores token in localStorage, includes in `Authorization` header. 7-day expiry with auto-refresh.
3. Session auth middleware
Check `Authorization: Bearer` header for valid session token. Skip for localhost. Session management endpoints for listing and revoking.
4. Responsive dashboard
Bottom nav bar on < 768px. Full-width approve/reject buttons. Vertical card stacking. Collapsible filter controls. Min 44px touch targets.
5. Local HTTPS (stretch)
Self-signed cert on first launch. Required for future Web Push.
DB Schema Addition
```sql
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
token_hash STRING NOT NULL,
device_name STRING DEFAULT 'Phone',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL,
last_active_at TIMESTAMPTZ NOT NULL DEFAULT now(),
revoked BOOLEAN NOT NULL DEFAULT false
);
CREATE INDEX idx_sessions_token ON sessions (token_hash) WHERE revoked = false;
CREATE INDEX idx_sessions_user ON sessions (user_id) WHERE revoked = false;
```
Acceptance Criteria
API server starts → `skytwin.local` resolves on same WiFi network from iOS Safari and Android Chrome (verify via `dns-sd -B _http._tcp` on macOS or equivalent)
Settings page shows QR code → scan with iPhone camera → URL opens in Safari → dashboard loads within 3 seconds
Session token stored as HMAC-SHA256 hash in DB — raw token never persisted server-side (verify by inspecting `sessions` table)
Phone makes API request with `Authorization: Bearer ` → request succeeds with 200
Phone makes API request with expired token → returns 401 with body `{"error": "Session expired", "message": "Scan the QR code again from your desktop"}`
Phone makes API request with revoked token → returns 401 (same as expired)
Desktop request from localhost without `Authorization` header → succeeds (localhost bypass)
Token within 1 day of expiry + successful API request → `expires_at` extended by 7 days → `last_active_at` updated
Settings page shows active sessions: device name, created date, last active relative time (e.g., "2 min ago")
Click "Revoke" on a session → `DELETE /api/sessions/:id` → session marked revoked → phone gets 401 on next request
Approvals page on 375px screen width → approve and reject buttons fully visible without horizontal scroll → buttons min height 48px
Bottom navigation bar appears on screens < 768px with 5 items (Home, Approvals, Decisions, Twin, Settings) → approval badge count visible
All 6 dashboard pages (dashboard, approvals, decisions, twin, settings, audit) render without horizontal scroll at 375px width
No interactive element (button, link, input) smaller than 44x44px on mobile layout
Sidebar completely hidden on < 768px (not collapsed — removed from DOM or `display: none`)
Decision detail view on mobile → full-screen overlay (not inline expand)
During implementation, maintain two sources of truth to survive context compaction:
Local context file: Write progress, decisions, and blockers to .context/issue-15-mobile.md (gitignored). Update this file after each meaningful step. On compaction, re-read this file to restore state.
Context
SkyTwin runs locally on the user's desktop. Approval requests need timely responses — but users aren't always at their computer. Today there's no way to approve actions from a phone. For daily use and the HN launch, phone access on the same WiFi network is essential: scan a QR code, open the dashboard, approve from your pocket.
Claude Code estimate: ~3-4h
Current State (verified 2026-04-04)
Proposed Change
1. mDNS service advertisement
Register Bonjour service on API start via `bonjour-service`. Phone finds desktop as `skytwin.local`.
2. QR code pairing
Desktop generates session token (crypto.randomUUID + HMAC), stores hash in DB, encodes URL as QR via `qrcode` npm package. Phone scans, stores token in localStorage, includes in `Authorization` header. 7-day expiry with auto-refresh.
3. Session auth middleware
Check `Authorization: Bearer` header for valid session token. Skip for localhost. Session management endpoints for listing and revoking.
4. Responsive dashboard
Bottom nav bar on < 768px. Full-width approve/reject buttons. Vertical card stacking. Collapsible filter controls. Min 44px touch targets.
5. Local HTTPS (stretch)
Self-signed cert on first launch. Required for future Web Push.
DB Schema Addition
```sql
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
token_hash STRING NOT NULL,
device_name STRING DEFAULT 'Phone',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL,
last_active_at TIMESTAMPTZ NOT NULL DEFAULT now(),
revoked BOOLEAN NOT NULL DEFAULT false
);
CREATE INDEX idx_sessions_token ON sessions (token_hash) WHERE revoked = false;
CREATE INDEX idx_sessions_user ON sessions (user_id) WHERE revoked = false;
```
Acceptance Criteria
Testing Plan
Files Reference
Out of Scope
Related
Working Context Protocol
During implementation, maintain two sources of truth to survive context compaction:
.context/issue-15-mobile.md(gitignored). Update this file after each meaningful step. On compaction, re-read this file to restore state.This ensures no quality loss across compaction events — the local file has granular state, the GitHub issue has durable history.