diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index d4247e1..b220ddd 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -10,7 +10,7 @@ {"id":"bd-1zy","title":"Update amc-hook to write spawn_id for spawn correlation","description":"## Overview\nModify bin/amc-hook to include spawn_id in session JSON when AMC_SPAWN_ID env var is present.\n\n## Background\nWhen the server spawns an agent, it generates a unique spawn_id and passes it via AMC_SPAWN_ID env var. The hook must write this to the session file so the server can correlate the spawned agent with its request.\n\nThis enables **deterministic spawn correlation**: instead of polling for 'any new session file' (which could match unrelated agent activity), the server waits for a file containing the specific spawn_id it generated.\n\n## Implementation (IMP-1b)\nAdd after reading hook JSON and before writing session file:\n\n```python\n# Include spawn_id if present in environment (for spawn correlation)\nspawn_id = os.environ.get('AMC_SPAWN_ID')\nif spawn_id:\n session_data['spawn_id'] = spawn_id\n```\n\n## Integration Flow\n1. Server generates spawn_id (UUID)\n2. Server passes AMC_SPAWN_ID= to Zellij new-pane via env dict\n3. Zellij spawns agent process with env var inherited\n4. Agent starts, Claude Code fires SessionStart hook\n5. amc-hook runs, reads AMC_SPAWN_ID from os.environ\n6. Hook writes session JSON including spawn_id field\n7. Server polls SESSIONS_DIR for file with matching spawn_id\n8. Server confirms spawn success\n\n## Why env var (not CLI arg)\n- env vars propagate naturally through subprocess tree\n- No need to modify agent command structure\n- Works identically for Claude and Codex\n\n## Existing amc-hook Structure\nThe hook already:\n- Parses Claude Code hook JSON from stdin\n- Extracts session_id, cwd, etc.\n- Resolves project name from cwd\n- Writes JSON to ~/.local/share/amc/sessions/{session_id}.json\n\nThis change adds one field to the output JSON.\n\n## Session JSON Structure (after change)\n```json\n{\n \"session_id\": \"abc123\",\n \"project\": \"amc\",\n \"status\": \"active\",\n \"started_at\": \"2026-02-26T21:00:00Z\",\n \"cwd\": \"/Users/taylor/projects/amc\",\n \"spawn_id\": \"550e8400-e29b-41d4-a716-446655440000\"\n}\n```\n\nNote: spawn_id is only present for spawned agents, not manually started ones.\n\n## Acceptance Criteria\n- AC-28: amc-hook writes spawn_id to session file when present in environment\n\n## Testing\n```bash\n# Manual test\nAMC_SPAWN_ID='test-uuid' bin/amc-hook <<< '{\"type\":\"SessionStart\",\"session_id\":\"test\",\"cwd\":\"/tmp\"}'\ncat ~/.local/share/amc/sessions/test.json | jq .spawn_id\n# Should output: \"test-uuid\"\n```\n\n## Success Criteria\n- spawn_id field present in session JSON when AMC_SPAWN_ID set\n- spawn_id field absent when AMC_SPAWN_ID not set\n- Existing hook functionality unchanged","status":"open","priority":1,"issue_type":"task","created_at":"2026-02-26T21:40:04.199087Z","created_by":"tayloreernisse","updated_at":"2026-02-26T21:40:04.199087Z","compaction_level":0,"original_size":0} {"id":"bd-253","title":"Implement autocomplete dropdown UI","description":"## Overview\nAdd the autocomplete dropdown JSX to SimpleInput.js render method, positioned above the input.\n\n## Background\nThe dropdown shows filtered skills with visual highlighting. Per AC-11, AC-15, AC-16-19:\n- Positioned above input (bottom-anchored)\n- Left-aligned\n- Max height with scroll\n- Highlighted item visually distinct\n- Respects color scheme\n- Handles both \"no skills available\" AND \"no matching skills\"\n\n## Implementation (from plan IMP-9, with AC-15 fix)\n```javascript\n${showAutocomplete && html`\n \n ${autocompleteConfig.skills.length === 0 ? html`\n \n
No skills available
\n ` : filteredSkills.length === 0 ? html`\n \n
No matching skills
\n ` : filteredSkills.map((skill, i) => html`\n insertSkill(skill)}\n onMouseEnter=${() => setSelectedIndex(i)}\n >\n
\n ${autocompleteConfig.trigger}${skill.name}\n
\n
${skill.description}
\n \n `)}\n \n`}\n```\n\n## Two Empty States (IMPORTANT)\n1. **\"No skills available\"** (AC-15): `autocompleteConfig.skills.length === 0`\n - Agent has zero skills installed\n - Dropdown still appears to inform user\n \n2. **\"No matching skills\"** (AC-11): `filteredSkills.length === 0`\n - Skills exist, but filter text matches none\n - User typed something that does not match any skill\n\n## Styling Decisions\n- **bottom-full mb-1**: Positions above input with 4px gap\n- **max-h-48**: ~192px max height (AC-17)\n- **overflow-y-auto**: Vertical scroll for long lists\n- **z-50**: Ensures dropdown above other content\n- **bg-selection/50**: Highlighted item background\n- **text-dim/text-bright**: Color hierarchy\n\n## Key Details\n- Uses index as key (plan note: handles duplicate skill names from curated + user)\n- onMouseEnter updates selectedIndex (mouse hover selection)\n- onClick calls insertSkill directly\n- Nested ternary: skills.length → filteredSkills.length → map\n\n## Container Requirements\nSimpleInput outer container needs position: relative for absolute positioning.\n\n## Success Criteria\n- Dropdown appears above input when showAutocomplete true\n- \"No skills available\" when agent has zero skills\n- \"No matching skills\" when filter matches nothing\n- Selected item visually highlighted\n- Scrollable when many skills\n- Mouse hover updates selection\n- Click inserts skill","status":"closed","priority":1,"issue_type":"task","created_at":"2026-02-26T20:10:35.317055Z","created_by":"tayloreernisse","updated_at":"2026-02-26T21:52:25.639932Z","closed_at":"2026-02-26T21:52:25.639671Z","close_reason":"Added autocomplete dropdown UI with empty states, mouse hover, click selection","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-253","depends_on_id":"bd-2uj","type":"blocks","created_at":"2026-02-26T20:10:38.204758Z","created_by":"tayloreernisse"},{"issue_id":"bd-253","depends_on_id":"bd-3us","type":"blocks","created_at":"2026-02-26T20:12:27.289036Z","created_by":"tayloreernisse"}]} {"id":"bd-29o","title":"Implement filtered skills computation","description":"## Overview\nAdd useMemo for filteredSkills in SimpleInput.js that filters the skills list based on user input.\n\n## Background\nAs the user types after the trigger character, the list should filter to show only matching skills. This provides instant feedback and makes finding skills faster.\n\n## Implementation (from plan IMP-6)\n```javascript\nconst filteredSkills = useMemo(() => {\n if (!autocompleteConfig || !triggerInfo) return [];\n \n const { skills } = autocompleteConfig;\n const { filterText } = triggerInfo;\n \n let filtered = skills;\n if (filterText) {\n filtered = skills.filter(s =>\n s.name.toLowerCase().includes(filterText)\n );\n }\n \n // Already sorted by server, but ensure alphabetical\n return filtered.sort((a, b) => a.name.localeCompare(b.name));\n}, [autocompleteConfig, triggerInfo]);\n```\n\n## Filtering Behavior (from AC-6)\n- Case-insensitive matching\n- Matches anywhere in skill name (not just prefix)\n- Empty filterText shows all skills\n- No matches returns empty array (handled by UI)\n\n## Why useMemo\n- Skills list could be large (50+)\n- Filter runs on every keystroke\n- Sorting on each render would be wasteful\n- Memoization prevents unnecessary recalculation\n\n## Key Details\n- triggerInfo.filterText is already lowercase\n- Server pre-sorts, but we re-sort after filtering (stability)\n- localeCompare for proper alphabetical ordering\n\n## Success Criteria\n- Returns empty array when no autocompleteConfig\n- Filters based on filterText (case-insensitive)\n- Results sorted alphabetically\n- Empty input shows all skills\n- Memoized to prevent unnecessary recalculation","status":"closed","priority":1,"issue_type":"task","created_at":"2026-02-26T20:09:29.436812Z","created_by":"tayloreernisse","updated_at":"2026-02-26T21:49:16.259177Z","closed_at":"2026-02-26T21:49:16.258997Z","close_reason":"Added triggerInfo state and filteredSkills useMemo with case-insensitive filtering","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-29o","depends_on_id":"bd-3s3","type":"blocks","created_at":"2026-02-26T20:09:31.898778Z","created_by":"tayloreernisse"}]} -{"id":"bd-2a1","title":"Implement backspace dismissal of autocomplete","description":"## Overview\nEnsure that backspacing over the trigger character dismisses the autocomplete dropdown (AC-9).\n\n## Background\nWhen the user types '/com' and then backspaces to '/', they might continue backspacing to remove the '/'. At that point, the autocomplete should dismiss.\n\n## Implementation\nThis is already handled by the existing trigger detection:\n- When text changes, triggerInfo is recomputed\n- If cursor is before the trigger char, getTriggerInfo returns null\n- showAutocomplete becomes false\n\n## Verification Needed\n- Type '/com'\n- Backspace to '/'\n- Backspace to ''\n- Dropdown should dismiss when '/' is deleted\n\n## Success Criteria\n- Backspacing past trigger dismisses dropdown\n- No special code needed if detection is correct\n- Add test case if not already covered","status":"open","priority":3,"issue_type":"task","created_at":"2026-02-26T20:12:00.191481Z","created_by":"tayloreernisse","updated_at":"2026-02-26T20:12:03.544224Z","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-2a1","depends_on_id":"bd-3s3","type":"blocks","created_at":"2026-02-26T20:12:03.544203Z","created_by":"tayloreernisse"}]} +{"id":"bd-2a1","title":"Implement backspace dismissal of autocomplete","description":"## Overview\nEnsure that backspacing over the trigger character dismisses the autocomplete dropdown (AC-9).\n\n## Background\nWhen the user types '/com' and then backspaces to '/', they might continue backspacing to remove the '/'. At that point, the autocomplete should dismiss.\n\n## Implementation\nThis is already handled by the existing trigger detection:\n- When text changes, triggerInfo is recomputed\n- If cursor is before the trigger char, getTriggerInfo returns null\n- showAutocomplete becomes false\n\n## Verification Needed\n- Type '/com'\n- Backspace to '/'\n- Backspace to ''\n- Dropdown should dismiss when '/' is deleted\n\n## Success Criteria\n- Backspacing past trigger dismisses dropdown\n- No special code needed if detection is correct\n- Add test case if not already covered","status":"closed","priority":3,"issue_type":"task","created_at":"2026-02-26T20:12:00.191481Z","created_by":"tayloreernisse","updated_at":"2026-02-26T21:55:09.492178Z","closed_at":"2026-02-26T21:55:09.492131Z","close_reason":"Already works via trigger detection - backspacing past trigger naturally returns null from getTriggerInfo","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-2a1","depends_on_id":"bd-3s3","type":"blocks","created_at":"2026-02-26T20:12:03.544203Z","created_by":"tayloreernisse"}]} {"id":"bd-2al","title":"Add HTTP routes for spawn API endpoints","description":"## Overview\nAdd routes for /api/spawn, /api/projects, /api/projects/refresh, and /api/health to amc_server/mixins/http.py.\n\n## Background\nThe spawn feature requires four new API endpoints:\n- POST /api/spawn - Spawn a new agent\n- GET /api/projects - List available projects\n- POST /api/projects/refresh - Refresh projects cache\n- GET /api/health - Server health check with Zellij status\n\n## Implementation (IMP-2)\n\n### Add to do_GET:\n```python\nelif self.path == '/api/projects':\n self._handle_projects()\nelif self.path == '/api/health':\n self._handle_health()\n```\n\n### Add to do_POST:\n```python\nelif self.path == '/api/spawn':\n self._handle_spawn()\nelif self.path == '/api/projects/refresh':\n self._handle_projects_refresh()\n```\n\n### Update do_OPTIONS for CORS:\n```python\ndef do_OPTIONS(self):\n # CORS preflight for API endpoints\n # AC-39: Keep wildcard CORS consistent with existing endpoints;\n # localhost-only binding (AC-24) is the real security boundary\n self.send_response(204)\n self.send_header('Access-Control-Allow-Origin', '*')\n self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')\n self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')\n self.end_headers()\n```\n\n## CORS Design Decision\nAC-39 specifies consistent wildcard CORS across all endpoints. The security boundary is localhost-only binding (AC-24), not CORS headers. This is appropriate for a dev-machine tool where:\n- Server binds to 127.0.0.1 only\n- External network access not possible\n- Auth token provides additional protection for spawn endpoint\n\n## Route Ordering\nPlace new routes after existing API routes but before catch-all/404 handling to maintain consistency with existing patterns.\n\n## Acceptance Criteria\n- AC-1, AC-3, AC-4: Routes accessible\n- AC-34: projects/refresh endpoint\n- AC-39: Consistent CORS headers\n\n## Verification\n```bash\n# Test routes\ncurl http://localhost:7400/api/projects\ncurl http://localhost:7400/api/health\ncurl -X POST http://localhost:7400/api/projects/refresh\ncurl -X OPTIONS http://localhost:7400/api/spawn -v\n```\n\n## Success Criteria\n- All four endpoints respond correctly\n- CORS preflight returns correct headers\n- No 404s for new routes","status":"open","priority":1,"issue_type":"task","created_at":"2026-02-26T21:40:18.578864Z","created_by":"tayloreernisse","updated_at":"2026-02-26T21:40:21.977440Z","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-2al","depends_on_id":"bd-5m4","type":"blocks","created_at":"2026-02-26T21:40:21.977425Z","created_by":"tayloreernisse"}]} {"id":"bd-2cw","title":"Add visual feedback when spawned agent appears in dashboard","description":"## Overview\nAdd visual feedback (highlight animation) when a newly spawned agent's session card appears in the dashboard.\n\n## Background\nAfter spawning, users need confirmation that the agent appeared. Currently:\n- Toast shows 'agent spawned for project'\n- Session card appears on next poll\n\nThe gap between toast and card appearance could be confusing. Visual feedback bridges this.\n\n## Implementation Options\n\n### Option A: Highlight Animation\nAdd CSS animation to new session cards:\n```css\n@keyframes spawn-highlight {\n 0% { box-shadow: 0 0 0 2px var(--color-active); }\n 100% { box-shadow: 0 0 0 0 transparent; }\n}\n\n.session-card-new {\n animation: spawn-highlight 2s ease-out;\n}\n```\n\nTrack which sessions are 'new' in App.js state:\n```javascript\nconst [newSessionIds, setNewSessionIds] = useState(new Set());\n\n// After successful spawn\nonSpawn={(result) => {\n if (result.success) {\n // Mark spawn_id as 'new'\n // When session with matching spawn_id appears, add highlight class\n }\n}}\n```\n\n### Option B: 'Just spawned' Badge\nShow temporary badge on new session card:\n```javascript\n{isNew && html`\n \n Just spawned\n \n`}\n```\n\n### Chosen Approach: Option A\nHighlight animation is less intrusive and automatically fades.\n\n## Integration\n1. Spawn returns spawn_id in response\n2. Store spawn_id in App.js state\n3. On session data update, check for session with matching spawn_id\n4. Apply animation class to that card\n5. Remove from tracked set after animation\n\n## Acceptance Criteria\n- AC-15: Spawned agent appears in dashboard within 10 seconds\n- (implied) User can see which session was just spawned\n\n## Success Criteria\n- Session card has visible highlight on appearance\n- Animation fades naturally (2s)\n- Works for both Claude and Codex agents","status":"open","priority":2,"issue_type":"task","created_at":"2026-02-26T21:43:30.558124Z","created_by":"tayloreernisse","updated_at":"2026-02-26T21:43:33.390376Z","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-2cw","depends_on_id":"bd-3v5","type":"blocks","created_at":"2026-02-26T21:43:33.390359Z","created_by":"tayloreernisse"}]} {"id":"bd-2n7","title":"Load autocomplete config in Modal","description":"## Overview\nAdd useEffect in Modal.js to load skills when a session is opened, and pass autocompleteConfig to SimpleInput.\n\n## Background\nSkills are agent-global, not session-specific. We fetch once when session.agent changes and cache client-side. This follows the plan's data flow: Modal opens -> GET /api/skills -> SimpleInput gets config.\n\n## Implementation (from plan IMP-4)\n```javascript\nconst [autocompleteConfig, setAutocompleteConfig] = useState(null);\n\n// Load skills when agent type changes\nuseEffect(() => {\n if (!session) {\n setAutocompleteConfig(null);\n return;\n }\n \n const agent = session.agent || 'claude';\n fetchSkills(agent)\n .then(config => setAutocompleteConfig(config))\n .catch(() => setAutocompleteConfig(null));\n}, [session?.agent]);\n\n// In render, pass to SimpleInput:\n<\\${SimpleInput}\n ...\n autocompleteConfig=\\${autocompleteConfig}\n/>\n```\n\n## Dependency Array\n- Uses session?.agent to re-fetch when agent type changes\n- NOT session.id (would refetch on every session switch unnecessarily)\n- Skills are agent-global, so same agent = same skills\n\n## Error Handling\n- fetch failure: autocompleteConfig stays null\n- null config: SimpleInput disables autocomplete silently\n\n## Props to Add\nSimpleInput gets new optional prop:\n- autocompleteConfig: { trigger, skills } | null\n\n## Success Criteria\n- useEffect fetches on session.agent change\n- autocompleteConfig state managed correctly\n- Passed to SimpleInput as prop\n- Null on error (graceful degradation)","status":"closed","priority":1,"issue_type":"task","created_at":"2026-02-26T20:08:58.959680Z","created_by":"tayloreernisse","updated_at":"2026-02-26T21:46:27.279511Z","closed_at":"2026-02-26T21:46:27.279344Z","close_reason":"Added useEffect to load skills, state management, and prop passing through SessionCard to SimpleInput","compaction_level":0,"original_size":0,"dependencies":[{"issue_id":"bd-2n7","depends_on_id":"bd-1ba","type":"blocks","created_at":"2026-02-26T20:09:01.617198Z","created_by":"tayloreernisse"}]} @@ -31,7 +31,7 @@ {"id":"bd-3us","title":"Ensure SimpleInput container has relative positioning","description":"## Overview\nVerify/add position: relative to SimpleInput's container for proper dropdown positioning.\n\n## Background\nThe autocomplete dropdown uses absolute positioning with 'bottom-full'. This requires the parent container to have position: relative.\n\n## Implementation\nCheck SimpleInput.js render:\n```javascript\n// The outer wrapper needs position: relative\n
\n