@prmichaelsen/acp-visualizer 0.1.5 → 0.1.7
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.
package/package.json
CHANGED
package/src/lib/file-watcher.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { watch } from 'fs'
|
|
2
|
-
import { getProgressYamlPath } from './config'
|
|
3
|
-
|
|
4
1
|
type Controller = ReadableStreamDefaultController
|
|
5
2
|
|
|
6
3
|
let watcher: {
|
|
@@ -8,9 +5,12 @@ let watcher: {
|
|
|
8
5
|
removeClient: (controller: Controller) => void
|
|
9
6
|
} | null = null
|
|
10
7
|
|
|
11
|
-
export function getFileWatcher() {
|
|
8
|
+
export async function getFileWatcher() {
|
|
12
9
|
if (watcher) return watcher
|
|
13
10
|
|
|
11
|
+
const { watch } = await import('fs')
|
|
12
|
+
const { getProgressYamlPath } = await import('./config')
|
|
13
|
+
|
|
14
14
|
const filePath = getProgressYamlPath()
|
|
15
15
|
const clients = new Set<Controller>()
|
|
16
16
|
|
|
@@ -186,6 +186,34 @@ milestones:
|
|
|
186
186
|
expect(result.milestones[0].id).toBe('milestone_1')
|
|
187
187
|
})
|
|
188
188
|
|
|
189
|
+
it('maps task keys to milestone IDs when formats differ', () => {
|
|
190
|
+
const yaml = `
|
|
191
|
+
milestones:
|
|
192
|
+
- id: M1
|
|
193
|
+
name: First
|
|
194
|
+
status: completed
|
|
195
|
+
- id: M2
|
|
196
|
+
name: Second
|
|
197
|
+
status: in_progress
|
|
198
|
+
|
|
199
|
+
tasks:
|
|
200
|
+
milestone_1:
|
|
201
|
+
- id: t1
|
|
202
|
+
name: Task A
|
|
203
|
+
status: completed
|
|
204
|
+
milestone_2:
|
|
205
|
+
- id: t2
|
|
206
|
+
name: Task B
|
|
207
|
+
status: in_progress
|
|
208
|
+
`
|
|
209
|
+
const result = parseProgressYaml(yaml)
|
|
210
|
+
// Tasks keyed as milestone_1 should map to milestone ID M1
|
|
211
|
+
expect(result.tasks['M1']).toHaveLength(1)
|
|
212
|
+
expect(result.tasks['M1'][0].name).toBe('Task A')
|
|
213
|
+
expect(result.tasks['M2']).toHaveLength(1)
|
|
214
|
+
expect(result.tasks['M2'][0].name).toBe('Task B')
|
|
215
|
+
})
|
|
216
|
+
|
|
189
217
|
it('handles null values in dates', () => {
|
|
190
218
|
const yaml = `
|
|
191
219
|
milestones:
|
package/src/lib/yaml-loader.ts
CHANGED
|
@@ -35,6 +35,7 @@ const TASK_ALIASES: Record<string, string> = {
|
|
|
35
35
|
done_date: 'completed_date',
|
|
36
36
|
filename: 'file',
|
|
37
37
|
path: 'file',
|
|
38
|
+
document: 'file',
|
|
38
39
|
milestone: 'milestone_id',
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -171,12 +172,35 @@ function normalizeTask(raw: unknown, milestoneId: string, index: number): Task {
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
function normalizeTasks(raw: unknown): Record<string, Task[]> {
|
|
175
|
+
function normalizeTasks(raw: unknown, milestones: Milestone[]): Record<string, Task[]> {
|
|
175
176
|
const result: Record<string, Task[]> = {}
|
|
176
177
|
const obj = asRecord(raw)
|
|
177
|
-
|
|
178
|
+
|
|
179
|
+
// Build a map from task key patterns to milestone IDs.
|
|
180
|
+
// Handles mismatch: tasks keyed as "milestone_1" but milestone.id = "M1"
|
|
181
|
+
const keyToMilestoneId = new Map<string, string>()
|
|
182
|
+
for (let i = 0; i < milestones.length; i++) {
|
|
183
|
+
const m = milestones[i]
|
|
184
|
+
// The task key might be the milestone ID itself, or "milestone_N"
|
|
185
|
+
keyToMilestoneId.set(m.id, m.id)
|
|
186
|
+
keyToMilestoneId.set(m.id.toLowerCase(), m.id)
|
|
187
|
+
keyToMilestoneId.set(`milestone_${i + 1}`, m.id)
|
|
188
|
+
// Also handle "milestone_N" where N matches the numeric part of "MN"
|
|
189
|
+
const numMatch = m.id.match(/(\d+)/)
|
|
190
|
+
if (numMatch) {
|
|
191
|
+
keyToMilestoneId.set(`milestone_${numMatch[1]}`, m.id)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const [rawKey, tasks] of Object.entries(obj)) {
|
|
178
196
|
if (Array.isArray(tasks)) {
|
|
179
|
-
|
|
197
|
+
// Resolve the key to a milestone ID, or keep as-is
|
|
198
|
+
const milestoneId = keyToMilestoneId.get(rawKey) || rawKey
|
|
199
|
+
const existing = result[milestoneId] || []
|
|
200
|
+
result[milestoneId] = [
|
|
201
|
+
...existing,
|
|
202
|
+
...tasks.map((t, i) => normalizeTask(t, milestoneId, existing.length + i)),
|
|
203
|
+
]
|
|
180
204
|
}
|
|
181
205
|
}
|
|
182
206
|
return result
|
|
@@ -248,10 +272,12 @@ export function parseProgressYaml(raw: string): ProgressData {
|
|
|
248
272
|
}
|
|
249
273
|
const d = doc as Record<string, unknown>
|
|
250
274
|
|
|
275
|
+
const milestones = normalizeMilestones(d.milestones)
|
|
276
|
+
|
|
251
277
|
return {
|
|
252
278
|
project: normalizeProject(d.project),
|
|
253
|
-
milestones
|
|
254
|
-
tasks: normalizeTasks(d.tasks),
|
|
279
|
+
milestones,
|
|
280
|
+
tasks: normalizeTasks(d.tasks, milestones),
|
|
255
281
|
recent_work: normalizeWorkEntries(d.recent_work),
|
|
256
282
|
next_steps: normalizeStringArray(d.next_steps),
|
|
257
283
|
notes: normalizeStringArray(d.notes),
|
package/src/routes/__root.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { HeadContent, Scripts, createRootRoute, Outlet } from '@tanstack/react-r
|
|
|
2
2
|
import { useAutoRefresh } from '../lib/useAutoRefresh'
|
|
3
3
|
import { Sidebar } from '../components/Sidebar'
|
|
4
4
|
import { Header } from '../components/Header'
|
|
5
|
-
import {
|
|
5
|
+
import { getProgressData } from '../services/progress-database.service'
|
|
6
6
|
import type { ProgressData } from '../lib/types'
|
|
7
7
|
|
|
8
8
|
import appCss from '../styles.css?url'
|
|
@@ -12,7 +12,7 @@ export const Route = createRootRoute({
|
|
|
12
12
|
let progressData: ProgressData | null = null
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
|
-
const result =
|
|
15
|
+
const result = await getProgressData()
|
|
16
16
|
if (result.ok) {
|
|
17
17
|
progressData = result.data
|
|
18
18
|
}
|
package/src/routes/api/watch.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { parseProgressYaml } from '../lib/yaml-loader'
|
|
3
|
-
import { getProgressYamlPath } from '../lib/config'
|
|
1
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
4
2
|
import type { ProgressData } from '../lib/types'
|
|
5
3
|
|
|
6
4
|
export type ProgressResult =
|
|
7
5
|
| { ok: true; data: ProgressData }
|
|
8
6
|
| { ok: false; error: 'FILE_NOT_FOUND' | 'PARSE_ERROR'; message: string; path: string }
|
|
9
7
|
|
|
10
|
-
export
|
|
11
|
-
|
|
8
|
+
export const getProgressData = createServerFn({ method: 'GET' }).handler(
|
|
9
|
+
async (): Promise<ProgressResult> => {
|
|
10
|
+
// Dynamic imports keep fs and yaml-loader out of the client bundle
|
|
11
|
+
const { readFileSync } = await import('fs')
|
|
12
|
+
const { parseProgressYaml } = await import('../lib/yaml-loader')
|
|
13
|
+
const { getProgressYamlPath } = await import('../lib/config')
|
|
14
|
+
|
|
12
15
|
const filePath = getProgressYamlPath()
|
|
13
16
|
|
|
14
17
|
try {
|
|
@@ -42,5 +45,5 @@ export class ProgressDatabaseService {
|
|
|
42
45
|
path: filePath,
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
|
-
}
|
|
46
|
-
|
|
48
|
+
},
|
|
49
|
+
)
|