@danypops/papyrus 0.34.2 → 0.34.3
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.
|
@@ -10,19 +10,26 @@ export interface ArtifactDetailContent {
|
|
|
10
10
|
relationships: string[];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export function artifactDetailContent(artifact: Artifact): ArtifactDetailContent {
|
|
13
|
+
export function artifactDetailContent(artifact: Artifact, relationshipLines: string[] = []): ArtifactDetailContent {
|
|
14
14
|
return {
|
|
15
15
|
title: artifact.title,
|
|
16
16
|
identity: `${artifact.id} [${artifact.kind}|${artifact.status}]${artifact.subtype ? ` · ${artifact.subtype}` : ""}`,
|
|
17
17
|
body: artifact.body || "(no body)",
|
|
18
18
|
labels: [...artifact.labels],
|
|
19
19
|
metadata: Object.keys(artifact.extra).length > 0 ? formatMetadata(artifact.extra) : [],
|
|
20
|
-
relationships:
|
|
20
|
+
relationships: relationshipLines,
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Deliberately plain, unlike ArtifactDetailViewport's TUI body (renderMarkdownBody): this path
|
|
26
|
+
* feeds ctx.ui.notify(), used outside interactive mode (RPC, piped/non-terminal callers) where
|
|
27
|
+
* ANSI escape codes are noise or corruption, not formatting. Raw Markdown source stays readable
|
|
28
|
+
* as plain text either way. relationshipLines is still upgraded (a real graph or a resolved
|
|
29
|
+
* arrow list, never raw ids) since that needs no color to read.
|
|
30
|
+
*/
|
|
31
|
+
export function artifactDetailsText(artifact: Artifact, relationshipLines: string[] = []): string {
|
|
32
|
+
const content = artifactDetailContent(artifact, relationshipLines);
|
|
26
33
|
let output = `${content.title}\n${content.identity}\n\n${content.body}`;
|
|
27
34
|
if (content.labels.length > 0) output += `\n\nLabels: ${content.labels.join(", ")}`;
|
|
28
35
|
if (content.metadata.length > 0) output += `\n\nMetadata:\n${content.metadata.map((line) => ` ${line}`).join("\n")}`;
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
ARTIFACT_DETAIL_RESERVED_ROWS,
|
|
8
8
|
} from "../../src/constants.ts";
|
|
9
9
|
import type { Artifact } from "../../src/domain/artifact.ts";
|
|
10
|
+
import type { GraphRenderer } from "../../src/ports/graph-renderer.ts";
|
|
10
11
|
import { artifactDetailContent, artifactDetailsText, type ArtifactDetailContent } from "./artifact-detail-format.ts";
|
|
12
|
+
import { buildArtifactRelationshipLines } from "./artifact-relationship-lines.ts";
|
|
13
|
+
import { BeautifulMermaidRenderer } from "./beautiful-mermaid-renderer.ts";
|
|
11
14
|
import { renderMarkdownBody, type ActiveTheme } from "./markdown.ts";
|
|
12
15
|
|
|
13
16
|
interface ArtifactDetailLine {
|
|
@@ -27,13 +30,14 @@ class ArtifactDetailViewport {
|
|
|
27
30
|
private readonly tui: TUI,
|
|
28
31
|
private readonly activeTheme: ActiveTheme,
|
|
29
32
|
artifact: Artifact,
|
|
33
|
+
relationshipLines: string[],
|
|
30
34
|
private readonly close: () => void,
|
|
31
35
|
) {
|
|
32
36
|
this.visibleLines = Math.max(
|
|
33
37
|
ARTIFACT_DETAIL_MIN_VISIBLE_LINES,
|
|
34
38
|
Math.min(ARTIFACT_DETAIL_MAX_VISIBLE_LINES, tui.terminal.rows - ARTIFACT_DETAIL_RESERVED_ROWS),
|
|
35
39
|
);
|
|
36
|
-
this.content = artifactDetailContent(artifact);
|
|
40
|
+
this.content = artifactDetailContent(artifact, relationshipLines);
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
invalidate(): void { this.renderedWidth = 0; }
|
|
@@ -104,9 +108,14 @@ class ArtifactDetailViewport {
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
export async function showArtifactDetailView(
|
|
108
|
-
|
|
111
|
+
export async function showArtifactDetailView(
|
|
112
|
+
ctx: ExtensionCommandContext,
|
|
113
|
+
artifact: Artifact,
|
|
114
|
+
renderer: GraphRenderer = new BeautifulMermaidRenderer(),
|
|
115
|
+
): Promise<void> {
|
|
116
|
+
const relationshipLines = buildArtifactRelationshipLines(artifact, renderer);
|
|
117
|
+
const output = artifactDetailsText(artifact, relationshipLines);
|
|
109
118
|
if (ctx.mode !== "tui") { ctx.ui.notify(output, "info"); return; }
|
|
110
119
|
await ctx.ui.custom<void>((tui, theme, _keybindings, done) =>
|
|
111
|
-
new ArtifactDetailViewport(tui, () => ctx.ui.theme ?? theme, artifact, done));
|
|
120
|
+
new ArtifactDetailViewport(tui, () => ctx.ui.theme ?? theme, artifact, relationshipLines, done));
|
|
112
121
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { GRAPH_RENDER_MAX_ROUTED_EDGES, GRAPH_RENDER_MAX_ROUTED_NODES } from "../../src/constants.ts";
|
|
2
|
+
import { projectArtifactRelationships } from "../../src/artifact-relationship-view.ts";
|
|
3
|
+
import type { Artifact } from "../../src/domain/artifact.ts";
|
|
4
|
+
import type { GraphRenderer } from "../../src/ports/graph-renderer.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders an artifact's direct relationships as a small graph when the neighbor set is real
|
|
8
|
+
* (more than one node) and within the same bound Task graph rendering already enforces;
|
|
9
|
+
* otherwise falls back to a plain, still name-resolved arrow list -- never the renderer's own
|
|
10
|
+
* separate box-style fallback, so this view has exactly two shapes, not three.
|
|
11
|
+
*/
|
|
12
|
+
export function buildArtifactRelationshipLines(artifact: Artifact, renderer: GraphRenderer): string[] {
|
|
13
|
+
const graph = projectArtifactRelationships(artifact);
|
|
14
|
+
if (graph.edges.length === 0) return [];
|
|
15
|
+
const withinBounds = graph.nodes.length > 1
|
|
16
|
+
&& graph.nodes.length <= GRAPH_RENDER_MAX_ROUTED_NODES
|
|
17
|
+
&& graph.edges.length <= GRAPH_RENDER_MAX_ROUTED_EDGES;
|
|
18
|
+
if (withinBounds) {
|
|
19
|
+
const rendered = renderer.render(graph);
|
|
20
|
+
if (rendered.lines.length > 0) return rendered.lines;
|
|
21
|
+
}
|
|
22
|
+
const labelById = new Map(graph.nodes.map((node) => [node.id, node.label]));
|
|
23
|
+
return graph.edges.map((edge) => `${labelById.get(edge.from) ?? edge.from} --${edge.label}--> ${labelById.get(edge.to) ?? edge.to}`);
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Artifact } from "./domain/artifact.ts";
|
|
2
|
+
import type { DisplayGraph, DisplayGraphEdge, DisplayGraphNode } from "./domain/display-graph.ts";
|
|
3
|
+
import { fallbackLabel } from "./task-relationship-view.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The generic-artifact counterpart to projectTaskRelationships: unlike a Task, which already
|
|
7
|
+
* has its containing TaskGraph's real titles in memory, a generic Doc/Rule/Skill/Playbook only
|
|
8
|
+
* has raw edge id pairs (artifact.show's tree fetch never resolves neighbor titles -- see
|
|
9
|
+
* ops.ts getArtifact). Reuses the same fallbackLabel heuristic rather than adding a network
|
|
10
|
+
* round-trip per neighbor, which would turn a rendering concern into a new daemon-adjacent one.
|
|
11
|
+
*/
|
|
12
|
+
export function projectArtifactRelationships(artifact: Artifact): DisplayGraph {
|
|
13
|
+
const edges: DisplayGraphEdge[] = (artifact.edges ?? []).map((edge) => ({ from: edge.from, to: edge.to, label: edge.relation }));
|
|
14
|
+
const nodeIds = new Set<string>();
|
|
15
|
+
for (const edge of edges) {
|
|
16
|
+
nodeIds.add(edge.from);
|
|
17
|
+
nodeIds.add(edge.to);
|
|
18
|
+
}
|
|
19
|
+
const nodes: DisplayGraphNode[] = [...nodeIds].map((id) => id === artifact.id
|
|
20
|
+
? { id, label: artifact.title, status: artifact.status }
|
|
21
|
+
: { id, label: fallbackLabel(id) });
|
|
22
|
+
return { direction: "LR", nodes, edges };
|
|
23
|
+
}
|
|
@@ -9,7 +9,8 @@ function normalizeEdge(edge: ArtifactEdge): DisplayGraphEdge {
|
|
|
9
9
|
return { from: edge.from, to: edge.to, label: edge.relation };
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
/** Best-effort readable label from a bare id when no real title is known -- shared with artifact-relationship-view.ts's generic version of this same problem. */
|
|
13
|
+
export function fallbackLabel(id: string): string {
|
|
13
14
|
return id.replace(/-[a-z0-9]{4}$/i, "").replaceAll("-", " ");
|
|
14
15
|
}
|
|
15
16
|
|