Add realtime watcher+SSE transport with tests and lock-retry read path
This commit is contained in:
parent
cc616c1543
commit
3f2ae384f5
15 changed files with 727 additions and 75 deletions
|
|
@ -1,15 +1,17 @@
|
|||
{"id":"bb-29x","title":"Quality Gates, Testing, and Performance Validation","description":"Establish verification confidence through unit/integration tests, boundary tests, and performance baselines for parser and realtime workflows.","acceptance_criteria":"Core functionality is covered by automated checks and target baselines are recorded.","status":"open","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:15.8368971-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:15.8368971-08:00","labels":["perf","quality","testing"],"dependencies":[{"issue_id":"bb-29x","depends_on_id":"bb-ymg","type":"blocks","created_at":"2026-02-11T17:12:23.6722466-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x","depends_on_id":"bb-xhm","type":"blocks","created_at":"2026-02-11T17:12:24.1823625-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x","depends_on_id":"bb-bvn","type":"blocks","created_at":"2026-02-11T17:12:24.6873031-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x","depends_on_id":"bb-u6f","type":"blocks","created_at":"2026-02-11T17:12:25.193566-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-29x.1","title":"Implement unit tests for parser, pathing, scanner, and bd bridge","description":"Add focused fast tests for foundational modules and error handling paths.","acceptance_criteria":"Unit tests cover nominal and edge-case logic for each foundational module.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:16.6578316-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:16.6578316-08:00","labels":["tests","unit"],"dependencies":[{"issue_id":"bb-29x.1","depends_on_id":"bb-29x","type":"parent-child","created_at":"2026-02-11T17:12:16.6594181-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-29x.2","title":"Implement API integration tests for read, mutate, and SSE routes","description":"Validate route contracts and interaction boundaries across read/write/realtime layers.","acceptance_criteria":"Integration suite verifies route behavior and error semantics.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:17.4912736-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:17.4912736-08:00","labels":["integration","tests"],"dependencies":[{"issue_id":"bb-29x.2","depends_on_id":"bb-29x","type":"parent-child","created_at":"2026-02-11T17:12:17.4923012-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x.2","depends_on_id":"bb-29x.1","type":"blocks","created_at":"2026-02-11T17:12:38.9423299-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-29x.3","title":"Record parser and realtime performance baseline against PRD targets","description":"Measure parse latency and update propagation using realistic sample sizes and document outcomes.","acceptance_criteria":"Performance report exists with methodology and observed timings.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:18.3210495-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:18.3210495-08:00","labels":["benchmark","perf"],"dependencies":[{"issue_id":"bb-29x.3","depends_on_id":"bb-29x","type":"parent-child","created_at":"2026-02-11T17:12:18.3220949-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x.3","depends_on_id":"bb-29x.2","type":"blocks","created_at":"2026-02-11T17:12:39.4534943-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-29x.4","title":"Document operational runbook and boundary rationale","description":"Write architecture docs covering scanner policy, bd bridge behavior, and consistency guardrails for future maintainers.","acceptance_criteria":"Runbook documents startup, troubleshooting, and boundary rules.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:19.1385778-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:19.1385778-08:00","labels":["docs","runbook"],"dependencies":[{"issue_id":"bb-29x.4","depends_on_id":"bb-29x","type":"parent-child","created_at":"2026-02-11T17:12:19.1402086-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x.4","depends_on_id":"bb-29x.2","type":"blocks","created_at":"2026-02-11T17:12:39.9591458-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-29x","title":"Quality Gates, Testing, and Performance Validation","description":"Establish verification confidence through unit/integration tests, boundary tests, and performance baselines for parser and realtime workflows.","acceptance_criteria":"Feature lanes are only closed after passing tests, capturing visual evidence, and documenting smoke-check results.","notes":"Definition of done locked (2026-02-12): every completed feature lane requires automated tests + visual screenshots + runtime smoke checks before close.","status":"open","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:15.8368971-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:54:11.1739286-08:00","labels":["perf","quality","testing"],"dependencies":[{"issue_id":"bb-29x","depends_on_id":"bb-ymg","type":"blocks","created_at":"2026-02-11T17:12:23.6722466-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x","depends_on_id":"bb-xhm","type":"blocks","created_at":"2026-02-11T17:12:24.1823625-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x","depends_on_id":"bb-bvn","type":"blocks","created_at":"2026-02-11T17:12:24.6873031-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x","depends_on_id":"bb-u6f","type":"blocks","created_at":"2026-02-11T17:12:25.193566-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-29x.1","title":"Implement unit tests for parser, pathing, scanner, and bd bridge","description":"Add focused fast tests for foundational modules and error handling paths.","acceptance_criteria":"Unit tests cover nominal and edge-case logic for each foundational module.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:16.6578316-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:16.6578316-08:00","labels":["tests","unit"],"dependencies":[{"issue_id":"bb-29x.1","depends_on_id":"bb-29x","type":"parent-child","created_at":"2026-02-11T17:12:16.6594181-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x.1","depends_on_id":"bb-29x.5","type":"blocks","created_at":"2026-02-11T20:10:11.5066258-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-29x.2","title":"Implement API integration tests for read, mutate, and SSE routes","description":"Validate route contracts and interaction boundaries across read/write/realtime layers.","acceptance_criteria":"Integration suite verifies route behavior and error semantics.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:17.4912736-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:17.4912736-08:00","labels":["integration","tests"],"dependencies":[{"issue_id":"bb-29x.2","depends_on_id":"bb-29x","type":"parent-child","created_at":"2026-02-11T17:12:17.4923012-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x.2","depends_on_id":"bb-29x.1","type":"blocks","created_at":"2026-02-11T17:12:38.9423299-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x.2","depends_on_id":"bb-29x.5","type":"blocks","created_at":"2026-02-11T20:10:10.6325422-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-29x.3","title":"Record parser and realtime performance baseline against PRD targets","description":"Measure parse latency and update propagation using realistic sample sizes and document outcomes.","acceptance_criteria":"Performance report exists with methodology and observed timings.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:18.3210495-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:18.3210495-08:00","labels":["benchmark","perf"],"dependencies":[{"issue_id":"bb-29x.3","depends_on_id":"bb-29x","type":"parent-child","created_at":"2026-02-11T17:12:18.3220949-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x.3","depends_on_id":"bb-29x.2","type":"blocks","created_at":"2026-02-11T17:12:39.4534943-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x.3","depends_on_id":"bb-29x.5","type":"blocks","created_at":"2026-02-11T20:10:13.1864837-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-29x.4","title":"Document operational runbook and boundary rationale","description":"Write architecture docs covering scanner policy, bd bridge behavior, and consistency guardrails for future maintainers.","acceptance_criteria":"Runbook documents startup, troubleshooting, and boundary rules.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:19.1385778-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:19.1385778-08:00","labels":["docs","runbook"],"dependencies":[{"issue_id":"bb-29x.4","depends_on_id":"bb-29x","type":"parent-child","created_at":"2026-02-11T17:12:19.1402086-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x.4","depends_on_id":"bb-29x.2","type":"blocks","created_at":"2026-02-11T17:12:39.9591458-08:00","created_by":"zenchantlive"},{"issue_id":"bb-29x.4","depends_on_id":"bb-29x.5","type":"blocks","created_at":"2026-02-11T20:10:12.3474801-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-29x.5","title":"Epic Design Gate: scope, decisions, and acceptance contract","description":"Design/discovery gate for bb-29x before further implementation.\n\nMust capture:\n- Product intent and user outcomes for this epic\n- Explicit architecture decisions and tradeoffs\n- API/data contracts and edge cases\n- Windows-specific constraints and path/process assumptions\n- Test strategy and verification commands\n- Non-goals and out-of-scope boundaries\n\nCompletion rule:\nDo not start new implementation tasks in this epic until this gate is closed with agreed decisions.","acceptance_criteria":"A written execution-grade plan exists for this epic and all child task descriptions are updated with concrete implementation details, dependencies, and testable acceptance criteria.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T20:09:42.1507616-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:09:42.1507616-08:00","dependencies":[{"issue_id":"bb-29x.5","depends_on_id":"bb-29x","type":"parent-child","created_at":"2026-02-11T20:09:42.1525436-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-3pr","title":"Smoke test mutation lifecycle 2","description":"Temporary issue for API mutation smoke test","status":"closed","priority":1,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T19:44:10.9737485-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T19:44:16.4912473-08:00","closed_at":"2026-02-11T19:44:16.4912473-08:00","close_reason":"Cleanup after API smoke test","labels":["api","smoke"],"comments":[{"id":1,"issue_id":"bb-3pr","author":"zenchantlive","text":"Smoke test comment via API route","created_at":"2026-02-12T03:44:13Z"},{"id":2,"issue_id":"bb-3pr","author":"zenchantlive","text":"Smoke test reopen","created_at":"2026-02-12T03:44:15Z"}]}
|
||||
{"id":"bb-6aj","title":"Project Registry and Multi-Project Scanner","description":"Support multiple Windows project roots using profile-scoped registry storage and safe discovery scanning tuned for developer machines.","acceptance_criteria":"Projects can be added/removed/listed and discovered via scanner with deterministic normalization.","status":"open","priority":0,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:47.7205517-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:11:47.7205517-08:00","labels":["multi-project","scanner"],"dependencies":[{"issue_id":"bb-6aj","depends_on_id":"bb-92d","type":"blocks","created_at":"2026-02-11T17:12:19.6374139-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-6aj","title":"Project Registry and Multi-Project Scanner","description":"Deliver a Windows-first multi-project registry and discovery pipeline: persist project roots in the user profile, expose add/remove/list APIs, and scan safe roots to find .beads directories. Normalize all paths to stable identity keys and support aggregate views without full-drive traversal by default.","acceptance_criteria":"Projects can be added/removed/listed and discovered via scanner with deterministic normalization.","status":"open","priority":0,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:47.7205517-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:16:49.4354917-08:00","labels":["multi-project","scanner"],"dependencies":[{"issue_id":"bb-6aj","depends_on_id":"bb-92d","type":"blocks","created_at":"2026-02-11T17:12:19.6374139-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-6aj.1","title":"Persist project registry in %USERPROFILE%\\\\.beadboard\\\\projects.json","description":"Implement read/write management for registry file in user profile path, isolated from repository files and safe for local machine usage.","acceptance_criteria":"Registry file is created lazily and survives app restarts.","status":"closed","priority":0,"issue_type":"task","assignee":"agent-a","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:48.5403111-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:53:17.2085722-08:00","closed_at":"2026-02-11T17:53:17.2085722-08:00","close_reason":"Implemented %USERPROFILE%/.beadboard/projects.json registry persistence with Windows-safe normalization and dedupe.","labels":["config","registry"],"dependencies":[{"issue_id":"bb-6aj.1","depends_on_id":"bb-6aj","type":"parent-child","created_at":"2026-02-11T17:11:48.5419102-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-6aj.2","title":"Implement registry API for add/remove/list operations","description":"Expose robust API endpoints with path validation and normalized identity checks to prevent duplicates.","acceptance_criteria":"API supports add, remove, list and returns clear validation errors.","status":"closed","priority":0,"issue_type":"task","assignee":"agent-a","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:49.3542564-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:53:23.9298353-08:00","closed_at":"2026-02-11T17:53:23.9298353-08:00","close_reason":"Implemented /api/projects GET/POST/DELETE with validation, normalization, and registry integration.","labels":["api","registry"],"dependencies":[{"issue_id":"bb-6aj.2","depends_on_id":"bb-6aj","type":"parent-child","created_at":"2026-02-11T17:11:49.3558158-08:00","created_by":"zenchantlive"},{"issue_id":"bb-6aj.2","depends_on_id":"bb-6aj.1","type":"blocks","created_at":"2026-02-11T17:12:26.7117348-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-6aj.3","title":"Build scanner with profile-root default and depth/ignore controls","description":"Scan %USERPROFILE% and user-defined roots for .beads directories with bounded recursion and ignore patterns to protect performance.","acceptance_criteria":"Scanner discovers projects without traversing entire drives by default.","status":"open","priority":0,"issue_type":"task","assignee":"agent-c","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:50.1925005-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:43:32.4095636-08:00","labels":["performance","scanner"],"dependencies":[{"issue_id":"bb-6aj.3","depends_on_id":"bb-6aj","type":"parent-child","created_at":"2026-02-11T17:11:50.1940841-08:00","created_by":"zenchantlive"},{"issue_id":"bb-6aj.3","depends_on_id":"bb-6aj.1","type":"blocks","created_at":"2026-02-11T17:12:27.2225981-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-6aj.3.1","title":"Add explicit full-drive scan mode for C:/D: by user action","description":"Provide an opt-in scan mode for full drive enumeration while retaining safe defaults and progress reporting expectations.","acceptance_criteria":"Full-drive scan is only activated explicitly, never by default startup logic.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:51.0244174-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:11:51.0244174-08:00","labels":["optional","scanner"],"dependencies":[{"issue_id":"bb-6aj.3.1","depends_on_id":"bb-6aj.3","type":"parent-child","created_at":"2026-02-11T17:11:51.0259617-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-6aj.3","title":"Build scanner with profile-root default and depth/ignore controls","description":"Implement a scanner that searches for .beads directories under %USERPROFILE% and any user-added roots. Enforce bounded recursion depth, ignore patterns (e.g., node_modules, .git, .next, dist, build), and de-duplicate results by normalized path. Return discovered project roots with source metadata and summary counts while avoiding drive-wide enumeration.","acceptance_criteria":"Scanner discovers projects without traversing entire drives by default.","status":"closed","priority":0,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:50.1925005-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:47:56.2978358-08:00","closed_at":"2026-02-11T20:47:56.2978358-08:00","close_reason":"Implemented scanner + /api/scan with safe defaults and full-drive mode.","labels":["performance","scanner"],"dependencies":[{"issue_id":"bb-6aj.3","depends_on_id":"bb-6aj","type":"parent-child","created_at":"2026-02-11T17:11:50.1940841-08:00","created_by":"zenchantlive"},{"issue_id":"bb-6aj.3","depends_on_id":"bb-6aj.1","type":"blocks","created_at":"2026-02-11T17:12:27.2225981-08:00","created_by":"zenchantlive"},{"issue_id":"bb-6aj.3","depends_on_id":"bb-6aj.5","type":"blocks","created_at":"2026-02-11T20:10:09.155154-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-6aj.3.1","title":"Add explicit full-drive scan mode for C:/D: by user action","description":"Add an explicit opt-in scan mode that enumerates entire drives (C:\\ and D:\\) only when the user requests it. Provide progress feedback and guardrails so this mode never runs on startup or default scan paths, and clearly label it as potentially slow.","acceptance_criteria":"Full-drive scan is only activated explicitly, never by default startup logic.","status":"closed","priority":2,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:51.0244174-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:42:04.4870337-08:00","closed_at":"2026-02-11T20:42:04.4870337-08:00","close_reason":"Added explicit full-drive scan mode gated by mode=full-drive.","labels":["optional","scanner"],"dependencies":[{"issue_id":"bb-6aj.3.1","depends_on_id":"bb-6aj.3","type":"parent-child","created_at":"2026-02-11T17:11:51.0259617-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-6aj.4","title":"Implement aggregate project issue context model","description":"Define normalized project identity payload attached to every issue for cross-project Kanban, timeline, and session views.","acceptance_criteria":"Aggregated read output always includes stable project metadata.","status":"closed","priority":1,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:51.8518922-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T19:45:21.5826669-08:00","closed_at":"2026-02-11T19:45:21.5826669-08:00","close_reason":"Added project context model and attached to read issues.","labels":["aggregation","data-model"],"dependencies":[{"issue_id":"bb-6aj.4","depends_on_id":"bb-6aj","type":"parent-child","created_at":"2026-02-11T17:11:51.8534893-08:00","created_by":"zenchantlive"},{"issue_id":"bb-6aj.4","depends_on_id":"bb-6aj.2","type":"blocks","created_at":"2026-02-11T17:12:27.7270195-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-6aj.5","title":"Epic Design Gate: scope, decisions, and acceptance contract","description":"Design/discovery gate for bb-6aj before further implementation.\n\nMust capture:\n- Product intent and user outcomes for this epic\n- Explicit architecture decisions and tradeoffs\n- API/data contracts and edge cases\n- Windows-specific constraints and path/process assumptions\n- Test strategy and verification commands\n- Non-goals and out-of-scope boundaries\n\nCompletion rule:\nDo not start new implementation tasks in this epic until this gate is closed with agreed decisions.","design":"Intent: Provide Windows-native multi-project discovery using registry + scanner with safe defaults; never scan full drives unless explicitly requested.\n\nDecisions:\n- Scan roots: %USERPROFILE% + registry entries; optional full-drive mode adds C:\\ and D:\\ only when mode=full-drive.\n- Bounded recursion (default maxDepth=6) and ignore list to protect performance.\n- Normalize paths with canonicalizeWindowsPath/windowsPathKey; dedupe by key.\n- API contract: GET /api/scan?mode=default|full-drive\u0026depth=\u003cint\u003e returns { mode, roots, projects, stats }.\n\nEdge cases:\n- Missing/unreadable directories are skipped (ENOENT/ENOTDIR/EACCES/EPERM) without aborting scan.\n- Invalid mode/depth returns 400.\n\nWindows constraints:\n- Use drive-letter paths only; no Unix assumptions.\n\nTesting:\n- scanner.test.ts covers default roots, full-drive roots, ignore list, and depth limits.\n- npm test to verify.\n\nNon-goals:\n- No background watcher or SSE here.\n- No default full-drive scan.","acceptance_criteria":"A written execution-grade plan exists for this epic and all child task descriptions are updated with concrete implementation details, dependencies, and testable acceptance criteria.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T20:09:37.50785-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:47:55.9830645-08:00","closed_at":"2026-02-11T20:47:55.9830645-08:00","close_reason":"Captured scanner design/contract and verification plan.","dependencies":[{"issue_id":"bb-6aj.5","depends_on_id":"bb-6aj","type":"parent-child","created_at":"2026-02-11T20:09:37.509509-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-92d","title":"Foundation and Read/Write Boundary","description":"Establish the Windows-native Next.js foundation, canonical Beads schema handling, and strict data boundaries: read from JSONL, write only via bd.exe. This epic defines the non-negotiable invariants that all later work must preserve.","acceptance_criteria":"App boots on Windows, schema/parser contracts exist, and no direct issues.jsonl write path exists in code.","status":"closed","priority":0,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:41.0756295-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:28:27.8108066-08:00","closed_at":"2026-02-11T17:28:27.8108066-08:00","close_reason":"Completed foundation milestone: bootstrap, licensing/docs, schema contracts, parser, windows path normalization, and write-boundary guardrails.","labels":["beadboard","foundation","windows"]}
|
||||
{"id":"bb-92d.1","title":"Bootstrap Next.js 15 + React 19 + TypeScript strict","description":"Initialize project scaffold with strict TypeScript, App Router baseline, and repeatable scripts for lint/typecheck/test in PowerShell.","acceptance_criteria":"npm install and dev startup work on Windows; strict type checking enabled.","status":"closed","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:41.9363647-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:23:14.0089901-08:00","closed_at":"2026-02-11T17:23:14.0089901-08:00","close_reason":"Bootstrapped Next.js 15 + React 19 + strict TypeScript; install/typecheck/dev startup verified on Windows.","labels":["foundation","nextjs"],"dependencies":[{"issue_id":"bb-92d.1","depends_on_id":"bb-92d","type":"parent-child","created_at":"2026-02-11T17:11:41.9379355-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-92d.2","title":"Add MIT license and baseline repository docs","description":"Add LICENSE and baseline docs that state Windows-native support, read/write boundaries, and required runtime dependencies.","acceptance_criteria":"MIT license present and docs describe core architecture constraints.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:42.7699961-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:23:50.7519159-08:00","closed_at":"2026-02-11T17:23:50.7519159-08:00","close_reason":"Added MIT license and baseline repository documentation with architecture boundary rules.","labels":["docs","license"],"dependencies":[{"issue_id":"bb-92d.2","depends_on_id":"bb-92d","type":"parent-child","created_at":"2026-02-11T17:11:42.7715653-08:00","created_by":"zenchantlive"}]}
|
||||
|
|
@ -26,32 +28,37 @@
|
|||
{"id":"bb-bc4.3","title":"Redesign tokenized theme and visual hierarchy","description":"Upgrade visual system quality using semantic tokens for surface/text/status/priority states, stronger typography hierarchy, and improved contrast. Move away from flat/basic palette while preserving clarity and performance.","acceptance_criteria":"UI theme shows clear hierarchy and contrast, aligns with premium demo quality expectations, and remains consistent across components.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T18:50:44.8548956-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T18:59:19.0348391-08:00","closed_at":"2026-02-11T18:59:19.0348391-08:00","close_reason":"Redesigned semantic tokens/theme contrast and hierarchy to improve production visual quality.","labels":["design-system","theme","tokens"],"dependencies":[{"issue_id":"bb-bc4.3","depends_on_id":"bb-bc4","type":"parent-child","created_at":"2026-02-11T18:50:44.8564376-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bc4.3","depends_on_id":"bb-bc4.1","type":"blocks","created_at":"2026-02-11T18:50:44.8606805-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bc4.4","title":"Implement mobile/tablet detail panel interaction model","description":"Adapt detail panel behavior for small screens (overlay or drawer model) with safe viewport sizing, accessible dismissal, and non-destructive navigation. Desktop retains efficient side-panel behavior.","acceptance_criteria":"Detail view is usable on mobile/tablet and does not trap or obscure board interaction irrecoverably.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T18:50:45.8342573-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T18:59:19.8911935-08:00","closed_at":"2026-02-11T18:59:19.8911935-08:00","close_reason":"Implemented mobile detail overlay flow while preserving desktop sticky side-detail behavior.","labels":["detail-panel","mobile","ux"],"dependencies":[{"issue_id":"bb-bc4.4","depends_on_id":"bb-bc4","type":"parent-child","created_at":"2026-02-11T18:50:45.8360334-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bc4.4","depends_on_id":"bb-bc4.2","type":"blocks","created_at":"2026-02-11T18:51:10.0929812-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bc4.4","depends_on_id":"bb-bc4.3","type":"blocks","created_at":"2026-02-11T18:51:10.9352149-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bc4.5","title":"Playwright multi-breakpoint visual verification","description":"Capture and review before/after screenshots at 390x844, 768x1024, and 1440x900 to validate reachability, clipping, control usability, and detail-panel behavior. Store artifacts under artifacts/ with explicit naming conventions.","acceptance_criteria":"Required six screenshots exist (before/after x 3 breakpoints) and observations confirm responsive/visual acceptance criteria.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T18:50:47.0018379-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T18:59:20.7427588-08:00","closed_at":"2026-02-11T18:59:20.7427588-08:00","close_reason":"Captured required Playwright before/after screenshots at mobile/tablet/desktop and validated layout usability.","labels":["playwright","verification","visual"],"dependencies":[{"issue_id":"bb-bc4.5","depends_on_id":"bb-bc4","type":"parent-child","created_at":"2026-02-11T18:50:47.0034039-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bc4.5","depends_on_id":"bb-bc4.4","type":"blocks","created_at":"2026-02-11T18:51:11.7817934-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bc4.5","depends_on_id":"bb-bc4.3","type":"blocks","created_at":"2026-02-11T18:51:12.6236762-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bq6","title":"Smoke test mutation lifecycle","description":"Temporary issue for API mutation smoke test","status":"open","priority":3,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T19:43:52.1686473-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T19:43:52.1686473-08:00","labels":["api","smoke"]}
|
||||
{"id":"bb-bvn","title":"Dependency Graph (React Flow)","description":"Visualize issue relationships and blocked chains through an interactive graph backed by parsed dependency edges.","acceptance_criteria":"Graph renders dependencies correctly and supports navigation to issue details.","status":"open","priority":2,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:09.2057278-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:09.2057278-08:00","labels":["graph","react-flow"],"dependencies":[{"issue_id":"bb-bvn","depends_on_id":"bb-trz","type":"blocks","created_at":"2026-02-11T17:12:22.6642419-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bvn.1","title":"Parse dependency edges and build adjacency structures","description":"Extract edges for blocks, parent, relates_to, duplicates, and supersedes to support graph rendering and analysis.","acceptance_criteria":"Adjacency output is complete and consistent for all supported edge types.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:10.0434044-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:10.0434044-08:00","labels":["graph","parser"],"dependencies":[{"issue_id":"bb-bvn.1","depends_on_id":"bb-bvn","type":"parent-child","created_at":"2026-02-11T17:12:10.0449367-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bvn.2","title":"Implement React Flow graph view with pan/zoom/select interactions","description":"Render nodes and edges with interactive navigation and issue selection integration.","acceptance_criteria":"Users can pan, zoom, and select nodes to inspect linked issue context.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:10.8683725-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:10.8683725-08:00","labels":["graph","ui"],"dependencies":[{"issue_id":"bb-bvn.2","depends_on_id":"bb-bvn","type":"parent-child","created_at":"2026-02-11T17:12:10.8694189-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.2","depends_on_id":"bb-bvn.1","type":"blocks","created_at":"2026-02-11T17:12:36.8736785-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bvn.3","title":"Add blocked-chain highlighting and cycle anomaly signaling","description":"Improve graph decision support by emphasizing blocked paths and flagging unexpected cycle conditions.","acceptance_criteria":"Blocked paths and cycle warnings are visible and actionable.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:11.687878-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:11.687878-08:00","labels":["analysis","graph"],"dependencies":[{"issue_id":"bb-bvn.3","depends_on_id":"bb-bvn","type":"parent-child","created_at":"2026-02-11T17:12:11.6890831-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.3","depends_on_id":"bb-bvn.2","type":"blocks","created_at":"2026-02-11T17:12:37.378326-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-tpc","title":"Live File Watching and SSE Transport","description":"Deliver real-time dashboard updates by watching Beads issue files and streaming one-way change notifications via SSE.","acceptance_criteria":"File changes trigger UI refresh without manual reload and reconnect behavior is stable.","status":"open","priority":0,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:52.6737283-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:11:52.6737283-08:00","labels":["realtime","sse","watcher"],"dependencies":[{"issue_id":"bb-tpc","depends_on_id":"bb-6aj","type":"blocks","created_at":"2026-02-11T17:12:20.1444149-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-tpc.1","title":"Implement chokidar watch manager for registered projects","description":"Start/stop watchers per active project and ensure watcher lifecycle tracks registry changes without leaking handles.","acceptance_criteria":"Watcher list updates correctly when projects are added or removed.","status":"open","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:53.5050717-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:11:53.5050717-08:00","labels":["chokidar","watcher"],"dependencies":[{"issue_id":"bb-tpc.1","depends_on_id":"bb-tpc","type":"parent-child","created_at":"2026-02-11T17:11:53.5071586-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.1","depends_on_id":"bb-6aj.2","type":"blocks","created_at":"2026-02-11T17:12:28.2304516-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-tpc.2","title":"Add debounce/coalescing and transient lock handling for file change bursts","description":"Coalesce rapid updates from agent activity and handle temporary read lock contention without surfacing noisy errors.","acceptance_criteria":"Burst writes produce stable event cadence and no hard failures from temporary locks.","status":"open","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:54.315119-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:11:54.315119-08:00","labels":["stability","watcher"],"dependencies":[{"issue_id":"bb-tpc.2","depends_on_id":"bb-tpc","type":"parent-child","created_at":"2026-02-11T17:11:54.3172104-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.2","depends_on_id":"bb-tpc.1","type":"blocks","created_at":"2026-02-11T17:12:28.7308524-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-tpc.3","title":"Implement SSE events API endpoint with heartbeat and event IDs","description":"Create SSE route supporting keepalive heartbeats and resumable event consumption patterns for browser clients.","acceptance_criteria":"SSE stream remains alive and clients can reconnect automatically.","status":"open","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:55.1518352-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:11:55.1518352-08:00","labels":["api","sse"],"dependencies":[{"issue_id":"bb-tpc.3","depends_on_id":"bb-tpc","type":"parent-child","created_at":"2026-02-11T17:11:55.1533991-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.3","depends_on_id":"bb-tpc.2","type":"blocks","created_at":"2026-02-11T17:12:29.2599782-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-tpc.4","title":"Build frontend SSE client with scoped React Query invalidation","description":"Consume server events and invalidate only affected query keys, limiting unnecessary re-fetches in multi-project mode.","acceptance_criteria":"Changed project views refresh while unrelated views remain stable.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:56.0008015-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:11:56.0008015-08:00","labels":["frontend","react-query"],"dependencies":[{"issue_id":"bb-tpc.4","depends_on_id":"bb-tpc","type":"parent-child","created_at":"2026-02-11T17:11:56.0024218-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.4","depends_on_id":"bb-tpc.3","type":"blocks","created_at":"2026-02-11T17:12:29.768818-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-trz","title":"Kanban Experience (Baseline Dashboard)","description":"Ship a production-ready Kanban baseline inspired by prototype behavior but backed by real Beads project data and strict typing.","acceptance_criteria":"Users can inspect and filter live Beads issues through stable Kanban workflows.","status":"closed","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:56.8115491-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:56:51.4226568-08:00","closed_at":"2026-02-11T17:56:51.4226568-08:00","close_reason":"Kanban epic complete for tracer bullet 1","labels":["kanban","ui"],"dependencies":[{"issue_id":"bb-trz","depends_on_id":"bb-92d","type":"blocks","created_at":"2026-02-11T17:12:20.6480287-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bq6","title":"Smoke test mutation lifecycle","description":"Temporary issue for API mutation smoke test","status":"open","priority":3,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T19:43:52.1686473-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:40:02.1018374-08:00","labels":["api","smoke"],"comments":[{"id":4,"issue_id":"bb-bq6","author":"zenchantlive","text":"UI visibility test complete: reopening","created_at":"2026-02-12T04:40:02Z"}]}
|
||||
{"id":"bb-bvn","title":"Dependency Graph (React Flow)","description":"Visualize issue relationships and blocked chains through an interactive graph backed by parsed dependency edges.","acceptance_criteria":"Graph defaults to 2-hop focused context and remains readable; deterministic layout, typed edges, and depth controls are implemented; mobile fallback is simplified and usable.","notes":"Product baseline locked (2026-02-12): Graph UX will use React Flow with deterministic DAG layout (no chaotic freeform). Default depth is 2 hops from selected issue with controls for 1 hop / 2 hops / full. Mobile uses simplified dependency focus view (selected + immediate blockers/dependents); desktop/tablet uses full graph workspace.","status":"open","priority":2,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:09.2057278-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:54:10.3326048-08:00","labels":["graph","react-flow"],"dependencies":[{"issue_id":"bb-bvn","depends_on_id":"bb-trz","type":"blocks","created_at":"2026-02-11T17:12:22.6642419-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bvn.1","title":"Parse dependency edges and build adjacency structures","description":"Build graph data preparation pipeline for dependency workspace.\n\nScope:\n- Input: parsed Bead issues from read layer only (`readIssuesFromDisk`).\n- Build normalized node map keyed by issue id.\n- Build typed edge list from `dependencies[]` supporting: blocks, parent, relates_to, duplicates, supersedes.\n- Include reverse index (incoming/outgoing) to support focus queries.\n- Preserve issue metadata needed by UI nodes: id, title, status, priority, issue_type, assignee, updated_at.\n\nRules:\n- Ignore dependency edges that point to missing issue IDs but record count for diagnostics.\n- Deduplicate duplicate edges (same source, target, type).\n- Treat path/project context as explicit API argument for future multi-project support.\n- Do not mutate source issues.\n\nOutput contracts:\n- `GraphModel = { nodes, edges, adjacency, diagnostics }`\n- `adjacency` includes incoming/outgoing arrays per node.\n- `diagnostics` includes counts for missing targets and dropped duplicates.\n\nTest plan:\n- Unit tests for edge extraction across all supported types.\n- Unit tests for dedupe and missing-target behavior.\n- Unit tests for adjacency correctness and deterministic ordering.\r\n","acceptance_criteria":"- Graph model contains all valid nodes and typed edges from issue dependencies.\n- Duplicate edges are removed deterministically.\n- Missing-target edges do not crash model generation and are surfaced in diagnostics.\n- Adjacency maps are correct for incoming/outgoing lookups.\n- Unit tests cover all supported dependency types and edge cases.\r\n","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:10.0434044-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:58:31.5313317-08:00","labels":["graph","parser"],"dependencies":[{"issue_id":"bb-bvn.1","depends_on_id":"bb-bvn","type":"parent-child","created_at":"2026-02-11T17:12:10.0449367-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.1","depends_on_id":"bb-bvn.4","type":"blocks","created_at":"2026-02-11T20:10:02.7644711-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bvn.2","title":"Implement React Flow graph view with pan/zoom/select interactions","description":"Implement deterministic React Flow graph UI (non-chaotic workspace mode).\n\nScope:\n- New graph page/view with React Flow canvas.\n- Deterministic auto-layout (DAG style) for stable mental model:\n - selected node centered in focus mode\n - upstream blockers left, downstream dependents right\n- Use card-like nodes (not bubbles) with minimal status accent.\n- Edge styling by dependency type:\n - blocks: solid\n - parent: thicker muted\n - relates_to: dashed\n - duplicates/supersedes: distinct but subtle styles\n\nInteraction:\n- Click node opens shared detail panel.\n- Controls: hop depth switch (1/2/full), collapse closed, fit-to-selection.\n- Disable freeform drag by default to avoid n8n-like chaos (optional manual toggle can be deferred).\n\nResponsive behavior:\n- Desktop/tablet: full canvas + detail panel split.\n- Mobile: simplified dependency focus mode (selected + immediate blockers/dependents list) instead of dense full canvas.\n\nIntegration:\n- Read-only against graph model from bb-bvn.1.\n- No writeback from graph lane.\n\nTest/verification:\n- Component tests for control toggles and selected-node behavior.\n- Guard test for responsive fallback contract.\n- Playwright screenshots: mobile/tablet/desktop graph view.\r\n","acceptance_criteria":"- Graph renders with deterministic layout and typed edges.\n- Default depth is 2 hops with controls for 1/2/full.\n- Node selection opens detail panel and fit-to-selection works.\n- Mobile shows simplified focus view (no unusable dense canvas).\n- Visual verification screenshots captured for mobile/tablet/desktop.\r\n","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:10.8683725-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:58:46.8359811-08:00","labels":["graph","ui"],"dependencies":[{"issue_id":"bb-bvn.2","depends_on_id":"bb-bvn","type":"parent-child","created_at":"2026-02-11T17:12:10.8694189-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.2","depends_on_id":"bb-bvn.1","type":"blocks","created_at":"2026-02-11T17:12:36.8736785-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.2","depends_on_id":"bb-bvn.4","type":"blocks","created_at":"2026-02-11T20:10:04.4783802-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bvn.3","title":"Add blocked-chain highlighting and cycle anomaly signaling","description":"Add analysis overlays for blocker triage and anomaly visibility.\n\nScope:\n- Compute and highlight blocked chains from selected node.\n- Show concise blocker summary:\n - open blocker count\n - in-progress blocker count\n - first actionable blocker\n- Cycle/anomaly signaling:\n - detect cycles in dependency graph\n - mark involved nodes/edges with warning style and explanation text\n\nUI behavior:\n- \"Show blocking path only\" toggle to reduce noise.\n- Hovering a node/edge highlights direct dependency chain.\n- Keep styling subtle and readable; avoid visual overload.\n\nRules:\n- Analysis is read-only and derived from current graph model.\n- Must not fail hard on malformed dependency data; degrade with warnings.\n\nTest plan:\n- Unit tests for blocked-chain derivation and cycle detection logic.\n- UI tests for toggle behavior and warning visibility.\n- Screenshot verification for normal and anomaly cases.\r\n","acceptance_criteria":"- Selected issue can display clear blocked-chain context.\n- Cycle/anomaly conditions are detected and visibly flagged.\n- Blocking-path-only mode materially reduces graph noise.\n- Analysis features remain performant and do not break base graph rendering.\n- Tests and screenshots verify normal + anomaly paths.\r\n","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:11.687878-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:59:01.6815133-08:00","labels":["analysis","graph"],"dependencies":[{"issue_id":"bb-bvn.3","depends_on_id":"bb-bvn","type":"parent-child","created_at":"2026-02-11T17:12:11.6890831-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.3","depends_on_id":"bb-bvn.2","type":"blocks","created_at":"2026-02-11T17:12:37.378326-08:00","created_by":"zenchantlive"},{"issue_id":"bb-bvn.3","depends_on_id":"bb-bvn.4","type":"blocks","created_at":"2026-02-11T20:10:03.6326727-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-bvn.4","title":"Epic Design Gate: scope, decisions, and acceptance contract","description":"Design/discovery gate for bb-bvn before further implementation.\n\nMust capture:\n- Product intent and user outcomes for this epic\n- Explicit architecture decisions and tradeoffs\n- API/data contracts and edge cases\n- Windows-specific constraints and path/process assumptions\n- Test strategy and verification commands\n- Non-goals and out-of-scope boundaries\n\nCompletion rule:\nDo not start new implementation tasks in this epic until this gate is closed with agreed decisions.","acceptance_criteria":"A written execution-grade plan exists for this epic and all child task descriptions are updated with concrete implementation details, dependencies, and testable acceptance criteria.","notes":"Graph design gate completed: agreed React Flow deterministic UX, default 2-hop depth controls, mobile simplified fallback, typed edge semantics, and verification contract (tests + screenshots + smoke). Child tasks bb-bvn.1/.2/.3 updated with execution-grade details.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T20:09:40.290642-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:59:12.4823711-08:00","closed_at":"2026-02-11T20:59:12.4823711-08:00","close_reason":"Design gate complete: bb-bvn child tasks now contain concrete scope, contracts, dependencies, and testable acceptance criteria.","dependencies":[{"issue_id":"bb-bvn.4","depends_on_id":"bb-bvn","type":"parent-child","created_at":"2026-02-11T20:09:40.2922349-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-sse-smoke","title":"SSE smoke 1770870992420","status":"open","priority":2,"issue_type":"task","created_at":"2026-02-12T04:36:32.42Z","updated_at":"2026-02-12T04:36:32.422Z"}
|
||||
{"id":"bb-tpc","title":"Live File Watching and SSE Transport","description":"Realtime transport epic to deliver deterministic file-change propagation from .beads/issues.jsonl(.new) into the Kanban UI.\n\nScope boundaries:\n- Read source remains disk JSONL via read-issues; no bd CLI reads.\n- Mutation/write path remains bd.exe only (already implemented in bb-ymg).\n- This epic adds one-way change detection + push invalidation, not business-rule mutation logic.\n\nImplementation contract:\n1) Watch manager (`src/lib/watcher.ts`)\n- Uses chokidar to monitor `\u003cprojectRoot\u003e/.beads/issues.jsonl` and `.beads/issues.jsonl.new`.\n- Normalizes project roots with existing Windows path helpers.\n- Supports start/stop per project and global cleanup for tests/dev reloads.\n- Emits typed change events with monotonic event ids and timestamps.\n\n2) Burst and lock stability (`bb-tpc.2`)\n- Debounce/coalesce rapid write bursts into one logical event per project window.\n- Treat transient lock/read contention as retryable (EBUSY/EPERM) in read path.\n\n3) SSE server (`src/app/api/events/route.ts`)\n- `text/event-stream` endpoint with heartbeat and `id:` fields.\n- Optional `projectRoot` filter for scoped subscribers.\n- Cleans up subscriptions and timers on disconnect.\n\n4) Frontend subscriber (`bb-tpc.4`)\n- EventSource client reconnect behavior handled by browser defaults.\n- On event, re-fetch affected project issues and reconcile local state.\n- No direct JSONL polling loops after SSE is active.\n\nNon-goals in this epic:\n- WebSocket transport.\n- Cross-process durable event bus.\n- React Query migration (deferred; current lane keeps existing local fetch/reconcile pattern).\r\n","acceptance_criteria":"- Editing `.beads/issues.jsonl` externally triggers UI refresh in \u003c1s without manual reload.\n- SSE stream remains connected with periodic heartbeat; reconnect path resumes safely.\n- Event stream and watcher code use Windows-safe path normalization.\n- No direct JSONL writes introduced (guard still passes).\n- `npm run typecheck`, `npm run test`, `npm run dev` pass.\r\n","notes":"Decoupled bb-tpc baseline from scanner epic: current implementation is project-scoped via query projectRoot and does not require registry integration. Multi-project watcher orchestration remains under scanner follow-up tasks.","status":"closed","priority":0,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:52.6737283-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:37:49.787539-08:00","closed_at":"2026-02-11T20:37:49.787539-08:00","close_reason":"Completed watcher/SSE tracer end-to-end for project-scoped realtime updates with tests and smoke checks.","labels":["realtime","sse","watcher"]}
|
||||
{"id":"bb-tpc.1","title":"Implement chokidar watch manager for registered projects","description":"Implement `src/lib/watcher.ts` watch manager for project-scoped issue files.\n\nScope:\n- Watch both `\u003cprojectRoot\u003e/.beads/issues.jsonl` and `\u003cprojectRoot\u003e/.beads/issues.jsonl.new`.\n- Support startWatch(projectRoot), stopWatch(projectRoot), stopAll().\n- Ensure idempotent start behavior (no duplicate watchers for same canonical root).\n- Emit typed events into in-process realtime bus with:\n - id (monotonic)\n - projectRoot (canonical path)\n - kind (changed|renamed)\n - path\n - at (ISO timestamp)\n\nImplementation notes:\n- chokidar with `ignoreInitial: true` and Windows-safe normalized paths.\n- Maintain internal map keyed by windowsPathKey(projectRoot).\n- Route event -\u003e coalescer (bb-tpc.2), not direct SSE writes.\n\nTest plan:\n- Unit tests verify idempotent lifecycle and key normalization behavior.\n- Unit tests verify events from both jsonl candidates are accepted.\r\n","acceptance_criteria":"- Starting watch for same project twice creates one active watcher.\n- Stopping watch removes watcher and prevents further events.\n- Events include canonical project root and unique monotonic event id.\n- Watch target includes both `.beads/issues.jsonl` and `.beads/issues.jsonl.new`.\r\n","notes":"Implemented src/lib/watcher.ts with chokidar manager, idempotent start/stop lifecycle, windowsPathKey normalization, and dual-file watch targets (.jsonl + .jsonl.new). Added tests/lib/watcher.test.ts.","status":"closed","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:53.5050717-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:36:50.2745024-08:00","closed_at":"2026-02-11T20:36:50.2745024-08:00","close_reason":"Watcher lifecycle manager implemented with canonical project scoping and tested watch behavior.","labels":["chokidar","watcher"],"dependencies":[{"issue_id":"bb-tpc.1","depends_on_id":"bb-tpc","type":"parent-child","created_at":"2026-02-11T17:11:53.5071586-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.1","depends_on_id":"bb-6aj.2","type":"blocks","created_at":"2026-02-11T17:12:28.2304516-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.1","depends_on_id":"bb-tpc.5","type":"blocks","created_at":"2026-02-11T20:10:00.4246352-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-tpc.2","title":"Add debounce/coalescing and transient lock handling for file change bursts","description":"Implement debounce/coalescing + transient lock resilience for realtime updates.\n\nScope:\n- Coalesce rapid filesystem bursts into a single logical change event per project in a short window (e.g. 100-250ms).\n- Suppress duplicate events for same project/path within the same window.\n- Handle transient file lock contention in read layer with bounded retry for EBUSY/EPERM before surfacing failure.\n\nIntegration points:\n- Coalescer sits between watcher and SSE broadcaster.\n- Read retry applied in `readIssuesFromDisk` path used by UI reconciliation.\n\nTest plan:\n- Unit tests for coalescer burst behavior (N events =\u003e 1 broadcast).\n- Unit tests for lock retry logic and eventual success/failure behavior.\r\n","acceptance_criteria":"- Burst writes within debounce window produce one emitted project event.\n- Distinct project events are not incorrectly merged.\n- Transient EBUSY/EPERM reads are retried with bounded backoff.\n- Permanent read errors still surface as explicit failures.\r\n","notes":"Implemented src/lib/coalescer.ts for burst event coalescing and integrated in watcher manager. Added src/lib/read-text-retry.ts and wired readIssuesFromDisk to retry transient lock errors (EBUSY/EPERM). Added tests/lib/coalescer.test.ts and tests/lib/read-text-retry.test.ts.","status":"closed","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:54.315119-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:36:50.8832053-08:00","closed_at":"2026-02-11T20:36:50.8832053-08:00","close_reason":"Burst coalescing and transient lock retry behavior implemented and covered by tests.","labels":["stability","watcher"],"dependencies":[{"issue_id":"bb-tpc.2","depends_on_id":"bb-tpc","type":"parent-child","created_at":"2026-02-11T17:11:54.3172104-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.2","depends_on_id":"bb-tpc.1","type":"blocks","created_at":"2026-02-11T17:12:28.7308524-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.2","depends_on_id":"bb-tpc.5","type":"blocks","created_at":"2026-02-11T20:09:59.5779123-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-tpc.3","title":"Implement SSE events API endpoint with heartbeat and event IDs","description":"Implement SSE endpoint at `src/app/api/events/route.ts` backed by in-process event bus.\n\nScope:\n- Response headers: `Content-Type: text/event-stream`, `Cache-Control: no-cache, no-transform`, `Connection: keep-alive`.\n- Emit named events (`event: issues`) with `id:` and JSON payload.\n- Heartbeat comments at fixed cadence to keep intermediaries alive.\n- Support optional `projectRoot` query filter so client receives only scoped updates.\n- Cleanup subscriber + heartbeat resources on request abort.\n\nEvent payload contract:\n{\n id: number,\n projectRoot: string,\n changedPath?: string,\n at: string\n}\n\nTest plan:\n- Unit tests for SSE formatting helper and filter matching.\n- Route-level test ensures proper content-type and streaming status.\r\n","acceptance_criteria":"- SSE endpoint responds with valid event-stream headers.\n- Each issue update includes event id + timestamp payload.\n- Subscriber cleanup occurs on disconnect without leaks.\n- Project filter limits event delivery to matching subscribers.\r\n","notes":"Implemented SSE route at src/app/api/events/route.ts with event-stream headers, connected frame, heartbeats, issue event frames, projectRoot filtering via bus subscription, and cleanup on abort/cancel. Added tests/api/events-route.test.ts + tests/lib/realtime.test.ts.","status":"closed","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:55.1518352-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:36:51.5000671-08:00","closed_at":"2026-02-11T20:36:51.5000671-08:00","close_reason":"SSE transport endpoint implemented with heartbeat/id frames and lifecycle cleanup.","labels":["api","sse"],"dependencies":[{"issue_id":"bb-tpc.3","depends_on_id":"bb-tpc","type":"parent-child","created_at":"2026-02-11T17:11:55.1533991-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.3","depends_on_id":"bb-tpc.2","type":"blocks","created_at":"2026-02-11T17:12:29.2599782-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.3","depends_on_id":"bb-tpc.5","type":"blocks","created_at":"2026-02-11T20:09:58.6992189-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-tpc.4","title":"Build frontend SSE client with scoped React Query invalidation","description":"Implement frontend realtime subscriber in Kanban page.\n\nScope:\n- Create EventSource subscription to `/api/events?projectRoot=...`.\n- Listen for `issues` events and trigger authoritative refresh (`/api/beads/read`).\n- Guard against duplicate subscriptions and ensure cleanup on unmount/project change.\n- Preserve current optimistic mutation flow and reconcile after both mutation success and realtime events.\n\nFailure handling:\n- Do not hard-fail UI on temporary SSE disconnect.\n- Keep page usable while EventSource auto-reconnects.\n\nTest plan:\n- Unit test(s) for event payload parsing and refresh trigger behavior.\n- Guard checks confirm no direct JSONL writes and existing UI contracts remain intact.\r\n","acceptance_criteria":"- Kanban refreshes automatically after external issue file changes.\n- EventSource subscription lifecycle is clean across mount/unmount.\n- Mutation UX remains functional with realtime enabled.\n- No regression to existing guard/test suite.\r\n","notes":"Integrated EventSource subscription in src/components/kanban/kanban-page.tsx. On issues events, Kanban performs authoritative refresh from /api/beads/read while preserving optimistic mutation flow.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:56.0008015-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:36:52.0959253-08:00","closed_at":"2026-02-11T20:36:52.0959253-08:00","close_reason":"Frontend SSE subscriber implemented with auto-refresh reconciliation and clean subscription lifecycle.","labels":["frontend","react-query"],"dependencies":[{"issue_id":"bb-tpc.4","depends_on_id":"bb-tpc","type":"parent-child","created_at":"2026-02-11T17:11:56.0024218-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.4","depends_on_id":"bb-tpc.3","type":"blocks","created_at":"2026-02-11T17:12:29.768818-08:00","created_by":"zenchantlive"},{"issue_id":"bb-tpc.4","depends_on_id":"bb-tpc.5","type":"blocks","created_at":"2026-02-11T20:10:01.2739557-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-tpc.5","title":"Epic Design Gate: scope, decisions, and acceptance contract","description":"Design/discovery gate for bb-tpc before further implementation.\n\nMust capture:\n- Product intent and user outcomes for this epic\n- Explicit architecture decisions and tradeoffs\n- API/data contracts and edge cases\n- Windows-specific constraints and path/process assumptions\n- Test strategy and verification commands\n- Non-goals and out-of-scope boundaries\n\nCompletion rule:\nDo not start new implementation tasks in this epic until this gate is closed with agreed decisions.","acceptance_criteria":"A written execution-grade plan exists for this epic and all child task descriptions are updated with concrete implementation details, dependencies, and testable acceptance criteria.","notes":"Readiness pass complete: child tasks now include scope boundaries, contracts, failure handling, and test plans. Execution order: .1 watcher lifecycle -\u003e .2 coalescing/retry -\u003e .3 SSE endpoint -\u003e .4 frontend subscriber.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T20:09:38.4238327-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:27:50.5456542-08:00","closed_at":"2026-02-11T20:27:50.5456542-08:00","close_reason":"Design gate satisfied with execution-grade contracts and explicit verification strategy for watcher/SSE lane.","dependencies":[{"issue_id":"bb-tpc.5","depends_on_id":"bb-tpc","type":"parent-child","created_at":"2026-02-11T20:09:38.4249429-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-trz","title":"Kanban Experience (Baseline Dashboard)","description":"Ship a production-ready Kanban baseline inspired by prototype behavior but backed by real Beads project data and strict typing.","acceptance_criteria":"Users can inspect and filter live Beads issues through stable Kanban workflows.","notes":"Product baseline locked (2026-02-12): Default landing view is Kanban for fast triage. Primary user is solo dev supervising multi-agent work. Project scope defaults to one project with explicit aggregate toggle.","status":"closed","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:56.8115491-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:54:10.0562801-08:00","closed_at":"2026-02-11T17:56:51.4226568-08:00","close_reason":"Kanban epic complete for tracer bullet 1","labels":["kanban","ui"],"dependencies":[{"issue_id":"bb-trz","depends_on_id":"bb-92d","type":"blocks","created_at":"2026-02-11T17:12:20.6480287-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-trz.1","title":"Implement Kanban column layout for Beads statuses","description":"Render columns for open, in_progress, blocked, deferred, and closed with responsive behavior and clear status counts.","acceptance_criteria":"All statuses map correctly and render with stable ordering.","status":"closed","priority":1,"issue_type":"task","assignee":"agent-b","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:57.6278082-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:56:50.8105288-08:00","closed_at":"2026-02-11T17:56:50.8105288-08:00","close_reason":"Tracer bullet 1 Kanban baseline implemented and verified","labels":["columns","kanban"],"dependencies":[{"issue_id":"bb-trz.1","depends_on_id":"bb-trz","type":"parent-child","created_at":"2026-02-11T17:11:57.6288535-08:00","created_by":"zenchantlive"},{"issue_id":"bb-trz.1","depends_on_id":"bb-92d.4","type":"blocks","created_at":"2026-02-11T17:12:30.2796473-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-trz.2","title":"Build bead cards with priority/type/labels/assignee/dependency metadata","description":"Design compact cards exposing the most actionable issue metadata while preserving readability at high board density.","acceptance_criteria":"Cards show id, priority, type, labels, assignee, and dependency indicators.","status":"closed","priority":1,"issue_type":"task","assignee":"agent-b","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:58.4435327-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:56:50.8141656-08:00","closed_at":"2026-02-11T17:56:50.8141656-08:00","close_reason":"Tracer bullet 1 Kanban baseline implemented and verified","labels":["cards","kanban"],"dependencies":[{"issue_id":"bb-trz.2","depends_on_id":"bb-trz","type":"parent-child","created_at":"2026-02-11T17:11:58.4450798-08:00","created_by":"zenchantlive"},{"issue_id":"bb-trz.2","depends_on_id":"bb-trz.1","type":"blocks","created_at":"2026-02-11T17:12:30.7837277-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-trz.3","title":"Implement detail slide-out panel with full issue metadata","description":"Add focused issue detail panel showing description, timestamps, dependencies, and lifecycle fields used by power users.","acceptance_criteria":"Selecting a card opens detail panel with complete issue context.","status":"closed","priority":1,"issue_type":"task","assignee":"agent-b","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:11:59.2746013-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:56:50.8161639-08:00","closed_at":"2026-02-11T17:56:50.8161639-08:00","close_reason":"Tracer bullet 1 Kanban baseline implemented and verified","labels":["details","kanban"],"dependencies":[{"issue_id":"bb-trz.3","depends_on_id":"bb-trz","type":"parent-child","created_at":"2026-02-11T17:11:59.2756402-08:00","created_by":"zenchantlive"},{"issue_id":"bb-trz.3","depends_on_id":"bb-trz.2","type":"blocks","created_at":"2026-02-11T17:12:31.2944-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-trz.4","title":"Add search/filter/stats controls for status/type/priority/labels","description":"Provide fast filtering and at-a-glance counts, including critical issue indicators, for daily planning and triage workflows.","acceptance_criteria":"Search and filters apply consistently across board and counts.","status":"closed","priority":1,"issue_type":"task","assignee":"agent-b","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:00.0927161-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:56:50.8186688-08:00","closed_at":"2026-02-11T17:56:50.8186688-08:00","close_reason":"Tracer bullet 1 Kanban baseline implemented and verified","labels":["filters","stats"],"dependencies":[{"issue_id":"bb-trz.4","depends_on_id":"bb-trz","type":"parent-child","created_at":"2026-02-11T17:12:00.0942721-08:00","created_by":"zenchantlive"},{"issue_id":"bb-trz.4","depends_on_id":"bb-trz.2","type":"blocks","created_at":"2026-02-11T17:12:31.798413-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-u6f","title":"Agent Session Views and Metrics","description":"Group work by agent session and actor fields to provide auditability and practical productivity insights for asynchronous coding workflows.","acceptance_criteria":"Session-based summaries and detail views are available per project and aggregate contexts.","status":"open","priority":2,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:12.5083912-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:12.5083912-08:00","labels":["agents","sessions"],"dependencies":[{"issue_id":"bb-u6f","depends_on_id":"bb-tpc","type":"blocks","created_at":"2026-02-11T17:12:23.1727361-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-u6f.1","title":"Extract and normalize session identity fields from issue data","description":"Derive session grouping from closed_by_session, assignee, and created_by with robust fallback semantics.","acceptance_criteria":"Issues are consistently assigned to session buckets when data exists.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:13.3239834-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:13.3239834-08:00","labels":["agents","data"],"dependencies":[{"issue_id":"bb-u6f.1","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-11T17:12:13.3255058-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-u6f.2","title":"Build session list and detail views for claimed/completed/open outcomes","description":"Present session-level issue outcomes and navigation for operational review and accountability.","acceptance_criteria":"Users can inspect session summaries and drill into individual session issue sets.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:14.1559358-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:14.1559358-08:00","labels":["agents","ui"],"dependencies":[{"issue_id":"bb-u6f.2","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-11T17:12:14.157502-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.2","depends_on_id":"bb-u6f.1","type":"blocks","created_at":"2026-02-11T17:12:37.9045555-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-u6f.3","title":"Add baseline productivity metrics (completion rate, throughput, active span)","description":"Compute lightweight operational metrics from session issue events and timestamps.","acceptance_criteria":"Metrics are available with documented definitions and caveats.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:15.0144056-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:15.0144056-08:00","labels":["agents","metrics"],"dependencies":[{"issue_id":"bb-u6f.3","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-11T17:12:15.0155323-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.3","depends_on_id":"bb-u6f.2","type":"blocks","created_at":"2026-02-11T17:12:38.4424336-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-xhm","title":"Timeline and Activity Feed","description":"Provide a chronological activity view derived from issue snapshots and updates, enabling users to review agent/system activity over time.","acceptance_criteria":"Users can inspect chronological issue lifecycle events with useful filtering.","status":"open","priority":2,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:05.8525088-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:05.8525088-08:00","labels":["activity","timeline"],"dependencies":[{"issue_id":"bb-xhm","depends_on_id":"bb-tpc","type":"blocks","created_at":"2026-02-11T17:12:22.1602338-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-xhm.1","title":"Define activity event model for created/updated/closed/reopened actions","description":"Create stable event schema to represent issue lifecycle transitions and their project/session attribution.","acceptance_criteria":"Event model supports all required timeline activity types.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:06.6781387-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:06.6781387-08:00","labels":["model","timeline"],"dependencies":[{"issue_id":"bb-xhm.1","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T17:12:06.6791721-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-xhm.2","title":"Implement snapshot diffing for derived timeline events","description":"Compare periodic snapshots and watcher updates to infer meaningful change events without requiring write interception.","acceptance_criteria":"Diff engine emits deterministic event records for relevant field changes.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:07.5007059-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:07.5007059-08:00","labels":["diff","timeline"],"dependencies":[{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T17:12:07.501756-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm.1","type":"blocks","created_at":"2026-02-11T17:12:35.3430513-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-tpc.2","type":"blocks","created_at":"2026-02-11T17:12:35.8495336-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-xhm.3","title":"Build timeline UI with date grouping and project/assignee/event filters","description":"Render reverse-chronological feed suitable for morning review workflows with practical filter controls.","acceptance_criteria":"Timeline view supports grouping and filter combinations with acceptable performance.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:08.3834905-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:08.3834905-08:00","labels":["timeline","ui"],"dependencies":[{"issue_id":"bb-xhm.3","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T17:12:08.3851144-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.3","depends_on_id":"bb-xhm.2","type":"blocks","created_at":"2026-02-11T17:12:36.3627477-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg","title":"CLI Write-Back via bd.exe","description":"Enable safe issue mutations from UI by routing all write operations through bd.exe and reflecting results through realtime reconciliation.","acceptance_criteria":"No direct JSONL writes exist; all mutations use bd commands and recover cleanly from failures.","status":"open","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:00.9164956-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:00.9164956-08:00","labels":["bd-cli","mutation"],"dependencies":[{"issue_id":"bb-ymg","depends_on_id":"bb-trz","type":"blocks","created_at":"2026-02-11T17:12:21.1512868-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg","depends_on_id":"bb-tpc","type":"blocks","created_at":"2026-02-11T17:12:21.6536312-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg.1","title":"Implement bd bridge using child_process.execFile with project-scoped cwd","description":"Wrap bd execution with command argument safety, Windows path compatibility, stdout/stderr parsing, and project-specific current working directory.","acceptance_criteria":"Bridge executes supported bd commands and returns structured result/error payloads.","notes":"Implemented src/lib/bridge.ts with execFile-based bd runner, project-scoped cwd, timeout support, structured command result payload, and failure classification (not_found, timeout, bad_args, non_zero_exit, unknown). Added RED-\u003eGREEN tests in tests/lib/bridge.test.ts.","status":"closed","priority":0,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:01.7327732-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T19:45:16.7478549-08:00","closed_at":"2026-02-11T19:45:16.7478549-08:00","close_reason":"Bridge implemented with structured result/error classification and project-scoped execFile command execution; tests added.","labels":["bridge","execfile"],"dependencies":[{"issue_id":"bb-ymg.1","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:01.7343468-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.1","depends_on_id":"bb-6aj.2","type":"blocks","created_at":"2026-02-11T17:12:32.3039711-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg.1.1","title":"Resolve bd.exe location from PATH and configuration fallback","description":"Add detection logic for bd executable and actionable errors when not found, including setup guidance.","acceptance_criteria":"Missing bd path returns clear setup instructions and diagnostics.","notes":"Implemented src/lib/bd-path.ts executable resolution with config-first then PATH lookup (bd.exe/bd.cmd/bd.bat/bd), plus actionable setup guidance when missing. Added tests/lib/bd-path.test.ts for success/failure cases.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:02.5593205-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T19:44:57.3720854-08:00","closed_at":"2026-02-11T19:44:57.3720854-08:00","close_reason":"Executable resolution implemented with config/PATH fallback and actionable missing-bd guidance; tests added.","labels":["bridge","setup"],"dependencies":[{"issue_id":"bb-ymg.1.1","depends_on_id":"bb-ymg.1","type":"parent-child","created_at":"2026-02-11T17:12:02.5603636-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg.2","title":"Implement mutation API for create/update/close/reopen/comment operations","description":"Expose strict server-side mutation endpoints translating UI actions to corresponding bd commands with validated arguments.","acceptance_criteria":"All required mutation operations execute via bd and return normalized responses.","notes":"Implemented mutation validation/mapping/execution layer in src/lib/mutations.ts and App Router endpoints: /api/beads/create|update|close|reopen|comment. Added payload validation tests, route validation tests, and smoke-tested create/update/comment/close/reopen lifecycle via API.","status":"closed","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:03.3757503-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T19:45:26.3234246-08:00","closed_at":"2026-02-11T19:45:26.3234246-08:00","close_reason":"Mutation API implemented for create/update/close/reopen/comment with payload validation, command mapping, normalized error shape, and verified smoke lifecycle via API.","labels":["api","mutation"],"dependencies":[{"issue_id":"bb-ymg.2","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:03.377343-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.2","depends_on_id":"bb-ymg.1","type":"blocks","created_at":"2026-02-11T17:12:32.810993-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.2","depends_on_id":"bb-ymg.1.1","type":"blocks","created_at":"2026-02-11T17:12:33.313807-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg.3","title":"Add optimistic updates with rollback and SSE reconciliation","description":"Apply immediate UI updates for responsiveness, rollback on command failure, and reconcile with watcher-triggered authoritative state updates.","acceptance_criteria":"Failed mutations restore previous UI state and emit meaningful error feedback.","notes":"Implemented optimistic status updates with rollback in Kanban page, per-issue pending state, and authoritative reconciliation via new GET /api/beads/read endpoint after successful mutations.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:04.1956393-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T19:59:02.289739-08:00","closed_at":"2026-02-11T19:59:02.289739-08:00","close_reason":"Optimistic board updates with rollback and authoritative post-mutation reconciliation via read route implemented and validated.","labels":["optimistic","state"],"dependencies":[{"issue_id":"bb-ymg.3","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:04.1966728-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.3","depends_on_id":"bb-ymg.2","type":"blocks","created_at":"2026-02-11T17:12:33.8246167-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg.4","title":"Implement drag-and-drop status transitions mapped to bd commands","description":"Map card moves to valid status transitions and use close/reopen semantics where applicable instead of direct file manipulation.","acceptance_criteria":"DnD transitions call proper bd commands and reject invalid transitions safely.","notes":"Implemented lane drag-and-drop interactions in Kanban board, status transition planning (including closed -\u003e reopen+update), and mapped transitions to bd mutation API routes with pending-state safeguards.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:05.0129676-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T19:59:21.7655834-08:00","closed_at":"2026-02-11T19:59:21.7655834-08:00","close_reason":"Kanban lane drag-and-drop transitions now map to bd-backed close/reopen/update mutations with transition planner tests and runtime smoke validation.","labels":["dnd","kanban"],"dependencies":[{"issue_id":"bb-ymg.4","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:05.014527-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.4","depends_on_id":"bb-ymg.2","type":"blocks","created_at":"2026-02-11T17:12:34.329788-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.4","depends_on_id":"bb-trz.1","type":"blocks","created_at":"2026-02-11T17:12:34.8422542-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-u6f","title":"Agent Session Views and Metrics","description":"Group work by agent session and actor fields to provide auditability and practical productivity insights for asynchronous coding workflows.","acceptance_criteria":"Session-based summaries and detail views are available per project and aggregate contexts.","notes":"Product baseline locked (2026-02-12): Agent-session features should optimize for solo supervisor workflows (who changed what, when, and why) with clear per-agent accountability and low-noise summaries.","status":"open","priority":2,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:12.5083912-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:54:10.8939662-08:00","labels":["agents","sessions"],"dependencies":[{"issue_id":"bb-u6f","depends_on_id":"bb-tpc","type":"blocks","created_at":"2026-02-11T17:12:23.1727361-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-u6f.1","title":"Extract and normalize session identity fields from issue data","description":"Derive session grouping from closed_by_session, assignee, and created_by with robust fallback semantics.","acceptance_criteria":"Issues are consistently assigned to session buckets when data exists.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:13.3239834-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:13.3239834-08:00","labels":["agents","data"],"dependencies":[{"issue_id":"bb-u6f.1","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-11T17:12:13.3255058-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.1","depends_on_id":"bb-u6f.4","type":"blocks","created_at":"2026-02-11T20:09:55.5193741-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-u6f.2","title":"Build session list and detail views for claimed/completed/open outcomes","description":"Present session-level issue outcomes and navigation for operational review and accountability.","acceptance_criteria":"Users can inspect session summaries and drill into individual session issue sets.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:14.1559358-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:14.1559358-08:00","labels":["agents","ui"],"dependencies":[{"issue_id":"bb-u6f.2","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-11T17:12:14.157502-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.2","depends_on_id":"bb-u6f.1","type":"blocks","created_at":"2026-02-11T17:12:37.9045555-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.2","depends_on_id":"bb-u6f.4","type":"blocks","created_at":"2026-02-11T20:09:57.2147927-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-u6f.3","title":"Add baseline productivity metrics (completion rate, throughput, active span)","description":"Compute lightweight operational metrics from session issue events and timestamps.","acceptance_criteria":"Metrics are available with documented definitions and caveats.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:15.0144056-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:15.0144056-08:00","labels":["agents","metrics"],"dependencies":[{"issue_id":"bb-u6f.3","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-11T17:12:15.0155323-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.3","depends_on_id":"bb-u6f.2","type":"blocks","created_at":"2026-02-11T17:12:38.4424336-08:00","created_by":"zenchantlive"},{"issue_id":"bb-u6f.3","depends_on_id":"bb-u6f.4","type":"blocks","created_at":"2026-02-11T20:09:56.3707709-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-u6f.4","title":"Epic Design Gate: scope, decisions, and acceptance contract","description":"Design/discovery gate for bb-u6f before further implementation.\n\nMust capture:\n- Product intent and user outcomes for this epic\n- Explicit architecture decisions and tradeoffs\n- API/data contracts and edge cases\n- Windows-specific constraints and path/process assumptions\n- Test strategy and verification commands\n- Non-goals and out-of-scope boundaries\n\nCompletion rule:\nDo not start new implementation tasks in this epic until this gate is closed with agreed decisions.","acceptance_criteria":"A written execution-grade plan exists for this epic and all child task descriptions are updated with concrete implementation details, dependencies, and testable acceptance criteria.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T20:09:41.2150441-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:09:41.2150441-08:00","dependencies":[{"issue_id":"bb-u6f.4","depends_on_id":"bb-u6f","type":"parent-child","created_at":"2026-02-11T20:09:41.216603-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-xhm","title":"Timeline and Activity Feed","description":"Provide a chronological activity view derived from issue snapshots and updates, enabling users to review agent/system activity over time.","acceptance_criteria":"Users can inspect chronological issue lifecycle events with useful filtering.","notes":"Product baseline locked (2026-02-12): Timeline is secondary to Kanban (not default landing). It should support solo-dev live supervision and focus on actionable event stream clarity rather than exhaustive noise.","status":"open","priority":2,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:05.8525088-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:54:10.6071785-08:00","labels":["activity","timeline"],"dependencies":[{"issue_id":"bb-xhm","depends_on_id":"bb-tpc","type":"blocks","created_at":"2026-02-11T17:12:22.1602338-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-xhm.1","title":"Define activity event model for created/updated/closed/reopened actions","description":"Create stable event schema to represent issue lifecycle transitions and their project/session attribution.","acceptance_criteria":"Event model supports all required timeline activity types.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:06.6781387-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:06.6781387-08:00","labels":["model","timeline"],"dependencies":[{"issue_id":"bb-xhm.1","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T17:12:06.6791721-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.1","depends_on_id":"bb-xhm.4","type":"blocks","created_at":"2026-02-11T20:10:05.9709567-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-xhm.2","title":"Implement snapshot diffing for derived timeline events","description":"Compare periodic snapshots and watcher updates to infer meaningful change events without requiring write interception.","acceptance_criteria":"Diff engine emits deterministic event records for relevant field changes.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:07.5007059-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:07.5007059-08:00","labels":["diff","timeline"],"dependencies":[{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T17:12:07.501756-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm.1","type":"blocks","created_at":"2026-02-11T17:12:35.3430513-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-tpc.2","type":"blocks","created_at":"2026-02-11T17:12:35.8495336-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.2","depends_on_id":"bb-xhm.4","type":"blocks","created_at":"2026-02-11T20:10:07.6688195-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-xhm.3","title":"Build timeline UI with date grouping and project/assignee/event filters","description":"Render reverse-chronological feed suitable for morning review workflows with practical filter controls.","acceptance_criteria":"Timeline view supports grouping and filter combinations with acceptable performance.","status":"open","priority":2,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:08.3834905-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T17:12:08.3834905-08:00","labels":["timeline","ui"],"dependencies":[{"issue_id":"bb-xhm.3","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T17:12:08.3851144-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.3","depends_on_id":"bb-xhm.2","type":"blocks","created_at":"2026-02-11T17:12:36.3627477-08:00","created_by":"zenchantlive"},{"issue_id":"bb-xhm.3","depends_on_id":"bb-xhm.4","type":"blocks","created_at":"2026-02-11T20:10:06.8100606-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-xhm.4","title":"Epic Design Gate: scope, decisions, and acceptance contract","description":"Design/discovery gate for bb-xhm before further implementation.\n\nMust capture:\n- Product intent and user outcomes for this epic\n- Explicit architecture decisions and tradeoffs\n- API/data contracts and edge cases\n- Windows-specific constraints and path/process assumptions\n- Test strategy and verification commands\n- Non-goals and out-of-scope boundaries\n\nCompletion rule:\nDo not start new implementation tasks in this epic until this gate is closed with agreed decisions.","acceptance_criteria":"A written execution-grade plan exists for this epic and all child task descriptions are updated with concrete implementation details, dependencies, and testable acceptance criteria.","status":"open","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T20:09:39.3625154-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:09:39.3625154-08:00","dependencies":[{"issue_id":"bb-xhm.4","depends_on_id":"bb-xhm","type":"parent-child","created_at":"2026-02-11T20:09:39.3645827-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg","title":"CLI Write-Back via bd.exe","description":"Write-back architecture for BeadBoard.\n\nScope:\n- All issue mutations must execute through bd.exe commands.\n- No direct writes to .beads/issues.jsonl are permitted anywhere in app code.\n- Mutation flow includes create/update/close/reopen/comment.\n\nDesign decisions:\n- Process execution uses child_process.execFile with arg arrays (no shell interpolation).\n- Commands run with project-scoped cwd so each request targets the intended repo.\n- Executable resolution supports explicit configured path and PATH fallback.\n- API responses are normalized with stable ok/error shape for frontend and tests.\n- UI writeback uses optimistic updates with rollback and authoritative re-read.\n\nImplemented artifacts:\n- src/lib/bd-path.ts\n- src/lib/bridge.ts\n- src/lib/mutations.ts\n- src/app/api/beads/{create,update,close,reopen,comment}/route.ts\n- src/app/api/beads/read/route.ts\n- src/lib/writeback.ts\n- Kanban drag-and-drop transition wiring in components.","acceptance_criteria":"Acceptance contract:\n1) Source tree has no direct issues.jsonl write path (guard test passes).\n2) Bridge returns structured command result including classification for timeout/not_found/non_zero_exit/bad_args.\n3) Mutation routes validate payloads and map operations to bd commands.\n4) Reopen and comment flows are supported and verified.\n5) Optimistic status updates rollback on failure and reconcile from authoritative read endpoint.\n6) typecheck + test + dev + mutation smoke lifecycle all pass.","status":"closed","priority":1,"issue_type":"epic","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:00.9164956-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:37:50.0859031-08:00","closed_at":"2026-02-11T20:37:50.0859031-08:00","close_reason":"Write-back epic unblocked and complete: bridge, mutation API, optimistic transitions, and drag/drop flows are implemented and verified.","labels":["bd-cli","mutation"],"dependencies":[{"issue_id":"bb-ymg","depends_on_id":"bb-trz","type":"blocks","created_at":"2026-02-11T17:12:21.1512868-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg","depends_on_id":"bb-tpc","type":"blocks","created_at":"2026-02-11T17:12:21.6536312-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg.1","title":"Implement bd bridge using child_process.execFile with project-scoped cwd","description":"Bridge layer requirements and implementation.\n\nCommand execution contract:\n- Uses execFile(command, args, options) with windowsHide and timeout.\n- Uses projectRoot as cwd for all commands.\n- Returns structured payload: success, classification, command, args, cwd, stdout, stderr, code, durationMs, error.\n\nError model:\n- not_found: executable missing / ENOENT.\n- timeout: ETIMEDOUT, killed process, or SIGTERM timeout path.\n- bad_args: non-zero exits with invalid/unknown/usage style stderr.\n- non_zero_exit: non-zero exits not classified as bad_args.\n- unknown: fallback classification.\n\nVerification:\n- tests/lib/bridge.test.ts covers success and all key failure classes.","acceptance_criteria":"Acceptance contract:\n- Bridge command execution is shell-safe and Windows-path-safe.\n- Structured result schema is stable and consumed by mutation layer.\n- Timeout and failure classes are deterministic under test.","notes":"Implemented src/lib/bridge.ts with execFile-based bd runner, project-scoped cwd, timeout support, structured command result payload, and failure classification (not_found, timeout, bad_args, non_zero_exit, unknown). Added RED-\u003eGREEN tests in tests/lib/bridge.test.ts.","status":"closed","priority":0,"issue_type":"task","assignee":"zenchantlive","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:01.7327732-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:14:33.7176637-08:00","closed_at":"2026-02-11T19:45:16.7478549-08:00","close_reason":"Bridge implemented with structured result/error classification and project-scoped execFile command execution; tests added.","labels":["bridge","execfile"],"dependencies":[{"issue_id":"bb-ymg.1","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:01.7343468-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.1","depends_on_id":"bb-6aj.2","type":"blocks","created_at":"2026-02-11T17:12:32.3039711-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg.1.1","title":"Resolve bd.exe location from PATH and configuration fallback","description":"Executable resolution contract.\n\nResolution order:\n1) explicit configured executable path (request/config)\n2) PATH scan for bd.exe, bd.cmd, bd.bat, bd\n\nFailure behavior:\n- Throw actionable guidance when missing, including install command:\n npm install -g @beads/bd\n- Error message explicitly mentions explicit path when provided but invalid.\n\nVerification:\n- tests/lib/bd-path.test.ts validates config-first behavior, PATH lookup, and missing executable guidance.","acceptance_criteria":"Acceptance contract:\n- Resolver is deterministic for config and PATH inputs.\n- Missing executable guidance is actionable and user-readable.","notes":"Implemented src/lib/bd-path.ts executable resolution with config-first then PATH lookup (bd.exe/bd.cmd/bd.bat/bd), plus actionable setup guidance when missing. Added tests/lib/bd-path.test.ts for success/failure cases.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:02.5593205-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:14:34.4905469-08:00","closed_at":"2026-02-11T19:44:57.3720854-08:00","close_reason":"Executable resolution implemented with config/PATH fallback and actionable missing-bd guidance; tests added.","labels":["bridge","setup"],"dependencies":[{"issue_id":"bb-ymg.1.1","depends_on_id":"bb-ymg.1","type":"parent-child","created_at":"2026-02-11T17:12:02.5603636-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg.2","title":"Implement mutation API for create/update/close/reopen/comment operations","description":"Mutation API contract and mapping.\n\nSupported operations:\n- create, update, close, reopen, comment\n\nValidation:\n- Payload shape validation enforces required fields and basic constraints.\n- update requires at least one mutable field.\n- status values are constrained to board-supported statuses.\n\nCommand mapping:\n- create -\u003e bd create \u003ctitle\u003e [flags] --json\n- update -\u003e bd update \u003cid\u003e [flags] --json\n- close -\u003e bd close \u003cid\u003e [-r reason] --json\n- reopen -\u003e bd reopen \u003cid\u003e [-r reason] --json\n- comment -\u003e bd comments add \u003cid\u003e \u003ctext\u003e --json\n\nResponse shape:\n- { ok, operation, command, error? }\n- command field always includes normalized bridge result.\n\nVerification:\n- tests/lib/mutations.test.ts and tests/api/mutations-routes.test.ts\n- Runtime smoke lifecycle executed across create/update/close/reopen/comment.","acceptance_criteria":"Acceptance contract:\n- Every mutation route maps to bd.exe only.\n- Invalid payloads return explicit bad_args responses.\n- Reopen and comment operations are first-class and tested.","notes":"Implemented mutation validation/mapping/execution layer in src/lib/mutations.ts and App Router endpoints: /api/beads/create|update|close|reopen|comment. Added payload validation tests, route validation tests, and smoke-tested create/update/comment/close/reopen lifecycle via API.","status":"closed","priority":0,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:03.3757503-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:14:35.2552257-08:00","closed_at":"2026-02-11T19:45:26.3234246-08:00","close_reason":"Mutation API implemented for create/update/close/reopen/comment with payload validation, command mapping, normalized error shape, and verified smoke lifecycle via API.","labels":["api","mutation"],"dependencies":[{"issue_id":"bb-ymg.2","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:03.377343-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.2","depends_on_id":"bb-ymg.1","type":"blocks","created_at":"2026-02-11T17:12:32.810993-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.2","depends_on_id":"bb-ymg.1.1","type":"blocks","created_at":"2026-02-11T17:12:33.313807-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg.3","title":"Add optimistic updates with rollback and SSE reconciliation","description":"Optimistic writeback behavior.\n\nFrontend mutation strategy:\n- Apply optimistic status update to local issue state.\n- Mark issue as pending during command execution.\n- On failure: rollback to previous issue snapshot and surface mutation error.\n- On success: fetch authoritative issue list from /api/beads/read and replace local state.\n\nRationale:\n- Preserves responsive UX without violating source-of-truth boundary.\n- Reconciliation avoids stale local drift when external agents mutate files.\n\nVerification:\n- tests/lib/writeback.test.ts for transition planning and optimistic updater helpers.\n- Runtime mutation smoke tests confirm end-to-end lifecycle.","acceptance_criteria":"Acceptance contract:\n- Failed mutation restores prior local state.\n- Successful mutation reconciles to authoritative read response.\n- Pending state prevents repeated conflicting transitions.","notes":"Implemented optimistic status updates with rollback in Kanban page, per-issue pending state, and authoritative reconciliation via new GET /api/beads/read endpoint after successful mutations.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:04.1956393-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:14:36.0400918-08:00","closed_at":"2026-02-11T19:59:02.289739-08:00","close_reason":"Optimistic board updates with rollback and authoritative post-mutation reconciliation via read route implemented and validated.","labels":["optimistic","state"],"dependencies":[{"issue_id":"bb-ymg.3","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:04.1966728-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.3","depends_on_id":"bb-ymg.2","type":"blocks","created_at":"2026-02-11T17:12:33.8246167-08:00","created_by":"zenchantlive"}]}
|
||||
{"id":"bb-ymg.4","title":"Implement drag-and-drop status transitions mapped to bd commands","description":"Kanban drag-and-drop transition semantics.\n\nTransition planner rules:\n- Any -\u003e closed: use close command.\n- closed -\u003e open: use reopen command.\n- closed -\u003e in_progress|blocked|deferred: reopen then update target status.\n- open|in_progress|blocked|deferred between non-closed states: update status.\n\nUI behavior:\n- Drag start attaches issue id/status metadata.\n- Drop lane executes planned mutation steps in order.\n- Lane card pending state is shown while mutation is in flight.\n\nVerification:\n- tests/lib/writeback.test.ts transition planning cases.\n- Runtime smoke checks for close/reopen/update transition chain.","acceptance_criteria":"Acceptance contract:\n- DnD invokes valid bd command sequence for each source-\u003etarget status.\n- Invalid/no-op transitions do not emit unnecessary commands.\n- Pending safeguards prevent duplicate conflicting moves.","notes":"Implemented lane drag-and-drop interactions in Kanban board, status transition planning (including closed -\u003e reopen+update), and mapped transitions to bd mutation API routes with pending-state safeguards.","status":"closed","priority":1,"issue_type":"task","owner":"jordanlive121@gmail.com","created_at":"2026-02-11T17:12:05.0129676-08:00","created_by":"zenchantlive","updated_at":"2026-02-11T20:14:36.8114668-08:00","closed_at":"2026-02-11T19:59:21.7655834-08:00","close_reason":"Kanban lane drag-and-drop transitions now map to bd-backed close/reopen/update mutations with transition planner tests and runtime smoke validation.","labels":["dnd","kanban"],"dependencies":[{"issue_id":"bb-ymg.4","depends_on_id":"bb-ymg","type":"parent-child","created_at":"2026-02-11T17:12:05.014527-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.4","depends_on_id":"bb-ymg.2","type":"blocks","created_at":"2026-02-11T17:12:34.329788-08:00","created_by":"zenchantlive"},{"issue_id":"bb-ymg.4","depends_on_id":"bb-trz.1","type":"blocks","created_at":"2026-02-11T17:12:34.8422542-08:00","created_by":"zenchantlive"}]}
|
||||
|
|
|
|||
101
package-lock.json
generated
101
package-lock.json
generated
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^5.0.0",
|
||||
"framer-motion": "^11.18.2",
|
||||
"next": "15.5.7",
|
||||
"react": "19.2.1",
|
||||
|
|
@ -1390,41 +1391,18 @@
|
|||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
|
|
@ -2248,16 +2226,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
|
|
@ -2496,6 +2474,44 @@
|
|||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/postcss-load-config": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
||||
|
|
@ -2532,6 +2548,19 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@
|
|||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/lib/kanban.test.ts && node --import tsx --test tests/lib/read-issues.test.ts && node --import tsx --test tests/lib/bd-path.test.ts && node --import tsx --test tests/lib/bridge.test.ts && node --import tsx --test tests/lib/mutations.test.ts && node --import tsx --test tests/lib/writeback.test.ts && node --import tsx --test tests/api/mutations-routes.test.ts && node --test tests/guards/no-direct-jsonl-write.test.mjs && node --test tests/guards/no-inline-style-in-kanban.test.mjs && node --test tests/guards/kanban-responsive-contract.test.mjs"
|
||||
"test": "node --test tests/bootstrap.test.mjs && node --import tsx --test tests/lib/parser.test.ts && node --import tsx --test tests/lib/pathing.test.ts && node --import tsx --test tests/lib/kanban.test.ts && node --import tsx --test tests/lib/read-text-retry.test.ts && node --import tsx --test tests/lib/read-issues.test.ts && node --import tsx --test tests/lib/bd-path.test.ts && node --import tsx --test tests/lib/bridge.test.ts && node --import tsx --test tests/lib/mutations.test.ts && node --import tsx --test tests/lib/writeback.test.ts && node --import tsx --test tests/lib/realtime.test.ts && node --import tsx --test tests/lib/coalescer.test.ts && node --import tsx --test tests/lib/watcher.test.ts && node --import tsx --test tests/api/mutations-routes.test.ts && node --import tsx --test tests/api/events-route.test.ts && node --test tests/guards/no-direct-jsonl-write.test.mjs && node --test tests/guards/no-inline-style-in-kanban.test.mjs && node --test tests/guards/kanban-responsive-contract.test.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"chokidar": "^5.0.0",
|
||||
"framer-motion": "^11.18.2",
|
||||
"next": "15.5.7",
|
||||
"react": "19.2.1",
|
||||
|
|
|
|||
85
src/app/api/events/route.ts
Normal file
85
src/app/api/events/route.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { canonicalizeWindowsPath } from '../../../lib/pathing';
|
||||
import { issuesEventBus, SSE_CONNECTED_FRAME, SSE_HEARTBEAT_FRAME, toSseFrame } from '../../../lib/realtime';
|
||||
import { getIssuesWatchManager } from '../../../lib/watcher';
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const HEARTBEAT_MS = 15_000;
|
||||
|
||||
export async function GET(request: Request): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
const projectRoot = canonicalizeWindowsPath(url.searchParams.get('projectRoot') ?? process.cwd());
|
||||
|
||||
try {
|
||||
getIssuesWatchManager().startWatch(projectRoot);
|
||||
} catch (error) {
|
||||
return Response.json(
|
||||
{
|
||||
ok: false,
|
||||
error: {
|
||||
classification: 'unknown',
|
||||
message: error instanceof Error ? error.message : 'Failed to initialize watcher.',
|
||||
},
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
|
||||
let cleanup = () => {};
|
||||
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
let closed = false;
|
||||
const write = (payload: string) => {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
controller.enqueue(encoder.encode(payload));
|
||||
};
|
||||
|
||||
write(SSE_CONNECTED_FRAME);
|
||||
|
||||
const unsubscribe = issuesEventBus.subscribe(
|
||||
(event) => {
|
||||
write(toSseFrame(event));
|
||||
},
|
||||
{ projectRoot },
|
||||
);
|
||||
|
||||
const heartbeat = setInterval(() => {
|
||||
write(SSE_HEARTBEAT_FRAME);
|
||||
}, HEARTBEAT_MS);
|
||||
|
||||
const close = () => {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
closed = true;
|
||||
clearInterval(heartbeat);
|
||||
unsubscribe();
|
||||
try {
|
||||
controller.close();
|
||||
} catch {
|
||||
// stream already closed
|
||||
}
|
||||
};
|
||||
cleanup = close;
|
||||
|
||||
request.signal.addEventListener('abort', close);
|
||||
},
|
||||
cancel() {
|
||||
// Called when client closes EventSource/reader.
|
||||
// Ensures heartbeat + subscriber cleanup always runs.
|
||||
cleanup();
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream; charset=utf-8',
|
||||
'Cache-Control': 'no-cache, no-transform',
|
||||
Connection: 'keep-alive',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import type { KanbanFilterOptions, KanbanStatus } from '../../lib/kanban';
|
||||
import { buildKanbanColumns, buildKanbanStats, filterKanbanIssues } from '../../lib/kanban';
|
||||
|
|
@ -61,6 +61,7 @@ export function KanbanPage({ issues, projectRoot }: KanbanPageProps) {
|
|||
const [desktopDetailMinimized, setDesktopDetailMinimized] = useState(false);
|
||||
const [pendingIssueIds, setPendingIssueIds] = useState<Set<string>>(new Set());
|
||||
const [mutationError, setMutationError] = useState<string | null>(null);
|
||||
const refreshInFlightRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalIssues(issues);
|
||||
|
|
@ -73,6 +74,38 @@ export function KanbanPage({ issues, projectRoot }: KanbanPageProps) {
|
|||
const selectedIssue = useMemo(() => filteredIssues.find((issue) => issue.id === selectedIssueId) ?? null, [filteredIssues, selectedIssueId]);
|
||||
const showDesktopDetail = Boolean(selectedIssue) && !desktopDetailMinimized;
|
||||
|
||||
const refreshIssues = useCallback(async (options: { silent?: boolean } = {}) => {
|
||||
if (refreshInFlightRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
refreshInFlightRef.current = true;
|
||||
try {
|
||||
const reconciled = await fetchIssues(projectRoot);
|
||||
setLocalIssues(reconciled);
|
||||
} catch (error) {
|
||||
if (!options.silent) {
|
||||
throw error;
|
||||
}
|
||||
} finally {
|
||||
refreshInFlightRef.current = false;
|
||||
}
|
||||
}, [projectRoot]);
|
||||
|
||||
useEffect(() => {
|
||||
const source = new EventSource(`/api/events?projectRoot=${encodeURIComponent(projectRoot)}`);
|
||||
const onIssues = () => {
|
||||
void refreshIssues({ silent: true });
|
||||
};
|
||||
|
||||
source.addEventListener('issues', onIssues as EventListener);
|
||||
|
||||
return () => {
|
||||
source.removeEventListener('issues', onIssues as EventListener);
|
||||
source.close();
|
||||
};
|
||||
}, [projectRoot, refreshIssues]);
|
||||
|
||||
const mutateStatus = async (issue: BeadIssue, targetStatus: KanbanStatus) => {
|
||||
const steps = planStatusTransition(issue, targetStatus);
|
||||
if (steps.length === 0) {
|
||||
|
|
@ -92,8 +125,7 @@ export function KanbanPage({ issues, projectRoot }: KanbanPageProps) {
|
|||
});
|
||||
}
|
||||
|
||||
const reconciled = await fetchIssues(projectRoot);
|
||||
setLocalIssues(reconciled);
|
||||
await refreshIssues();
|
||||
} catch (error) {
|
||||
setLocalIssues(previous);
|
||||
setMutationError(error instanceof Error ? error.message : 'Mutation failed');
|
||||
|
|
|
|||
76
src/lib/coalescer.ts
Normal file
76
src/lib/coalescer.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { windowsPathKey } from './pathing';
|
||||
|
||||
export interface CoalescedEventInput<T> {
|
||||
projectRoot: string;
|
||||
payload: T;
|
||||
}
|
||||
|
||||
interface PendingEvent<T> {
|
||||
timer: NodeJS.Timeout;
|
||||
projectRoot: string;
|
||||
payload: T;
|
||||
}
|
||||
|
||||
export class ProjectEventCoalescer<T> {
|
||||
private readonly pending = new Map<string, PendingEvent<T>>();
|
||||
|
||||
private readonly debounceMs: number;
|
||||
|
||||
private readonly onFlush: (event: CoalescedEventInput<T>) => void;
|
||||
|
||||
constructor(debounceMs: number, onFlush: (event: CoalescedEventInput<T>) => void) {
|
||||
this.debounceMs = debounceMs;
|
||||
this.onFlush = onFlush;
|
||||
}
|
||||
|
||||
queue(projectRoot: string, payload: T): void {
|
||||
const projectKey = windowsPathKey(projectRoot);
|
||||
const existing = this.pending.get(projectKey);
|
||||
if (existing) {
|
||||
clearTimeout(existing.timer);
|
||||
existing.projectRoot = projectRoot;
|
||||
existing.payload = payload;
|
||||
existing.timer = setTimeout(() => this.flush(projectKey), this.debounceMs);
|
||||
return;
|
||||
}
|
||||
|
||||
this.pending.set(projectKey, {
|
||||
projectRoot,
|
||||
payload,
|
||||
timer: setTimeout(() => this.flush(projectKey), this.debounceMs),
|
||||
});
|
||||
}
|
||||
|
||||
cancel(projectRoot: string): void {
|
||||
const projectKey = windowsPathKey(projectRoot);
|
||||
const pending = this.pending.get(projectKey);
|
||||
if (!pending) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(pending.timer);
|
||||
this.pending.delete(projectKey);
|
||||
}
|
||||
|
||||
cancelAll(): void {
|
||||
for (const pending of this.pending.values()) {
|
||||
clearTimeout(pending.timer);
|
||||
}
|
||||
this.pending.clear();
|
||||
}
|
||||
|
||||
pendingCount(): number {
|
||||
return this.pending.size;
|
||||
}
|
||||
|
||||
private flush(projectKey: string): void {
|
||||
const pending = this.pending.get(projectKey);
|
||||
if (!pending) {
|
||||
return;
|
||||
}
|
||||
this.pending.delete(projectKey);
|
||||
this.onFlush({
|
||||
projectRoot: pending.projectRoot,
|
||||
payload: pending.payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import { parseIssuesJsonl } from './parser';
|
||||
import { canonicalizeWindowsPath } from './pathing';
|
||||
import { readTextFileWithRetry } from './read-text-retry';
|
||||
import type { BeadIssue } from './types';
|
||||
|
||||
export interface ReadIssuesOptions {
|
||||
|
|
@ -26,7 +26,7 @@ export async function readIssuesFromDisk(options: ReadIssuesOptions = {}): Promi
|
|||
|
||||
for (const issuesPath of candidates) {
|
||||
try {
|
||||
const jsonl = await fs.readFile(issuesPath, 'utf8');
|
||||
const jsonl = await readTextFileWithRetry(issuesPath);
|
||||
return parseIssuesJsonl(jsonl, {
|
||||
includeTombstones: options.includeTombstones ?? false,
|
||||
});
|
||||
|
|
|
|||
41
src/lib/read-text-retry.ts
Normal file
41
src/lib/read-text-retry.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import fs from 'node:fs/promises';
|
||||
|
||||
const DEFAULT_RETRY_CODES = new Set(['EBUSY', 'EPERM']);
|
||||
|
||||
export interface ReadTextRetryOptions {
|
||||
retries?: number;
|
||||
delayMs?: number;
|
||||
retryCodes?: Set<string>;
|
||||
}
|
||||
|
||||
function sleep(delayMs: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, delayMs));
|
||||
}
|
||||
|
||||
function shouldRetry(error: unknown, retryCodes: Set<string>): boolean {
|
||||
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
||||
return typeof code === 'string' && retryCodes.has(code);
|
||||
}
|
||||
|
||||
export async function readTextFileWithRetry(
|
||||
filePath: string,
|
||||
options: ReadTextRetryOptions = {},
|
||||
): Promise<string> {
|
||||
const retries = options.retries ?? 2;
|
||||
const delayMs = options.delayMs ?? 40;
|
||||
const retryCodes = options.retryCodes ?? DEFAULT_RETRY_CODES;
|
||||
|
||||
let attempt = 0;
|
||||
while (true) {
|
||||
try {
|
||||
return await fs.readFile(filePath, 'utf8');
|
||||
} catch (error) {
|
||||
if (attempt >= retries || !shouldRetry(error, retryCodes)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
attempt += 1;
|
||||
await sleep(delayMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/lib/realtime.ts
Normal file
82
src/lib/realtime.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { canonicalizeWindowsPath, windowsPathKey } from './pathing';
|
||||
|
||||
export type IssuesChangeKind = 'changed' | 'renamed';
|
||||
|
||||
export interface IssuesChangedEvent {
|
||||
id: number;
|
||||
projectRoot: string;
|
||||
changedPath?: string;
|
||||
kind: IssuesChangeKind;
|
||||
at: string;
|
||||
}
|
||||
|
||||
interface Subscriber {
|
||||
projectKey?: string;
|
||||
listener: (event: IssuesChangedEvent) => void;
|
||||
}
|
||||
|
||||
export interface SubscribeOptions {
|
||||
projectRoot?: string;
|
||||
}
|
||||
|
||||
export class IssuesEventBus {
|
||||
private nextEventId = 1;
|
||||
|
||||
private readonly subscribers = new Map<number, Subscriber>();
|
||||
|
||||
private nextSubscriberId = 1;
|
||||
|
||||
emit(projectRoot: string, changedPath?: string, kind: IssuesChangeKind = 'changed'): IssuesChangedEvent {
|
||||
const canonicalProjectRoot = canonicalizeWindowsPath(projectRoot);
|
||||
const projectKey = windowsPathKey(canonicalProjectRoot);
|
||||
const event: IssuesChangedEvent = {
|
||||
id: this.nextEventId,
|
||||
projectRoot: canonicalProjectRoot,
|
||||
changedPath: changedPath ? canonicalizeWindowsPath(changedPath) : undefined,
|
||||
kind,
|
||||
at: new Date().toISOString(),
|
||||
};
|
||||
this.nextEventId += 1;
|
||||
|
||||
for (const subscriber of this.subscribers.values()) {
|
||||
if (!subscriber.projectKey || subscriber.projectKey === projectKey) {
|
||||
subscriber.listener(event);
|
||||
}
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
subscribe(listener: (event: IssuesChangedEvent) => void, options: SubscribeOptions = {}): () => void {
|
||||
const id = this.nextSubscriberId;
|
||||
this.nextSubscriberId += 1;
|
||||
|
||||
this.subscribers.set(id, {
|
||||
listener,
|
||||
projectKey: options.projectRoot ? windowsPathKey(options.projectRoot) : undefined,
|
||||
});
|
||||
|
||||
return () => {
|
||||
this.subscribers.delete(id);
|
||||
};
|
||||
}
|
||||
|
||||
getSubscriberCount(): number {
|
||||
return this.subscribers.size;
|
||||
}
|
||||
|
||||
resetForTests(): void {
|
||||
this.subscribers.clear();
|
||||
this.nextSubscriberId = 1;
|
||||
this.nextEventId = 1;
|
||||
}
|
||||
}
|
||||
|
||||
export const issuesEventBus = new IssuesEventBus();
|
||||
|
||||
export function toSseFrame(event: IssuesChangedEvent): string {
|
||||
return `id: ${event.id}\nevent: issues\ndata: ${JSON.stringify(event)}\n\n`;
|
||||
}
|
||||
|
||||
export const SSE_HEARTBEAT_FRAME = ': heartbeat\n\n';
|
||||
export const SSE_CONNECTED_FRAME = ': connected\n\n';
|
||||
114
src/lib/watcher.ts
Normal file
114
src/lib/watcher.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
import chokidar, { type FSWatcher } from 'chokidar';
|
||||
|
||||
import { ProjectEventCoalescer } from './coalescer';
|
||||
import { windowsPathKey } from './pathing';
|
||||
import { issuesEventBus, type IssuesChangeKind, type IssuesEventBus } from './realtime';
|
||||
import { resolveIssuesJsonlPathCandidates } from './read-issues';
|
||||
|
||||
type FileEventName = 'add' | 'change' | 'unlink';
|
||||
|
||||
interface WatchRegistration {
|
||||
projectRoot: string;
|
||||
watcher: FSWatcher;
|
||||
}
|
||||
|
||||
export interface WatchManagerOptions {
|
||||
debounceMs?: number;
|
||||
eventBus?: IssuesEventBus;
|
||||
}
|
||||
|
||||
export class IssuesWatchManager {
|
||||
private readonly registrations = new Map<string, WatchRegistration>();
|
||||
|
||||
private readonly eventBus: IssuesEventBus;
|
||||
|
||||
private readonly coalescer: ProjectEventCoalescer<{
|
||||
changedPath?: string;
|
||||
kind: IssuesChangeKind;
|
||||
}>;
|
||||
|
||||
constructor(options: WatchManagerOptions = {}) {
|
||||
const debounceMs = options.debounceMs ?? 150;
|
||||
this.eventBus = options.eventBus ?? issuesEventBus;
|
||||
this.coalescer = new ProjectEventCoalescer(debounceMs, ({ projectRoot, payload }) => {
|
||||
this.eventBus.emit(projectRoot, payload.changedPath, payload.kind);
|
||||
});
|
||||
}
|
||||
|
||||
startWatch(projectRoot: string): void {
|
||||
const projectKey = windowsPathKey(projectRoot);
|
||||
if (this.registrations.has(projectKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const watchedPaths = resolveIssuesJsonlPathCandidates(projectRoot);
|
||||
const watcher = chokidar.watch(watchedPaths, {
|
||||
ignoreInitial: true,
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 80,
|
||||
pollInterval: 15,
|
||||
},
|
||||
});
|
||||
|
||||
const onFileEvent = (eventName: FileEventName, changedPath: string) => {
|
||||
const kind: IssuesChangeKind = eventName === 'unlink' ? 'renamed' : 'changed';
|
||||
this.queueCoalescedEvent(projectRoot, changedPath, kind);
|
||||
};
|
||||
|
||||
watcher.on('add', (changedPath) => onFileEvent('add', changedPath));
|
||||
watcher.on('change', (changedPath) => onFileEvent('change', changedPath));
|
||||
watcher.on('unlink', (changedPath) => onFileEvent('unlink', changedPath));
|
||||
|
||||
this.registrations.set(projectKey, {
|
||||
projectRoot,
|
||||
watcher,
|
||||
});
|
||||
}
|
||||
|
||||
async stopWatch(projectRoot: string): Promise<void> {
|
||||
const projectKey = windowsPathKey(projectRoot);
|
||||
const registration = this.registrations.get(projectKey);
|
||||
if (!registration) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.coalescer.cancel(projectRoot);
|
||||
this.registrations.delete(projectKey);
|
||||
await registration.watcher.close();
|
||||
}
|
||||
|
||||
async stopAll(): Promise<void> {
|
||||
const closeOps: Promise<void>[] = [];
|
||||
|
||||
for (const registration of this.registrations.values()) {
|
||||
closeOps.push(registration.watcher.close());
|
||||
}
|
||||
|
||||
this.coalescer.cancelAll();
|
||||
this.registrations.clear();
|
||||
await Promise.all(closeOps);
|
||||
}
|
||||
|
||||
getWatchedProjectCount(): number {
|
||||
return this.registrations.size;
|
||||
}
|
||||
|
||||
private queueCoalescedEvent(projectRoot: string, changedPath: string, kind: IssuesChangeKind): void {
|
||||
this.coalescer.queue(projectRoot, {
|
||||
changedPath,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const globalRegistry = globalThis as typeof globalThis & {
|
||||
__beadboardWatchManager?: IssuesWatchManager;
|
||||
};
|
||||
|
||||
export function getIssuesWatchManager(): IssuesWatchManager {
|
||||
if (!globalRegistry.__beadboardWatchManager) {
|
||||
globalRegistry.__beadboardWatchManager = new IssuesWatchManager();
|
||||
}
|
||||
|
||||
return globalRegistry.__beadboardWatchManager;
|
||||
}
|
||||
34
tests/api/events-route.test.ts
Normal file
34
tests/api/events-route.test.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { GET as eventsGet } from '../../src/app/api/events/route';
|
||||
import { getIssuesWatchManager } from '../../src/lib/watcher';
|
||||
|
||||
test.after(async () => {
|
||||
await getIssuesWatchManager().stopAll();
|
||||
});
|
||||
|
||||
test('events route returns SSE response with expected headers', async () => {
|
||||
const response = await eventsGet(new Request('http://localhost/api/events?projectRoot=C:/Repo/Test'));
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(response.headers.get('content-type')?.includes('text/event-stream'), true);
|
||||
assert.equal(response.headers.get('cache-control')?.includes('no-cache'), true);
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (reader) {
|
||||
await reader.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
test('events route emits initial connected frame', async () => {
|
||||
const response = await eventsGet(new Request('http://localhost/api/events?projectRoot=C:/Repo/Test'));
|
||||
const reader = response.body?.getReader();
|
||||
assert.equal(Boolean(reader), true);
|
||||
|
||||
const first = await reader!.read();
|
||||
const chunk = new TextDecoder().decode(first.value);
|
||||
assert.equal(chunk.includes(': connected'), true);
|
||||
|
||||
await reader!.cancel();
|
||||
});
|
||||
33
tests/lib/coalescer.test.ts
Normal file
33
tests/lib/coalescer.test.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { ProjectEventCoalescer } from '../../src/lib/coalescer';
|
||||
|
||||
test('coalescer emits latest payload once per project within debounce window', async () => {
|
||||
const flushed: Array<{ projectRoot: string; payload: { value: string } }> = [];
|
||||
const coalescer = new ProjectEventCoalescer<{ value: string }>(20, (event) => {
|
||||
flushed.push(event);
|
||||
});
|
||||
|
||||
coalescer.queue('C:/Repo/One', { value: 'first' });
|
||||
coalescer.queue('c:\\repo\\one', { value: 'second' });
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 45));
|
||||
|
||||
assert.equal(flushed.length, 1);
|
||||
assert.equal(flushed[0].payload.value, 'second');
|
||||
});
|
||||
|
||||
test('coalescer keeps distinct projects separated', async () => {
|
||||
const flushed: Array<{ projectRoot: string; payload: { value: string } }> = [];
|
||||
const coalescer = new ProjectEventCoalescer<{ value: string }>(20, (event) => {
|
||||
flushed.push(event);
|
||||
});
|
||||
|
||||
coalescer.queue('C:/Repo/One', { value: 'one' });
|
||||
coalescer.queue('D:/Repo/Two', { value: 'two' });
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 45));
|
||||
|
||||
assert.equal(flushed.length, 2);
|
||||
});
|
||||
27
tests/lib/read-text-retry.test.ts
Normal file
27
tests/lib/read-text-retry.test.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { readTextFileWithRetry } from '../../src/lib/read-text-retry';
|
||||
|
||||
test('readTextFileWithRetry reads file content', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'beadboard-retry-read-'));
|
||||
const target = path.join(root, 'sample.txt');
|
||||
await fs.writeFile(target, 'ok', 'utf8');
|
||||
|
||||
const content = await readTextFileWithRetry(target);
|
||||
assert.equal(content, 'ok');
|
||||
});
|
||||
|
||||
test('readTextFileWithRetry does not retry non-retryable errors', async () => {
|
||||
await assert.rejects(
|
||||
() => readTextFileWithRetry('C:/definitely/missing/file.txt', { retries: 3, delayMs: 1 }),
|
||||
(error: unknown) => {
|
||||
const code = (error as NodeJS.ErrnoException).code;
|
||||
assert.equal(code, 'ENOENT');
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
46
tests/lib/realtime.test.ts
Normal file
46
tests/lib/realtime.test.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { IssuesEventBus, toSseFrame } from '../../src/lib/realtime';
|
||||
|
||||
test('IssuesEventBus emits monotonically increasing IDs', () => {
|
||||
const bus = new IssuesEventBus();
|
||||
const seen: number[] = [];
|
||||
const unsubscribe = bus.subscribe((event) => seen.push(event.id));
|
||||
|
||||
bus.emit('C:/Repo/One');
|
||||
bus.emit('C:/Repo/One');
|
||||
unsubscribe();
|
||||
|
||||
assert.deepEqual(seen, [1, 2]);
|
||||
});
|
||||
|
||||
test('IssuesEventBus filters by project root', () => {
|
||||
const bus = new IssuesEventBus();
|
||||
const one: number[] = [];
|
||||
const two: number[] = [];
|
||||
const stopOne = bus.subscribe((event) => one.push(event.id), { projectRoot: 'C:/Repo/One' });
|
||||
const stopTwo = bus.subscribe((event) => two.push(event.id), { projectRoot: 'D:/Repo/Two' });
|
||||
|
||||
bus.emit('c:\\repo\\one');
|
||||
bus.emit('D:/Repo/Two');
|
||||
|
||||
stopOne();
|
||||
stopTwo();
|
||||
|
||||
assert.deepEqual(one, [1]);
|
||||
assert.deepEqual(two, [2]);
|
||||
});
|
||||
|
||||
test('toSseFrame includes id, event name, and data payload', () => {
|
||||
const frame = toSseFrame({
|
||||
id: 9,
|
||||
projectRoot: 'C:\\Repo\\One',
|
||||
kind: 'changed',
|
||||
at: '2026-02-12T01:00:00.000Z',
|
||||
});
|
||||
|
||||
assert.equal(frame.includes('id: 9'), true);
|
||||
assert.equal(frame.includes('event: issues'), true);
|
||||
assert.equal(frame.includes('"projectRoot":"C:\\\\Repo\\\\One"'), true);
|
||||
});
|
||||
45
tests/lib/watcher.test.ts
Normal file
45
tests/lib/watcher.test.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { IssuesEventBus } from '../../src/lib/realtime';
|
||||
import { IssuesWatchManager } from '../../src/lib/watcher';
|
||||
|
||||
test('IssuesWatchManager startWatch is idempotent per project', async () => {
|
||||
const bus = new IssuesEventBus();
|
||||
const manager = new IssuesWatchManager({ eventBus: bus, debounceMs: 20 });
|
||||
|
||||
manager.startWatch('C:/Repo/One');
|
||||
manager.startWatch('c:\\repo\\one');
|
||||
|
||||
assert.equal(manager.getWatchedProjectCount(), 1);
|
||||
await manager.stopAll();
|
||||
});
|
||||
|
||||
test('IssuesWatchManager emits event after file change in watched .beads path', async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'beadboard-watch-'));
|
||||
const beadsDir = path.join(root, '.beads');
|
||||
const issuesPath = path.join(beadsDir, 'issues.jsonl');
|
||||
await fs.mkdir(beadsDir, { recursive: true });
|
||||
await fs.writeFile(issuesPath, '', 'utf8');
|
||||
|
||||
const bus = new IssuesEventBus();
|
||||
const manager = new IssuesWatchManager({ eventBus: bus, debounceMs: 40 });
|
||||
|
||||
const events: string[] = [];
|
||||
const stop = bus.subscribe((event) => {
|
||||
events.push(event.projectRoot);
|
||||
});
|
||||
|
||||
manager.startWatch(root);
|
||||
|
||||
await fs.writeFile(issuesPath, `${JSON.stringify({ id: 'bb-1', title: 'watch' })}\n`, 'utf8');
|
||||
await new Promise((resolve) => setTimeout(resolve, 220));
|
||||
|
||||
stop();
|
||||
await manager.stopAll();
|
||||
|
||||
assert.equal(events.length >= 1, true);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue