coaia-visualizer 1.5.10 → 1.6.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/README.md +5 -0
- package/components/chart-detail-editable.tsx +2 -0
- package/components/chart-detail.tsx +6 -1
- package/components/edit-action-step.tsx +2 -0
- package/components/github-provenance.tsx +226 -0
- package/components/relation-graph.tsx +91 -27
- package/index.tsx +26 -0
- package/lib/github-provenance.ts +316 -0
- package/lib/types.ts +15 -2
- package/next-env.d.ts +6 -0
- package/package.json +27 -1
- package/.coaia/jgwill-coaia-visualizer.jsonl +0 -6
- package/.dockerignore +0 -9
- package/COMPLETE_IMPLEMENTATION_REPORT.md +0 -215
- package/QUICK_START_MCP_TESTING.md +0 -236
- package/README_TELESCOPED_NAVIGATION.md +0 -247
- package/STC.md +0 -0
- package/STCGOAL.md +0 -0
- package/STCISSUE.md +0 -0
- package/STCKIN.md +0 -0
- package/STCMASTERY.md +0 -0
- package/STUDY_REPORT.md +0 -510
- package/components.json +0 -21
- package/direct-test.sh +0 -180
- package/docker-compose.test.yml +0 -69
- package/test-data/test-master.jsonl +0 -11
- package/test-run.log +0 -101
- package/test-scripts/README.md +0 -325
- package/test-scripts/run-all-tests.sh +0 -38
- package/test-scripts/test-01-basic-operations.sh +0 -87
- package/test-scripts/test-02-telescope-creation.sh +0 -91
- package/test-scripts/test-03-navigation.sh +0 -87
- package/test-scripts/test-global-cli.spec.ts +0 -220
- package/test-scripts/verify-global-cli.sh +0 -87
- package/tsconfig.json +0 -41
package/README.md
CHANGED
|
@@ -109,3 +109,8 @@ Continue building your app on:
|
|
|
109
109
|
2. Deploy your chats from the v0 interface.
|
|
110
110
|
3. Changes are automatically pushed to this repository.
|
|
111
111
|
4. Vercel deploys the latest version from this repository.
|
|
112
|
+
|
|
113
|
+
## Alternative
|
|
114
|
+
* You can run it locally
|
|
115
|
+
|
|
116
|
+
|
|
@@ -16,6 +16,7 @@ import { EditActionStep } from "./edit-action-step"
|
|
|
16
16
|
import { AddActionStep } from "./add-action-step"
|
|
17
17
|
import { EditChartDueDate } from "./edit-chart-due-date"
|
|
18
18
|
import { ChartEditor } from "@/lib/chart-editor"
|
|
19
|
+
import { ProjectSourceBadge } from "./github-provenance"
|
|
19
20
|
|
|
20
21
|
interface ChartDetailEditableProps {
|
|
21
22
|
chart: Chart
|
|
@@ -88,6 +89,7 @@ export function ChartDetailEditable({
|
|
|
88
89
|
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
|
89
90
|
<span>Created {createdDate}</span>
|
|
90
91
|
</div>
|
|
92
|
+
<ProjectSourceBadge entity={chart.chartEntity} className="mt-3" />
|
|
91
93
|
</div>
|
|
92
94
|
<div>
|
|
93
95
|
<EditChartDueDate
|
|
@@ -8,6 +8,7 @@ import type { ParsedData, Chart, EntityRecord } from "@/lib/types"
|
|
|
8
8
|
import { Target, MapPin, ListChecks, Network, BookOpen, Calendar, TrendingUp, CheckCircle2, Circle } from "lucide-react"
|
|
9
9
|
import { NarrativeBeats } from "./narrative-beats"
|
|
10
10
|
import { RelationGraph } from "./relation-graph"
|
|
11
|
+
import { ActionStepIssueBadge, ProjectSourceBadge } from "./github-provenance"
|
|
11
12
|
|
|
12
13
|
interface ChartDetailProps {
|
|
13
14
|
chart: Chart
|
|
@@ -41,6 +42,7 @@ export function ChartDetail({ chart, data }: ChartDetailProps) {
|
|
|
41
42
|
</div>
|
|
42
43
|
)}
|
|
43
44
|
</div>
|
|
45
|
+
<ProjectSourceBadge entity={chart.chartEntity} className="mt-3" />
|
|
44
46
|
</div>
|
|
45
47
|
</div>
|
|
46
48
|
</CardHeader>
|
|
@@ -194,7 +196,10 @@ function ActionItem({ action, index }: ActionItemProps) {
|
|
|
194
196
|
</div>
|
|
195
197
|
<div className="flex-1 min-w-0">
|
|
196
198
|
<div className="flex flex-col gap-1 sm:flex-row sm:items-start sm:justify-between sm:gap-2 mb-1">
|
|
197
|
-
<
|
|
199
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
200
|
+
<span className="text-xs md:text-sm font-medium">Action {index}</span>
|
|
201
|
+
<ActionStepIssueBadge action={action} />
|
|
202
|
+
</div>
|
|
198
203
|
{dueDate && (
|
|
199
204
|
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
200
205
|
<Calendar className="w-3 h-3" />
|
|
@@ -8,6 +8,7 @@ import { Calendar } from "@/components/ui/calendar"
|
|
|
8
8
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
|
9
9
|
import { format } from "date-fns"
|
|
10
10
|
import type { EntityRecord } from "@/lib/types"
|
|
11
|
+
import { ActionStepIssueBadge } from "@/components/github-provenance"
|
|
11
12
|
|
|
12
13
|
interface EditActionStepProps {
|
|
13
14
|
action: EntityRecord
|
|
@@ -113,6 +114,7 @@ export function EditActionStep({
|
|
|
113
114
|
<div className="flex items-start justify-between gap-2">
|
|
114
115
|
<div className="flex items-center gap-2 flex-wrap">
|
|
115
116
|
<span className="text-sm font-medium">Action {index}</span>
|
|
117
|
+
<ActionStepIssueBadge action={action} />
|
|
116
118
|
{isTelescopedChart && (
|
|
117
119
|
<span className="text-xs bg-primary text-primary-foreground px-2.5 py-1 rounded-md font-semibold shadow-sm">
|
|
118
120
|
📊 Chart
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
import type { LucideIcon } from "lucide-react"
|
|
5
|
+
import { AlertTriangle, CheckCircle2, Cloud, ExternalLink, FolderOpen, Github, GitBranch, XCircle } from "lucide-react"
|
|
6
|
+
|
|
7
|
+
import { Badge } from "@/components/ui/badge"
|
|
8
|
+
import type { EntityRecord } from "@/lib/types"
|
|
9
|
+
import {
|
|
10
|
+
formatGithubIssueRef,
|
|
11
|
+
formatProjectItemRef,
|
|
12
|
+
getGithubEntityProvenance,
|
|
13
|
+
getGithubIssueRef,
|
|
14
|
+
getGithubSyncState,
|
|
15
|
+
getProjectFieldEntries,
|
|
16
|
+
type GithubSyncState,
|
|
17
|
+
} from "@/lib/github-provenance"
|
|
18
|
+
import { cn } from "@/lib/utils"
|
|
19
|
+
|
|
20
|
+
interface SyncStatePillProps {
|
|
21
|
+
syncState?: GithubSyncState
|
|
22
|
+
className?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const syncStateConfig: Record<GithubSyncState, { label: string; className: string; Icon: LucideIcon }> = {
|
|
26
|
+
synced: {
|
|
27
|
+
label: "synced",
|
|
28
|
+
className: "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
29
|
+
Icon: CheckCircle2,
|
|
30
|
+
},
|
|
31
|
+
diverged: {
|
|
32
|
+
label: "diverged",
|
|
33
|
+
className: "border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300",
|
|
34
|
+
Icon: AlertTriangle,
|
|
35
|
+
},
|
|
36
|
+
conflict: {
|
|
37
|
+
label: "conflict",
|
|
38
|
+
className: "border-destructive/40 bg-destructive/10 text-destructive",
|
|
39
|
+
Icon: XCircle,
|
|
40
|
+
},
|
|
41
|
+
"project-only": {
|
|
42
|
+
label: "project-only",
|
|
43
|
+
className: "border-slate-500/40 bg-slate-500/10 text-slate-700 dark:text-slate-300",
|
|
44
|
+
Icon: Cloud,
|
|
45
|
+
},
|
|
46
|
+
"chart-only": {
|
|
47
|
+
label: "chart-only",
|
|
48
|
+
className: "border-slate-500/40 bg-slate-500/10 text-slate-700 dark:text-slate-300",
|
|
49
|
+
Icon: FolderOpen,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function SyncStatePill({ syncState, className }: SyncStatePillProps) {
|
|
54
|
+
if (!syncState) return null
|
|
55
|
+
|
|
56
|
+
const { label, className: stateClassName, Icon } = syncStateConfig[syncState]
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Badge variant="outline" className={cn("gap-1 text-xs", stateClassName, className)}>
|
|
60
|
+
<Icon className="w-3 h-3" />
|
|
61
|
+
{label}
|
|
62
|
+
</Badge>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ProjectSourceBadgeProps {
|
|
67
|
+
entity: EntityRecord
|
|
68
|
+
className?: string
|
|
69
|
+
compact?: boolean
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function ProjectSourceBadge({ entity, className, compact = false }: ProjectSourceBadgeProps) {
|
|
73
|
+
const [selectedLensIndex, setSelectedLensIndex] = useState(0)
|
|
74
|
+
const provenance = getGithubEntityProvenance(entity)
|
|
75
|
+
|
|
76
|
+
if (!provenance.hasGithubMetadata) return null
|
|
77
|
+
|
|
78
|
+
const projectItems = provenance.projectItems
|
|
79
|
+
const activeLensIndex = projectItems.length > 0 ? Math.min(selectedLensIndex, projectItems.length - 1) : 0
|
|
80
|
+
const activeProjectItem = projectItems[activeLensIndex]
|
|
81
|
+
const projectRef = formatProjectItemRef(activeProjectItem)
|
|
82
|
+
const issueRef = formatGithubIssueRef(provenance.issue)
|
|
83
|
+
const projectTitle = activeProjectItem?.projectTitle
|
|
84
|
+
const fieldEntries = getProjectFieldEntries(entity, activeProjectItem)
|
|
85
|
+
const sourceLabel = getSourceLabel(provenance.source?.system, provenance.source?.toolName, provenance.lastSyncedAt)
|
|
86
|
+
const projectTooltip = [
|
|
87
|
+
projectTitle,
|
|
88
|
+
activeProjectItem?.itemId && `item ${activeProjectItem.itemId}`,
|
|
89
|
+
activeProjectItem?.projectId && `project ${activeProjectItem.projectId}`,
|
|
90
|
+
activeProjectItem?.url,
|
|
91
|
+
]
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
.join(" · ")
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div
|
|
97
|
+
className={cn(
|
|
98
|
+
"rounded-md border border-indigo-500/20 bg-indigo-500/5 p-3 text-sm",
|
|
99
|
+
compact && "p-2",
|
|
100
|
+
className,
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
104
|
+
{activeProjectItem?.url ? (
|
|
105
|
+
<a href={activeProjectItem.url} target="_blank" rel="noreferrer" title={projectTooltip}>
|
|
106
|
+
<ProjectBadge label={projectRef ?? "Project bridge"} />
|
|
107
|
+
</a>
|
|
108
|
+
) : (
|
|
109
|
+
<ProjectBadge label={projectRef ?? "Project bridge"} title={projectTooltip} />
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{provenance.issue &&
|
|
113
|
+
(provenance.issue.url ? (
|
|
114
|
+
<a href={provenance.issue.url} target="_blank" rel="noreferrer" title={issueRef}>
|
|
115
|
+
<Badge variant="outline" className="gap-1 text-xs">
|
|
116
|
+
<Github className="w-3 h-3" />#{provenance.issue.number}
|
|
117
|
+
<ExternalLink className="w-3 h-3" />
|
|
118
|
+
</Badge>
|
|
119
|
+
</a>
|
|
120
|
+
) : (
|
|
121
|
+
<Badge variant="outline" className="gap-1 text-xs" title={issueRef}>
|
|
122
|
+
<Github className="w-3 h-3" />#{provenance.issue.number}
|
|
123
|
+
</Badge>
|
|
124
|
+
))}
|
|
125
|
+
|
|
126
|
+
<SyncStatePill syncState={provenance.syncState} />
|
|
127
|
+
|
|
128
|
+
{projectItems.length > 1 && (
|
|
129
|
+
<select
|
|
130
|
+
aria-label="Active GitHub project lens"
|
|
131
|
+
value={activeLensIndex}
|
|
132
|
+
onChange={(event) => setSelectedLensIndex(Number(event.target.value))}
|
|
133
|
+
className="h-7 rounded-md border border-border bg-background px-2 text-xs text-foreground"
|
|
134
|
+
>
|
|
135
|
+
{projectItems.map((item, index) => (
|
|
136
|
+
<option key={`${formatProjectItemRef(item) ?? item.itemId ?? "project"}-${index}`} value={index}>
|
|
137
|
+
{item.projectTitle ?? formatProjectItemRef(item) ?? `Project ${index + 1}`}
|
|
138
|
+
</option>
|
|
139
|
+
))}
|
|
140
|
+
</select>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
{!compact && (projectTitle || sourceLabel || fieldEntries.length > 0) && (
|
|
145
|
+
<div className="mt-2 space-y-2 text-xs text-muted-foreground">
|
|
146
|
+
{(projectTitle || sourceLabel) && (
|
|
147
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
148
|
+
{projectTitle && <span className="font-medium text-foreground">{projectTitle}</span>}
|
|
149
|
+
{sourceLabel && <span>{sourceLabel}</span>}
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{fieldEntries.length > 0 && (
|
|
154
|
+
<div className="flex flex-wrap gap-1.5">
|
|
155
|
+
{fieldEntries.map((field) => (
|
|
156
|
+
<span
|
|
157
|
+
key={field.key}
|
|
158
|
+
className="inline-flex max-w-full items-center gap-1 rounded-md border border-border bg-background px-2 py-1"
|
|
159
|
+
title={`${field.label}: ${field.value}`}
|
|
160
|
+
>
|
|
161
|
+
<span className="font-medium text-foreground">{field.label}</span>
|
|
162
|
+
<span className="max-w-[18rem] truncate">{field.value}</span>
|
|
163
|
+
</span>
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function ProjectBadge({ label, title }: { label: string; title?: string }) {
|
|
174
|
+
return (
|
|
175
|
+
<Badge className="gap-1 bg-indigo-600 text-white hover:bg-indigo-600/90" title={title}>
|
|
176
|
+
<GitBranch className="w-3 h-3" />
|
|
177
|
+
Project: {label}
|
|
178
|
+
</Badge>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface ActionStepIssueBadgeProps {
|
|
183
|
+
action: EntityRecord
|
|
184
|
+
className?: string
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function ActionStepIssueBadge({ action, className }: ActionStepIssueBadgeProps) {
|
|
188
|
+
const issue = getGithubIssueRef(action)
|
|
189
|
+
const syncState = getGithubSyncState(action)
|
|
190
|
+
const issueRef = formatGithubIssueRef(issue)
|
|
191
|
+
|
|
192
|
+
if (!issue && !syncState) return null
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<span className={cn("inline-flex flex-wrap items-center gap-1", className)}>
|
|
196
|
+
{issue &&
|
|
197
|
+
(issue.url ? (
|
|
198
|
+
<a href={issue.url} target="_blank" rel="noreferrer" title={issueRef}>
|
|
199
|
+
<Badge variant="outline" className="gap-1 text-xs">
|
|
200
|
+
<Github className="w-3 h-3" />#{issue.number}
|
|
201
|
+
<ExternalLink className="w-3 h-3" />
|
|
202
|
+
</Badge>
|
|
203
|
+
</a>
|
|
204
|
+
) : (
|
|
205
|
+
<Badge variant="outline" className="gap-1 text-xs" title={issueRef}>
|
|
206
|
+
<Github className="w-3 h-3" />#{issue.number}
|
|
207
|
+
</Badge>
|
|
208
|
+
))}
|
|
209
|
+
<SyncStatePill syncState={syncState} />
|
|
210
|
+
</span>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function getSourceLabel(system?: string, toolName?: string, lastSyncedAt?: string): string | undefined {
|
|
215
|
+
const parts = [system, toolName, formatDateTime(lastSyncedAt)].filter(Boolean)
|
|
216
|
+
return parts.length > 0 ? parts.join(" · ") : undefined
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function formatDateTime(value?: string): string | undefined {
|
|
220
|
+
if (!value) return undefined
|
|
221
|
+
|
|
222
|
+
const date = new Date(value)
|
|
223
|
+
if (Number.isNaN(date.getTime())) return value
|
|
224
|
+
|
|
225
|
+
return date.toLocaleString()
|
|
226
|
+
}
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
4
4
|
import { Badge } from "@/components/ui/badge"
|
|
5
|
-
import type { ParsedData, Chart, RelationRecord } from "@/lib/types"
|
|
6
|
-
import { ArrowRight } from "lucide-react"
|
|
5
|
+
import type { ParsedData, Chart, EntityRecord, RelationRecord } from "@/lib/types"
|
|
6
|
+
import { ArrowRight, Github } from "lucide-react"
|
|
7
|
+
import { getGithubBridgeRelationType, getGithubSyncState } from "@/lib/github-provenance"
|
|
8
|
+
import { SyncStatePill } from "./github-provenance"
|
|
7
9
|
|
|
8
10
|
interface RelationGraphProps {
|
|
9
11
|
chart: Chart
|
|
@@ -23,7 +25,10 @@ export function RelationGraph({ chart, data }: RelationGraphProps) {
|
|
|
23
25
|
)
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
const
|
|
28
|
+
const githubBridgeRelations = relations.filter((relation) => getGithubBridgeRelationType(relation))
|
|
29
|
+
const standardRelations = relations.filter((relation) => !getGithubBridgeRelationType(relation))
|
|
30
|
+
|
|
31
|
+
const relationsByType = standardRelations.reduce(
|
|
27
32
|
(acc, rel) => {
|
|
28
33
|
if (!acc[rel.relationType]) {
|
|
29
34
|
acc[rel.relationType] = []
|
|
@@ -41,6 +46,28 @@ export function RelationGraph({ chart, data }: RelationGraphProps) {
|
|
|
41
46
|
<CardDescription>Connections between chart entities</CardDescription>
|
|
42
47
|
</CardHeader>
|
|
43
48
|
<CardContent className="space-y-6">
|
|
49
|
+
{githubBridgeRelations.length > 0 && (
|
|
50
|
+
<div>
|
|
51
|
+
<h3 className="text-sm font-semibold mb-3 flex items-center gap-2">
|
|
52
|
+
<Badge className="gap-1 bg-indigo-600 text-white">
|
|
53
|
+
<Github className="w-3 h-3" />
|
|
54
|
+
GitHub bridge
|
|
55
|
+
</Badge>
|
|
56
|
+
<span className="text-muted-foreground">({githubBridgeRelations.length})</span>
|
|
57
|
+
</h3>
|
|
58
|
+
<div className="space-y-2">
|
|
59
|
+
{githubBridgeRelations.map((rel, idx) => (
|
|
60
|
+
<RelationRow
|
|
61
|
+
key={`github-${idx}`}
|
|
62
|
+
relation={rel}
|
|
63
|
+
data={data}
|
|
64
|
+
bridgeLabel={getGithubBridgeRelationType(rel)}
|
|
65
|
+
/>
|
|
66
|
+
))}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
|
|
44
71
|
{Object.entries(relationsByType).map(([type, rels]) => (
|
|
45
72
|
<div key={type}>
|
|
46
73
|
<h3 className="text-sm font-semibold mb-3 flex items-center gap-2">
|
|
@@ -48,30 +75,9 @@ export function RelationGraph({ chart, data }: RelationGraphProps) {
|
|
|
48
75
|
<span className="text-muted-foreground">({rels.length})</span>
|
|
49
76
|
</h3>
|
|
50
77
|
<div className="space-y-2">
|
|
51
|
-
{rels.map((rel, idx) =>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<div key={idx} className="flex items-center gap-3 p-3 bg-muted/30 rounded-lg text-sm">
|
|
57
|
-
<div className="flex-1 min-w-0">
|
|
58
|
-
<div className="font-mono text-xs text-muted-foreground mb-1">{rel.from}</div>
|
|
59
|
-
{fromEntity && (
|
|
60
|
-
<div className="text-xs line-clamp-1">
|
|
61
|
-
{fromEntity.observations[0] || fromEntity.entityType}
|
|
62
|
-
</div>
|
|
63
|
-
)}
|
|
64
|
-
</div>
|
|
65
|
-
<ArrowRight className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
|
66
|
-
<div className="flex-1 min-w-0">
|
|
67
|
-
<div className="font-mono text-xs text-muted-foreground mb-1">{rel.to}</div>
|
|
68
|
-
{toEntity && (
|
|
69
|
-
<div className="text-xs line-clamp-1">{toEntity.observations[0] || toEntity.entityType}</div>
|
|
70
|
-
)}
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
)
|
|
74
|
-
})}
|
|
78
|
+
{rels.map((rel, idx) => (
|
|
79
|
+
<RelationRow key={idx} relation={rel} data={data} />
|
|
80
|
+
))}
|
|
75
81
|
</div>
|
|
76
82
|
</div>
|
|
77
83
|
))}
|
|
@@ -79,3 +85,61 @@ export function RelationGraph({ chart, data }: RelationGraphProps) {
|
|
|
79
85
|
</Card>
|
|
80
86
|
)
|
|
81
87
|
}
|
|
88
|
+
|
|
89
|
+
interface RelationRowProps {
|
|
90
|
+
relation: RelationRecord
|
|
91
|
+
data: ParsedData
|
|
92
|
+
bridgeLabel?: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function RelationRow({ relation, data, bridgeLabel }: RelationRowProps) {
|
|
96
|
+
const fromEntity = data.entities.get(relation.from)
|
|
97
|
+
const toEntity = data.entities.get(relation.to)
|
|
98
|
+
const syncState = getGithubSyncState(relation.metadata)
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div className="flex flex-col gap-2 p-3 bg-muted/30 rounded-lg text-sm sm:flex-row sm:items-center sm:gap-3">
|
|
102
|
+
<RelationEndpoint name={relation.from} entity={fromEntity} />
|
|
103
|
+
|
|
104
|
+
<div className="flex items-center gap-2 text-muted-foreground sm:flex-col">
|
|
105
|
+
<ArrowRight className="w-4 h-4 flex-shrink-0 rotate-90 sm:rotate-0" />
|
|
106
|
+
{(bridgeLabel || syncState) && (
|
|
107
|
+
<div className="flex flex-wrap justify-center gap-1">
|
|
108
|
+
{bridgeLabel && (
|
|
109
|
+
<Badge variant="outline" className="text-xs">
|
|
110
|
+
{bridgeLabel.replace(/_/g, " ")}
|
|
111
|
+
</Badge>
|
|
112
|
+
)}
|
|
113
|
+
<SyncStatePill syncState={syncState} />
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<RelationEndpoint name={relation.to} entity={toEntity} />
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function RelationEndpoint({
|
|
124
|
+
name,
|
|
125
|
+
entity,
|
|
126
|
+
}: {
|
|
127
|
+
name: string
|
|
128
|
+
entity?: EntityRecord
|
|
129
|
+
}) {
|
|
130
|
+
const isGithubVirtualNode = name.startsWith("gh:")
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className="flex-1 min-w-0">
|
|
134
|
+
<div className="font-mono text-xs text-muted-foreground mb-1 break-all">{name}</div>
|
|
135
|
+
{entity ? (
|
|
136
|
+
<div className="text-xs line-clamp-1">{entity.observations[0] || entity.entityType}</div>
|
|
137
|
+
) : isGithubVirtualNode ? (
|
|
138
|
+
<Badge variant="outline" className="gap-1 text-xs">
|
|
139
|
+
<Github className="w-3 h-3" />
|
|
140
|
+
GitHub issue
|
|
141
|
+
</Badge>
|
|
142
|
+
) : null}
|
|
143
|
+
</div>
|
|
144
|
+
)
|
|
145
|
+
}
|
package/index.tsx
CHANGED
|
@@ -13,6 +13,31 @@ export type {
|
|
|
13
13
|
// JSONL Parser
|
|
14
14
|
export { parseJSONL, organizeData, getChartSummary, getChartProgress } from "./lib/jsonl-parser"
|
|
15
15
|
|
|
16
|
+
// GitHub provenance helpers
|
|
17
|
+
export {
|
|
18
|
+
GITHUB_BRIDGE_RELATION_TYPES,
|
|
19
|
+
GITHUB_PROJECT_FIELD_KEYS,
|
|
20
|
+
GITHUB_SYNC_STATES,
|
|
21
|
+
formatGithubIssueRef,
|
|
22
|
+
formatProjectItemRef,
|
|
23
|
+
getGithubBridgeRelationType,
|
|
24
|
+
getGithubEntityProvenance,
|
|
25
|
+
getGithubIssueRef,
|
|
26
|
+
getGithubProjectItems,
|
|
27
|
+
getGithubSource,
|
|
28
|
+
getGithubSyncState,
|
|
29
|
+
getProjectFieldEntries,
|
|
30
|
+
} from "./lib/github-provenance"
|
|
31
|
+
export type {
|
|
32
|
+
GithubBridgeRelationType,
|
|
33
|
+
GithubEntityProvenance,
|
|
34
|
+
GithubIssueRef,
|
|
35
|
+
GithubProjectFieldEntry,
|
|
36
|
+
GithubProjectItemRef,
|
|
37
|
+
GithubSourceProvenance,
|
|
38
|
+
GithubSyncState,
|
|
39
|
+
} from "./lib/github-provenance"
|
|
40
|
+
|
|
16
41
|
// Utilities
|
|
17
42
|
export { cn } from "./lib/utils"
|
|
18
43
|
|
|
@@ -28,6 +53,7 @@ export { ChartDetail } from "./components/chart-detail"
|
|
|
28
53
|
export { DataStats } from "./components/data-stats"
|
|
29
54
|
export { CreateChartForm } from "./components/create-chart-form"
|
|
30
55
|
export { NarrativeBeats } from "./components/narrative-beats"
|
|
56
|
+
export { ActionStepIssueBadge, ProjectSourceBadge, SyncStatePill } from "./components/github-provenance"
|
|
31
57
|
export { LiveIndicator } from "./components/live-indicator"
|
|
32
58
|
export { ThemeToggle } from "./components/theme-toggle"
|
|
33
59
|
export { EditDesiredOutcome } from "./components/edit-desired-outcome"
|