- Bump all package versions to 1.3.0
- README: version badge, blocked column, new feature highlights
- CHANGELOG: full v1.2.0 and v1.3.0 entries
- FEATURES.md: GitHub sync, activity feed, standup summary sections
- FEATURES.md: storage architecture, API envelope docs, new CLI commands
- FEATURES.md: updated API endpoints table and response format docs
- Bidirectional GitHub Issues sync (#21)
- GitHubSyncService with polling, label-based field mapping, circuit breaker
- Inbound: import issues with 'kanban' label as tasks
- Outbound: push status changes and comments back to GitHub
- Config/state persistence, 5 new API endpoints
- CLI: vk github sync/status/config/mappings
- TaskGitHub interface added to shared types
- Activity feed view (#33)
- Full-page chronological feed with day grouping
- Filter bar: agent, type, date range (combinable)
- Compact vs detailed view toggle
- Infinite scroll via IntersectionObserver
- Real-time WebSocket updates with animation
- Agent field added to Activity, MAX_ACTIVITIES 1000→5000
- New ViewContext for board/activity navigation
- Daily standup summary generation (#34)
- GET /api/summary/standup with date, format params
- JSON, markdown, and plain text output formats
- Sections: completed, in-progress, blocked, upcoming, stats
- CLI: vk summary standup with --yesterday, --date, --json flags
- 12 new tests for standup logic
Closes#21, closes#33, closes#34
Fixes#35
- Copy real web/package.json instead of creating a minimal stub, which
caused pnpm-lock.yaml specifier mismatch with --frozen-lockfile
- Add --ignore-scripts to skip husky prepare hook in container builds
- Remove web/node_modules after install (frontend is pre-built static)
- Create .veritas-kanban directory with correct ownership for non-root user
Closes#35
- Add webhookUrl to NotificationSettingsSchema
- Create clawdbot-webhook-service with fire-and-forget delivery + 1 retry
- Hook into broadcastTaskChange and broadcastChatMessage in broadcast-service
- Support VERITAS_WEBHOOK_URL and VERITAS_WEBHOOK_SECRET env vars
- HMAC-SHA256 payload signing via X-Webhook-Signature header
- Add 16 tests covering config, signing, delivery, retry, and payload formatting
- Update .env.example with new env var documentation
- Fix delete not clearing UI (React Query kept stale data on 404 refetch)
- Fix post-delete send breaking (server recreates task sessions instead of 404)
- Add chat export as markdown (download icon next to trash)
- Auto-focus input on panel open and after each send
- Defensive delete on server (handle already-deleted sessions)
- Board Chat / Task Chat dynamic title
- Trash icon in header to clear chat history
- Confirmation dialog before deleting
- Re-initializes task session after clear
- Floating chat bubble from previous commit
- Floating chat icon in bottom-right corner
- Opens board-level chat (not tied to any task)
- Pulsing green dot when new response arrives while closed
- Hides bubble when chat panel is open
- Clears unread indicator on open
- Chat now generates actual AI responses via Clawdbot gateway WebSocket
- Gateway chat client handles auth, streaming deltas, and final response
- Chat responses broadcast to UI via kanban WebSocket (shared connection)
- Fix double X close button on chat panel (SheetContent built-in + manual)
- Move Chat/Template buttons above tab row to reduce cramping
- Chat/Template buttons now 50/50 full width
- Fix subtask add button height mismatch
- Fix chat send crash (API response type mismatch)
- Remove non-functional agent/model dropdowns from chat panel
- Add model picker (sonnet/opus/haiku) to Agent panel in task detail
- Replace tooltip with inline mode hint text in chat
- Load chat history on panel open (deterministic task session ID)
- Chat stream uses shared WebSocket instead of opening duplicate connection
- Agent dropdown in same row as search, project, type filters
- Options: All Agents, Auto (routing), Unassigned, + each configured agent
- URL sync support (?agent=claude-code)
- Filter logic handles auto, unassigned, and specific agent matching
- Add agent field to CreateTaskForm state with 'auto' default
- Agent dropdown in CreateTaskDialog (Auto + all enabled agents)
- Agent field in TaskMetadataSection (task detail panel) — editable inline
- Pass agent through useTemplateForm to task creation API
- Server: accept agent field in create/update task schemas
- TaskService: persist agent field on task creation
- AgentPanel: pre-select task's assigned agent (priority: manual > pre-assigned > routed > default)
- Sprint and Agent dropdowns now share a row for compact layout
- Add AgentRoutingConfig types with RoutingRule, RoutingMatchCriteria, RoutingResult
- Add DEFAULT_ROUTING_CONFIG with sensible defaults (code/bug/docs/review rules)
- Create AgentRoutingService with resolveAgent() and getFallback() methods
- First-match-wins rule evaluation with type, priority, project, minSubtasks criteria
- Array support for match criteria (e.g., type: ['code', 'bug'])
- Routing API: POST /agents/route, GET/PUT /agents/routing
- Integrate routing into ClawdbotAgentService.startAgent() for 'auto' agent selection
- Add agent field to Task, CreateTaskInput, UpdateTaskInput, TaskSummary schemas
- Settings UI: routing rules section in Agents tab with add/edit/remove/reorder
- Agent Panel: show routing recommendation when starting agent
- 17 unit tests for routing service (all passing)
- Full typecheck clean across shared, server, and web packages
Root cause: TimeTrackingSection subscribed to the React Query ['tasks']
cache for timer state, but concurrent invalidations from debounced saves
(useUpdateTask.onSettled) triggered background refetches that overwrote
timer state with stale data. The cache sync effect then propagated that
stale data back into local state, causing the UI to snap back.
Fix: TimeTrackingSection is now fully self-contained:
- Local state initialized from prop, updated ONLY from API responses
- No React Query cache subscription (removed useTasks dependency)
- No cache sync effect — eliminates all race conditions
- On error, fetches fresh task state directly from server
- Cache is still patched after mutations (for board/other components)
Also removed onSettled invalidation from useUpdateTask — the onSuccess
handler already patches the cache with merged server response. The
aggressive invalidateQueries was the primary source of stale refetches.
Fixes: start/stop not reflecting, multiple timers per task, multi-second
delays, delete not refreshing, requiring panel close/reopen.
Closes#7
Multiple tasks can each have running timers simultaneously — this supports
multi-agent workflows where different agents track different tasks.
Removed:
- getRunningTimerTask() method (global scan)
- Auto-stop of other tasks' timers in startTimer()
- otherRunningTask UI check that hid the Start button
Kept: per-task guard (can't start a timer on a task that already has one running).
Root cause of all timer UI bugs: the component relied on React Query's
cache propagation chain (mutation → patchTaskInList → useTasks() hook →
re-render) which was unreliable due to structural sharing, concurrent
invalidations from debounced saves, and the multi-layer prop pipeline
(useDebouncedSave → localTask → TaskDetailsTab → TimeTrackingSection).
The fix: TimeTrackingSection now owns its own local state, initialized
from the task prop and updated DIRECTLY from API responses after each
mutation. This guarantees instant UI updates regardless of React Query's
internal state:
- Start timer → API response → setTimeTracking → UI shows Stop immediately
- Stop timer → API response → setTimeTracking → UI shows Start immediately
- Delete entry → API response → setTimeTracking → entry disappears immediately
- Add entry → API response → setTimeTracking → entry appears immediately
The query cache is still patched as a secondary update path (for other
components) and synced from for external changes (WebSocket events,
background refetches), but it's no longer the primary driver.
Also:
- Server timer methods now throw ConflictError (409) / NotFoundError (404)
instead of generic Error (which mapped to 500)
- Delete buttons disabled while a mutation is in flight
- Removed useCallback (unnecessary for onClick handlers)
- JSON fingerprint for cache sync prevents unnecessary effect fires
- Server: startTimer() auto-stops any running timer on another task before
starting the new one. Prevents multiple simultaneous timers.
- Server: updateTask() re-reads from cache inside file lock to prevent
concurrent writes (debounced saves) from clobbering timer state.
- Server: Added WebSocket broadcasts to all timer routes (start/stop/add/delete)
so other clients get real-time updates.
- UI: TimeTrackingSection hides Start button when another task has an
active timer, showing 'Timer active on another task' instead.
- UI: Subscribe directly to query cache for timer state instead of relying
on the debounced-save prop pipeline.
- UI: Added mutatingRef guard to prevent double-clicks on Start/Stop.
Fixes the issue where 3 tasks could have running timers simultaneously.
When editing a task field (title, description, etc.) while a timer was
running, the debounced save's onSuccess handler would overwrite the
timeTracking cache with stale server data. This happened because:
1. User stops timer → useStopTimer patches cache (isRunning: false)
2. Pending debounced save fires → server responds with pre-stop data
3. useUpdateTask.onSuccess writes server response → isRunning: true again
The fix: in useUpdateTask.onSuccess, preserve the cached timeTracking
data unless the update explicitly included timeTracking changes. Since
useDebouncedSave only sends dirty fields (title, description, etc.),
timeTracking is never in the update input for normal field edits, so the
cache's more recent timer state is preserved.
Fixes#7
ManagedListService.create() now accepts an optional 'id' field.
If provided, it uses the explicit ID instead of generating slug-nanoid.
Also adds duplicate ID check to prevent conflicts.
This fixes the corrupted task-type IDs (e.g., 'bug-GdN5rT' instead of 'bug')
that were caused by always appending random suffixes.
- Add toggleGroup() to useBulkActions for toggling groups of IDs
- Add status filter buttons (Todo/In Progress/Blocked/Done) to BulkActionsBar
- Buttons are toggleable: click Done then Blocked to select both groups
- Visual states: filled (all selected), dimmed (partial), outline (none)
- Add column header checkboxes with tri-state (checked/indeterminate/unchecked)
- Empty columns hide their checkbox
- Status buttons hide when column has 0 tasks
Root cause: Each route file created its own TaskService instance via
'new TaskService()', resulting in 13+ separate in-memory caches.
When task-archive.ts archived a task, it removed it from its own cache,
but tasks.ts (which serves GET /api/tasks) still had the task in its
separate cache — so archived tasks continued appearing in the list.
Fix: All route files now use getTaskService() singleton, ensuring a
single shared cache across all endpoints. Service files retain their
own instances for test isolation.
Fixes#22
- Remove unused React imports in AgentStatusIndicator and WebSocketIndicator tests
- Fix Response type cast in api-helpers test (as unknown as Response)
File-based locking with wx flag doesn't guarantee ordering within the
same Node.js process — concurrent fs.writeFile calls race at the OS
level. Added a per-file Promise chain that serializes lock requests
in FIFO order within the same process. The file lock still provides
cross-process protection.
Fixes flaky withFileLock serialization test (was ~30% failure rate,
now 20/20 passes).