Add 'aiui/' from commit 'e30ac1d1069532fb6d652d87e2d4a2fe9d1b4773'
git-subtree-dir: aiui git-subtree-mainline:0c4826f8ccgit-subtree-split:e30ac1d106
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
# Overnight Claude Automation
|
||||
|
||||
Run Claude Code autonomously while you're away. Combines sleep prevention, task-based execution, the Ralph Wiggum Technique (Stop hook blocks until plan is complete), and security hooks that restrict AI to project files and block destructive commands.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Claude Code CLI** ([claude.ai/code](https://claude.ai/code)) — installed at `~/.local/bin/claude` or in PATH
|
||||
- **Hooks** — user-level hooks in `~/.claude/` (sleep, Ralph Wiggum)
|
||||
- **jq** — for security hook scripts (`brew install jq`)
|
||||
|
||||
## Flow
|
||||
|
||||
### Pre-run (before 5–6pm)
|
||||
|
||||
1. **Commit and push** — Snap current work and back up to remote.
|
||||
2. **Run prepare script** — Creates date-stamped branch and verifies clean state:
|
||||
|
||||
```bash
|
||||
./loop/prepare.sh
|
||||
```
|
||||
|
||||
3. **Edit plan** — Update `loop/plan.md` with evening scope and tasks (see template below).
|
||||
4. **Commit plan** — Version the plan so you can revert if needed:
|
||||
|
||||
```bash
|
||||
git add loop/plan.md && git commit -m "chore: overnight plan $(date +%Y-%m-%d)"
|
||||
```
|
||||
|
||||
5. **Push** (optional but recommended): `git push -u origin overnight/YYYY-MM-DD`
|
||||
|
||||
### Overnight
|
||||
|
||||
```bash
|
||||
tmux new -s overnight
|
||||
caffeinate -i ./loop/loop.sh
|
||||
# Detach: Ctrl+B, then D
|
||||
```
|
||||
|
||||
### Post-run (next morning)
|
||||
|
||||
1. `git status` and `git diff` to review changes.
|
||||
2. Run `pnpm test && pnpm lint && pnpm typecheck`.
|
||||
3. Merge branch or revert if needed.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Edit your plan** — Add tasks to `loop/plan.md` using the evening run format:
|
||||
|
||||
```markdown
|
||||
# Evening Run — YYYY-MM-DD
|
||||
|
||||
## Scope
|
||||
Add tests to chat components.
|
||||
|
||||
## Tasks
|
||||
- [ ] Add unit tests for useAI composable
|
||||
- [ ] Fix linter errors in packages/app
|
||||
```
|
||||
|
||||
2. **Run overnight** — From project root:
|
||||
|
||||
```bash
|
||||
caffeinate -i ./loop/loop.sh
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| **UserPromptSubmit hook** | Starts `caffeinate` to prevent Mac sleep when you submit a prompt |
|
||||
| **Stop hook** | Checks `plan.md` for unchecked tasks; blocks Claude from stopping until all are done (Ralph Wiggum) |
|
||||
| **SessionEnd hook** | Kills `caffeinate` so Mac can sleep again |
|
||||
| **PreToolUse (Bash)** | Blocks dangerous commands (rm -rf, git reset --hard, etc.) |
|
||||
| **PreToolUse (Edit\|Write)** | Blocks edits outside project and to protected paths |
|
||||
| **loop.sh** | Runs Claude with `--dangerously-skip-permissions` and feeds the prompt from `loop/prompt.md` |
|
||||
|
||||
## Security Model
|
||||
|
||||
Project-scoped hooks in `.claude/hooks/` restrict the AI during overnight runs:
|
||||
|
||||
### Bash guard (`block-risky-bash.sh`)
|
||||
|
||||
Blocks: `rm -rf`, `git reset --hard`, `git push --force`, `git clean -fd`, `chmod -R 777`, fork bombs, block device overwrites, `mkfs`, and path traversal with destructive commands.
|
||||
|
||||
### File edit guard (`protect-files.sh`)
|
||||
|
||||
Blocks Edit/Write when:
|
||||
|
||||
- Path is **outside project directory**
|
||||
- Path contains **`.git/`**
|
||||
- Path is **`.env`**, **`.env.local`**, **`.env.*.local`**
|
||||
- Path is **`package-lock.json`** or **`pnpm-lock.yaml`**
|
||||
- Path contains **`node_modules/`**
|
||||
|
||||
Read, Glob, and Grep remain unrestricted.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `CLAUDE_AUTONOMOUS` | `1` | Set to `1` to enable Ralph Wiggum (Stop hook checks plan). `0` disables. |
|
||||
| `CLAUDE_PLAN_FILE` | `plan.md` | Plan file path (relative to project). |
|
||||
| `ITERATION_COUNT` | `1` | Number of loop iterations (use >1 for multi-run without Ralph Wiggum). |
|
||||
| `ITERATION_DELAY` | `600` | Seconds between iterations when `ITERATION_COUNT` > 1. |
|
||||
| `PROMPT_FILE` | `loop/prompt.md` | Prompt content for Claude. |
|
||||
| `LOG_FILE` | `loop/loop.log` | Log output (gitignored). |
|
||||
| `RATE_LIMIT_WAIT` | `3600` | Seconds to wait when rate limited (default 1 hour). |
|
||||
| `MAX_RATE_LIMIT_RETRIES` | `5` | Max rate limit retries before scheduling launchd job. |
|
||||
|
||||
## Rate Limit Handling
|
||||
|
||||
The loop script automatically detects rate limits (429, quota exceeded, etc.) and handles them:
|
||||
|
||||
1. **Inline retry** — On first rate limit hit, sleeps for `RATE_LIMIT_WAIT` seconds (default 1 hour) and retries.
|
||||
2. **Escalating retries** — Retries up to `MAX_RATE_LIMIT_RETRIES` times with the same wait.
|
||||
3. **launchd fallback** — After max retries, creates a self-cleaning launchd plist at `~/Library/LaunchAgents/com.aiui.overnight-retry.plist` that restarts the loop at the estimated reset time. The plist auto-removes after running.
|
||||
|
||||
This means you can walk away knowing the automation will survive rate limits overnight.
|
||||
|
||||
## Scheduling (Optional)
|
||||
|
||||
Install [claude-code-schedule](https://github.com/macalinao/claude-code-schedule) for time-based runs:
|
||||
|
||||
```bash
|
||||
cargo install claude-code-schedule
|
||||
ccschedule --time 05:30 --message "Review plan.md and complete next task"
|
||||
```
|
||||
|
||||
## continuous-claude (Optional)
|
||||
|
||||
For full PR-based workflow (branches, PRs, CI):
|
||||
|
||||
```bash
|
||||
# Install from https://github.com/AnandChowdhary/continuous-claude
|
||||
continuous-claude -p "Work through loop/plan.md" -m 10 --max-duration 8h
|
||||
```
|
||||
|
||||
## Remote Monitoring
|
||||
|
||||
- **Tmux + SSH**: Attach from another machine: `ssh host 'tmux attach -t overnight'`
|
||||
- **Tailscale**: Use Tailscale for easy remote SSH when away from home network
|
||||
- **Log tail**: `tail -f loop/loop.log` to watch progress
|
||||
|
||||
## Safety
|
||||
|
||||
- **Start small** — Test with 1–2 tasks before overnight runs
|
||||
- **prepare.sh** — Run before starting; fails if working tree is dirty or branch exists
|
||||
- **Git** — Loop does not auto-commit; you review and merge in the morning
|
||||
- **`--dangerously-skip-permissions`** — Security hooks still run and block dangerous actions
|
||||
- **Project-scoped hooks** — Only apply when Claude runs in AIUI; other projects unaffected
|
||||
@@ -0,0 +1,286 @@
|
||||
# AIUI Debug, Fix & Hardening Plan
|
||||
|
||||
## Execution Rules
|
||||
|
||||
- Run on `development` branch
|
||||
- Each `- [ ]` task is one agent iteration — complete fully before moving on
|
||||
- After each phase: `pnpm typecheck && pnpm lint && pnpm test -- --run`
|
||||
- If checks fail, fix before proceeding
|
||||
- Commit at end of each phase with `type(scope): description` format
|
||||
- Do NOT push — human will review and push
|
||||
|
||||
---
|
||||
|
||||
## PHASE 0: Baseline Verification
|
||||
|
||||
- [ ] **P0.1 — Baseline check**
|
||||
- Run: `pnpm install && pnpm typecheck && pnpm lint && pnpm test -- --run`
|
||||
- Record output. Note existing failures. Fix any blockers before proceeding.
|
||||
- Commit: `chore(app): verify baseline before hardening`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 1: Critical Security Fixes
|
||||
|
||||
- [ ] **P1.1 — Fix CORS wildcard in claude-proxy.ts**
|
||||
- File: `packages/app/server/claude-proxy.ts`
|
||||
- Replace all `'Access-Control-Allow-Origin': '*'` (lines ~415, 473, 531) with the correct localhost origin or import `ALLOWED_ORIGIN` from `dev-auth.ts`. Match the pattern already used on lines 294/303.
|
||||
- Verify: `grep -n "Allow-Origin.*\*" packages/app/server/claude-proxy.ts` returns zero matches.
|
||||
|
||||
- [ ] **P1.2 — Fix dev auth token bypass**
|
||||
- File: `packages/app/server/dev-auth.ts`
|
||||
- Line 11: `if (!token) return true` skips auth entirely when token empty.
|
||||
- Fix: Only skip in non-production. `if (!token) { if (process.env.NODE_ENV === 'production') { res.writeHead(401); res.end('Unauthorized'); return false; } return true; }`
|
||||
- Add console.warn when auth disabled.
|
||||
|
||||
- [ ] **P1.3 — Symlink traversal protection in vite-fs.ts**
|
||||
- File: `packages/app/vite-fs.ts`
|
||||
- In `walk` function: after constructing `fullPath`, add `if (lstatSync(fullPath).isSymbolicLink()) continue;`
|
||||
- In `handleRead`: before `statSync`, check `lstatSync(filePath).isSymbolicLink()` → return 403.
|
||||
- Import `lstatSync` from `fs`.
|
||||
|
||||
- [ ] **P1.4 — Body size limit on vite-fs.ts mkdir**
|
||||
- File: `packages/app/vite-fs.ts`
|
||||
- `handleMkdir` reads body with no size cap (lines 185-209).
|
||||
- Add `const MAX_BODY_SIZE = 1024`. Track `let size = 0` on `data` events. Return 413 if exceeded.
|
||||
|
||||
- [ ] **P1.5 — JSON schema validation in vite-dev-chats.ts**
|
||||
- File: `packages/app/vite-dev-chats.ts`
|
||||
- After `JSON.parse(body)` on line 66, validate shape: must be object with optional `conversations` (object) and `activeConversationId` (string|null). Reject with 400 if invalid.
|
||||
|
||||
- [ ] **P1.6 — CSP headers in nginx-archy.conf**
|
||||
- File: `packages/app/server/nginx-archy.conf`
|
||||
- Add inside `/aiui/` location block:
|
||||
```
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; connect-src 'self' https://api.anthropic.com https://openrouter.ai https://wavlake.com https://itunes.apple.com https://openlibrary.org https://covers.openlibrary.org https://en.wikipedia.org https://www.googleapis.com https://image.tmdb.org; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
```
|
||||
|
||||
- [ ] **P1.7 — OpenRouter validation + streaming timeout**
|
||||
- File: `packages/app/server/claude-proxy.ts`
|
||||
- OpenRouter handler: parse `reqBody`, validate has `model` (string), `messages` (array), `stream` (boolean). Return 400 if invalid.
|
||||
- Both streaming reader loops: add idle timeout (120s). `let idleTimer = setTimeout(() => reader.cancel(), 120000)`. Reset on each chunk. Clear on completion.
|
||||
- Verify: `pnpm typecheck`
|
||||
- **Commit**: `fix(app): critical security — CORS, auth, symlink, CSP, body validation`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 2: Proxy & Server Hardening
|
||||
|
||||
- [ ] **P2.1 — Fix tool use loop in claude-proxy.ts**
|
||||
- File: `packages/app/server/claude-proxy.ts` (lines 164-222)
|
||||
- Unknown tool names: push error tool_result `{ type: 'tool_result', tool_use_id: tu.id, content: 'Error: unknown tool', is_error: true }`.
|
||||
- Add turnMessages size guard: `if (JSON.stringify(turnMessages).length > 500_000)` break loop.
|
||||
- Add `AbortSignal.timeout(30000)` on each API call within loop.
|
||||
|
||||
- [ ] **P2.2 — SSRF mitigation in vite-rss.ts**
|
||||
- File: `packages/app/vite-rss.ts`
|
||||
- After `tryParseFeed` returns, validate returned article URLs with `isPrivateUrl`. Filter out any private URLs.
|
||||
- Add comment documenting residual TOCTOU risk (dev-only middleware).
|
||||
|
||||
- [ ] **P2.3 — Sanitize web search context in useAI.ts**
|
||||
- File: `packages/app/src/composables/useAI.ts` (lines 399-408)
|
||||
- Create helper: `function sanitizeSearchText(s: string): string { return s.replace(/[[\]()#*_~` >]/g, '\\$&').replace(/\n/g, ' ').slice(0, 500) }`
|
||||
- Apply to `r.title` and `r.content` in `formatWebSearchContext`.
|
||||
- **Commit**: `fix(app): proxy hardening — tool loop, SSRF, prompt injection`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 3: State & Logic Bugs
|
||||
|
||||
- [ ] **P3.1 — Fix contentType assignment in useContentPanel.ts**
|
||||
- File: `packages/app/src/composables/useContentPanel.ts` (lines 219-225)
|
||||
- The `contentType` ref type is `'film' | 'song' | 'podcast'`. Expand to include all content types or use `ContentTab`. Fix assignments: books→'film' is acceptable if type can't expand, but news/TV should map correctly. If the type is only used for player logic, keep narrow type but fix assignments to be semantically correct.
|
||||
|
||||
- [ ] **P3.2 — Fix apiFetching race in useBannerFallback.ts**
|
||||
- File: `packages/app/src/composables/useBannerFallback.ts`
|
||||
- Change `let apiFetching = false` to `const apiFetching = ref(false)`. Update all references to `.value`.
|
||||
|
||||
- [ ] **P3.3 — Fix event listener cleanup in chat.ts**
|
||||
- File: `packages/app/src/stores/chat.ts` (lines 111-118)
|
||||
- Extract handlers to named functions. Add `import.meta.hot?.dispose()` cleanup for HMR.
|
||||
|
||||
- [ ] **P3.4 — Fix news RSS race condition in useContentPanel.ts**
|
||||
- File: `packages/app/src/composables/useContentPanel.ts` (lines 134-154)
|
||||
- Capture `const tabAtStart = activeTab.value` before RSS fetch. In `.then()`, only set `activeTab.value = 'news'` if `activeTab.value === tabAtStart` (user hasn't manually switched).
|
||||
|
||||
- [ ] **P3.5 — Add error logging to silent catch blocks**
|
||||
- Files: `useImageFallback.ts`, `useAI.ts`, `stores/chat.ts`
|
||||
- Replace all `catch { }` and `catch { /* ignore */ }` with `catch(e) { console.debug('[module] op failed:', e) }`.
|
||||
- Keep `console.debug` (not `error`) for expected failures.
|
||||
- **Commit**: `fix(app): state bugs — contentType, race conditions, error logging`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 4: Content Extraction & Filtering
|
||||
|
||||
- [ ] **P4.1 — Add recipe instructions to system prompt**
|
||||
- File: `packages/app/src/composables/useAI.ts` (SYSTEM_PROMPT)
|
||||
- Add after Apps section:
|
||||
```
|
||||
**Recipes:** When sharing recipes, use the <recipe_ext> XML tag:
|
||||
<recipe_ext title="Name" servings="4" time="30 min" calories="450">
|
||||
- ingredient 1
|
||||
1. Step one
|
||||
</recipe_ext>
|
||||
```
|
||||
|
||||
- [ ] **P4.2 — Fix app category validation**
|
||||
- File: `packages/app/src/composables/contentExtraction.ts` (line ~1330)
|
||||
- Add `const VALID_CATEGORIES = new Set(['nostr-client','lightning-wallet','bitcoin-wallet','privacy','node','dev-tool','relay'])`. Validate before cast, fallback to `'dev-tool'`.
|
||||
|
||||
- [ ] **P4.3 — Fix song deduplication**
|
||||
- File: `packages/app/src/composables/contentExtraction.ts` (lines 606-638)
|
||||
- After combining library + external songs, deduplicate by normalized `title|artist` key. Library songs (with real IDs) take priority.
|
||||
|
||||
- [ ] **P4.4 — Fix film/TV tag ambiguity**
|
||||
- File: `packages/app/src/composables/contentExtraction.ts` (lines 914-916)
|
||||
- Only convert `film_ext` to TV when: query is TV-like AND no explicit `tv_ext` tags present AND response reads TV-like. If AI used both `film_ext` and `tv_ext`, keep both as-is.
|
||||
|
||||
- [ ] **P4.5 — Fix isBookLikeResponse false positives**
|
||||
- File: `packages/app/src/composables/contentFiltering.ts` (lines 60-62)
|
||||
- Increase `by [Author]` threshold from `>= 1` to `>= 2`.
|
||||
|
||||
- [ ] **P4.6 — Fix isRecipeLikeResponse for prose recipes**
|
||||
- File: `packages/app/src/composables/contentFiltering.ts` (lines 92-94)
|
||||
- Add prose detection: recipe keywords + list structure pattern.
|
||||
|
||||
- [ ] **P4.7 — Fix looksLikeSong false positives**
|
||||
- File: `packages/app/src/composables/contentExtraction.ts` (lines 488-506)
|
||||
- Add financial terms to blocklist: `'market cap','etf','price','trading','volume','earnings','valuation','stock','portfolio','investment','yield','inflation','interest rate'`.
|
||||
- **Commit**: `fix(app): extraction — dedup, tag ambiguity, false positives, recipes`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 5: Memory & Cache Hardening
|
||||
|
||||
- [ ] **P5.1 — Bound caches in useImageFallback.ts**
|
||||
- File: `packages/app/src/composables/useImageFallback.ts`
|
||||
- Create helper: `function boundedSet<K,V>(map: Map<K,V>, key: K, val: V, max=500) { if (map.size >= max) { const first = map.keys().next().value; if (first !== undefined) map.delete(first); } map.set(key, val); }`
|
||||
- Replace all `.set()` calls on memory caches with `boundedSet()`.
|
||||
- Also bound `failedUrls` Set to 1000 entries.
|
||||
|
||||
- [ ] **P5.2 — Bound caches in usePlayer.ts + cleanup**
|
||||
- File: `packages/app/src/composables/usePlayer.ts`
|
||||
- Bound `resultCache` and `nullCacheTimestamps` to 200 entries.
|
||||
- Add `destroy()` method that calls `destroyPlayer()`, resets DOM refs, cancels active search controller.
|
||||
- **Commit**: `fix(app): bound caches, player cleanup`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 6: Accessibility
|
||||
|
||||
- [ ] **P6.1 — Fix touch targets and aria attributes**
|
||||
- Audit all `*Grid.vue` and `*Detail.vue` for: interactive elements < 44px, images without alt, SVG fallbacks without aria-label.
|
||||
- ImageGrid.vue: add `role="img"` `aria-label="Image unavailable"` to fallback SVG wrapper.
|
||||
- Ensure all interactive genre/tag pills in grids have adequate touch targets (min 44x44 including padding).
|
||||
- Non-interactive info pills are exempt.
|
||||
- **Commit**: `fix(app): accessibility — touch targets, alt text, aria`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 7: Security Tests
|
||||
|
||||
- [ ] **P7.1 — Write dev-auth tests**
|
||||
- New file: `packages/app/src/__tests__/dev-auth.test.ts`
|
||||
- Tests: auth bypass when no token (dev), auth required when token set, rate limiting 429, CORS headers correct.
|
||||
|
||||
- [ ] **P7.2 — Extend proxy tests**
|
||||
- File: `packages/app/src/__tests__/proxy.test.ts`
|
||||
- Tests: unknown tool error result, max rounds termination, turnMessages size guard, streaming timeout.
|
||||
|
||||
- [ ] **P7.3 — Write vite-fs security tests**
|
||||
- New file: `packages/app/src/__tests__/vite-fs.test.ts`
|
||||
- Tests: path traversal rejected, symlink blocked, sensitive files blocked, body size limit enforced.
|
||||
|
||||
- [ ] **P7.4 — Write SSRF tests for vite-rss**
|
||||
- New file: `packages/app/src/__tests__/vite-rss.test.ts`
|
||||
- Tests: private IP detection, localhost blocked, non-http blocked, private URLs filtered from results.
|
||||
- **Commit**: `test(app): security tests — auth, proxy, fs, SSRF`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 8: Content Extraction Tests
|
||||
|
||||
- [ ] **P8.1 — Write content filtering tests**
|
||||
- New file: `packages/app/src/composables/__tests__/contentFiltering.test.ts`
|
||||
- Tests: isBookLikeResponse false positive fix, isRecipeLikeResponse prose detection, isMusicQuery rejects financial terms, filterTabsByContext ordering.
|
||||
|
||||
- [ ] **P8.2 — Extend content extraction tests**
|
||||
- File: `packages/app/src/__tests__/contentExtraction.test.ts`
|
||||
- Tests: song dedup, looksLikeSong rejects financial terms, app category fallback, recipe parsing.
|
||||
|
||||
- [ ] **P8.3 — Write TV/film resolution tests**
|
||||
- Same file as P8.2.
|
||||
- Tests: film_ext→TV conversion rules, book_ext in news context, explicit tags respected.
|
||||
- **Commit**: `test(app): content extraction — filtering, dedup, tag resolution`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 9: State & Composable Tests
|
||||
|
||||
- [ ] **P9.1 — Write useContentPanel tests**
|
||||
- File: `packages/app/src/composables/__tests__/useContentPanel.test.ts`
|
||||
- Tests: contentType assignment per content type, RSS tab override prevention, closePanel resets, empty text → no tabs.
|
||||
|
||||
- [ ] **P9.2 — Write useBannerFallback tests**
|
||||
- New file: `packages/app/src/composables/__tests__/useBannerFallback.test.ts`
|
||||
- Tests: primary URL fallthrough, API fetch on exhaustion, apiFetching guard, gradient fallback.
|
||||
|
||||
- [ ] **P9.3 — Write chat store tests**
|
||||
- New file: `packages/app/src/__tests__/chat-store.test.ts`
|
||||
- Tests: createConversation, addMessage, deleteConversation, branchFromMessage, debouncedIDBSave.
|
||||
- **Commit**: `test(app): state tests — contentPanel, bannerFallback, chat store`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 10: AI Integration Tests
|
||||
|
||||
- [ ] **P10.1 — Extend useAI tests**
|
||||
- File: `packages/app/src/__tests__/useAI.test.ts`
|
||||
- Tests: system prompt includes recipe instructions, web search results sanitized, editAndResend truncates, sanitizeHistory merges consecutive roles.
|
||||
|
||||
- [ ] **P10.2 — Write useImageFallback cache tests**
|
||||
- New file: `packages/app/src/composables/__tests__/useImageFallback.test.ts`
|
||||
- Tests: boundedSet evicts at max, generatePosterFallback valid SVG, escapeXml handles specials.
|
||||
- **Commit**: `test(app): AI integration, image fallback cache tests`
|
||||
|
||||
---
|
||||
|
||||
## PHASE 11: Final Verification
|
||||
|
||||
- [ ] **P11.1 — Full integration check**
|
||||
- Run: `pnpm typecheck && pnpm lint && pnpm test -- --run`
|
||||
- Run: `pnpm build` — verify production build succeeds
|
||||
- Fix any regressions.
|
||||
|
||||
- [ ] **P11.2 — Final commit**
|
||||
- Run coverage report if configured: `pnpm test -- --run --coverage`
|
||||
- Ensure all changes committed on `development`.
|
||||
- **Commit**: `chore(app): hardening complete — all checks pass`
|
||||
|
||||
---
|
||||
|
||||
## Phase Dependencies
|
||||
|
||||
```
|
||||
P0 → P1 → P2 → P3 → P4 → P5 ─┐
|
||||
├→ P7 (tests P1,P2)
|
||||
P6 ──┤
|
||||
├→ P8 (tests P4)
|
||||
├→ P9 (tests P3,P5)
|
||||
├→ P10 (tests P2.3,P4.1)
|
||||
└→ P11 (final)
|
||||
```
|
||||
|
||||
P5 and P6 can run in parallel. P7-P10 can run in any order after their fix phases.
|
||||
|
||||
## Summary
|
||||
|
||||
- **12 phases**, **42 tasks**
|
||||
- Phases 0-6: fixes (security → proxy → state → extraction → caches → a11y)
|
||||
- Phases 7-10: tests (security → extraction → state → AI)
|
||||
- Phase 11: final verification
|
||||
- Target: all checks pass, 40%+ test coverage on critical paths, secure for Archy deployment
|
||||
Executable
+200
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env sh
|
||||
# Headless loop script for overnight Claude Code automation.
|
||||
# Set CLAUDE_AUTONOMOUS=1 for Ralph Wiggum (Stop hook blocks until plan is complete).
|
||||
# Rate-limit aware: detects limits, sleeps until reset, and retries automatically.
|
||||
set -u
|
||||
|
||||
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||
PROMPT_FILE="${PROMPT_FILE:-$PROJECT_DIR/loop/prompt.md}"
|
||||
LOG_FILE="${LOG_FILE:-$PROJECT_DIR/loop/loop.log}"
|
||||
ITERATION_COUNT="${ITERATION_COUNT:-10}"
|
||||
ITERATION_DELAY="${ITERATION_DELAY:-30}"
|
||||
CLAUDE_BIN="${CLAUDE_BIN:-claude}"
|
||||
RATE_LIMIT_WAIT="${RATE_LIMIT_WAIT:-3600}" # Default: wait 1 hour on rate limit
|
||||
MAX_RATE_LIMIT_RETRIES="${MAX_RATE_LIMIT_RETRIES:-5}" # Max retries before giving up
|
||||
CLAUDE_EXIT=0
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Human-readable log with visual separators
|
||||
log() {
|
||||
echo "$1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
banner() {
|
||||
log ""
|
||||
log "════════════════════════════════════════════════════════════════"
|
||||
log " $1"
|
||||
log " $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
log "════════════════════════════════════════════════════════════════"
|
||||
log ""
|
||||
}
|
||||
|
||||
section() {
|
||||
log ""
|
||||
log "────────────────────────────────────────"
|
||||
log " $1"
|
||||
log "────────────────────────────────────────"
|
||||
log ""
|
||||
}
|
||||
|
||||
# Check if plan has remaining tasks
|
||||
plan_has_tasks() {
|
||||
grep -q '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null
|
||||
}
|
||||
|
||||
# Show remaining task count
|
||||
remaining_tasks() {
|
||||
grep -c '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null || echo "0"
|
||||
}
|
||||
|
||||
# Show next task
|
||||
next_task() {
|
||||
grep -m1 '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null | sed 's/^- \[ \] //' || echo "(none)"
|
||||
}
|
||||
|
||||
# Detect rate limit from Claude output (only when Claude exited non-zero)
|
||||
check_rate_limit() {
|
||||
[ "${CLAUDE_EXIT:-0}" -eq 0 ] && return 1
|
||||
# Check last 50 lines for rate limit indicators, excluding our own log lines
|
||||
tail -50 "$LOG_FILE" 2>/dev/null | grep -v "^Rate limit detected" | grep -v "^Sleeping" | grep -v "^═" | grep -v "^─" | grep -qi \
|
||||
-e "rate.limit" \
|
||||
-e "too.many.requests" \
|
||||
-e "429" \
|
||||
-e "quota.exceeded" \
|
||||
-e "usage.limit" \
|
||||
-e "limit.reached" 2>/dev/null
|
||||
}
|
||||
|
||||
banner "OVERNIGHT AUTOMATION STARTED"
|
||||
log " Project: $PROJECT_DIR"
|
||||
log " Prompt: $PROMPT_FILE"
|
||||
log " Autonomous: ${CLAUDE_AUTONOMOUS:-0}"
|
||||
log " Iterations: $ITERATION_COUNT (${ITERATION_DELAY}s between each)"
|
||||
log " Rate limit: wait ${RATE_LIMIT_WAIT}s, retry up to ${MAX_RATE_LIMIT_RETRIES}x"
|
||||
log " Tasks left: $(remaining_tasks)"
|
||||
log " Next task: $(next_task)"
|
||||
log ""
|
||||
|
||||
i=1
|
||||
rate_limit_retries=0
|
||||
while [ "$i" -le "$ITERATION_COUNT" ]; do
|
||||
|
||||
# Check if there are tasks remaining before starting
|
||||
if ! plan_has_tasks; then
|
||||
banner "ALL TASKS COMPLETE"
|
||||
log " No remaining tasks in plan.md. Stopping."
|
||||
break
|
||||
fi
|
||||
|
||||
section "ITERATION $i/$ITERATION_COUNT"
|
||||
log " Tasks remaining: $(remaining_tasks)"
|
||||
log " Next task: $(next_task)"
|
||||
log ""
|
||||
|
||||
export CLAUDE_PROJECT_DIR="$PROJECT_DIR"
|
||||
export CLAUDE_AUTONOMOUS="${CLAUDE_AUTONOMOUS:-1}"
|
||||
|
||||
# Run Claude with autonomous permissions; prompt from file
|
||||
if [ -f "$PROMPT_FILE" ]; then
|
||||
log " Starting Claude session..."
|
||||
log ""
|
||||
"$CLAUDE_BIN" -p --dangerously-skip-permissions \
|
||||
< "$PROMPT_FILE" 2>&1 | tee -a "$LOG_FILE"
|
||||
CLAUDE_EXIT=$?
|
||||
log ""
|
||||
log " Claude exited with code: $CLAUDE_EXIT"
|
||||
else
|
||||
log " ERROR: $PROMPT_FILE not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for rate limit after Claude exits
|
||||
if check_rate_limit; then
|
||||
rate_limit_retries=$((rate_limit_retries + 1))
|
||||
if [ "$rate_limit_retries" -ge "$MAX_RATE_LIMIT_RETRIES" ]; then
|
||||
section "RATE LIMITED — SCHEDULING LAUNCHD RETRY"
|
||||
log " Hit rate limit $rate_limit_retries times. Creating launchd job to retry later."
|
||||
|
||||
# Schedule a retry using launchd for after rate limit resets
|
||||
PLIST_LABEL="com.aiui.overnight-retry"
|
||||
PLIST_PATH="$HOME/Library/LaunchAgents/${PLIST_LABEL}.plist"
|
||||
RETRY_TIME=$(date -v+${RATE_LIMIT_WAIT}S '+%H:%M' 2>/dev/null || date -d "+${RATE_LIMIT_WAIT} seconds" '+%H:%M')
|
||||
RETRY_HOUR=$(echo "$RETRY_TIME" | cut -d: -f1)
|
||||
RETRY_MIN=$(echo "$RETRY_TIME" | cut -d: -f2)
|
||||
|
||||
cat > "$PLIST_PATH" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>${PLIST_LABEL}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/sh</string>
|
||||
<string>-c</string>
|
||||
<string>cd ${PROJECT_DIR} && caffeinate -i ./loop/loop.sh >> ${LOG_FILE} 2>&1; launchctl unload ${PLIST_PATH}; rm -f ${PLIST_PATH}</string>
|
||||
</array>
|
||||
<key>StartCalendarInterval</key>
|
||||
<dict>
|
||||
<key>Hour</key>
|
||||
<integer>${RETRY_HOUR}</integer>
|
||||
<key>Minute</key>
|
||||
<integer>${RETRY_MIN}</integer>
|
||||
</dict>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>CLAUDE_AUTONOMOUS</key>
|
||||
<string>1</string>
|
||||
<key>CLAUDE_PROJECT_DIR</key>
|
||||
<string>${PROJECT_DIR}</string>
|
||||
<key>PATH</key>
|
||||
<string>/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin</string>
|
||||
</dict>
|
||||
<key>StandardOutPath</key>
|
||||
<string>${LOG_FILE}</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>${LOG_FILE}</string>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
launchctl load "$PLIST_PATH" 2>/dev/null || true
|
||||
log " Scheduled retry at ~${RETRY_TIME}"
|
||||
log " Plist: $PLIST_PATH (auto-removes after running)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
section "RATE LIMITED — WAITING"
|
||||
log " Attempt $rate_limit_retries/$MAX_RATE_LIMIT_RETRIES"
|
||||
log " Sleeping ${RATE_LIMIT_WAIT}s until $(date -v+${RATE_LIMIT_WAIT}S '+%H:%M:%S' 2>/dev/null || date -d "+${RATE_LIMIT_WAIT} seconds" '+%H:%M:%S')..."
|
||||
sleep "$RATE_LIMIT_WAIT"
|
||||
|
||||
# Check if plan still has tasks before retrying
|
||||
if ! plan_has_tasks; then
|
||||
banner "ALL TASKS COMPLETE (during rate limit wait)"
|
||||
break
|
||||
fi
|
||||
log " Retrying..."
|
||||
continue # Retry same iteration
|
||||
fi
|
||||
|
||||
# Reset rate limit counter on successful run
|
||||
rate_limit_retries=0
|
||||
|
||||
section "ITERATION $i COMPLETE"
|
||||
log " Tasks remaining: $(remaining_tasks)"
|
||||
log " Next task: $(next_task)"
|
||||
|
||||
i=$((i + 1))
|
||||
if [ "$i" -le "$ITERATION_COUNT" ] && [ "$ITERATION_DELAY" -gt 0 ]; then
|
||||
log " Pausing ${ITERATION_DELAY}s before next iteration..."
|
||||
sleep "$ITERATION_DELAY"
|
||||
fi
|
||||
done
|
||||
|
||||
banner "LOOP FINISHED"
|
||||
log " Completed $((i - 1)) iterations"
|
||||
log " Tasks remaining: $(remaining_tasks)"
|
||||
log ""
|
||||
@@ -0,0 +1,59 @@
|
||||
# Overnight Plan — 2026-03-04
|
||||
|
||||
## Phase 1: Critical Fixes
|
||||
|
||||
- [x] P1-1: Brighten SVG fallbacks — increase background lightness from 18% to 28% across all 8 generators in `useImageFallback.ts` (generateSongCoverFallback, generatePodcastCoverFallback, generateNewsFallback, generateImageFallback, generatePosterFallback, generateTVSeriesFallback, generateBookCoverFallback, generatePlaceFallback). Proportionally increase all inner element lightness by +10%. TEST: run `pnpm typecheck` and visually confirm SVGs generate valid data URIs.
|
||||
- [x] P1-2: Add `.catch(() => {})` to all cover fetch promise chains in grid components — SongGrid.vue (line 157), FilmGrid.vue (line 143), TVSeriesGrid.vue (line 160), BookGrid.vue (line 141), PodcastGrid.vue (line 130). Prevents unhandled rejection if fetch throws unexpectedly. TEST: `pnpm typecheck && pnpm lint`.
|
||||
- [x] P1-3: Refine mobile keyboard handling — In `useVisualViewport.ts`, add debounce to viewport change handler (50ms) to prevent jittery resizing. In `App.vue`, ensure the `rootStyle` computed applies `overflow: hidden` when keyboard is open. TEST: `pnpm typecheck`.
|
||||
- [x] P1-4: Verify service worker cleanup — Confirm `dev-dist/sw.js` contains the self-destructing SW and `vite.config.ts` has `devOptions.enabled: false`. If not, fix. TEST: read both files and verify.
|
||||
|
||||
## Phase 2: Error Handling Hardening
|
||||
|
||||
- [x] P2-1: Wrap JSON.parse calls in try/catch — `useContentDiscovery.ts` sessionStorage parse, all sessionStorage/localStorage reads in composables. Search for `JSON.parse` across all `.ts` and `.vue` files, wrap any unprotected calls. TEST: `pnpm typecheck && pnpm lint`.
|
||||
- [x] P2-2: Add `.ok` checks before `.json()` on fetch calls — `useBitcoinPrice.ts` (Mempool API), `MempoolTxCard.vue` (tip height), `useNip05Verification.ts` (NIP-05 lookup), `ZapDialog.vue` (Lightning address). Search for `fetch(` → `.json()` patterns without `.ok` check. TEST: `pnpm typecheck && pnpm lint`.
|
||||
- [x] P2-3: Harden SSE streaming — In `useAI.ts` `readSSE()`, wrap `reader.read()` in try/catch, close reader on error. In `openrouter-adapter.ts`, add same pattern. TEST: `pnpm typecheck`.
|
||||
- [x] P2-4: Add error handling to async watchers — `PdfViewer.vue` watch calling `renderPage()`, `VideoPlayer.vue` `initHls()` in onMounted. Wrap in try/catch with user-friendly error state. TEST: `pnpm typecheck`.
|
||||
|
||||
## Phase 3: Security Hardening
|
||||
|
||||
- [x] P3-1: postMessage origin validation — In `archyBridge.ts`, replace `'*'` targetOrigin with configurable origin. Add origin check on incoming message handler. TEST: `pnpm typecheck`.
|
||||
- [x] P3-2: URL validation — In `contentExtraction.ts`, add URL length limit (2048 chars) to `extractUrlFromText()`. Validate URLs before fetch. TEST: `pnpm typecheck && pnpm lint`.
|
||||
- [x] P3-3: Content sanitization — Review `html.ts` for innerHTML usage, ensure SVG injection is covered. Replace `innerHTML = ''` with `textContent = ''` in `usePlayer.ts`. TEST: `pnpm typecheck`.
|
||||
- [x] P3-4: Add CSP meta tag — Add `<meta http-equiv="Content-Security-Policy" ...>` to `index.html` with appropriate directives for the app (allow self, API hosts, image CDNs). TEST: `pnpm typecheck`.
|
||||
|
||||
## Phase 4: Test Suite
|
||||
|
||||
- [x] P4-1: Unit tests for usePlayer — Create `packages/app/src/composables/__tests__/usePlayer.test.ts`. Test playback state, queue management, play/pause/next/prev. Minimum 8 test cases. TEST: `pnpm test`.
|
||||
- [x] P4-2: Unit tests for useContentPanel — Create `packages/app/src/composables/__tests__/useContentPanel.test.ts`. Test tab switching, detail opening, panel state management. Minimum 6 test cases. TEST: `pnpm test`.
|
||||
- [x] P4-3: Unit tests for useVisualViewport — Create `packages/app/src/composables/__tests__/useVisualViewport.test.ts`. Mock visualViewport API, test keyboard detection, viewport height calculation. Minimum 5 test cases. TEST: `pnpm test`.
|
||||
- [x] P4-4: Content extraction edge case tests — Create `packages/app/src/composables/__tests__/contentExtraction.test.ts`. Test interleaved tags, malformed tags, unicode content, missing fields. Minimum 10 test cases. TEST: `pnpm test`.
|
||||
- [x] P4-5: Seeded prompt regression tests — Create `packages/app/src/__tests__/seed-conversations.test.ts`. Import all seed conversations from mocks, run content extraction on each, verify expected content types are produced. Minimum 1 test per seed. TEST: `pnpm test`.
|
||||
|
||||
## Phase 5: Feature Work
|
||||
|
||||
- [x] P5-1: File browser page — Create `packages/app/src/pages/BrowsePage.vue` with file tree navigation. Add route `/browse` to router. Use the existing `vite-fs.ts` plugin for file reading. Show files/folders with icons, breadcrumb nav. TEST: `pnpm typecheck && pnpm lint`.
|
||||
- [x] P5-2: File tree component — Create `packages/app/src/components/browse/FileTree.vue`. Recursive tree with expand/collapse, file type icons (folder, code, image, document). Use glass morphism styling. TEST: `pnpm typecheck`.
|
||||
- [x] P5-3: File preview component — Create `packages/app/src/components/browse/FilePreview.vue`. Preview text files with syntax highlighting (reuse code viewer), images inline, show file metadata. TEST: `pnpm typecheck`.
|
||||
- [x] P5-4: Allow .claude folder in code viewer — Update `vite-fs.ts` to allow `.claude/` path. Update any path validation that blocks dotfiles. Show CLAUDE.md, settings, hooks, memory files. TEST: `pnpm typecheck`.
|
||||
- [x] P5-5: Archy local search guide — Create `packages/app/src/docs/archy-local-search.md` documenting how file types map to content surfaces (images→ImageGrid, music→SongGrid, etc.), how ContextBroker filtering works. Also add a help section component that can display this in-app. TEST: file exists and is valid markdown.
|
||||
|
||||
## Phase 6: Accessibility
|
||||
|
||||
- [x] P6-1: Add aria-labels to icon buttons — Audit all icon-only buttons across chat components (ChatHeader.vue, ChatMessage.vue, ChatInput.vue, ChatSearch.vue). Add descriptive `aria-label` to each. TEST: `pnpm lint`.
|
||||
- [x] P6-2: Add aria-labels to content grids — All Grid components (SongGrid, FilmGrid, TVSeriesGrid, PlaceGrid, BookGrid, PodcastGrid, NewsGrid, ImageGrid). Each card button needs `aria-label` with content title. TEST: `pnpm lint`.
|
||||
- [x] P6-3: Focus management for dialogs — `ZapDialog.vue`: add focus trap, auto-focus close button, `aria-modal="true"`, `role="dialog"`. Same for `SettingsModal.vue`. Ensure Escape key closes. TEST: `pnpm typecheck`.
|
||||
- [x] P6-4: Color contrast audit — Check `text-white/40` against dark backgrounds for WCAG AA (4.5:1). Verify `#F7931A` accent contrast. Fix any failing ratios by increasing opacity. Document findings in comments. TEST: `pnpm lint`.
|
||||
- [x] P6-5: Alt text improvements — `ImageGrid.vue`: use `img.title || img.alt` instead of generic. All content grids: ensure img alt includes meaningful content (title + artist/director/author). TEST: `pnpm lint`.
|
||||
|
||||
## Phase 7: Performance & Compatibility
|
||||
|
||||
- [x] P7-1: Lazy load heavy renderers — Use `defineAsyncComponent` for PdfViewer, VideoPlayer, MapView. Add loading skeleton components for each. TEST: `pnpm typecheck`.
|
||||
- [x] P7-2: Add in-memory caching — `useNip05Verification.ts`: cache results with 5-min TTL. `useBitcoinPrice.ts`: cache price with 30s TTL. TEST: `pnpm typecheck`.
|
||||
- [x] P7-3: Error boundaries for grid items — Create `packages/app/src/components/ui/ErrorBoundary.vue` using `onErrorCaptured`. Wrap each grid item renderer to prevent cascade failures. Show fallback UI on component crash. TEST: `pnpm typecheck`.
|
||||
- [x] P7-4: Code file size limits — In `useCodeContext.ts` `openFile()`, add file size check before reading (reject > 1MB). Add loading indicator for large files. TEST: `pnpm typecheck`.
|
||||
|
||||
## Phase 8: Research & Documentation
|
||||
|
||||
- [x] P8-1: iOS app research — Research Capacitor vs WKWebView wrapper vs React Native WebView for shipping AIUI as iOS app. Document in `docs/research/ios-app.md`: pros/cons, App Store requirements, push notification integration, offline capability. Include concrete next steps.
|
||||
- [x] P8-2: Mac desktop app research — Research Tauri v2 vs Electron for Mac desktop app. Document in `docs/research/mac-desktop.md`: binary size, memory usage, menu bar app pattern (like Raycast), global hotkey/command invocation, tray API. Include concrete next steps.
|
||||
- [x] P8-3: Plugin system hardening research — Document in `docs/research/plugin-security.md`: signature validation for community plugins, sandboxed iframe execution, permission system per plugin. Reference existing plugin interfaces in `packages/core/src/plugins/`.
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env sh
|
||||
# Pre-run script: verify repo state and create overnight branch.
|
||||
# Run before 5-6pm to ensure you can safely start the overnight loop.
|
||||
set -eu
|
||||
|
||||
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
DATE=$(date '+%Y-%m-%d')
|
||||
BRANCH="overnight/${DATE}"
|
||||
|
||||
echo "=== Overnight pre-run check @ $(date '+%Y-%m-%dT%H:%M:%S') ==="
|
||||
|
||||
# 1. Check git status is clean
|
||||
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||
echo "Error: Working tree not clean. Commit or stash changes first." >&2
|
||||
echo " git status" >&2
|
||||
git status --short >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. Check we're not already on an overnight branch
|
||||
current=$(git branch --show-current 2>/dev/null || true)
|
||||
if [ -n "$current" ] && [ "$current" = "$BRANCH" ]; then
|
||||
echo "Already on $BRANCH. Ready to run." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 3. Create date-stamped branch
|
||||
if git rev-parse --verify "$BRANCH" >/dev/null 2>&1; then
|
||||
echo "Branch $BRANCH already exists. Checkout or use a different date." >&2
|
||||
exit 1
|
||||
fi
|
||||
git checkout -b "$BRANCH"
|
||||
echo "Created branch $BRANCH"
|
||||
|
||||
# 4. Remind to push
|
||||
echo ""
|
||||
echo "Reminder: Push before starting overnight run: git push -u origin $BRANCH"
|
||||
echo "Then run: caffeinate -i ./loop/loop.sh"
|
||||
echo "=== Ready ==="
|
||||
@@ -0,0 +1,73 @@
|
||||
You are working through an overnight automation plan for the AIUI app. Read these files first:
|
||||
|
||||
1. `loop/plan.md` — Your task checklist (mark items `- [x]` as you complete them)
|
||||
2. `CLAUDE.md` — Project conventions, design system rules, and coding standards
|
||||
|
||||
## Project Context
|
||||
|
||||
AIUI is an AI content surface UI — a Vue 3 + TypeScript + Tailwind CSS app with chat, content panels (films, music, books, TV, places, news, images, podcasts), and a plugin system. It runs as a PWA and inside Archy (an iframe host).
|
||||
|
||||
Key directories:
|
||||
- `packages/app/src/` — Main application source
|
||||
- `packages/app/src/composables/` — Shared composition functions
|
||||
- `packages/app/src/components/content/` — Content grid and detail components
|
||||
- `packages/app/src/components/chat/` — Chat interface components
|
||||
- `packages/app/src/styles/main.css` — Glass morphism design system
|
||||
- `packages/core/src/` — Core library and types
|
||||
|
||||
## Working Process
|
||||
|
||||
For each task in `loop/plan.md`:
|
||||
|
||||
1. Find the first unchecked `- [ ]` item
|
||||
2. Read the task description carefully — it tells you what to change and where
|
||||
3. Read the relevant source files before making changes
|
||||
4. Make the change following CLAUDE.md conventions
|
||||
5. Run the TEST command specified in the task
|
||||
6. Fix any errors from the test command before proceeding
|
||||
7. Commit with conventional commit format: `type(scope): description`
|
||||
8. Mark the task done: change `- [ ]` to `- [x]` in `loop/plan.md`
|
||||
9. Move to the next unchecked task immediately
|
||||
|
||||
## Testing Gates
|
||||
|
||||
Every task specifies a TEST command. You MUST run it and pass before committing:
|
||||
- `pnpm typecheck` — TypeScript strict mode compilation
|
||||
- `pnpm lint` — ESLint checks
|
||||
- `pnpm test` — Vitest unit tests
|
||||
- Multiple commands joined with `&&` must ALL pass
|
||||
|
||||
If a test fails, fix the issue and re-run. Do not skip tests. Do not mark a task as done if tests fail.
|
||||
|
||||
## Coding Rules
|
||||
|
||||
- **Vue 3 Composition API only** — `<script setup lang="ts">`, never Options API
|
||||
- **Glass morphism design** — use `.glass`, `.glass-card`, `.glass-button` from `main.css`
|
||||
- **Dark theme** — `bg-white/5`, `text-white/80`, never `bg-gray-*` or plain `bg-white`
|
||||
- **Text opacity scale** — `text-white/25` → `/40` → `/60` → `/70` → `/80` → `/90` → `/96`
|
||||
- **Accent** — `text-accent` (`#F7931A`)
|
||||
- **Touch targets** — minimum 44x44px for all interactive elements
|
||||
- **Font minimums** — never smaller than 11px
|
||||
- **No over-engineering** — only change what the task asks for
|
||||
- **Keep existing patterns** — match the style of surrounding code
|
||||
|
||||
## Commit Format
|
||||
|
||||
```
|
||||
type(scope): description
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `perf`
|
||||
Scope: `app`, `core`, or specific area like `chat`, `content`, `player`
|
||||
|
||||
## Rules
|
||||
|
||||
- Never skip a testing gate — if tests fail, fix them before moving on
|
||||
- If a task is proving difficult, make at least 10 genuine attempts before moving on
|
||||
- Always read source files before editing them
|
||||
- Do not stop until all tasks are checked or you are rate limited
|
||||
- Commit after each completed task
|
||||
- For research tasks (Phase 8), create the docs directory if needed: `mkdir -p docs/research`
|
||||
- For test tasks (Phase 4), create the test directory if needed: `mkdir -p packages/app/src/composables/__tests__`
|
||||
Reference in New Issue
Block a user