@prmichaelsen/acp-visualizer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +68 -0
  2. package/agent/commands/acp.clarification-address.md +417 -0
  3. package/agent/commands/acp.clarification-capture.md +386 -0
  4. package/agent/commands/acp.clarification-create.md +437 -0
  5. package/agent/commands/acp.clarifications-research.md +326 -0
  6. package/agent/commands/acp.command-create.md +432 -0
  7. package/agent/commands/acp.design-create.md +286 -0
  8. package/agent/commands/acp.design-reference.md +355 -0
  9. package/agent/commands/acp.handoff.md +270 -0
  10. package/agent/commands/acp.index.md +423 -0
  11. package/agent/commands/acp.init.md +546 -0
  12. package/agent/commands/acp.package-create.md +895 -0
  13. package/agent/commands/acp.package-info.md +212 -0
  14. package/agent/commands/acp.package-install.md +539 -0
  15. package/agent/commands/acp.package-list.md +280 -0
  16. package/agent/commands/acp.package-publish.md +541 -0
  17. package/agent/commands/acp.package-remove.md +293 -0
  18. package/agent/commands/acp.package-search.md +307 -0
  19. package/agent/commands/acp.package-update.md +361 -0
  20. package/agent/commands/acp.package-validate.md +540 -0
  21. package/agent/commands/acp.pattern-create.md +386 -0
  22. package/agent/commands/acp.plan.md +587 -0
  23. package/agent/commands/acp.proceed.md +882 -0
  24. package/agent/commands/acp.project-create.md +675 -0
  25. package/agent/commands/acp.project-info.md +312 -0
  26. package/agent/commands/acp.project-list.md +226 -0
  27. package/agent/commands/acp.project-remove.md +379 -0
  28. package/agent/commands/acp.project-set.md +227 -0
  29. package/agent/commands/acp.project-update.md +307 -0
  30. package/agent/commands/acp.projects-restore.md +228 -0
  31. package/agent/commands/acp.projects-sync.md +347 -0
  32. package/agent/commands/acp.report.md +407 -0
  33. package/agent/commands/acp.resume.md +239 -0
  34. package/agent/commands/acp.sessions.md +301 -0
  35. package/agent/commands/acp.status.md +293 -0
  36. package/agent/commands/acp.sync.md +364 -0
  37. package/agent/commands/acp.task-create.md +500 -0
  38. package/agent/commands/acp.update.md +302 -0
  39. package/agent/commands/acp.validate.md +466 -0
  40. package/agent/commands/acp.version-check-for-updates.md +276 -0
  41. package/agent/commands/acp.version-check.md +191 -0
  42. package/agent/commands/acp.version-update.md +289 -0
  43. package/agent/commands/command.template.md +339 -0
  44. package/agent/commands/git.commit.md +526 -0
  45. package/agent/commands/git.init.md +514 -0
  46. package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
  47. package/agent/commands/tanstack-cloudflare.tail.md +275 -0
  48. package/agent/design/.gitkeep +0 -0
  49. package/agent/design/design.template.md +154 -0
  50. package/agent/design/local.dashboard-layout-routing.md +288 -0
  51. package/agent/design/local.data-model-yaml-parsing.md +310 -0
  52. package/agent/design/local.search-filtering.md +331 -0
  53. package/agent/design/local.server-api-auto-refresh.md +235 -0
  54. package/agent/design/local.table-tree-views.md +299 -0
  55. package/agent/design/local.visualizer-requirements.md +349 -0
  56. package/agent/design/requirements.template.md +387 -0
  57. package/agent/index/.gitkeep +0 -0
  58. package/agent/index/acp.core.yaml +137 -0
  59. package/agent/index/local.main.template.yaml +37 -0
  60. package/agent/manifest.template.yaml +13 -0
  61. package/agent/manifest.yaml +302 -0
  62. package/agent/milestones/.gitkeep +0 -0
  63. package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
  64. package/agent/milestones/milestone-1-{title}.template.md +206 -0
  65. package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
  66. package/agent/package.template.yaml +86 -0
  67. package/agent/patterns/.gitkeep +0 -0
  68. package/agent/patterns/bootstrap.template.md +1237 -0
  69. package/agent/patterns/pattern.template.md +382 -0
  70. package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
  71. package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
  72. package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
  73. package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
  74. package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
  75. package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
  76. package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
  77. package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
  78. package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
  79. package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
  80. package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
  81. package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
  82. package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
  83. package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
  84. package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
  85. package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
  86. package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
  87. package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
  88. package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
  89. package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
  90. package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
  91. package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
  92. package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
  93. package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
  94. package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
  95. package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
  96. package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
  97. package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
  98. package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
  99. package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
  100. package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
  101. package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
  102. package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
  103. package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
  104. package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
  105. package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
  106. package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
  107. package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
  108. package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
  109. package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
  110. package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
  111. package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
  112. package/agent/progress.template.yaml +161 -0
  113. package/agent/progress.yaml +145 -0
  114. package/agent/schemas/package.schema.yaml +276 -0
  115. package/agent/scripts/acp.common.sh +1781 -0
  116. package/agent/scripts/acp.install.sh +333 -0
  117. package/agent/scripts/acp.package-create.sh +924 -0
  118. package/agent/scripts/acp.package-info.sh +288 -0
  119. package/agent/scripts/acp.package-install.sh +893 -0
  120. package/agent/scripts/acp.package-list.sh +311 -0
  121. package/agent/scripts/acp.package-publish.sh +420 -0
  122. package/agent/scripts/acp.package-remove.sh +348 -0
  123. package/agent/scripts/acp.package-search.sh +156 -0
  124. package/agent/scripts/acp.package-update.sh +517 -0
  125. package/agent/scripts/acp.package-validate.sh +1018 -0
  126. package/agent/scripts/acp.uninstall.sh +85 -0
  127. package/agent/scripts/acp.version-check-for-updates.sh +98 -0
  128. package/agent/scripts/acp.version-check.sh +47 -0
  129. package/agent/scripts/acp.version-update.sh +176 -0
  130. package/agent/scripts/acp.yaml-parser.sh +985 -0
  131. package/agent/scripts/acp.yaml-validate.sh +205 -0
  132. package/agent/tasks/.gitkeep +0 -0
  133. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
  134. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
  135. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
  136. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
  137. package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
  138. package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
  139. package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
  140. package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
  141. package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
  142. package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
  143. package/agent/tasks/task-1-{title}.template.md +244 -0
  144. package/bin/visualize.mjs +84 -0
  145. package/package.json +48 -0
  146. package/src/components/ExtraFieldsBadge.tsx +15 -0
  147. package/src/components/FilterBar.tsx +33 -0
  148. package/src/components/Header.tsx +23 -0
  149. package/src/components/MilestoneTable.tsx +167 -0
  150. package/src/components/MilestoneTree.tsx +84 -0
  151. package/src/components/ProgressBar.tsx +20 -0
  152. package/src/components/SearchInput.tsx +22 -0
  153. package/src/components/Sidebar.tsx +54 -0
  154. package/src/components/StatusBadge.tsx +23 -0
  155. package/src/components/StatusDot.tsx +12 -0
  156. package/src/components/TaskList.tsx +36 -0
  157. package/src/components/ViewToggle.tsx +31 -0
  158. package/src/lib/config.ts +8 -0
  159. package/src/lib/file-watcher.ts +43 -0
  160. package/src/lib/search.ts +48 -0
  161. package/src/lib/types.ts +73 -0
  162. package/src/lib/useAutoRefresh.ts +31 -0
  163. package/src/lib/useCollapse.ts +31 -0
  164. package/src/lib/useFilteredData.ts +55 -0
  165. package/src/lib/yaml-loader-real.spec.ts +47 -0
  166. package/src/lib/yaml-loader.spec.ts +201 -0
  167. package/src/lib/yaml-loader.ts +265 -0
  168. package/src/routeTree.gen.ts +140 -0
  169. package/src/router.tsx +10 -0
  170. package/src/routes/__root.tsx +75 -0
  171. package/src/routes/api/watch.ts +29 -0
  172. package/src/routes/index.tsx +115 -0
  173. package/src/routes/milestones.tsx +50 -0
  174. package/src/routes/search.tsx +84 -0
  175. package/src/routes/tasks.tsx +63 -0
  176. package/src/services/progress-database.service.ts +46 -0
  177. package/src/styles.css +25 -0
  178. package/tsconfig.json +24 -0
  179. package/vite.config.ts +16 -0
  180. package/vitest.config.ts +27 -0
@@ -0,0 +1,331 @@
1
+ # Search & Filtering
2
+
3
+ **Concept**: Fuse.js-powered fuzzy search and status-based filtering across milestones and tasks
4
+ **Created**: 2026-03-14
5
+ **Status**: Design Specification
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ Defines the search and filtering system that lets users quickly find milestones and tasks by name/content (via fuse.js fuzzy search) and narrow results by status. Search and filter state is global — it persists across view switches (table ↔ tree) and is accessible from the sidebar.
12
+
13
+ ---
14
+
15
+ ## Problem Statement
16
+
17
+ With 10+ milestones and 50+ tasks, users need to:
18
+ - Find a specific milestone or task by name or keyword
19
+ - Focus on only in-progress items without scrolling past completed work
20
+ - Combine search and filter (e.g., "auth" tasks that are in-progress)
21
+
22
+ Without search/filter, users are back to mentally parsing a long list — defeating the purpose of a visual dashboard.
23
+
24
+ ---
25
+
26
+ ## Solution
27
+
28
+ 1. **Fuse.js search index** built from milestones and tasks on data load
29
+ 2. **Status filter** (all / not_started / in_progress / completed) applied as a pre-filter
30
+ 3. **FilterContext** React context providing shared state across all views
31
+ 4. **SearchBar** component in sidebar for global access
32
+ 5. **FilterBar** component on milestone/task pages for quick status toggles
33
+
34
+ ---
35
+
36
+ ## Implementation
37
+
38
+ ### Search Index
39
+
40
+ ```typescript
41
+ // app/lib/search.ts
42
+
43
+ import Fuse from 'fuse.js';
44
+ import type { ProgressData, Milestone, Task } from './types';
45
+
46
+ export type SearchResult = {
47
+ type: 'milestone' | 'task';
48
+ milestone: Milestone;
49
+ task?: Task;
50
+ score: number;
51
+ };
52
+
53
+ export function buildSearchIndex(data: ProgressData) {
54
+ const items: Array<{
55
+ type: 'milestone' | 'task';
56
+ milestone: Milestone;
57
+ task?: Task;
58
+ name: string;
59
+ notes: string;
60
+ extra: string; // stringified extra fields for search coverage
61
+ }> = [];
62
+
63
+ for (const milestone of data.milestones) {
64
+ items.push({
65
+ type: 'milestone',
66
+ milestone,
67
+ name: milestone.name,
68
+ notes: milestone.notes,
69
+ extra: JSON.stringify(milestone.extra),
70
+ });
71
+
72
+ const tasks = data.tasks[milestone.id] || [];
73
+ for (const task of tasks) {
74
+ items.push({
75
+ type: 'task',
76
+ milestone,
77
+ task,
78
+ name: task.name,
79
+ notes: task.notes,
80
+ extra: JSON.stringify(task.extra),
81
+ });
82
+ }
83
+ }
84
+
85
+ return new Fuse(items, {
86
+ keys: [
87
+ { name: 'name', weight: 2 },
88
+ { name: 'notes', weight: 1 },
89
+ { name: 'extra', weight: 0.5 },
90
+ ],
91
+ threshold: 0.4, // moderate fuzziness
92
+ includeScore: true,
93
+ ignoreLocation: true, // match anywhere in string
94
+ });
95
+ }
96
+ ```
97
+
98
+ ### Filter Context
99
+
100
+ ```tsx
101
+ // app/lib/filter-context.tsx
102
+
103
+ import { createContext, useContext, useState, useMemo } from 'react';
104
+ import type { ProgressData, Status } from './types';
105
+
106
+ interface FilterState {
107
+ status: Status | 'all';
108
+ search: string;
109
+ }
110
+
111
+ interface FilterContextValue {
112
+ filters: FilterState;
113
+ setStatus: (status: Status | 'all') => void;
114
+ setSearch: (query: string) => void;
115
+ filteredData: ProgressData;
116
+ }
117
+
118
+ const FilterContext = createContext<FilterContextValue>(null!);
119
+
120
+ export function FilterProvider({ data, children }: { data: ProgressData; children: React.ReactNode }) {
121
+ const [filters, setFilters] = useState<FilterState>({ status: 'all', search: '' });
122
+
123
+ const filteredData = useMemo(() => {
124
+ let milestones = data.milestones;
125
+ let tasks = { ...data.tasks };
126
+
127
+ // Status filter
128
+ if (filters.status !== 'all') {
129
+ milestones = milestones.filter(m => m.status === filters.status);
130
+ tasks = Object.fromEntries(
131
+ Object.entries(tasks).map(([id, ts]) => [
132
+ id,
133
+ ts.filter(t => t.status === filters.status),
134
+ ])
135
+ );
136
+ }
137
+
138
+ // Search filter (if query present, intersect with search results)
139
+ if (filters.search.trim()) {
140
+ const index = buildSearchIndex(data);
141
+ const results = index.search(filters.search);
142
+ const matchedMilestoneIds = new Set(
143
+ results.map(r => r.item.milestone.id)
144
+ );
145
+ const matchedTaskIds = new Set(
146
+ results.filter(r => r.item.task).map(r => r.item.task!.id)
147
+ );
148
+ milestones = milestones.filter(m => matchedMilestoneIds.has(m.id));
149
+ tasks = Object.fromEntries(
150
+ Object.entries(tasks).map(([id, ts]) => [
151
+ id,
152
+ ts.filter(t => matchedTaskIds.has(t.id) || matchedMilestoneIds.has(id)),
153
+ ])
154
+ );
155
+ }
156
+
157
+ return { ...data, milestones, tasks };
158
+ }, [data, filters]);
159
+
160
+ return (
161
+ <FilterContext.Provider value={{
162
+ filters,
163
+ setStatus: (status) => setFilters(f => ({ ...f, status })),
164
+ setSearch: (search) => setFilters(f => ({ ...f, search })),
165
+ filteredData,
166
+ }}>
167
+ {children}
168
+ </FilterContext.Provider>
169
+ );
170
+ }
171
+
172
+ export const useFilters = () => useContext(FilterContext);
173
+ ```
174
+
175
+ ### FilterBar Component
176
+
177
+ Status toggle buttons displayed above milestone/task views:
178
+
179
+ ```tsx
180
+ // app/components/FilterBar.tsx
181
+
182
+ const statusOptions: Array<{ value: Status | 'all'; label: string }> = [
183
+ { value: 'all', label: 'All' },
184
+ { value: 'in_progress', label: 'In Progress' },
185
+ { value: 'not_started', label: 'Not Started' },
186
+ { value: 'completed', label: 'Completed' },
187
+ ];
188
+
189
+ function FilterBar() {
190
+ const { filters, setStatus } = useFilters();
191
+
192
+ return (
193
+ <div className="flex gap-1 mb-4">
194
+ {statusOptions.map(opt => (
195
+ <button
196
+ key={opt.value}
197
+ onClick={() => setStatus(opt.value)}
198
+ className={`px-3 py-1 text-xs rounded-full border transition-colors ${
199
+ filters.status === opt.value
200
+ ? 'bg-gray-700 border-gray-600 text-gray-100'
201
+ : 'bg-transparent border-gray-800 text-gray-500 hover:text-gray-300'
202
+ }`}
203
+ >
204
+ {opt.label}
205
+ </button>
206
+ ))}
207
+ </div>
208
+ );
209
+ }
210
+ ```
211
+
212
+ ### SearchBar Component
213
+
214
+ Located in the sidebar footer, accessible from any page:
215
+
216
+ ```tsx
217
+ // app/components/SearchBar.tsx
218
+
219
+ function SearchBar() {
220
+ const { filters, setSearch } = useFilters();
221
+
222
+ return (
223
+ <div className="relative">
224
+ <SearchIcon className="absolute left-2 top-2 w-4 h-4 text-gray-500" />
225
+ <input
226
+ type="text"
227
+ value={filters.search}
228
+ onChange={e => setSearch(e.target.value)}
229
+ placeholder="Search..."
230
+ className="w-full bg-gray-900 border border-gray-800 rounded-md pl-8 pr-3 py-1.5 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-gray-600"
231
+ />
232
+ </div>
233
+ );
234
+ }
235
+ ```
236
+
237
+ ### Search Results Page
238
+
239
+ Dedicated `/search` route for viewing detailed search results when triggered from sidebar:
240
+
241
+ ```tsx
242
+ // app/routes/search.tsx
243
+
244
+ function SearchPage() {
245
+ const { filteredData, filters } = useFilters();
246
+
247
+ if (!filters.search.trim()) {
248
+ return <p className="text-gray-500 text-sm">Type to search milestones and tasks</p>;
249
+ }
250
+
251
+ return (
252
+ <div className="space-y-4">
253
+ <h2 className="text-sm text-gray-400">
254
+ Results for "{filters.search}"
255
+ </h2>
256
+ {/* Grouped by milestones, then tasks */}
257
+ {filteredData.milestones.map(m => (
258
+ <SearchResultCard key={m.id} milestone={m} tasks={filteredData.tasks[m.id]} />
259
+ ))}
260
+ </div>
261
+ );
262
+ }
263
+ ```
264
+
265
+ ---
266
+
267
+ ## Benefits
268
+
269
+ - **Fast**: Fuse.js search is client-side, instant feedback
270
+ - **Fuzzy**: Typo-tolerant matching (threshold 0.4)
271
+ - **Combined**: Search + status filter work together naturally
272
+ - **Global**: Shared state persists across view switches
273
+ - **Extra fields searchable**: Agent-added properties are included in the search index
274
+
275
+ ---
276
+
277
+ ## Trade-offs
278
+
279
+ - **Client-side only**: Search index is rebuilt on each data load (acceptable for <100KB data)
280
+ - **No server-side search**: All data is loaded upfront (fine for single-project P0)
281
+ - **Fuse.js bundle size**: ~15KB gzipped (acceptable for admin dashboard)
282
+
283
+ ---
284
+
285
+ ## Applicable Patterns
286
+
287
+ | Pattern | How It Applies |
288
+ |---------|----------------|
289
+ | [`tanstack-cloudflare.global-search-context`](../patterns/tanstack-cloudflare.global-search-context.md) | Adopt the lightweight pub/sub `useRef` store for cross-component search/filter state instead of heavy React Context re-renders. `useGlobalSearch(key)` returns `[value, setValue]` and only re-renders subscribers of that key. Key convention: `milestones:search`, `milestones:status`. Wrap app in `<GlobalSearchProvider>` in root layout. This replaces the FilterContext approach in the implementation section — the GlobalSearchContext pattern is more performant for string-based filter state. |
290
+ | [`tanstack-cloudflare.pill-input`](../patterns/tanstack-cloudflare.pill-input.md) | Provides the proven fuse.js configuration: `threshold: 0.4`, `ignoreLocation: true`, keyboard navigation (arrows, Enter, Escape). The PillInput pattern's fuzzy matching config should be adopted for the search index. Also demonstrates combining predefined options with custom entries — applicable to filter presets. |
291
+ | [`tanstack-cloudflare.searchable-settings`](../patterns/tanstack-cloudflare.searchable-settings.md) | The registry-based AND-logic search pattern applies to multi-field filtering. Each searchable item has `name`, `description`, `keywords` — map to milestone/task names, notes, and extra field values. Hash-based scroll navigation could enable direct linking to search results. |
292
+ | [`tanstack-cloudflare.form-controls`](../patterns/tanstack-cloudflare.form-controls.md) | ToggleSwitch component (iOS-style, ARIA-accessible) for boolean filter controls. Could be used for "show completed" toggle. Fully keyboard-accessible with proper ARIA labels. |
293
+ | [`tanstack-cloudflare.unified-header`](../patterns/tanstack-cloudflare.unified-header.md) | FilterTabs component (inline pill-style filter controls): `flex gap-1 mb-4 p-1 bg-gray-800/50 rounded-lg`. Active button gets gradient background, inactive gets gray text with hover. Adopt this exact styling for the status filter bar rather than custom implementation. |
294
+
295
+ ---
296
+
297
+ ## Dependencies
298
+
299
+ - `fuse.js` — fuzzy search library
300
+ - React context API (or GlobalSearchContext pub/sub — see Applicable Patterns)
301
+
302
+ ---
303
+
304
+ ## Testing Strategy
305
+
306
+ - **Unit tests**: `buildSearchIndex` returns correct results for known queries
307
+ - **Fuzzy matching**: Typos still find results (e.g., "infrastrcture" → "infrastructure")
308
+ - **Filter tests**: Status filter correctly narrows milestone/task lists
309
+ - **Combined tests**: Search + filter intersection works correctly
310
+ - **Empty states**: No results shows appropriate message
311
+
312
+ ---
313
+
314
+ ## Migration Path
315
+
316
+ N/A — greenfield project.
317
+
318
+ ---
319
+
320
+ ## Future Considerations
321
+
322
+ - **Keyboard shortcut**: `Cmd+K` to focus search bar
323
+ - **Search history**: Recent searches stored in localStorage
324
+ - **Advanced filters**: Filter by date range, estimated hours, completion percentage
325
+ - **P1: Recent work search**: Include WorkEntry items in search index
326
+
327
+ ---
328
+
329
+ **Status**: Design Specification
330
+ **Recommendation**: Implement alongside views — search/filter enhances all view components
331
+ **Related Documents**: local.visualizer-requirements.md, local.table-tree-views.md, tanstack-cloudflare.global-search-context, tanstack-cloudflare.pill-input, tanstack-cloudflare.searchable-settings, tanstack-cloudflare.form-controls, tanstack-cloudflare.unified-header
@@ -0,0 +1,235 @@
1
+ # Server API & Auto-Refresh
2
+
3
+ **Concept**: TanStack Start server routes for loading progress.yaml and SSE-based auto-refresh
4
+ **Created**: 2026-03-14
5
+ **Status**: Design Specification
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ Defines the server-side architecture for loading `progress.yaml` from the local filesystem and pushing updates to the browser in real time when the file changes. Uses TanStack Start server functions and Server-Sent Events (SSE) for a simple, reliable auto-refresh mechanism.
12
+
13
+ ---
14
+
15
+ ## Problem Statement
16
+
17
+ The visualizer needs to:
18
+ - Read `progress.yaml` from a configurable filesystem path
19
+ - Serve parsed data to the React frontend
20
+ - Automatically refresh the dashboard when agents modify `progress.yaml` during work
21
+ - Handle file-not-found and parse errors gracefully
22
+
23
+ A simple "refresh the page" approach is insufficient — agents may update progress.yaml dozens of times during a session, and the dashboard should reflect changes without manual intervention.
24
+
25
+ ---
26
+
27
+ ## Solution
28
+
29
+ 1. **Server function**: TanStack Start `createServerFn` that reads and parses `progress.yaml`
30
+ 2. **SSE endpoint**: Watches the file for changes and pushes notifications to connected clients
31
+ 3. **Client hook**: `useAutoRefresh()` React hook that listens for SSE events and triggers re-fetch
32
+
33
+ ---
34
+
35
+ ## Implementation
36
+
37
+ ### Configuration
38
+
39
+ The path to `progress.yaml` is resolved from (in order):
40
+ 1. `PROGRESS_YAML_PATH` environment variable (absolute path)
41
+ 2. CLI argument: `npm run dev -- --progress /path/to/progress.yaml`
42
+ 3. Default: `./agent/progress.yaml` (relative to cwd)
43
+
44
+ ```typescript
45
+ // app/lib/config.ts
46
+
47
+ export function getProgressYamlPath(): string {
48
+ return (
49
+ process.env.PROGRESS_YAML_PATH ||
50
+ process.argv.find((_, i, a) => a[i - 1] === '--progress') ||
51
+ './agent/progress.yaml'
52
+ );
53
+ }
54
+ ```
55
+
56
+ ### Server Function
57
+
58
+ ```typescript
59
+ // app/lib/data-source.ts
60
+
61
+ import { createServerFn } from '@tanstack/react-start';
62
+ import { readFileSync } from 'fs';
63
+ import { parseProgressYaml } from './yaml-loader';
64
+ import { getProgressYamlPath } from './config';
65
+
66
+ export const getProgressData = createServerFn({ method: 'GET' })
67
+ .handler(async () => {
68
+ const filePath = getProgressYamlPath();
69
+ const raw = readFileSync(filePath, 'utf-8');
70
+ return parseProgressYaml(raw);
71
+ });
72
+ ```
73
+
74
+ ### File Watcher & SSE
75
+
76
+ ```typescript
77
+ // server/routes/api/watch.ts
78
+
79
+ import { watch } from 'fs';
80
+ import { getProgressYamlPath } from '../../app/lib/config';
81
+
82
+ export function createFileWatcher() {
83
+ const filePath = getProgressYamlPath();
84
+ const clients = new Set<ReadableStreamDefaultController>();
85
+
86
+ watch(filePath, (eventType) => {
87
+ if (eventType === 'change') {
88
+ for (const controller of clients) {
89
+ controller.enqueue(`data: refresh\n\n`);
90
+ }
91
+ }
92
+ });
93
+
94
+ return {
95
+ addClient(controller: ReadableStreamDefaultController) {
96
+ clients.add(controller);
97
+ },
98
+ removeClient(controller: ReadableStreamDefaultController) {
99
+ clients.delete(controller);
100
+ },
101
+ };
102
+ }
103
+ ```
104
+
105
+ ### SSE API Route
106
+
107
+ ```typescript
108
+ // app/routes/api/watch.ts
109
+
110
+ import { createAPIFileRoute } from '@tanstack/react-start/api';
111
+
112
+ let watcher: ReturnType<typeof createFileWatcher> | null = null;
113
+
114
+ export const APIRoute = createAPIFileRoute('/api/watch')({
115
+ GET: async () => {
116
+ if (!watcher) {
117
+ watcher = createFileWatcher();
118
+ }
119
+
120
+ const stream = new ReadableStream({
121
+ start(controller) {
122
+ watcher!.addClient(controller);
123
+ },
124
+ cancel(controller) {
125
+ watcher!.removeClient(controller);
126
+ },
127
+ });
128
+
129
+ return new Response(stream, {
130
+ headers: {
131
+ 'Content-Type': 'text/event-stream',
132
+ 'Cache-Control': 'no-cache',
133
+ 'Connection': 'keep-alive',
134
+ },
135
+ });
136
+ },
137
+ });
138
+ ```
139
+
140
+ ### Client Hook
141
+
142
+ ```typescript
143
+ // app/lib/useAutoRefresh.ts
144
+
145
+ import { useRouter } from '@tanstack/react-router';
146
+ import { useEffect } from 'react';
147
+
148
+ export function useAutoRefresh() {
149
+ const router = useRouter();
150
+
151
+ useEffect(() => {
152
+ const eventSource = new EventSource('/api/watch');
153
+
154
+ eventSource.onmessage = () => {
155
+ router.invalidate();
156
+ };
157
+
158
+ eventSource.onerror = () => {
159
+ // Reconnect handled automatically by EventSource
160
+ };
161
+
162
+ return () => eventSource.close();
163
+ }, [router]);
164
+ }
165
+ ```
166
+
167
+ ### Error Handling
168
+
169
+ - **File not found**: Server function returns a structured error object with `{ error: 'FILE_NOT_FOUND', path }`. Frontend shows a configuration prompt.
170
+ - **Parse error**: Returns `{ error: 'PARSE_ERROR', message }`. Frontend shows the error with the raw YAML path.
171
+ - **File watcher failure**: Falls back to manual refresh. Log warning to console.
172
+
173
+ ---
174
+
175
+ ## Benefits
176
+
177
+ - **Real-time updates**: Dashboard reflects changes within ~100ms of file save
178
+ - **Low overhead**: SSE is lightweight compared to WebSocket for one-way push
179
+ - **Simple client**: `EventSource` API handles reconnection automatically
180
+ - **No polling**: File watcher is event-driven, not interval-based
181
+
182
+ ---
183
+
184
+ ## Trade-offs
185
+
186
+ - **Local only**: `fs.watch` only works for local development (P1 GitHub source uses polling instead)
187
+ - **Single file**: Watches one progress.yaml path — no multi-project support in P0
188
+ - **SSE limitations**: One-way communication only (sufficient for refresh notifications)
189
+
190
+ ---
191
+
192
+ ## Applicable Patterns
193
+
194
+ | Pattern | How It Applies |
195
+ |---------|----------------|
196
+ | [`tanstack-cloudflare.ssr-preload`](../patterns/tanstack-cloudflare.ssr-preload.md) | Progress data should be loaded server-side via `beforeLoad` (not `loader`), passed through route context, and accessed via `Route.useRouteContext()`. Components initialize state with SSR data and skip client fetch if present. Error handling must be graceful — `try/catch` with empty-data fallback, never fail the page load. |
197
+ | [`tanstack-cloudflare.api-route-handlers`](../patterns/tanstack-cloudflare.api-route-handlers.md) | The SSE `/api/watch` endpoint and any future REST endpoints should use `createFileRoute` with `server.handlers` returning Web Standard `Response` objects. Consistent error response format: `{ error, message }`. No auth needed for P0 (local dev tool). |
198
+ | [`tanstack-cloudflare.library-services`](../patterns/tanstack-cloudflare.library-services.md) | File reading and YAML parsing must go through a `ProgressDatabaseService` (server-side) — never call `readFileSync` directly in routes or `beforeLoad`. The service handles path resolution, file reading, parsing, and error wrapping. |
199
+ | [`tanstack-cloudflare.websocket-manager`](../patterns/tanstack-cloudflare.websocket-manager.md) | While we use SSE (not WebSocket) for P0, the reconnection and lifecycle patterns apply: exponential backoff on connection loss, page visibility recovery (reconnect on tab focus), and clean teardown in `useEffect` cleanup. `EventSource` handles reconnection natively but visibility recovery should still be explicit. |
200
+
201
+ ---
202
+
203
+ ## Dependencies
204
+
205
+ - Node.js `fs` module (built-in)
206
+ - TanStack Start server functions
207
+ - No additional npm packages needed
208
+
209
+ ---
210
+
211
+ ## Testing Strategy
212
+
213
+ - **Unit tests**: `getProgressYamlPath()` resolution order
214
+ - **Integration tests**: Server function reads real YAML file, returns typed data
215
+ - **Manual testing**: Modify progress.yaml while dashboard is open, verify auto-refresh
216
+
217
+ ---
218
+
219
+ ## Migration Path
220
+
221
+ N/A — greenfield project.
222
+
223
+ ---
224
+
225
+ ## Future Considerations
226
+
227
+ - **P1: GitHub remote source** — Replace `readFileSync` with GitHub API fetch, replace SSE with configurable polling interval
228
+ - **P1: Multi-project** — Watch multiple progress.yaml paths, route by project
229
+ - **WebSocket upgrade** — If bidirectional communication needed in future (unlikely for read-only dashboard)
230
+
231
+ ---
232
+
233
+ **Status**: Design Specification
234
+ **Recommendation**: Implement after data model — provides the data pipeline for all views
235
+ **Related Documents**: local.visualizer-requirements.md, local.data-model-yaml-parsing.md, tanstack-cloudflare.ssr-preload, tanstack-cloudflare.api-route-handlers, tanstack-cloudflare.library-services, tanstack-cloudflare.websocket-manager