@danypops/pi-papyrus 0.43.4 → 0.43.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.
@@ -59,10 +59,6 @@ export function emptyState(noun: string): string {
59
59
  return `No ${noun}.`;
60
60
  }
61
61
 
62
- export function treeConnector(last: boolean): string {
63
- return last ? "└─" : "├─";
64
- }
65
-
66
62
  export function expandHint(): string {
67
63
  return "expand for details";
68
64
  }
@@ -1,7 +1,8 @@
1
1
  import { TOOL_COLLAPSED_ROW_LIMIT } from "@danypops/papyrus";
2
2
  import type { Theme } from "@earendil-works/pi-coding-agent";
3
3
  import { type Component, truncateToWidth } from "@earendil-works/pi-tui";
4
- import { countSummary, expandHint, kindGlyph, statusGlyph, treeConnector } from "./artifact-card.ts";
4
+ import { type TreeNode, TreeView } from "malevich-tui-components";
5
+ import { countSummary, expandHint, kindGlyph, statusGlyph } from "./artifact-card.ts";
5
6
  import type { ArtifactListToolDetails, GraphToolDetails, ToolArtifactSummary } from "./render-model.ts";
6
7
 
7
8
  function pluralKind(rows: readonly ToolArtifactSummary[]): string {
@@ -85,44 +86,79 @@ export class ArtifactListCard implements Component {
85
86
  }
86
87
  }
87
88
 
88
- interface HierarchyRow {
89
- node: ToolArtifactSummary;
90
- prefix: string;
91
- connector: string;
89
+ /** A fixed-text Component embedding one already-styled, width-truncated
90
+ * line -- used for the dependency-edge annotation Malevich's TreeView
91
+ * renders indented beneath a node. */
92
+ function textLine(text: string): Component {
93
+ return {
94
+ render: (width: number) => text.split("\n").map((line) => truncateToWidth(line, width)),
95
+ invalidate: () => {},
96
+ };
92
97
  }
93
98
 
94
- function hierarchyRows(details: GraphToolDetails): HierarchyRow[] {
99
+ /** Projects GraphToolDetails' containment edges (`relation === "contains"`)
100
+ * into Malevich TreeNodes, rendered via the real, shared TreeView instead
101
+ * of hand-rolled connector math. Any non-containment edge (depends_on,
102
+ * references, blocks, ...) naming a node as its `to` -- previously
103
+ * silently dropped entirely -- surfaces as a "depends on: ..." annotation
104
+ * embedded under that node, matching DagView's own edge-annotation wording. */
105
+ function toTreeNodes(details: GraphToolDetails, theme: Theme, expanded: boolean): TreeNode[] {
95
106
  const byId = new Map(details.nodes.map((node) => [node.id, node]));
96
107
  const childIds = new Map<string, string[]>();
97
108
  const contained = new Set<string>();
109
+ const dependencySources = new Map<string, string[]>();
98
110
  for (const edge of details.edges) {
99
- if (edge.relation !== "contains" || !byId.has(edge.from) || !byId.has(edge.to)) continue;
100
- const children = childIds.get(edge.from) ?? [];
101
- children.push(edge.to);
102
- childIds.set(edge.from, children);
103
- contained.add(edge.to);
111
+ if (edge.relation === "contains") {
112
+ if (!byId.has(edge.from) || !byId.has(edge.to)) continue;
113
+ const children = childIds.get(edge.from) ?? [];
114
+ children.push(edge.to);
115
+ childIds.set(edge.from, children);
116
+ contained.add(edge.to);
117
+ } else if (byId.has(edge.to)) {
118
+ const sources = dependencySources.get(edge.to) ?? [];
119
+ sources.push(edge.from);
120
+ dependencySources.set(edge.to, sources);
121
+ }
104
122
  }
105
- const roots = details.nodes.filter((node) => !contained.has(node.id));
106
- const rows: HierarchyRow[] = [];
123
+
107
124
  const visited = new Set<string>();
108
- const visit = (node: ToolArtifactSummary, prefix: string, connector: string): void => {
109
- if (visited.has(node.id)) return;
125
+ const buildNode = (node: ToolArtifactSummary): TreeNode => {
110
126
  visited.add(node.id);
111
- rows.push({ node, prefix, connector });
112
- const children = (childIds.get(node.id) ?? [])
113
- .map((id) => byId.get(id))
114
- .filter((child): child is ToolArtifactSummary => child !== undefined);
115
- children.forEach((child, index) => {
116
- const last = index === children.length - 1;
117
- visit(child, `${prefix}${connector ? (connector === "└─" ? " " : "│ ") : ""}`, treeConnector(last));
118
- });
127
+ const identity = expanded ? `${node.id} ` : "";
128
+ const label = `${theme.fg("accent", kindGlyph(node.kind))} ${theme.fg("muted", statusGlyph(node.status))} ${theme.fg("accent", identity)}${theme.fg("text", node.title)}`;
129
+ const children: TreeNode[] = [];
130
+ for (const id of childIds.get(node.id) ?? []) {
131
+ if (visited.has(id)) continue;
132
+ const child = byId.get(id);
133
+ if (child) children.push(buildNode(child));
134
+ }
135
+ const metadata = expanded ? rowMetadata(node) : "";
136
+ const sources = dependencySources.get(node.id);
137
+ const annotations = [
138
+ ...(metadata ? [theme.fg("dim", metadata)] : []),
139
+ ...(sources && sources.length > 0
140
+ ? [theme.fg("dim", `depends on: ${sources.map((id) => byId.get(id)?.title ?? id).join(", ")}`)]
141
+ : []),
142
+ ];
143
+ return {
144
+ label,
145
+ children: children.length > 0 ? children : undefined,
146
+ component: annotations.length > 0 ? textLine(annotations.join("\n")) : undefined,
147
+ };
119
148
  };
120
- for (const root of roots) visit(root, "", "");
121
- for (const node of details.nodes) visit(node, "", "");
122
- return rows;
149
+
150
+ const tree: TreeNode[] = [];
151
+ for (const root of details.nodes.filter((node) => !contained.has(node.id))) {
152
+ if (!visited.has(root.id)) tree.push(buildNode(root));
153
+ }
154
+ for (const node of details.nodes) {
155
+ if (!visited.has(node.id)) tree.push(buildNode(node));
156
+ }
157
+ return tree;
123
158
  }
124
159
 
125
- /** Bounded task containment preview; dependency graphs use the dedicated graph renderer. */
160
+ /** Bounded task containment preview; dependency edges surface as an
161
+ * annotation under the node they target (see toTreeNodes). */
126
162
  export class TaskHierarchyPreview implements Component {
127
163
  private details: GraphToolDetails;
128
164
  private theme: Theme;
@@ -146,26 +182,12 @@ export class TaskHierarchyPreview implements Component {
146
182
  render(width: number): string[] {
147
183
  const safeWidth = Math.max(1, width);
148
184
  if (this.cachedLines && this.cachedWidth === safeWidth) return this.cachedLines;
149
- const rows = hierarchyRows(this.details);
150
- const lines = [
151
- truncateToWidth(
152
- this.theme.fg("toolTitle", this.theme.bold(`${this.details.nodes.length} tasks · ${this.details.edges.length} edges`)),
153
- safeWidth,
154
- ),
155
- ];
156
- for (const row of rows) {
157
- const identity = this.expanded ? `${row.node.id} ` : "";
158
- lines.push(
159
- truncateToWidth(
160
- `${row.prefix}${row.connector}${row.connector ? " " : ""}${this.theme.fg("accent", kindGlyph(row.node.kind))} ${this.theme.fg("muted", statusGlyph(row.node.status))} ${this.theme.fg("accent", identity)}${this.theme.fg("text", row.node.title)}`,
161
- safeWidth,
162
- ),
163
- );
164
- if (this.expanded) {
165
- const metadata = rowMetadata(row.node);
166
- if (metadata) lines.push(truncateToWidth(this.theme.fg("dim", `${row.prefix} ${metadata}`), safeWidth));
167
- }
168
- }
185
+ const header = truncateToWidth(
186
+ this.theme.fg("toolTitle", this.theme.bold(`${this.details.nodes.length} tasks · ${this.details.edges.length} edges`)),
187
+ safeWidth,
188
+ );
189
+ const tree = new TreeView({ nodes: toTreeNodes(this.details, this.theme, this.expanded) });
190
+ const lines = [header, ...tree.render(safeWidth)];
169
191
  this.cachedWidth = safeWidth;
170
192
  this.cachedLines = lines;
171
193
  return lines;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-papyrus",
3
- "version": "0.43.4",
3
+ "version": "0.43.5",
4
4
  "description": "Pi host extension for Papyrus: native tools, TUI panels, and context injection over the daemon-backed graph store",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package"],
@@ -24,7 +24,7 @@
24
24
  "@danypops/vehicle-core": "^0.3.0",
25
25
  "@danypops/vehicle-server": "^0.4.1",
26
26
  "beautiful-mermaid": "1.1.3",
27
- "malevich-tui-components": "^0.16.1"
27
+ "malevich-tui-components": "^0.20.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@earendil-works/pi-coding-agent": "^0.80.10",