RF-24: Frontend state management improvements
1. useCreateTaskForm hook - Replaces 11 useState calls in CreateTaskDialog with useReducer:
- Single source of truth for form state
- Predictable state transitions via typed actions
- Atomic state updates (e.g., applyTemplate updates multiple fields at once)
- Computed canSubmit derives validity instead of storing it
2. Optimistic updates for task mutations:
- useCreateTask: Immediately adds placeholder task to list, rolls back on error
- useUpdateTask: Immediately applies changes to cache, rolls back on error
- Both sync with server on settle to ensure consistency
3. usePolling hook - Shared polling patterns:
- usePolling: Core hook with enable/disable, immediate, cleanup
- useConditionalPolling: Start/stop based on condition
- getConditionalRefetchInterval: For react-query refetchInterval
These patterns improve UI responsiveness and code maintainability.
- Created useTaskMetrics hook to fetch and aggregate telemetry events for a task
- Built TaskMetricsPanel component with:
- Summary cards for total runs, success rate, duration, tokens, cost
- Last run status display
- Expandable per-attempt breakdown with full details
- Integrated as new 'Metrics' tab in TaskDetailPanel
- Fetches via GET /api/telemetry/events/task/:taskId endpoint
- Create useRealtimeAgentStatus hook with WebSocket subscription
- Subscribe to agent:status events as primary transport
- Fall back to polling every 10s when WebSocket disconnects
- Auto-reconnect on WebSocket disconnect (via useWebSocket)
- Stale detection marks agent as idle after 5+ min without updates
- Memoized return value to prevent unnecessary re-renders
- Full TypeScript types for AgentStatusData, SubAgent, AgentStatusState
- Maintain backwards compatibility with useGlobalAgentStatus (polling-only)
- Add POST endpoint for ingesting run telemetry events
- Accepts: run.started, run.completed, run.error, run.tokens
- Zod validation with discriminated union schema
- Stores events in date-partitioned NDJSON files
- Returns 201 with generated id and timestamp
- Add WebSocket broadcast for telemetry events
- New broadcastTelemetryEvent() in broadcast-service
- Emits 'telemetry:event' messages to connected clients
- Update shared telemetry types for flexibility
- RunStartedEvent, RunCompletedEvent, RunErrorEvent types
- TokenTelemetryEvent with optional cacheTokens and cost fields
- Change agent field from AgentType to string for external sources
- Update metrics-service to handle optional totalTokens
- Calculate totalTokens from input+output when not provided
- Updated TaskStatus union from 'review' to 'blocked' in shared/src/types/task.types.ts
- Updated Zod enum in server/src/routes/tasks.ts
- ReviewComment and ReviewState interfaces unchanged (still valid for code review)
- Created hooks/useWebSocket.ts with connection lifecycle, reconnection, typed messages
- Refactored useTaskSync.ts to use shared hook (80 → 35 lines)
- Refactored useAgent.ts to use shared hook (cleaner separation)
- Added useWebSocket to barrel export
- Removed dead agent-service.ts (replaced by clawdbot-agent-service.ts)
- Moved AgentOutput type to clawdbot-agent-service.ts
- Added web/src/hooks/index.ts barrel export for cleaner imports
- Noted: useToast is correct shadcn pattern, not a reimplementation
- Noted: UI components are shadcn standard, no docs needed
- Noted: Monorepo scripts already complete
- Add typeof check for process.env to avoid ReferenceError in browser
- process.env only exists in Node.js, not browser environments
- Fixes white screen issue when loading web frontend
- Add aria-live regions to SaveIndicator for screen reader announcements
- Add Move Up/Down buttons to ManagedListManager for keyboard navigation
- Implement focus management: focus first tab on dialog open, focus content on tab switch
- Add comprehensive ARIA labels to all interactive elements (switches, buttons)
- Add aria-hidden to decorative icons
- Improve Switch accessibility with proper labels and aria-label attributes
- Add role=tabpanel and aria-controls for proper tab/tabpanel relationships
- Radix Dialog provides built-in focus trap functionality
- Add duration support to toast system
- Replace all 5 alert() calls in SettingsDialog with toasts
- Success toasts auto-dismiss after 3s
- Error/warning toasts persist until manually dismissed
- Consistent with ManageTab toast patterns
- Add syncSettingsToServices() to propagate feature settings to services
- Telemetry service reads enabled/retention/traces from feature settings
- Attachment service reads dynamic limits from task behavior settings
- Sync on startup from config file and on every PATCH /api/settings/features
- Add setLimits() to AttachmentService for runtime limit updates
- Vertical sidebar navigation with 7 tabs: General, Board, Tasks, Agents, Data, Notifications, Manage
- Toggle rows with Switch components + descriptive labels
- Number inputs with min/max validation and unit labels
- Auto-save with debounced 500ms updates + save indicator
- Reset to Defaults button per section with confirmation
- Mobile: tabs collapse to dropdown selector
- Keyboard navigation between tabs (arrow keys)
- Existing managed lists (task types, projects, sprints, templates) moved to Manage tab
- Add broadcast-service.ts that sends task:changed events to all
connected WebSocket clients on any task mutation
- Wire broadcasts into task routes (create, update, delete, archive,
restore, reorder)
- Add useTaskSync hook — connects to WS, listens for task:changed,
invalidates React Query cache for instant board updates
- Add polling fallback: refetchInterval=10s, staleTime=5s on useTasks
- Auto-reconnect WebSocket with 3s backoff on disconnect
Board now updates within seconds of any external change (API calls
from Clawdbot agents, CLI, etc.) without manual page refresh.
Fixes: task_20260128_Zk4oV0
- Add readOnly prop to TaskDetailPanel — shows archived badge,
displays all fields as non-editable text, hides delete/template buttons,
shows 'Restore to Board' button instead
- Wire up task click in ArchiveSidebar to open read-only detail panel
- Add pagination to archive list (25 per page with 'Load More' button)
- Show results count when filters reduce the list
- ArchiveSidebar now manages its own detail panel internally
Fixes: task_20260128_tDh8SO
- Add position field to Task type (shared/types.ts)
- Add POST /api/tasks/reorder endpoint to persist ordering
- Update task-service with reorderTasks() method
- Switch TaskCard from useDraggable to useSortable (@dnd-kit/sortable)
- Wrap KanbanColumn tasks in SortableContext with verticalListSortingStrategy
- Update KanbanBoard DnD handlers to support both within-column reorder
and cross-column status changes
- Sort tasks by position in useTasksByStatus hook
- Use closestCorners collision detection for better sortable support
Fixes: task_20260128_4qCzGr
- stopTimer() set activeEntryId: undefined which js-yaml cannot serialize
- Changed to omit activeEntryId when clearing (conditional spread)
- Same fix in deleteTimeEntry() for active entry deletion
- Added deepCleanUndefined() to taskToMarkdown() as defense-in-depth
- Imported TimeTracking type for explicit typing
Fixes: task_20260128_RrQUWH
- Added SprintConfig interface extending ManagedListItem in shared types
- Created SprintService with seed migration from existing tasks
- Added /api/sprints endpoints using managed list router pattern
- Implemented useSprints and useSprintsManager hooks
- Updated TaskDetailPanel and CreateTaskDialog to use dynamic sprint list
- Added sprint management UI to SettingsDialog
- Sprint IDs match existing task.sprint values for backward compatibility
- Reference counter prevents deletion of sprints with tasks
- US-700 series (archive): US-701 through US-711
- US-800 series (archive): US-801 through US-808
- US-900 series (active): US-901 through US-919
- Created US-917 (sprint assignment), US-918 (subtask count), US-919 (task type on cards)
- All three marked done with closing comments
- Fixed path traversal in attachment filename sanitization
- Fixed Radix Select crash: empty string value ('') not supported
Changed 'No project' SelectItem from value='' to value='__none__'
in both TaskDetailPanel and CreateTaskDialog
- Updated task type colors: removed Rose, added bright Pink (fuchsia),
darkened Blue (700), darkened Green (700), added Yellow, added Brown
- Removed isDefault protection from delete (any item can be deleted)
- Removed hide/show toggle button from ManagedListManager
- Delete still blocked if item has task references (with clear message)
- Cleaned up isHidden flags from task-types.json
- Simplified delete dialog messaging
- ProjectService.init() had infinite recursion: list() → init() → seed → list() → init()...
- TagService seed checked file existence (always true after super.init creates it)
- Seed migrations now use seedItem() with raw project/tag strings as IDs
so they match existing task.project and task.tags values
- Added seedItem() to ManagedListService base for custom-ID seeding
- Cleaned up task-types.json (removed duplicate, restored Research)