@danypops/pi-papyrus 0.45.8 → 0.45.9

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.
@@ -1,8 +1,21 @@
1
1
  import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
  import { type Component, truncateToWidth } from "@earendil-works/pi-tui";
3
- import { buildDetailLines, type DetailField, type DetailSection } from "malevich-tui-components";
3
+ import { buildDetailLines, type DetailField, type DetailSection, type DetailViewTheme } from "malevich-tui-components";
4
4
  import type { ArtifactToolDetails } from "./render-model.ts";
5
5
 
6
+ /** Shared by every buildDetailLines caller in this extension (ArtifactCard, and
7
+ * vehicle-artifact-renderers.ts's discuss/tasks.complete renderers) -- one Theme -> DetailViewTheme
8
+ * mapping instead of the same four-field object literal re-typed at each call site. */
9
+ export function detailViewTheme(theme: Theme): DetailViewTheme {
10
+ return {
11
+ field: (s) => theme.fg("text", s),
12
+ heading: (s) => theme.fg("toolTitle", theme.bold(s)),
13
+ byline: (s) => theme.fg("muted", s),
14
+ body: (s) => theme.fg("text", s),
15
+ line: (s) => theme.fg("warning", s),
16
+ };
17
+ }
18
+
6
19
  const KIND_GLYPHS: Readonly<Record<string, string>> = {
7
20
  task: "◇",
8
21
  doc: "▤",
@@ -100,6 +113,7 @@ export class ArtifactCard implements Component {
100
113
  const status = this.theme.fg(statusColor(artifact.status), `${statusGlyph(artifact.status)} ${artifact.status}`);
101
114
  return [
102
115
  { label: "Title", value: artifact.title },
116
+ { label: "Alias", value: artifact.alias ?? artifact.id },
103
117
  { label: "Kind", value: `${kindGlyph(artifact.kind)} ${artifact.kind}` },
104
118
  { label: "Status", value: status },
105
119
  ...(this.expanded ? [{ label: "ID", value: artifact.id }] : []),
@@ -128,13 +142,7 @@ export class ArtifactCard implements Component {
128
142
  fields: this.fields(),
129
143
  sections: this.sections(),
130
144
  alignFields: true,
131
- theme: {
132
- field: (s) => theme.fg("text", s),
133
- heading: (s) => theme.fg("toolTitle", theme.bold(s)),
134
- byline: (s) => theme.fg("muted", s),
135
- body: (s) => theme.fg("text", s),
136
- line: (s) => theme.fg("warning", s),
137
- },
145
+ theme: detailViewTheme(theme),
138
146
  });
139
147
 
140
148
  if (this.details.focus) {
@@ -18,6 +18,8 @@ export interface ResultCompleteness {
18
18
 
19
19
  export interface ToolArtifactSummary {
20
20
  id: string;
21
+ /** Optional only for a detail object persisted before this field existed -- ArtifactCard falls back to id when absent. */
22
+ alias?: string;
21
23
  kind: string;
22
24
  title: string;
23
25
  status: string;
@@ -154,6 +156,7 @@ function boundedText(value: string, maximum: number): { value: string; completen
154
156
  function artifactSummary(artifact: Artifact): ToolArtifactSummary {
155
157
  return {
156
158
  id: artifact.id,
159
+ alias: artifact.alias,
157
160
  kind: artifact.kind,
158
161
  title: artifact.title,
159
162
  status: artifact.status,
@@ -20,7 +20,7 @@ import type { VehicleOperationDescriptor } from "@danypops/vehicle-core";
20
20
  import type { Theme } from "@earendil-works/pi-coding-agent";
21
21
  import { type Component, Text, truncateToWidth } from "@earendil-works/pi-tui";
22
22
  import { buildDetailLines, type DagEdge, type DagNode, DagView, type DetailField, type DetailSection } from "malevich-tui-components";
23
- import { ArtifactCard, expandHint, statusColor, statusGlyph } from "./tool-rendering/artifact-card.ts";
23
+ import { ArtifactCard, detailViewTheme, expandHint, statusColor, statusGlyph } from "./tool-rendering/artifact-card.ts";
24
24
  import { ArtifactListCard } from "./tool-rendering/artifact-list.ts";
25
25
  import { type ArtifactFocusAnnotation, createArtifactDetails, createArtifactListDetails } from "./tool-rendering/render-model.ts";
26
26
 
@@ -261,13 +261,6 @@ function isDiscussionListOutput(value: unknown): value is DiscussionListOutput {
261
261
  return isArtifactArray(row.discussions);
262
262
  }
263
263
 
264
- const discussTheme = (theme: Theme) => ({
265
- field: (s: string) => theme.fg("text", s),
266
- heading: (s: string) => theme.fg("toolTitle", theme.bold(s)),
267
- byline: (s: string) => theme.fg("muted", s),
268
- body: (s: string) => theme.fg("text", s),
269
- });
270
-
271
264
  function roundsSection(rounds: readonly DiscussionRoundOutput[]): DetailSection {
272
265
  return {
273
266
  heading: `Rounds (${rounds.length}):`,
@@ -275,35 +268,35 @@ function roundsSection(rounds: readonly DiscussionRoundOutput[]): DetailSection
275
268
  };
276
269
  }
277
270
 
271
+ /** A one-shot render function with nothing to invalidate -- every buildDetailLines-based
272
+ * renderer in this file (unlike ArtifactCard/DagView) has no cache to clear. */
273
+ function statelessComponent(render: (width: number) => string[]): Component {
274
+ return { render, invalidate: () => {} };
275
+ }
276
+
278
277
  function renderDiscussionAndRounds(output: DiscussionAndRoundsOutput, theme: Theme, expanded: boolean): Component {
279
278
  const discussion = output.discussion;
280
- return {
281
- render: (width: number) => {
282
- const safeWidth = Math.max(1, width);
283
- const fields: DetailField[] = [
284
- { label: "Title", value: discussion.title },
285
- { label: "Status", value: theme.fg(statusColor(discussion.status), `${statusGlyph(discussion.status)} ${discussion.status}`) },
286
- ];
287
- const sections: DetailSection[] = expanded && output.rounds.length > 0 ? [roundsSection(output.rounds)] : [];
288
- const lines = buildDetailLines(safeWidth, { fields, sections, alignFields: true, theme: discussTheme(theme) });
289
- if (!expanded && output.rounds.length > 0) {
290
- const count = output.rounds.length;
291
- lines.push(truncateToWidth(theme.fg("dim", `${count} round${count === 1 ? "" : "s"} · ${expandHint()}`), safeWidth));
292
- }
293
- return lines;
294
- },
295
- invalidate: () => {},
296
- };
279
+ return statelessComponent((width) => {
280
+ const safeWidth = Math.max(1, width);
281
+ const fields: DetailField[] = [
282
+ { label: "Title", value: discussion.title },
283
+ { label: "Status", value: theme.fg(statusColor(discussion.status), `${statusGlyph(discussion.status)} ${discussion.status}`) },
284
+ ];
285
+ const sections: DetailSection[] = expanded && output.rounds.length > 0 ? [roundsSection(output.rounds)] : [];
286
+ const lines = buildDetailLines(safeWidth, { fields, sections, alignFields: true, theme: detailViewTheme(theme) });
287
+ if (!expanded && output.rounds.length > 0) {
288
+ const count = output.rounds.length;
289
+ lines.push(truncateToWidth(theme.fg("dim", `${count} round${count === 1 ? "" : "s"} · ${expandHint()}`), safeWidth));
290
+ }
291
+ return lines;
292
+ });
297
293
  }
298
294
 
299
295
  function renderDiscussionRoundsOnly(output: DiscussionRoundsOnlyOutput, theme: Theme): Component {
300
- return {
301
- render: (width: number) => {
302
- const sections: DetailSection[] = output.rounds.length > 0 ? [roundsSection(output.rounds)] : [{ lines: ["No rounds."] }];
303
- return buildDetailLines(Math.max(1, width), { sections, theme: discussTheme(theme) });
304
- },
305
- invalidate: () => {},
306
- };
296
+ return statelessComponent((width) => {
297
+ const sections: DetailSection[] = output.rounds.length > 0 ? [roundsSection(output.rounds)] : [{ lines: ["No rounds."] }];
298
+ return buildDetailLines(Math.max(1, width), { sections, theme: detailViewTheme(theme) });
299
+ });
307
300
  }
308
301
 
309
302
  /** tasks.complete's own TaskCompletion shape -- a completed (or rejected) task plus its own
@@ -350,45 +343,42 @@ function isTaskCompletion(value: unknown): value is TaskCompletionOutput {
350
343
 
351
344
  function renderTaskCompletion(result: TaskCompletionOutput, theme: Theme, expanded: boolean): Component {
352
345
  const task = result.artifact;
353
- return {
354
- render: (width: number) => {
355
- const safeWidth = Math.max(1, width);
356
- const fields: DetailField[] = [
357
- { label: "Title", value: task.title },
358
- { label: "Status", value: theme.fg(statusColor(task.status), `${statusGlyph(task.status)} ${task.status}`) },
359
- ];
360
- const sections: DetailSection[] = [];
361
- if (result.gates.length > 0) {
362
- sections.push({
363
- heading: "Gates:",
364
- lines: result.gates.map((gate) => theme.fg(gate.passed ? "success" : "error", `${gate.passed ? "✓" : "✗"} ${gate.output}`)),
365
- });
366
- }
367
- if (result.checklist.length > 0) {
368
- sections.push({
369
- heading: "Checklist:",
370
- lines: result.checklist.map((entry) =>
371
- theme.fg(
372
- entry.accepted ? "success" : "error",
373
- `${entry.accepted ? "✓" : "✗"} ${entry.item}${entry.reason ? ` — ${entry.reason}` : ""}`,
374
- ),
346
+ return statelessComponent((width) => {
347
+ const safeWidth = Math.max(1, width);
348
+ const fields: DetailField[] = [
349
+ { label: "Title", value: task.title },
350
+ { label: "Status", value: theme.fg(statusColor(task.status), `${statusGlyph(task.status)} ${task.status}`) },
351
+ ];
352
+ const sections: DetailSection[] = [];
353
+ if (result.gates.length > 0) {
354
+ sections.push({
355
+ heading: "Gates:",
356
+ lines: result.gates.map((gate) => theme.fg(gate.passed ? "success" : "error", `${gate.passed ? "✓" : "✗"} ${gate.output}`)),
357
+ });
358
+ }
359
+ if (result.checklist.length > 0) {
360
+ sections.push({
361
+ heading: "Checklist:",
362
+ lines: result.checklist.map((entry) =>
363
+ theme.fg(
364
+ entry.accepted ? "success" : "error",
365
+ `${entry.accepted ? "" : ""} ${entry.item}${entry.reason ? ` — ${entry.reason}` : ""}`,
375
366
  ),
376
- });
377
- }
378
- if (result.blocked.length > 0) {
379
- sections.push({
380
- heading: "Still blocked:",
381
- lines: result.blocked.map((entry) => theme.fg("warning", `◼ ${entry.artifact.title}`)),
382
- });
383
- }
384
- const lines = buildDetailLines(safeWidth, { fields, sections, alignFields: true, theme: discussTheme(theme) });
385
- if (result.focused && expanded) {
386
- lines.push(truncateToWidth(theme.fg("accent", `▶ focus ${result.focused.title}`), safeWidth));
387
- }
388
- return lines;
389
- },
390
- invalidate: () => {},
391
- };
367
+ ),
368
+ });
369
+ }
370
+ if (result.blocked.length > 0) {
371
+ sections.push({
372
+ heading: "Still blocked:",
373
+ lines: result.blocked.map((entry) => theme.fg("warning", `◼ ${entry.artifact.title}`)),
374
+ });
375
+ }
376
+ const lines = buildDetailLines(safeWidth, { fields, sections, alignFields: true, theme: detailViewTheme(theme) });
377
+ if (result.focused && expanded) {
378
+ lines.push(truncateToWidth(theme.fg("accent", `▶ focus ${result.focused.title}`), safeWidth));
379
+ }
380
+ return lines;
381
+ });
392
382
  }
393
383
 
394
384
  export function papyrusVehicleRenderers(descriptor: VehicleOperationDescriptor): VehicleToolRenderers {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-papyrus",
3
- "version": "0.45.8",
3
+ "version": "0.45.9",
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"],