@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,167 @@
1
+ import { Fragment, useState } from 'react'
2
+ import {
3
+ createColumnHelper,
4
+ useReactTable,
5
+ getCoreRowModel,
6
+ getSortedRowModel,
7
+ flexRender,
8
+ type SortingState,
9
+ } from '@tanstack/react-table'
10
+ import { ChevronDown, ChevronRight, ArrowUpDown } from 'lucide-react'
11
+ import { StatusBadge } from './StatusBadge'
12
+ import { ProgressBar } from './ProgressBar'
13
+ import { TaskList } from './TaskList'
14
+ import type { Milestone, Task } from '@/lib/types'
15
+
16
+ const columnHelper = createColumnHelper<Milestone>()
17
+
18
+ interface MilestoneTableProps {
19
+ milestones: Milestone[]
20
+ tasks: Record<string, Task[]>
21
+ }
22
+
23
+ export function MilestoneTable({ milestones, tasks }: MilestoneTableProps) {
24
+ const [sorting, setSorting] = useState<SortingState>([])
25
+ const [expanded, setExpanded] = useState<Set<string>>(new Set())
26
+
27
+ const toggle = (id: string) => {
28
+ setExpanded((prev) => {
29
+ const next = new Set(prev)
30
+ next.has(id) ? next.delete(id) : next.add(id)
31
+ return next
32
+ })
33
+ }
34
+
35
+ const columns = [
36
+ columnHelper.display({
37
+ id: 'expand',
38
+ cell: (info) => (
39
+ <button
40
+ onClick={() => toggle(info.row.original.id)}
41
+ className="p-0.5 text-gray-500 hover:text-gray-300"
42
+ >
43
+ {expanded.has(info.row.original.id) ? (
44
+ <ChevronDown className="w-4 h-4" />
45
+ ) : (
46
+ <ChevronRight className="w-4 h-4" />
47
+ )}
48
+ </button>
49
+ ),
50
+ size: 32,
51
+ }),
52
+ columnHelper.accessor('name', {
53
+ header: 'Milestone',
54
+ cell: (info) => (
55
+ <span className="text-sm font-medium">{info.getValue()}</span>
56
+ ),
57
+ }),
58
+ columnHelper.accessor('status', {
59
+ header: 'Status',
60
+ cell: (info) => <StatusBadge status={info.getValue()} />,
61
+ size: 120,
62
+ }),
63
+ columnHelper.accessor('progress', {
64
+ header: 'Progress',
65
+ cell: (info) => (
66
+ <div className="flex items-center gap-2">
67
+ <div className="w-20">
68
+ <ProgressBar value={info.getValue()} size="sm" />
69
+ </div>
70
+ <span className="text-xs text-gray-400 font-mono w-8">
71
+ {info.getValue()}%
72
+ </span>
73
+ </div>
74
+ ),
75
+ size: 140,
76
+ }),
77
+ columnHelper.display({
78
+ id: 'tasks',
79
+ header: 'Tasks',
80
+ cell: (info) => (
81
+ <span className="font-mono text-xs text-gray-400">
82
+ {info.row.original.tasks_completed}/{info.row.original.tasks_total}
83
+ </span>
84
+ ),
85
+ size: 80,
86
+ }),
87
+ columnHelper.accessor('started', {
88
+ header: 'Started',
89
+ cell: (info) => (
90
+ <span className="text-xs text-gray-500">{info.getValue() || '—'}</span>
91
+ ),
92
+ size: 100,
93
+ }),
94
+ columnHelper.accessor('estimated_weeks', {
95
+ header: 'Est.',
96
+ cell: (info) => (
97
+ <span className="text-xs text-gray-500 font-mono">
98
+ {info.getValue()}w
99
+ </span>
100
+ ),
101
+ size: 60,
102
+ }),
103
+ ]
104
+
105
+ const table = useReactTable({
106
+ data: milestones,
107
+ columns,
108
+ state: { sorting },
109
+ onSortingChange: setSorting,
110
+ getCoreRowModel: getCoreRowModel(),
111
+ getSortedRowModel: getSortedRowModel(),
112
+ })
113
+
114
+ if (milestones.length === 0) {
115
+ return <p className="text-gray-600 text-sm">No milestones</p>
116
+ }
117
+
118
+ return (
119
+ <div className="border border-gray-800 rounded-lg overflow-hidden">
120
+ <table className="w-full">
121
+ <thead>
122
+ {table.getHeaderGroups().map((headerGroup) => (
123
+ <tr key={headerGroup.id} className="border-b border-gray-800 bg-gray-900/50">
124
+ {headerGroup.headers.map((header) => (
125
+ <th
126
+ key={header.id}
127
+ className="text-left text-xs text-gray-400 font-medium px-3 py-2 cursor-pointer select-none hover:text-gray-200"
128
+ style={{ width: header.getSize() !== 150 ? header.getSize() : undefined }}
129
+ onClick={header.column.getToggleSortingHandler()}
130
+ >
131
+ <div className="flex items-center gap-1">
132
+ {header.isPlaceholder
133
+ ? null
134
+ : flexRender(header.column.columnDef.header, header.getContext())}
135
+ {header.column.getCanSort() && (
136
+ <ArrowUpDown className="w-3 h-3 text-gray-600" />
137
+ )}
138
+ </div>
139
+ </th>
140
+ ))}
141
+ </tr>
142
+ ))}
143
+ </thead>
144
+ <tbody>
145
+ {table.getRowModel().rows.map((row) => (
146
+ <Fragment key={row.id}>
147
+ <tr className="border-b border-gray-800/50 hover:bg-gray-800/30 transition-colors">
148
+ {row.getVisibleCells().map((cell) => (
149
+ <td key={cell.id} className="px-3 py-2">
150
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
151
+ </td>
152
+ ))}
153
+ </tr>
154
+ {expanded.has(row.original.id) && (
155
+ <tr>
156
+ <td colSpan={columns.length} className="bg-gray-900/30 px-3 py-1">
157
+ <TaskList tasks={tasks[row.original.id] || []} />
158
+ </td>
159
+ </tr>
160
+ )}
161
+ </Fragment>
162
+ ))}
163
+ </tbody>
164
+ </table>
165
+ </div>
166
+ )
167
+ }
@@ -0,0 +1,84 @@
1
+ import { useState } from 'react'
2
+ import { ChevronDown, ChevronRight } from 'lucide-react'
3
+ import { StatusBadge } from './StatusBadge'
4
+ import { ProgressBar } from './ProgressBar'
5
+ import { TaskList } from './TaskList'
6
+ import { useCollapse } from '@/lib/useCollapse'
7
+ import type { Milestone, Task } from '@/lib/types'
8
+
9
+ interface MilestoneTreeProps {
10
+ milestones: Milestone[]
11
+ tasks: Record<string, Task[]>
12
+ }
13
+
14
+ function MilestoneTreeRow({
15
+ milestone,
16
+ tasks,
17
+ expanded,
18
+ onToggle,
19
+ }: {
20
+ milestone: Milestone
21
+ tasks: Task[]
22
+ expanded: boolean
23
+ onToggle: () => void
24
+ }) {
25
+ const collapse = useCollapse(expanded)
26
+
27
+ return (
28
+ <div className="border-b border-gray-800/50">
29
+ <button
30
+ onClick={onToggle}
31
+ className="w-full flex items-center gap-3 px-4 py-3 hover:bg-gray-800/30 transition-colors text-left"
32
+ >
33
+ {expanded ? (
34
+ <ChevronDown className="w-4 h-4 text-gray-500 shrink-0" />
35
+ ) : (
36
+ <ChevronRight className="w-4 h-4 text-gray-500 shrink-0" />
37
+ )}
38
+ <span className="flex-1 text-sm font-medium">{milestone.name}</span>
39
+ <StatusBadge status={milestone.status} />
40
+ <div className="w-20">
41
+ <ProgressBar value={milestone.progress} size="sm" />
42
+ </div>
43
+ <span className="text-xs text-gray-500 font-mono w-12 text-right">
44
+ {milestone.tasks_completed}/{milestone.tasks_total}
45
+ </span>
46
+ </button>
47
+ <div ref={collapse.ref} style={collapse.style}>
48
+ <div className="px-4 pb-2">
49
+ <TaskList tasks={tasks} />
50
+ </div>
51
+ </div>
52
+ </div>
53
+ )
54
+ }
55
+
56
+ export function MilestoneTree({ milestones, tasks }: MilestoneTreeProps) {
57
+ const [expanded, setExpanded] = useState<Set<string>>(new Set())
58
+
59
+ const toggle = (id: string) => {
60
+ setExpanded((prev) => {
61
+ const next = new Set(prev)
62
+ next.has(id) ? next.delete(id) : next.add(id)
63
+ return next
64
+ })
65
+ }
66
+
67
+ if (milestones.length === 0) {
68
+ return <p className="text-gray-600 text-sm">No milestones</p>
69
+ }
70
+
71
+ return (
72
+ <div className="border border-gray-800 rounded-lg overflow-hidden">
73
+ {milestones.map((milestone) => (
74
+ <MilestoneTreeRow
75
+ key={milestone.id}
76
+ milestone={milestone}
77
+ tasks={tasks[milestone.id] || []}
78
+ expanded={expanded.has(milestone.id)}
79
+ onToggle={() => toggle(milestone.id)}
80
+ />
81
+ ))}
82
+ </div>
83
+ )
84
+ }
@@ -0,0 +1,20 @@
1
+ interface ProgressBarProps {
2
+ value: number
3
+ size?: 'sm' | 'md'
4
+ }
5
+
6
+ export function ProgressBar({ value, size = 'md' }: ProgressBarProps) {
7
+ const height = size === 'sm' ? 'h-1.5' : 'h-2.5'
8
+ const clamped = Math.min(100, Math.max(0, value))
9
+
10
+ return (
11
+ <div className={`w-full bg-gray-800 rounded-full ${height} overflow-hidden`}>
12
+ <div
13
+ className={`${height} rounded-full transition-all duration-300 ${
14
+ clamped === 100 ? 'bg-green-500' : 'bg-blue-500'
15
+ }`}
16
+ style={{ width: `${clamped}%` }}
17
+ />
18
+ </div>
19
+ )
20
+ }
@@ -0,0 +1,22 @@
1
+ import { Search } from 'lucide-react'
2
+
3
+ interface SearchInputProps {
4
+ value: string
5
+ onChange: (value: string) => void
6
+ placeholder?: string
7
+ }
8
+
9
+ export function SearchInput({ value, onChange, placeholder = 'Search...' }: SearchInputProps) {
10
+ return (
11
+ <div className="relative">
12
+ <Search className="absolute left-2.5 top-2 w-4 h-4 text-gray-500" />
13
+ <input
14
+ type="text"
15
+ value={value}
16
+ onChange={(e) => onChange(e.target.value)}
17
+ placeholder={placeholder}
18
+ 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 transition-colors"
19
+ />
20
+ </div>
21
+ )
22
+ }
@@ -0,0 +1,54 @@
1
+ import { Link, useRouterState } from '@tanstack/react-router'
2
+ import { LayoutDashboard, Flag, CheckSquare, Search } from 'lucide-react'
3
+
4
+ const navItems = [
5
+ { to: '/' as const, icon: LayoutDashboard, label: 'Overview' },
6
+ { to: '/milestones' as const, icon: Flag, label: 'Milestones' },
7
+ { to: '/tasks' as const, icon: CheckSquare, label: 'Tasks' },
8
+ ]
9
+
10
+ export function Sidebar() {
11
+ const location = useRouterState({ select: (s) => s.location })
12
+
13
+ return (
14
+ <nav className="w-56 border-r border-gray-800 bg-gray-950 flex flex-col shrink-0">
15
+ <div className="p-4 border-b border-gray-800">
16
+ <span className="text-sm font-semibold text-gray-300 tracking-wide">
17
+ ACP Visualizer
18
+ </span>
19
+ </div>
20
+ <div className="flex-1 py-2">
21
+ {navItems.map((item) => {
22
+ const isActive =
23
+ item.to === '/'
24
+ ? location.pathname === '/'
25
+ : location.pathname.startsWith(item.to)
26
+
27
+ return (
28
+ <Link
29
+ key={item.to}
30
+ to={item.to}
31
+ className={`flex items-center gap-3 px-4 py-2 text-sm transition-colors ${
32
+ isActive
33
+ ? 'text-gray-100 bg-gray-800/50'
34
+ : 'text-gray-400 hover:text-gray-200 hover:bg-gray-800/30'
35
+ }`}
36
+ >
37
+ <item.icon className="w-4 h-4" />
38
+ {item.label}
39
+ </Link>
40
+ )
41
+ })}
42
+ </div>
43
+ <div className="p-3 border-t border-gray-800">
44
+ <Link
45
+ to="/search"
46
+ className="flex items-center gap-2 px-3 py-1.5 text-sm text-gray-500 bg-gray-900 border border-gray-800 rounded-md hover:text-gray-300 hover:border-gray-600 transition-colors"
47
+ >
48
+ <Search className="w-4 h-4" />
49
+ Search...
50
+ </Link>
51
+ </div>
52
+ </nav>
53
+ )
54
+ }
@@ -0,0 +1,23 @@
1
+ import type { Status } from '@/lib/types'
2
+
3
+ const statusStyles: Record<Status, string> = {
4
+ completed: 'bg-green-500/15 text-green-400 border-green-500/20',
5
+ in_progress: 'bg-blue-500/15 text-blue-400 border-blue-500/20',
6
+ not_started: 'bg-gray-500/15 text-gray-500 border-gray-500/20',
7
+ }
8
+
9
+ const statusLabels: Record<Status, string> = {
10
+ completed: 'Completed',
11
+ in_progress: 'In Progress',
12
+ not_started: 'Not Started',
13
+ }
14
+
15
+ export function StatusBadge({ status }: { status: Status }) {
16
+ return (
17
+ <span
18
+ className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs border ${statusStyles[status]}`}
19
+ >
20
+ {statusLabels[status]}
21
+ </span>
22
+ )
23
+ }
@@ -0,0 +1,12 @@
1
+ import type { Status } from '@/lib/types'
2
+
3
+ const dotStyles: Record<Status, { symbol: string; color: string }> = {
4
+ completed: { symbol: '✓', color: 'text-green-400' },
5
+ in_progress: { symbol: '●', color: 'text-blue-400' },
6
+ not_started: { symbol: '○', color: 'text-gray-500' },
7
+ }
8
+
9
+ export function StatusDot({ status }: { status: Status }) {
10
+ const { symbol, color } = dotStyles[status]
11
+ return <span className={`${color} text-xs w-4 text-center`}>{symbol}</span>
12
+ }
@@ -0,0 +1,36 @@
1
+ import { StatusDot } from './StatusDot'
2
+ import { ExtraFieldsBadge } from './ExtraFieldsBadge'
3
+ import type { Task } from '@/lib/types'
4
+
5
+ export function TaskList({ tasks }: { tasks: Task[] }) {
6
+ if (tasks.length === 0) {
7
+ return (
8
+ <div className="pl-6 py-2">
9
+ <span className="text-xs text-gray-600">No tasks</span>
10
+ </div>
11
+ )
12
+ }
13
+
14
+ return (
15
+ <div className="pl-6 py-1 space-y-0.5">
16
+ {tasks.map((task) => (
17
+ <div key={task.id} className="flex items-center gap-2 py-1 text-sm">
18
+ <StatusDot status={task.status} />
19
+ <span
20
+ className={
21
+ task.status === 'completed' ? 'text-gray-500' : 'text-gray-200'
22
+ }
23
+ >
24
+ {task.name}
25
+ </span>
26
+ {task.notes && (
27
+ <span className="text-xs text-gray-600 ml-auto truncate max-w-[200px]">
28
+ {task.notes}
29
+ </span>
30
+ )}
31
+ <ExtraFieldsBadge fields={task.extra} />
32
+ </div>
33
+ ))}
34
+ </div>
35
+ )
36
+ }
@@ -0,0 +1,31 @@
1
+ interface ViewToggleProps {
2
+ value: 'table' | 'tree'
3
+ onChange: (view: 'table' | 'tree') => void
4
+ }
5
+
6
+ export function ViewToggle({ value, onChange }: ViewToggleProps) {
7
+ return (
8
+ <div className="flex gap-1 bg-gray-900 border border-gray-800 rounded-lg p-0.5">
9
+ <button
10
+ onClick={() => onChange('table')}
11
+ className={`px-3 py-1 text-xs rounded-md transition-colors ${
12
+ value === 'table'
13
+ ? 'bg-gray-700 text-gray-100'
14
+ : 'text-gray-500 hover:text-gray-300'
15
+ }`}
16
+ >
17
+ Table
18
+ </button>
19
+ <button
20
+ onClick={() => onChange('tree')}
21
+ className={`px-3 py-1 text-xs rounded-md transition-colors ${
22
+ value === 'tree'
23
+ ? 'bg-gray-700 text-gray-100'
24
+ : 'text-gray-500 hover:text-gray-300'
25
+ }`}
26
+ >
27
+ Tree
28
+ </button>
29
+ </div>
30
+ )
31
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Resolves the path to progress.yaml from (in order):
3
+ * 1. PROGRESS_YAML_PATH environment variable
4
+ * 2. Default: ./agent/progress.yaml (relative to cwd)
5
+ */
6
+ export function getProgressYamlPath(): string {
7
+ return process.env.PROGRESS_YAML_PATH || './agent/progress.yaml'
8
+ }
@@ -0,0 +1,43 @@
1
+ import { watch } from 'fs'
2
+ import { getProgressYamlPath } from './config'
3
+
4
+ type Controller = ReadableStreamDefaultController
5
+
6
+ let watcher: {
7
+ addClient: (controller: Controller) => void
8
+ removeClient: (controller: Controller) => void
9
+ } | null = null
10
+
11
+ export function getFileWatcher() {
12
+ if (watcher) return watcher
13
+
14
+ const filePath = getProgressYamlPath()
15
+ const clients = new Set<Controller>()
16
+
17
+ try {
18
+ watch(filePath, (eventType) => {
19
+ if (eventType === 'change') {
20
+ for (const controller of clients) {
21
+ try {
22
+ controller.enqueue(new TextEncoder().encode('data: refresh\n\n'))
23
+ } catch {
24
+ clients.delete(controller)
25
+ }
26
+ }
27
+ }
28
+ })
29
+ } catch (err) {
30
+ console.warn('[FileWatcher] Could not watch file:', err)
31
+ }
32
+
33
+ watcher = {
34
+ addClient(controller: Controller) {
35
+ clients.add(controller)
36
+ },
37
+ removeClient(controller: Controller) {
38
+ clients.delete(controller)
39
+ },
40
+ }
41
+
42
+ return watcher
43
+ }
@@ -0,0 +1,48 @@
1
+ import Fuse from 'fuse.js'
2
+ import type { ProgressData, Milestone, Task } from './types'
3
+
4
+ export interface SearchItem {
5
+ type: 'milestone' | 'task'
6
+ milestone: Milestone
7
+ task?: Task
8
+ name: string
9
+ notes: string
10
+ extra: string
11
+ }
12
+
13
+ export function buildSearchIndex(data: ProgressData) {
14
+ const items: SearchItem[] = []
15
+
16
+ for (const milestone of data.milestones) {
17
+ items.push({
18
+ type: 'milestone',
19
+ milestone,
20
+ name: milestone.name,
21
+ notes: milestone.notes,
22
+ extra: JSON.stringify(milestone.extra),
23
+ })
24
+
25
+ const tasks = data.tasks[milestone.id] || []
26
+ for (const task of tasks) {
27
+ items.push({
28
+ type: 'task',
29
+ milestone,
30
+ task,
31
+ name: task.name,
32
+ notes: task.notes,
33
+ extra: JSON.stringify(task.extra),
34
+ })
35
+ }
36
+ }
37
+
38
+ return new Fuse(items, {
39
+ keys: [
40
+ { name: 'name', weight: 2 },
41
+ { name: 'notes', weight: 1 },
42
+ { name: 'extra', weight: 0.5 },
43
+ ],
44
+ threshold: 0.4,
45
+ includeScore: true,
46
+ ignoreLocation: true,
47
+ })
48
+ }
@@ -0,0 +1,73 @@
1
+ /** Status values for milestones and tasks */
2
+ export type Status = 'completed' | 'in_progress' | 'not_started'
3
+
4
+ /** Unknown properties from agent-maintained YAML are preserved here */
5
+ export type ExtraFields = Record<string, unknown>
6
+
7
+ export interface ProgressData {
8
+ project: ProjectMetadata
9
+ milestones: Milestone[]
10
+ tasks: Record<string, Task[]> // keyed by milestone_id
11
+ recent_work: WorkEntry[]
12
+ next_steps: string[]
13
+ notes: string[]
14
+ current_blockers: string[]
15
+ documentation: DocumentationStats
16
+ progress: ProgressSummary
17
+ }
18
+
19
+ export interface ProjectMetadata {
20
+ name: string
21
+ version: string
22
+ started: string
23
+ status: Status
24
+ current_milestone?: string
25
+ description: string
26
+ extra: ExtraFields
27
+ }
28
+
29
+ export interface Milestone {
30
+ id: string
31
+ name: string
32
+ status: Status
33
+ progress: number // 0-100
34
+ started: string | null
35
+ completed: string | null
36
+ estimated_weeks: string
37
+ tasks_completed: number
38
+ tasks_total: number
39
+ notes: string
40
+ extra: ExtraFields
41
+ }
42
+
43
+ export interface Task {
44
+ id: string
45
+ name: string
46
+ status: Status
47
+ milestone_id: string
48
+ file: string
49
+ estimated_hours: string
50
+ completed_date: string | null
51
+ notes: string
52
+ extra: ExtraFields
53
+ }
54
+
55
+ export interface WorkEntry {
56
+ date: string
57
+ description: string
58
+ items: string[]
59
+ extra: ExtraFields
60
+ }
61
+
62
+ export interface DocumentationStats {
63
+ design_documents: number
64
+ milestone_documents: number
65
+ pattern_documents: number
66
+ task_documents: number
67
+ }
68
+
69
+ export interface ProgressSummary {
70
+ planning: number
71
+ implementation: number
72
+ overall: number
73
+ }
@@ -0,0 +1,31 @@
1
+ import { useRouter } from '@tanstack/react-router'
2
+ import { useEffect } from 'react'
3
+
4
+ export function useAutoRefresh() {
5
+ const router = useRouter()
6
+
7
+ useEffect(() => {
8
+ const eventSource = new EventSource('/api/watch')
9
+
10
+ eventSource.onmessage = () => {
11
+ router.invalidate()
12
+ }
13
+
14
+ eventSource.onerror = () => {
15
+ // EventSource handles reconnection automatically
16
+ }
17
+
18
+ // Reconnect on tab visibility change
19
+ const handleVisibility = () => {
20
+ if (document.visibilityState === 'visible') {
21
+ router.invalidate()
22
+ }
23
+ }
24
+ document.addEventListener('visibilitychange', handleVisibility)
25
+
26
+ return () => {
27
+ eventSource.close()
28
+ document.removeEventListener('visibilitychange', handleVisibility)
29
+ }
30
+ }, [router])
31
+ }