@prmichaelsen/acp-visualizer 0.6.1 → 0.7.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.
package/package.json
CHANGED
|
@@ -1,12 +1,73 @@
|
|
|
1
1
|
import ReactMarkdown from 'react-markdown'
|
|
2
2
|
import rehypeHighlight from 'rehype-highlight'
|
|
3
|
+
import { Link } from '@tanstack/react-router'
|
|
4
|
+
import type { ProgressData } from '../lib/types'
|
|
3
5
|
|
|
4
6
|
interface MarkdownContentProps {
|
|
5
7
|
content: string
|
|
6
8
|
className?: string
|
|
9
|
+
basePath?: string
|
|
10
|
+
linkMap?: Record<string, string>
|
|
7
11
|
}
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a relative path against a base file path.
|
|
15
|
+
* e.g. resolvePath("agent/milestones/milestone-3.md", "../tasks/task-11.md")
|
|
16
|
+
* → "agent/tasks/task-11.md"
|
|
17
|
+
*/
|
|
18
|
+
export function resolvePath(base: string, relative: string): string {
|
|
19
|
+
const dir = base.split('/').slice(0, -1)
|
|
20
|
+
const relParts = relative.split('/')
|
|
21
|
+
const result = [...dir]
|
|
22
|
+
for (const part of relParts) {
|
|
23
|
+
if (part === '..') result.pop()
|
|
24
|
+
else if (part !== '.') result.push(part)
|
|
25
|
+
}
|
|
26
|
+
return result.join('/')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build a map from file paths to visualizer routes.
|
|
31
|
+
*/
|
|
32
|
+
export function buildLinkMap(data: ProgressData): Record<string, string> {
|
|
33
|
+
const map: Record<string, string> = {}
|
|
34
|
+
for (const ms of data.milestones) {
|
|
35
|
+
for (const task of data.tasks[ms.id] || []) {
|
|
36
|
+
if (task.file) {
|
|
37
|
+
map[task.file] = `/tasks/${task.id}`
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return map
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function createMarkdownLink(basePath: string, linkMap: Record<string, string>) {
|
|
45
|
+
return function MarkdownLink({
|
|
46
|
+
href,
|
|
47
|
+
children,
|
|
48
|
+
...props
|
|
49
|
+
}: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
|
|
50
|
+
if (href && !href.startsWith('http') && !href.startsWith('#')) {
|
|
51
|
+
const resolved = resolvePath(basePath, href)
|
|
52
|
+
const route = linkMap[resolved]
|
|
53
|
+
if (route) {
|
|
54
|
+
return <Link to={route}>{children}</Link>
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return (
|
|
58
|
+
<a href={href} {...props}>
|
|
59
|
+
{children}
|
|
60
|
+
</a>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function MarkdownContent({ content, className, basePath, linkMap }: MarkdownContentProps) {
|
|
66
|
+
const components =
|
|
67
|
+
basePath && linkMap
|
|
68
|
+
? { a: createMarkdownLink(basePath, linkMap) }
|
|
69
|
+
: undefined
|
|
70
|
+
|
|
10
71
|
return (
|
|
11
72
|
<div
|
|
12
73
|
className={`prose prose-invert prose-sm max-w-none
|
|
@@ -22,7 +83,7 @@ export function MarkdownContent({ content, className }: MarkdownContentProps) {
|
|
|
22
83
|
prose-blockquote:border-gray-700 prose-blockquote:text-gray-400
|
|
23
84
|
${className ?? ''}`}
|
|
24
85
|
>
|
|
25
|
-
<ReactMarkdown rehypePlugins={[rehypeHighlight]}>
|
|
86
|
+
<ReactMarkdown rehypePlugins={[rehypeHighlight]} components={components}>
|
|
26
87
|
{content}
|
|
27
88
|
</ReactMarkdown>
|
|
28
89
|
</div>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
-
import { useState, useEffect } from 'react'
|
|
2
|
+
import { useState, useEffect, useMemo } from 'react'
|
|
3
3
|
import { useProgressData } from '../contexts/ProgressContext'
|
|
4
4
|
import { Breadcrumb } from '../components/Breadcrumb'
|
|
5
5
|
import { DetailHeader } from '../components/DetailHeader'
|
|
6
6
|
import { ProgressBar } from '../components/ProgressBar'
|
|
7
7
|
import { StatusDot } from '../components/StatusDot'
|
|
8
|
-
import { MarkdownContent } from '../components/MarkdownContent'
|
|
8
|
+
import { MarkdownContent, buildLinkMap } from '../components/MarkdownContent'
|
|
9
9
|
import { getMarkdownContent, resolveMilestoneFile } from '../services/markdown.service'
|
|
10
10
|
import type { MarkdownResult, ResolveFileResult } from '../services/markdown.service'
|
|
11
11
|
|
|
@@ -29,10 +29,12 @@ function MilestoneDetailPage() {
|
|
|
29
29
|
const data = useProgressData()
|
|
30
30
|
const [markdown, setMarkdown] = useState<string | null>(null)
|
|
31
31
|
const [markdownError, setMarkdownError] = useState<string | null>(null)
|
|
32
|
+
const [markdownFilePath, setMarkdownFilePath] = useState<string | null>(null)
|
|
32
33
|
const [loading, setLoading] = useState(true)
|
|
33
34
|
|
|
34
35
|
const milestone = data?.milestones.find((m) => m.id === milestoneId)
|
|
35
36
|
const tasks = data?.tasks[milestoneId] || []
|
|
37
|
+
const linkMap = useMemo(() => (data ? buildLinkMap(data) : {}), [data])
|
|
36
38
|
|
|
37
39
|
useEffect(() => {
|
|
38
40
|
if (!milestoneId) return
|
|
@@ -40,6 +42,7 @@ function MilestoneDetailPage() {
|
|
|
40
42
|
setLoading(true)
|
|
41
43
|
setMarkdown(null)
|
|
42
44
|
setMarkdownError(null)
|
|
45
|
+
setMarkdownFilePath(null)
|
|
43
46
|
|
|
44
47
|
const github = getGitHubParams()
|
|
45
48
|
|
|
@@ -51,6 +54,7 @@ function MilestoneDetailPage() {
|
|
|
51
54
|
return
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
setMarkdownFilePath(resolveResult.filePath)
|
|
54
58
|
return getMarkdownContent({ data: { filePath: resolveResult.filePath, github } })
|
|
55
59
|
.then((mdResult: MarkdownResult) => {
|
|
56
60
|
if (mdResult.ok) {
|
|
@@ -111,7 +115,7 @@ function MilestoneDetailPage() {
|
|
|
111
115
|
{loading ? (
|
|
112
116
|
<p className="text-sm text-gray-600">Loading document...</p>
|
|
113
117
|
) : markdown ? (
|
|
114
|
-
<MarkdownContent content={markdown} />
|
|
118
|
+
<MarkdownContent content={markdown} basePath={markdownFilePath ?? undefined} linkMap={linkMap} />
|
|
115
119
|
) : markdownError ? (
|
|
116
120
|
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-4 text-sm text-gray-500">
|
|
117
121
|
No document found — {markdownError}
|
|
@@ -3,7 +3,7 @@ import { useState, useEffect, useMemo } from 'react'
|
|
|
3
3
|
import { useProgressData } from '../contexts/ProgressContext'
|
|
4
4
|
import { Breadcrumb } from '../components/Breadcrumb'
|
|
5
5
|
import { DetailHeader } from '../components/DetailHeader'
|
|
6
|
-
import { MarkdownContent } from '../components/MarkdownContent'
|
|
6
|
+
import { MarkdownContent, buildLinkMap } from '../components/MarkdownContent'
|
|
7
7
|
import { getMarkdownContent } from '../services/markdown.service'
|
|
8
8
|
import { resolveTaskFile } from '../services/markdown.service'
|
|
9
9
|
import type { MarkdownResult } from '../services/markdown.service'
|
|
@@ -83,6 +83,9 @@ function TaskDetailPage() {
|
|
|
83
83
|
})
|
|
84
84
|
}, [task])
|
|
85
85
|
|
|
86
|
+
const linkMap = useMemo(() => (data ? buildLinkMap(data) : {}), [data])
|
|
87
|
+
const taskFilePath = useMemo(() => resolveTaskFile(task), [task])
|
|
88
|
+
|
|
86
89
|
if (!data || !task || !milestone) {
|
|
87
90
|
return (
|
|
88
91
|
<div className="p-6">
|
|
@@ -130,7 +133,7 @@ function TaskDetailPage() {
|
|
|
130
133
|
{loading ? (
|
|
131
134
|
<p className="text-sm text-gray-600">Loading document...</p>
|
|
132
135
|
) : markdown ? (
|
|
133
|
-
<MarkdownContent content={markdown} />
|
|
136
|
+
<MarkdownContent content={markdown} basePath={taskFilePath ?? undefined} linkMap={linkMap} />
|
|
134
137
|
) : markdownError ? (
|
|
135
138
|
<div className="bg-gray-900/50 border border-gray-800 rounded-xl p-4 text-sm text-gray-500">
|
|
136
139
|
No document found — {markdownError}
|