coaia-visualizer 1.6.3 → 1.6.5
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/Dockerfile +61 -0
- package/Dockerfile.app +50 -0
- package/README.md +2 -1
- package/cli.ts +11 -5
- package/components/asterion-foundation-card.tsx +175 -0
- package/components/asterion-session-context-badge.tsx +122 -0
- package/components/asterion-session-lineage-card.tsx +119 -0
- package/components/chart-detail-editable.tsx +2 -0
- package/components/chart-detail.tsx +9 -1
- package/components/edit-action-step.tsx +2 -0
- package/components/github-provenance.tsx +226 -0
- package/components/metadata-projections.tsx +50 -16
- package/components/narrative-beats.tsx +16 -3
- package/components/relation-graph.tsx +91 -27
- package/dist/cli.js +9 -5
- package/docker-build-push.sh +20 -0
- package/docker-entrypoint.sh +27 -0
- package/index.tsx +39 -2
- package/lib/asterion-metadata.ts +673 -0
- package/lib/chart-editor.ts +8 -3
- package/lib/github-provenance.ts +316 -0
- package/lib/jsonl-parser.ts +7 -2
- package/lib/types.ts +112 -2
- package/mcp/test_mcp/.gemini/settings.json +18 -0
- package/next-env.d.ts +1 -1
- package/package.json +14 -13
- package/rispecs/README.md +170 -0
- package/rispecs/accountability-responsibility.rispec.md +110 -0
- package/rispecs/api-mcp-interface.spec.md +287 -0
- package/rispecs/app.spec.md +364 -0
- package/rispecs/chart-editing-workflow.spec.md +297 -0
- package/rispecs/chart-visualization-hierarchy.spec.md +235 -0
- package/rispecs/cli-mode.spec.md +224 -0
- package/rispecs/github-project-runtime-memory-integration.spec.md +381 -0
- package/rispecs/jsonl-parsing-data-types.spec.md +243 -0
- package/rispecs/narrative-beats-display.spec.md +189 -0
- package/rispecs/pde-integration.spec.md +280 -0
- package/rispecs/planning-integration.spec.md +329 -0
- package/rispecs/relation-graph-visualization.spec.md +171 -0
- package/rispecs/relation-to-mcp-structural-thinking.kin.md +65 -0
- package/rispecs/ui-component-library.spec.md +258 -0
- package/mcp/dist/api-client.d.ts +0 -138
- package/mcp/dist/api-client.d.ts.map +0 -1
- package/mcp/dist/api-client.js +0 -115
- package/mcp/dist/api-client.js.map +0 -1
- package/mcp/dist/index.d.ts +0 -2
- package/mcp/dist/index.d.ts.map +0 -1
- package/mcp/dist/index.js +0 -286
- package/mcp/dist/index.js.map +0 -1
- package/mcp/dist/tools/index.d.ts +0 -18
- package/mcp/dist/tools/index.d.ts.map +0 -1
- package/mcp/dist/tools/index.js +0 -322
- package/mcp/dist/tools/index.js.map +0 -1
- package/mcp/package-lock.json +0 -210
package/Dockerfile
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# COAIA Visualizer — Docker Image
|
|
3
|
+
# jgwill/coaia:visualizer
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# docker run --rm -p 4321:4321 \
|
|
7
|
+
# -v /abs/path/to/file.jsonl:/data/memory.jsonl \
|
|
8
|
+
# jgwill/coaia:visualizer
|
|
9
|
+
# =============================================================================
|
|
10
|
+
|
|
11
|
+
# ---- Build stage ----
|
|
12
|
+
FROM node:22-alpine AS builder
|
|
13
|
+
|
|
14
|
+
WORKDIR /app
|
|
15
|
+
|
|
16
|
+
# Install pnpm
|
|
17
|
+
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
18
|
+
|
|
19
|
+
# Copy lockfile + manifests first (layer cache)
|
|
20
|
+
COPY package.json pnpm-lock.yaml ./
|
|
21
|
+
|
|
22
|
+
# Install all deps — skip prepare hook (cli.ts not copied yet)
|
|
23
|
+
RUN pnpm install --no-frozen-lockfile --ignore-scripts
|
|
24
|
+
|
|
25
|
+
# Copy source
|
|
26
|
+
COPY . .
|
|
27
|
+
|
|
28
|
+
# Build CLI + Next.js
|
|
29
|
+
RUN pnpm run prepare && pnpm build
|
|
30
|
+
|
|
31
|
+
# ---- Runner stage ----
|
|
32
|
+
FROM node:22-alpine AS runner
|
|
33
|
+
|
|
34
|
+
WORKDIR /app
|
|
35
|
+
|
|
36
|
+
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
37
|
+
|
|
38
|
+
# Copy manifests + node_modules from builder, then prune dev deps
|
|
39
|
+
COPY package.json pnpm-lock.yaml ./
|
|
40
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
41
|
+
RUN pnpm prune --prod 2>/dev/null || true
|
|
42
|
+
|
|
43
|
+
# Copy built assets from builder
|
|
44
|
+
COPY --from=builder /app/.next ./.next
|
|
45
|
+
COPY --from=builder /app/public ./public
|
|
46
|
+
COPY --from=builder /app/next.config.mjs ./next.config.mjs
|
|
47
|
+
|
|
48
|
+
# Entrypoint script
|
|
49
|
+
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
|
50
|
+
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
|
51
|
+
|
|
52
|
+
# Volume for user JSONL files
|
|
53
|
+
VOLUME ["/data"]
|
|
54
|
+
|
|
55
|
+
ENV NODE_ENV=production
|
|
56
|
+
ENV PORT=4321
|
|
57
|
+
ENV COAIAV_MEMORY_PATH=/data/memory.jsonl
|
|
58
|
+
|
|
59
|
+
EXPOSE 4321
|
|
60
|
+
|
|
61
|
+
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
package/Dockerfile.app
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Build stage
|
|
2
|
+
FROM node:20-alpine AS builder
|
|
3
|
+
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
# Copy package files
|
|
7
|
+
COPY package.json pnpm-lock.yaml ./
|
|
8
|
+
|
|
9
|
+
# Install pnpm and dependencies
|
|
10
|
+
RUN npm install -g pnpm@latest && \
|
|
11
|
+
pnpm install --frozen-lockfile
|
|
12
|
+
|
|
13
|
+
# Copy application code
|
|
14
|
+
COPY . .
|
|
15
|
+
|
|
16
|
+
# Build Next.js application
|
|
17
|
+
RUN pnpm build
|
|
18
|
+
|
|
19
|
+
# Production stage
|
|
20
|
+
FROM node:20-alpine AS runner
|
|
21
|
+
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
|
|
24
|
+
# Install pnpm
|
|
25
|
+
RUN npm install -g pnpm@latest
|
|
26
|
+
|
|
27
|
+
# Copy package files
|
|
28
|
+
COPY package.json pnpm-lock.yaml ./
|
|
29
|
+
|
|
30
|
+
# Install production dependencies only, skip prepare hook
|
|
31
|
+
RUN pnpm install --prod --frozen-lockfile --ignore-scripts
|
|
32
|
+
|
|
33
|
+
# Copy built application from builder
|
|
34
|
+
COPY --from=builder /app/.next ./.next
|
|
35
|
+
COPY --from=builder /app/public ./public
|
|
36
|
+
COPY --from=builder /app/next.config.mjs ./
|
|
37
|
+
COPY --from=builder /app/app ./app
|
|
38
|
+
COPY --from=builder /app/components ./components
|
|
39
|
+
COPY --from=builder /app/lib ./lib
|
|
40
|
+
COPY --from=builder /app/dist ./dist
|
|
41
|
+
|
|
42
|
+
# Create data directories
|
|
43
|
+
RUN mkdir -p /app/samples /app/test-data
|
|
44
|
+
|
|
45
|
+
ENV NODE_ENV=production
|
|
46
|
+
ENV PORT=4321
|
|
47
|
+
|
|
48
|
+
EXPOSE 4321
|
|
49
|
+
|
|
50
|
+
CMD ["pnpm", "start"]
|
package/README.md
CHANGED
|
@@ -17,7 +17,8 @@ This tool allows users to upload their `coaia-narrative` memory files and gain c
|
|
|
17
17
|
* **Dynamic Structural Tension Display:** Clearly distinguishes and presents the core Desired Outcome and Current Reality observations, making the generative force of structural tension visible.
|
|
18
18
|
* **Action Step Tracking:** View all Action Steps for a selected chart, complete with their descriptions, completion status, and due dates, facilitating a clear understanding of strategic advancement.
|
|
19
19
|
* **Narrative Beat Integration:** Displays associated Narrative Beats, offering multi-universe perspectives (Engineer-World, Ceremony-World, Story-Engine-World) to enrich the context of your creative efforts.
|
|
20
|
-
* **Rich JSONL Metadata Projection:** Separates chart, action-step, and narrative-beat records; exposes nested `metadata.github`, RISE/spec fields, unknown extension metadata, and preservation warnings aligned with [`avadisabelle/coaia-narrative#35`](https://github.com/avadisabelle/coaia-narrative/issues/35).
|
|
20
|
+
* **Rich JSONL Metadata Projection:** Separates chart, action-step, and narrative-beat records; exposes nested `metadata.github`, RISE/spec fields, Asterion foundation/session-lineage projections, unknown extension metadata, and preservation warnings aligned with [`avadisabelle/coaia-narrative#35`](https://github.com/avadisabelle/coaia-narrative/issues/35).
|
|
21
|
+
* **Asterion Foundation & Lineage Cards:** Visualizes foundation packet metadata, artifact diffs, issue references, session branch maps, and beat-linked lineage without exposing raw Chronicle content by default.
|
|
21
22
|
* **Inter-Chart Relation Graph:** Visualizes the relationships between charts and entities, illustrating how different creative projects and their components are interconnected.
|
|
22
23
|
* **Data Statistics Overview:** Provides summary statistics of the loaded data, including total charts, entities, and relations, for quick comprehension of your creative landscape.
|
|
23
24
|
|
package/cli.ts
CHANGED
|
@@ -88,7 +88,7 @@ function loadConfig(args: minimist.ParsedArgs): Config {
|
|
|
88
88
|
if (args['port'] || args['p']) {
|
|
89
89
|
config.port = args['port'] || args['p'];
|
|
90
90
|
}
|
|
91
|
-
if (args['no-open']) {
|
|
91
|
+
if (args['no-open'] || args.open === false) {
|
|
92
92
|
config.noOpen = true;
|
|
93
93
|
}
|
|
94
94
|
if (args['live']) {
|
|
@@ -242,11 +242,14 @@ EXAMPLES:
|
|
|
242
242
|
}, 4000);
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
|
|
245
|
+
const shutdownDocker = () => {
|
|
246
246
|
console.log('\n👋 Shutting down visualizer...');
|
|
247
247
|
dockerProcess.kill();
|
|
248
248
|
process.exit(0);
|
|
249
|
-
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
process.on('SIGINT', shutdownDocker);
|
|
252
|
+
process.on('SIGTERM', shutdownDocker);
|
|
250
253
|
|
|
251
254
|
dockerProcess.on('exit', (code) => { process.exit(code || 0); });
|
|
252
255
|
return;
|
|
@@ -295,11 +298,14 @@ EXAMPLES:
|
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
// Handle shutdown
|
|
298
|
-
|
|
301
|
+
const shutdownLocal = () => {
|
|
299
302
|
console.log('\n👋 Shutting down visualizer...');
|
|
300
303
|
nextProcess.kill();
|
|
301
304
|
process.exit(0);
|
|
302
|
-
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
process.on('SIGINT', shutdownLocal);
|
|
308
|
+
process.on('SIGTERM', shutdownLocal);
|
|
303
309
|
|
|
304
310
|
nextProcess.on('exit', (code) => {
|
|
305
311
|
process.exit(code || 0);
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { AlertTriangle, CheckCircle2, ExternalLink, FileCheck2, FileX2, Github, Shield, UploadCloud } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { Badge } from "@/components/ui/badge"
|
|
6
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
7
|
+
import type { FoundationProjection, ParsedIssueReference } from "@/lib/types"
|
|
8
|
+
|
|
9
|
+
interface AsterionFoundationCardProps {
|
|
10
|
+
projection: FoundationProjection
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const evaluationTone: Record<NonNullable<FoundationProjection["foundation"]["evaluationStatus"]>, string> = {
|
|
14
|
+
expected: "border-slate-500/40 bg-slate-500/10 text-slate-700 dark:text-slate-300",
|
|
15
|
+
delegated: "border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300",
|
|
16
|
+
produced: "border-sky-500/40 bg-sky-500/10 text-sky-700 dark:text-sky-300",
|
|
17
|
+
evaluated: "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const privacyTone: Record<NonNullable<FoundationProjection["foundation"]["privacyClass"]>, string> = {
|
|
21
|
+
"public-safe": "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
22
|
+
private: "border-destructive/40 bg-destructive/10 text-destructive",
|
|
23
|
+
mixed: "border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const publicationTone: Record<NonNullable<FoundationProjection["foundation"]["publicationStatus"]>, string> = {
|
|
27
|
+
planned: "border-slate-500/40 bg-slate-500/10 text-slate-700 dark:text-slate-300",
|
|
28
|
+
draft: "border-sky-500/40 bg-sky-500/10 text-sky-700 dark:text-sky-300",
|
|
29
|
+
reviewed: "border-violet-500/40 bg-violet-500/10 text-violet-700 dark:text-violet-300",
|
|
30
|
+
published: "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function AsterionFoundationCard({ projection }: AsterionFoundationCardProps) {
|
|
34
|
+
const foundation = projection.foundation
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Card>
|
|
38
|
+
<CardHeader>
|
|
39
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
40
|
+
<Shield className="h-5 w-5" />
|
|
41
|
+
Asterion Foundation
|
|
42
|
+
</CardTitle>
|
|
43
|
+
</CardHeader>
|
|
44
|
+
<CardContent className="space-y-4">
|
|
45
|
+
{projection.warnings.length > 0 && (
|
|
46
|
+
<div className="space-y-2 rounded-md border border-amber-500/30 bg-amber-500/5 p-3">
|
|
47
|
+
{projection.warnings.map((warning, index) => (
|
|
48
|
+
<div key={`${warning.recordName || "foundation"}-${index}`} className="flex items-start gap-2 text-sm">
|
|
49
|
+
<AlertTriangle className="mt-0.5 h-4 w-4 text-amber-600" />
|
|
50
|
+
<span>{warning.message}</span>
|
|
51
|
+
</div>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
|
|
56
|
+
<div className="grid gap-3 md:grid-cols-2">
|
|
57
|
+
<MetaBlock label="Packet Root" value={foundation.packetRoot} />
|
|
58
|
+
<MetaBlock label="Foundation Type" value={foundation.foundationType} />
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div className="flex flex-wrap gap-2">
|
|
62
|
+
{foundation.evaluationStatus && (
|
|
63
|
+
<Badge variant="outline" className={evaluationTone[foundation.evaluationStatus]}>
|
|
64
|
+
evaluation: {foundation.evaluationStatus}
|
|
65
|
+
</Badge>
|
|
66
|
+
)}
|
|
67
|
+
{foundation.privacyClass && (
|
|
68
|
+
<Badge variant="outline" className={privacyTone[foundation.privacyClass]}>
|
|
69
|
+
privacy: {foundation.privacyClass}
|
|
70
|
+
</Badge>
|
|
71
|
+
)}
|
|
72
|
+
{foundation.publicationStatus && (
|
|
73
|
+
<Badge variant="outline" className={publicationTone[foundation.publicationStatus]}>
|
|
74
|
+
publication: {foundation.publicationStatus}
|
|
75
|
+
</Badge>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{projection.issueRefs.length > 0 && (
|
|
80
|
+
<div>
|
|
81
|
+
<div className="mb-2 text-sm font-medium">Issue References</div>
|
|
82
|
+
<div className="flex flex-wrap gap-2">
|
|
83
|
+
{projection.issueRefs.map((issue) => (
|
|
84
|
+
<IssueBadge key={issue.key} issue={issue} />
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
{projection.artifactDiff.length > 0 && (
|
|
91
|
+
<div>
|
|
92
|
+
<div className="mb-2 text-sm font-medium">Artifact Checklist</div>
|
|
93
|
+
<div className="space-y-2">
|
|
94
|
+
{projection.artifactDiff.map((artifact) => (
|
|
95
|
+
<div key={artifact.artifact} className="flex items-center justify-between rounded-md border p-3 text-sm">
|
|
96
|
+
<div className="font-mono text-xs md:text-sm">{artifact.artifact}</div>
|
|
97
|
+
<div className="flex items-center gap-2">
|
|
98
|
+
{artifact.status === "matched" ? (
|
|
99
|
+
<Badge variant="outline" className="gap-1 border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300">
|
|
100
|
+
<CheckCircle2 className="h-3 w-3" />
|
|
101
|
+
matched
|
|
102
|
+
</Badge>
|
|
103
|
+
) : artifact.status === "missing" ? (
|
|
104
|
+
<Badge variant="outline" className="gap-1 border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300">
|
|
105
|
+
<FileX2 className="h-3 w-3" />
|
|
106
|
+
expected only
|
|
107
|
+
</Badge>
|
|
108
|
+
) : (
|
|
109
|
+
<Badge variant="outline" className="gap-1 border-sky-500/40 bg-sky-500/10 text-sky-700 dark:text-sky-300">
|
|
110
|
+
<UploadCloud className="h-3 w-3" />
|
|
111
|
+
produced only
|
|
112
|
+
</Badge>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{foundation.commitHandles && foundation.commitHandles.length > 0 && (
|
|
122
|
+
<div>
|
|
123
|
+
<div className="mb-2 text-sm font-medium">Commit Handles</div>
|
|
124
|
+
<div className="flex flex-wrap gap-2">
|
|
125
|
+
{foundation.commitHandles.map((handle) => (
|
|
126
|
+
<Badge key={handle} variant="secondary" className="font-mono text-xs">
|
|
127
|
+
{handle}
|
|
128
|
+
</Badge>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
{projection.sourceRecords.length > 0 && (
|
|
135
|
+
<div className="flex flex-wrap gap-2 text-xs text-muted-foreground">
|
|
136
|
+
<Badge variant="outline" className="gap-1">
|
|
137
|
+
<FileCheck2 className="h-3 w-3" />
|
|
138
|
+
{projection.sourceRecords.length} source record{projection.sourceRecords.length === 1 ? "" : "s"}
|
|
139
|
+
</Badge>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
</CardContent>
|
|
143
|
+
</Card>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function MetaBlock({ label, value }: { label: string; value?: string }) {
|
|
148
|
+
if (!value) return null
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div className="rounded-md border p-3">
|
|
152
|
+
<div className="text-xs uppercase tracking-wide text-muted-foreground">{label}</div>
|
|
153
|
+
<div className="mt-1 text-sm font-medium">{value}</div>
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function IssueBadge({ issue }: { issue: ParsedIssueReference }) {
|
|
159
|
+
const content = (
|
|
160
|
+
<Badge variant="outline" className="gap-1 text-xs">
|
|
161
|
+
<Github className="h-3 w-3" />
|
|
162
|
+
{issue.key}
|
|
163
|
+
<span className="font-normal text-muted-foreground">{issue.label}</span>
|
|
164
|
+
{issue.href && <ExternalLink className="h-3 w-3" />}
|
|
165
|
+
</Badge>
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if (!issue.href) return content
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<a href={issue.href} target="_blank" rel="noreferrer">
|
|
172
|
+
{content}
|
|
173
|
+
</a>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { AlertTriangle, Clock, FileCheck2, Link2, MapPin, Mic, Monitor, ScrollText, Shield } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { Badge } from "@/components/ui/badge"
|
|
6
|
+
import type { SessionContextProjection } from "@/lib/types"
|
|
7
|
+
|
|
8
|
+
interface AsterionSessionContextBadgeProps {
|
|
9
|
+
projection: SessionContextProjection
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const captureQualityTone: Record<NonNullable<SessionContextProjection["context"]["captureQuality"]>, string> = {
|
|
13
|
+
complete: "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
14
|
+
partial: "border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300",
|
|
15
|
+
noisy: "border-orange-500/40 bg-orange-500/10 text-orange-700 dark:text-orange-300",
|
|
16
|
+
"transcript-only": "border-sky-500/40 bg-sky-500/10 text-sky-700 dark:text-sky-300",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const continuationKindTone: Record<NonNullable<SessionContextProjection["context"]["continuationKind"]>, string> = {
|
|
20
|
+
new: "border-slate-500/40 bg-slate-500/10 text-slate-700 dark:text-slate-300",
|
|
21
|
+
resumption: "border-sky-500/40 bg-sky-500/10 text-sky-700 dark:text-sky-300",
|
|
22
|
+
"branch-return": "border-violet-500/40 bg-violet-500/10 text-violet-700 dark:text-violet-300",
|
|
23
|
+
"daily-chronicle": "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const modeIcon: Record<string, React.ElementType> = {
|
|
27
|
+
voice: Mic,
|
|
28
|
+
terminal: Monitor,
|
|
29
|
+
mixed: ScrollText,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const settingIcon: Record<string, React.ElementType> = {
|
|
33
|
+
outdoor: MapPin,
|
|
34
|
+
walking: MapPin,
|
|
35
|
+
"land-based": MapPin,
|
|
36
|
+
indoor: Monitor,
|
|
37
|
+
office: Monitor,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function AsterionSessionContextBadge({ projection }: AsterionSessionContextBadgeProps) {
|
|
41
|
+
const context = projection.context
|
|
42
|
+
|
|
43
|
+
if (Object.keys(context).length === 0) {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hasWarnings = projection.warnings.length > 0
|
|
48
|
+
const captureWarnings = projection.warnings.filter(
|
|
49
|
+
(w) => w.message.includes("Capture quality") || w.message.includes("partial") || w.message.includes("noisy"),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const ModeIcon = context.mode ? modeIcon[context.mode.toLowerCase()] || Monitor : Monitor
|
|
53
|
+
const SettingIcon = context.setting ? settingIcon[context.setting.toLowerCase()] || MapPin : MapPin
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="flex flex-wrap items-center gap-2 rounded-md border border-sky-500/30 bg-sky-500/5 p-2">
|
|
57
|
+
<Badge variant="outline" className="gap-1 border-sky-500/40 bg-sky-500/10 text-sky-700 dark:text-sky-300">
|
|
58
|
+
<Clock className="h-3 w-3" />
|
|
59
|
+
Session Context
|
|
60
|
+
</Badge>
|
|
61
|
+
|
|
62
|
+
{context.mode && (
|
|
63
|
+
<Badge variant="outline" className="gap-1">
|
|
64
|
+
<ModeIcon className="h-3 w-3" />
|
|
65
|
+
{context.mode}
|
|
66
|
+
</Badge>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
{context.setting && (
|
|
70
|
+
<Badge variant="outline" className="gap-1">
|
|
71
|
+
<SettingIcon className="h-3 w-3" />
|
|
72
|
+
{context.setting}
|
|
73
|
+
</Badge>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{context.captureQuality && (
|
|
77
|
+
<Badge variant="outline" className={captureQualityTone[context.captureQuality]}>
|
|
78
|
+
<FileCheck2 className="h-3 w-3" />
|
|
79
|
+
capture: {context.captureQuality}
|
|
80
|
+
</Badge>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
{context.continuationKind && (
|
|
84
|
+
<Badge variant="outline" className={continuationKindTone[context.continuationKind]}>
|
|
85
|
+
{context.continuationKind}
|
|
86
|
+
</Badge>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{context.publicSummaryAllowed !== undefined && (
|
|
90
|
+
<Badge variant="outline" className={context.publicSummaryAllowed ? "border-emerald-500/40" : "border-red-500/40"}>
|
|
91
|
+
<Shield className="h-3 w-3" />
|
|
92
|
+
{context.publicSummaryAllowed ? "public-safe" : "private"}
|
|
93
|
+
</Badge>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{context.handle && (
|
|
97
|
+
<Badge variant="secondary" className="gap-1 font-mono text-xs">
|
|
98
|
+
<Link2 className="h-3 w-3" />
|
|
99
|
+
{context.handle}
|
|
100
|
+
</Badge>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{captureWarnings.length > 0 && (
|
|
104
|
+
<Badge variant="outline" className="gap-1 border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300">
|
|
105
|
+
<AlertTriangle className="h-3 w-3" />
|
|
106
|
+
quality note
|
|
107
|
+
</Badge>
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
{hasWarnings && (
|
|
111
|
+
<div className="w-full space-y-1 pt-1 text-xs text-muted-foreground">
|
|
112
|
+
{projection.warnings.map((warning, index) => (
|
|
113
|
+
<div key={`${projection.beatName}-${index}`} className="flex items-start gap-1">
|
|
114
|
+
<AlertTriangle className="mt-0.5 h-3 w-3 flex-shrink-0 text-amber-600" />
|
|
115
|
+
<span>{warning.message}</span>
|
|
116
|
+
</div>
|
|
117
|
+
))}
|
|
118
|
+
</div>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { AlertTriangle, ExternalLink, Github, GitBranch, Link2, Workflow } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
import { Badge } from "@/components/ui/badge"
|
|
6
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
7
|
+
import type { SessionLineageProjection } from "@/lib/types"
|
|
8
|
+
|
|
9
|
+
interface AsterionSessionLineageCardProps {
|
|
10
|
+
projection: SessionLineageProjection
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const handoffTone: Record<NonNullable<SessionLineageProjection["branches"][number]["metadata"]["handoffState"]>, string> = {
|
|
14
|
+
"requirements-created": "border-slate-500/40 bg-slate-500/10 text-slate-700 dark:text-slate-300",
|
|
15
|
+
"implementation-ready": "border-sky-500/40 bg-sky-500/10 text-sky-700 dark:text-sky-300",
|
|
16
|
+
"returned-to-parent": "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function AsterionSessionLineageCard({ projection }: AsterionSessionLineageCardProps) {
|
|
20
|
+
return (
|
|
21
|
+
<Card>
|
|
22
|
+
<CardHeader>
|
|
23
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
24
|
+
<Workflow className="h-5 w-5" />
|
|
25
|
+
Asterion Session Lineage
|
|
26
|
+
</CardTitle>
|
|
27
|
+
</CardHeader>
|
|
28
|
+
<CardContent className="space-y-4">
|
|
29
|
+
{projection.warnings.length > 0 && (
|
|
30
|
+
<div className="space-y-2 rounded-md border border-amber-500/30 bg-amber-500/5 p-3">
|
|
31
|
+
{projection.warnings.map((warning, index) => (
|
|
32
|
+
<div key={`${warning.recordName || "lineage"}-${index}`} className="flex items-start gap-2 text-sm">
|
|
33
|
+
<AlertTriangle className="mt-0.5 h-4 w-4 text-amber-600" />
|
|
34
|
+
<span>{warning.message}</span>
|
|
35
|
+
</div>
|
|
36
|
+
))}
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
39
|
+
|
|
40
|
+
{projection.groupedBranches.map((group) => (
|
|
41
|
+
<div key={group.sourceBeat} className="rounded-md border p-4">
|
|
42
|
+
<div className="mb-3 flex flex-wrap items-center gap-2">
|
|
43
|
+
<Badge variant="outline" className="gap-1">
|
|
44
|
+
<GitBranch className="h-3 w-3" />
|
|
45
|
+
{group.branches.length} branch{group.branches.length === 1 ? "" : "es"}
|
|
46
|
+
</Badge>
|
|
47
|
+
{group.sourceBeatExistsInChart && group.sourceBeatAnchor ? (
|
|
48
|
+
<a href={`#${group.sourceBeatAnchor}`} className="inline-flex items-center gap-1 text-sm font-medium underline">
|
|
49
|
+
<Link2 className="h-3.5 w-3.5" />
|
|
50
|
+
{group.sourceBeat}
|
|
51
|
+
</a>
|
|
52
|
+
) : (
|
|
53
|
+
<div className="text-sm font-medium">{group.sourceBeat}</div>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="space-y-3">
|
|
58
|
+
{group.branches.map((branch) => (
|
|
59
|
+
<div key={`${branch.sourceRecordName || branch.metadata.branchSessionId}-${branch.metadata.branchIndex || 0}`} className="rounded-md border bg-muted/20 p-3">
|
|
60
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
61
|
+
<Badge variant="secondary">branch {branch.metadata.branchIndex ?? "?"}</Badge>
|
|
62
|
+
{branch.metadata.handoffState && (
|
|
63
|
+
<Badge variant="outline" className={handoffTone[branch.metadata.handoffState]}>
|
|
64
|
+
{branch.metadata.handoffState}
|
|
65
|
+
</Badge>
|
|
66
|
+
)}
|
|
67
|
+
{branch.metadata.platform && <Badge variant="outline">{branch.metadata.platform}</Badge>}
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div className="mt-3 grid gap-2 text-sm md:grid-cols-2">
|
|
71
|
+
<Field label="Purpose" value={branch.metadata.branchPurpose} />
|
|
72
|
+
<Field label="Copied Messages" value={branch.metadata.copiedMessageCount?.toString()} />
|
|
73
|
+
<Field label="Parent Chart" value={branch.metadata.parentChartId} />
|
|
74
|
+
<Field label="Original Session" value={branch.metadata.originalSessionId} mono />
|
|
75
|
+
<Field label="Branch Session" value={branch.metadata.branchSessionId} mono />
|
|
76
|
+
<Field label="Source Record" value={branch.sourceRecordName} mono />
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{branch.relatedIssueRefs.length > 0 && (
|
|
80
|
+
<div className="mt-3 flex flex-wrap gap-2">
|
|
81
|
+
{branch.relatedIssueRefs.map((issue) => {
|
|
82
|
+
const badge = (
|
|
83
|
+
<Badge variant="outline" className="gap-1 text-xs">
|
|
84
|
+
<Github className="h-3 w-3" />
|
|
85
|
+
{issue.label}
|
|
86
|
+
{issue.href && <ExternalLink className="h-3 w-3" />}
|
|
87
|
+
</Badge>
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return issue.href ? (
|
|
91
|
+
<a key={`${issue.key}-${issue.label}`} href={issue.href} target="_blank" rel="noreferrer">
|
|
92
|
+
{badge}
|
|
93
|
+
</a>
|
|
94
|
+
) : (
|
|
95
|
+
<span key={`${issue.key}-${issue.label}`}>{badge}</span>
|
|
96
|
+
)
|
|
97
|
+
})}
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
))}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
))}
|
|
105
|
+
</CardContent>
|
|
106
|
+
</Card>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function Field({ label, value, mono = false }: { label: string; value?: string; mono?: boolean }) {
|
|
111
|
+
if (!value) return null
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div>
|
|
115
|
+
<div className="text-xs uppercase tracking-wide text-muted-foreground">{label}</div>
|
|
116
|
+
<div className={`mt-1 ${mono ? "font-mono text-xs" : "text-sm"}`}>{value}</div>
|
|
117
|
+
</div>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
@@ -17,6 +17,7 @@ import { EditActionStep } from "./edit-action-step"
|
|
|
17
17
|
import { AddActionStep } from "./add-action-step"
|
|
18
18
|
import { EditChartDueDate } from "./edit-chart-due-date"
|
|
19
19
|
import { ChartEditor } from "@/lib/chart-editor"
|
|
20
|
+
import { ProjectSourceBadge } from "./github-provenance"
|
|
20
21
|
|
|
21
22
|
interface ChartDetailEditableProps {
|
|
22
23
|
chart: Chart
|
|
@@ -89,6 +90,7 @@ export function ChartDetailEditable({
|
|
|
89
90
|
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
|
90
91
|
<span>Created {createdDate}</span>
|
|
91
92
|
</div>
|
|
93
|
+
<ProjectSourceBadge entity={chart.chartEntity} className="mt-3" />
|
|
92
94
|
</div>
|
|
93
95
|
<div>
|
|
94
96
|
<EditChartDueDate
|
|
@@ -7,7 +7,9 @@ import { Separator } from "@/components/ui/separator"
|
|
|
7
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
|
+
import { MetadataProjections } from "./metadata-projections"
|
|
10
11
|
import { RelationGraph } from "./relation-graph"
|
|
12
|
+
import { ActionStepIssueBadge, ProjectSourceBadge } from "./github-provenance"
|
|
11
13
|
|
|
12
14
|
interface ChartDetailProps {
|
|
13
15
|
chart: Chart
|
|
@@ -41,6 +43,7 @@ export function ChartDetail({ chart, data }: ChartDetailProps) {
|
|
|
41
43
|
</div>
|
|
42
44
|
)}
|
|
43
45
|
</div>
|
|
46
|
+
<ProjectSourceBadge entity={chart.chartEntity} className="mt-3" />
|
|
44
47
|
</div>
|
|
45
48
|
</div>
|
|
46
49
|
</CardHeader>
|
|
@@ -140,6 +143,8 @@ export function ChartDetail({ chart, data }: ChartDetailProps) {
|
|
|
140
143
|
</div>
|
|
141
144
|
)}
|
|
142
145
|
|
|
146
|
+
<MetadataProjections chart={chart} data={data} />
|
|
147
|
+
|
|
143
148
|
{/* Sub-charts */}
|
|
144
149
|
{chart.subCharts.length > 0 && (
|
|
145
150
|
<Card>
|
|
@@ -194,7 +199,10 @@ function ActionItem({ action, index }: ActionItemProps) {
|
|
|
194
199
|
</div>
|
|
195
200
|
<div className="flex-1 min-w-0">
|
|
196
201
|
<div className="flex flex-col gap-1 sm:flex-row sm:items-start sm:justify-between sm:gap-2 mb-1">
|
|
197
|
-
<
|
|
202
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
203
|
+
<span className="text-xs md:text-sm font-medium">Action {index}</span>
|
|
204
|
+
<ActionStepIssueBadge action={action} />
|
|
205
|
+
</div>
|
|
198
206
|
{dueDate && (
|
|
199
207
|
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
200
208
|
<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
|