@danypops/pi-papyrus 0.55.9 → 0.56.1
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/extension/src/index.ts +147 -90
- package/extension/src/note/note-widget.ts +12 -9
- package/package.json +2 -2
package/extension/src/index.ts
CHANGED
|
@@ -23,9 +23,10 @@ import {
|
|
|
23
23
|
type TaskStatus,
|
|
24
24
|
} from "@danypops/papyrus";
|
|
25
25
|
import type { PushChannelClient } from "@danypops/vehicle-client/daemon-client";
|
|
26
|
-
import {
|
|
26
|
+
import { vehicleWidgetOwner } from "@danypops/vehicle-client-pi/widget-header";
|
|
27
27
|
import type { ExtensionAPI, ExtensionContext, ExtensionUIContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
28
28
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
29
|
+
import { renderWidgetSectionGroup, type WidgetSection } from "malevich-tui-components";
|
|
29
30
|
import { Type } from "typebox";
|
|
30
31
|
import { formatMetadata } from "./artifact/artifact-format.ts";
|
|
31
32
|
import { BoundedPoll } from "./bounded-poll.ts";
|
|
@@ -34,7 +35,7 @@ import { PAPYRUS_CONTEXT_HUB_PRODUCER_NAME, papyrusContextSegment } from "./cont
|
|
|
34
35
|
import { buildContextInjection } from "./context/context-injection-telemetry.ts";
|
|
35
36
|
import { ensureTypingCourtesyTracking, isLiveAskPending } from "./discuss/discuss-ask-view.ts";
|
|
36
37
|
import { resolveNameFields } from "./domain-tools.ts";
|
|
37
|
-
import {
|
|
38
|
+
import { buildNoteWidgetSection } from "./note/note-widget.ts";
|
|
38
39
|
import { PLAYBOOK_BRIDGE_MAX_PLAYBOOKS, registerPlaybookBridge } from "./playbook/playbook-bridge.ts";
|
|
39
40
|
import { callService, subscribeTaskPushChannel } from "./service-client.ts";
|
|
40
41
|
import { cacheSessionSecret, forgetSessionSecret, sessionSecretField } from "./session-identity.ts";
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
import { emitTaskFocusEvent, setTaskFocusEventBus } from "./task/task-focus-events.ts";
|
|
48
49
|
import { TASK_STATUS_PRESENTATION, taskTreeConnector } from "./task/task-presentation.ts";
|
|
49
50
|
import { buildTaskWidgetProjection, type TaskWidgetProjection } from "./task/task-widget.ts";
|
|
51
|
+
import { measure as artifactCardMeasure } from "./tool-rendering/artifact-card.ts";
|
|
50
52
|
import { renderPapyrusToolCall, renderPapyrusToolResult } from "./tool-rendering/index.ts";
|
|
51
53
|
import {
|
|
52
54
|
createArtifactDetails,
|
|
@@ -91,15 +93,14 @@ async function artifactNamesById(ids: readonly string[]): Promise<Map<string, st
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
// ---------------------------------------------------------------------------
|
|
94
|
-
// Task widget (
|
|
96
|
+
// Task + Notes widget (one shared aboveEditor tree -- PapyrusWidgetGroup below
|
|
97
|
+
// composes both overlays' own sections via renderWidgetSectionGroup, instead of
|
|
98
|
+
// each registering its own separate "Papyrus · <Label>" flat header)
|
|
95
99
|
// ---------------------------------------------------------------------------
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (projection.openTotal === 0) return [];
|
|
101
|
-
const header = truncateToWidth(theme.fg("muted", vehicleWidgetTitle(PAPYRUS_VEHICLE_NAME, "Tasks", projection.scopeLabel)), width, "…");
|
|
102
|
-
const lines: string[] = [header];
|
|
101
|
+
/** The rows only -- no header line; PapyrusWidgetGroup's own owner header covers that now. */
|
|
102
|
+
export function renderTaskSectionBodyLines(theme: Theme, projection: TaskWidgetProjection, width: number): string[] {
|
|
103
|
+
const lines: string[] = [];
|
|
103
104
|
for (let index = 0; index < projection.rows.length; index++) {
|
|
104
105
|
const row = projection.rows[index]!;
|
|
105
106
|
const laterSibling = projection.rows.slice(index + 1).some((candidate) => candidate.depth === row.depth);
|
|
@@ -117,22 +118,26 @@ export function renderTaskWidgetLines(theme: Theme, projection: TaskWidgetProjec
|
|
|
117
118
|
return lines;
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
/** "Tasks" or "Tasks · <scope>" -- no more "Papyrus ·" prefix; the group's own owner header covers that. */
|
|
122
|
+
export function taskSectionLabel(projection: TaskWidgetProjection): string {
|
|
123
|
+
return projection.scopeLabel ? `Tasks · ${projection.scopeLabel}` : "Tasks";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function buildTaskWidgetSection(theme: Theme, projection: TaskWidgetProjection): WidgetSection | undefined {
|
|
127
|
+
if (projection.openTotal === 0) return undefined;
|
|
128
|
+
return { label: taskSectionLabel(projection), render: (width) => renderTaskSectionBodyLines(theme, projection, width) };
|
|
129
|
+
}
|
|
130
|
+
|
|
120
131
|
export class TaskOverlay {
|
|
121
|
-
private
|
|
122
|
-
private registered = false;
|
|
123
|
-
private tui: any | undefined;
|
|
132
|
+
private widgetGroup: PapyrusWidgetGroup | undefined;
|
|
124
133
|
private snapshot: TaskGraph = { nodes: [], rootIds: [] };
|
|
125
134
|
private projectRoot: string | undefined;
|
|
126
135
|
private sessionId: string | undefined;
|
|
127
136
|
private readonly poll = new BoundedPoll();
|
|
128
137
|
private pushChannel: PushChannelClient | undefined;
|
|
129
138
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.uiCtx = ctx;
|
|
133
|
-
this.registered = false;
|
|
134
|
-
this.tui = undefined;
|
|
135
|
-
}
|
|
139
|
+
setWidgetGroup(group: PapyrusWidgetGroup): void {
|
|
140
|
+
this.widgetGroup = group;
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
setProjectRoot(projectRoot: string): void {
|
|
@@ -162,7 +167,7 @@ export class TaskOverlay {
|
|
|
162
167
|
this.snapshot = { nodes: [], rootIds: [] };
|
|
163
168
|
}
|
|
164
169
|
try {
|
|
165
|
-
this.
|
|
170
|
+
this.widgetGroup?.requestUpdate();
|
|
166
171
|
} catch {
|
|
167
172
|
// A rendering bug must not crash the extension host over a best-effort status widget.
|
|
168
173
|
}
|
|
@@ -183,43 +188,15 @@ export class TaskOverlay {
|
|
|
183
188
|
});
|
|
184
189
|
}
|
|
185
190
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (this.snapshot.nodes.length === 0) {
|
|
191
|
-
if (this.registered) {
|
|
192
|
-
this.uiCtx.setWidget(WIDGET_KEY, undefined);
|
|
193
|
-
this.registered = false;
|
|
194
|
-
this.tui = undefined;
|
|
195
|
-
}
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (!this.registered) {
|
|
200
|
-
this.uiCtx.setWidget(
|
|
201
|
-
WIDGET_KEY,
|
|
202
|
-
(tui: any, theme: Theme) => {
|
|
203
|
-
this.tui = tui;
|
|
204
|
-
return {
|
|
205
|
-
render: (width: number) => this.renderLines(theme, width),
|
|
206
|
-
invalidate: () => {
|
|
207
|
-
// Theme changed — force re-registration
|
|
208
|
-
this.registered = false;
|
|
209
|
-
this.tui = undefined;
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
},
|
|
213
|
-
{ placement: "aboveEditor" },
|
|
214
|
-
);
|
|
215
|
-
this.registered = true;
|
|
216
|
-
} else {
|
|
217
|
-
this.tui?.requestRender?.();
|
|
218
|
-
}
|
|
191
|
+
/** Theme-free existence check -- PapyrusWidgetGroup's own eager (pre-paint) hide decision needs
|
|
192
|
+
* this without needing a theme, which is only ever available inside the widget's own render(width). */
|
|
193
|
+
hasOpenWork(): boolean {
|
|
194
|
+
return buildTaskWidgetProjection(this.snapshot).openTotal > 0;
|
|
219
195
|
}
|
|
220
196
|
|
|
221
|
-
|
|
222
|
-
|
|
197
|
+
/** undefined when there is no open work to show -- PapyrusWidgetGroup's own signal to omit this section entirely. */
|
|
198
|
+
buildSection(theme: Theme): WidgetSection | undefined {
|
|
199
|
+
return buildTaskWidgetSection(theme, buildTaskWidgetProjection(this.snapshot));
|
|
223
200
|
}
|
|
224
201
|
|
|
225
202
|
/**
|
|
@@ -240,17 +217,12 @@ export class TaskOverlay {
|
|
|
240
217
|
this.stopPolling();
|
|
241
218
|
this.pushChannel?.close();
|
|
242
219
|
this.pushChannel = undefined;
|
|
243
|
-
this.
|
|
244
|
-
this.registered = false;
|
|
245
|
-
this.tui = undefined;
|
|
246
|
-
this.uiCtx = undefined;
|
|
220
|
+
this.widgetGroup = undefined;
|
|
247
221
|
this.projectRoot = undefined;
|
|
248
222
|
this.sessionId = undefined;
|
|
249
223
|
}
|
|
250
224
|
}
|
|
251
225
|
|
|
252
|
-
const NOTE_WIDGET_KEY = "pi-papyrus-notes";
|
|
253
|
-
|
|
254
226
|
/**
|
|
255
227
|
* Deliberately simple, unlike TaskOverlay's tree: just an open-note count for this session's own
|
|
256
228
|
* CWD -- notes.list already scopes to project_root exactly (a note's projectRoot is fixed at
|
|
@@ -258,19 +230,13 @@ const NOTE_WIDGET_KEY = "pi-papyrus-notes";
|
|
|
258
230
|
* default.
|
|
259
231
|
*/
|
|
260
232
|
export class NoteOverlay {
|
|
261
|
-
private
|
|
262
|
-
private registered = false;
|
|
263
|
-
private tui: any | undefined;
|
|
233
|
+
private widgetGroup: PapyrusWidgetGroup | undefined;
|
|
264
234
|
private openCount = 0;
|
|
265
235
|
private projectRoot: string | undefined;
|
|
266
236
|
private readonly poll = new BoundedPoll();
|
|
267
237
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
this.uiCtx = ctx;
|
|
271
|
-
this.registered = false;
|
|
272
|
-
this.tui = undefined;
|
|
273
|
-
}
|
|
238
|
+
setWidgetGroup(group: PapyrusWidgetGroup): void {
|
|
239
|
+
this.widgetGroup = group;
|
|
274
240
|
}
|
|
275
241
|
|
|
276
242
|
setProjectRoot(projectRoot: string): void {
|
|
@@ -289,18 +255,80 @@ export class NoteOverlay {
|
|
|
289
255
|
this.openCount = 0;
|
|
290
256
|
}
|
|
291
257
|
try {
|
|
292
|
-
this.
|
|
258
|
+
this.widgetGroup?.requestUpdate();
|
|
293
259
|
} catch {
|
|
294
260
|
// A rendering bug must not crash the extension host over a best-effort status widget.
|
|
295
261
|
}
|
|
296
262
|
}
|
|
297
263
|
|
|
298
|
-
|
|
264
|
+
/** Theme-free existence check -- see TaskOverlay's own hasOpenWork() for why this needs to stay theme-free. */
|
|
265
|
+
hasOpenNotes(): boolean {
|
|
266
|
+
return this.openCount > 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** undefined when there are no open notes -- PapyrusWidgetGroup's own signal to omit this section entirely. */
|
|
270
|
+
buildSection(): WidgetSection | undefined {
|
|
271
|
+
return buildNoteWidgetSection(this.openCount);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
startPolling(intervalMs: number = NOTE_WIDGET_POLL_INTERVAL_MS): void {
|
|
275
|
+
this.poll.start(intervalMs, () => {
|
|
276
|
+
void this.refresh();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
stopPolling(): void {
|
|
281
|
+
this.poll.stop();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
dispose(): void {
|
|
285
|
+
this.stopPolling();
|
|
286
|
+
this.widgetGroup = undefined;
|
|
287
|
+
this.projectRoot = undefined;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const PAPYRUS_WIDGET_KEY = "pi-papyrus";
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Owns the ONE real aboveEditor widget registration for both TaskOverlay and NoteOverlay, composing
|
|
295
|
+
* their own current sections via renderWidgetSectionGroup -- "Papyrus" once, with "Tasks"/"Notes"
|
|
296
|
+
* as its own indented children (or side-by-side columns once the terminal is wide enough), instead
|
|
297
|
+
* of each overlay registering its own separate "Papyrus · <Label>" flat-header widget. Hidden
|
|
298
|
+
* entirely (fully unregistered, not just empty lines) only once NEITHER overlay has anything to
|
|
299
|
+
* show -- either overlay alone still renders its own section normally.
|
|
300
|
+
*/
|
|
301
|
+
export class PapyrusWidgetGroup {
|
|
302
|
+
private uiCtx: ExtensionUIContext | undefined;
|
|
303
|
+
private registered = false;
|
|
304
|
+
private tui: any | undefined;
|
|
305
|
+
private taskOverlay: TaskOverlay | undefined;
|
|
306
|
+
private noteOverlay: NoteOverlay | undefined;
|
|
307
|
+
|
|
308
|
+
setUI(ctx: ExtensionUIContext): void {
|
|
309
|
+
if (ctx !== this.uiCtx) {
|
|
310
|
+
this.uiCtx = ctx;
|
|
311
|
+
this.registered = false;
|
|
312
|
+
this.tui = undefined;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
setOverlays(taskOverlay: TaskOverlay, noteOverlay: NoteOverlay): void {
|
|
317
|
+
this.taskOverlay = taskOverlay;
|
|
318
|
+
this.noteOverlay = noteOverlay;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Called by either overlay after its own refresh() -- re-renders (or registers/unregisters) the shared widget. */
|
|
322
|
+
requestUpdate(): void {
|
|
299
323
|
if (!this.uiCtx) return;
|
|
300
324
|
|
|
301
|
-
|
|
325
|
+
// Eager (pre-paint) hide check, theme-free -- matches both overlays' own prior convention of
|
|
326
|
+
// deciding this immediately after a data refresh, not waiting for Pi's own next repaint to
|
|
327
|
+
// notice via renderLines()'s own (still-present, defense-in-depth) empty check below.
|
|
328
|
+
const hasContent = (this.taskOverlay?.hasOpenWork() ?? false) || (this.noteOverlay?.hasOpenNotes() ?? false);
|
|
329
|
+
if (!hasContent) {
|
|
302
330
|
if (this.registered) {
|
|
303
|
-
this.uiCtx.setWidget(
|
|
331
|
+
this.uiCtx.setWidget(PAPYRUS_WIDGET_KEY, undefined);
|
|
304
332
|
this.registered = false;
|
|
305
333
|
this.tui = undefined;
|
|
306
334
|
}
|
|
@@ -309,12 +337,13 @@ export class NoteOverlay {
|
|
|
309
337
|
|
|
310
338
|
if (!this.registered) {
|
|
311
339
|
this.uiCtx.setWidget(
|
|
312
|
-
|
|
340
|
+
PAPYRUS_WIDGET_KEY,
|
|
313
341
|
(tui: any, theme: Theme) => {
|
|
314
342
|
this.tui = tui;
|
|
315
343
|
return {
|
|
316
|
-
render: (width: number) =>
|
|
344
|
+
render: (width: number) => this.renderLines(theme, width),
|
|
317
345
|
invalidate: () => {
|
|
346
|
+
// Theme changed — force re-registration
|
|
318
347
|
this.registered = false;
|
|
319
348
|
this.tui = undefined;
|
|
320
349
|
},
|
|
@@ -328,23 +357,42 @@ export class NoteOverlay {
|
|
|
328
357
|
}
|
|
329
358
|
}
|
|
330
359
|
|
|
331
|
-
|
|
332
|
-
this.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
360
|
+
private renderLines(theme: Theme, width: number): string[] {
|
|
361
|
+
const sections = [this.taskOverlay?.buildSection(theme), this.noteOverlay?.buildSection()].filter(
|
|
362
|
+
(section): section is WidgetSection => section !== undefined,
|
|
363
|
+
);
|
|
364
|
+
if (sections.length === 0) {
|
|
365
|
+
// Hide entirely (unregister), not just an empty-but-registered widget -- matches both
|
|
366
|
+
// overlays' own prior "nothing open" convention.
|
|
367
|
+
if (this.uiCtx && this.registered) {
|
|
368
|
+
this.uiCtx.setWidget(PAPYRUS_WIDGET_KEY, undefined);
|
|
369
|
+
this.registered = false;
|
|
370
|
+
this.tui = undefined;
|
|
371
|
+
}
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
return renderWidgetSectionGroup({
|
|
375
|
+
owner: vehicleWidgetOwner(PAPYRUS_VEHICLE_NAME),
|
|
376
|
+
sections,
|
|
377
|
+
ownerStyle: (s) => theme.fg("muted", s),
|
|
378
|
+
// Without this, renderWidgetSectionGroup falls back to malevich's own asciiTextMeasure,
|
|
379
|
+
// which is documented as having no ANSI-escape awareness -- every task row here is real
|
|
380
|
+
// ANSI-colored content (theme.fg() for focus/status glyphs), so the naive measure
|
|
381
|
+
// mismeasures its true visible width, breaking the two-column grid's own alignment (a
|
|
382
|
+
// real, live incident: the SplitPane border landed mid-line instead of after the actual
|
|
383
|
+
// column boundary). See tool-rendering/artifact-card.ts's own identical measure -- same
|
|
384
|
+
// fix, reused rather than re-derived.
|
|
385
|
+
measure: artifactCardMeasure,
|
|
386
|
+
}).render(width);
|
|
339
387
|
}
|
|
340
388
|
|
|
341
389
|
dispose(): void {
|
|
342
|
-
this.
|
|
343
|
-
this.uiCtx?.setWidget(NOTE_WIDGET_KEY, undefined);
|
|
390
|
+
this.uiCtx?.setWidget(PAPYRUS_WIDGET_KEY, undefined);
|
|
344
391
|
this.registered = false;
|
|
345
392
|
this.tui = undefined;
|
|
346
393
|
this.uiCtx = undefined;
|
|
347
|
-
this.
|
|
394
|
+
this.taskOverlay = undefined;
|
|
395
|
+
this.noteOverlay = undefined;
|
|
348
396
|
}
|
|
349
397
|
}
|
|
350
398
|
|
|
@@ -639,6 +687,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
639
687
|
]);
|
|
640
688
|
let overlay: TaskOverlay | undefined;
|
|
641
689
|
let noteOverlay: NoteOverlay | undefined;
|
|
690
|
+
let widgetGroup: PapyrusWidgetGroup | undefined;
|
|
642
691
|
|
|
643
692
|
pi.registerCommand("tasks", {
|
|
644
693
|
description: "Browse and manage Papyrus tasks (interactive)",
|
|
@@ -728,16 +777,22 @@ export default async function (pi: ExtensionAPI) {
|
|
|
728
777
|
// keystrokes from the moment that tool call happens to begin, missing typing already in
|
|
729
778
|
// progress when it started (the exact case Discuss's typing-courtesy wait protects against).
|
|
730
779
|
ensureTypingCourtesyTracking(ctx.ui);
|
|
780
|
+
widgetGroup ??= new PapyrusWidgetGroup();
|
|
781
|
+
widgetGroup.setUI(ctx.ui);
|
|
782
|
+
|
|
731
783
|
overlay ??= new TaskOverlay();
|
|
732
|
-
overlay.
|
|
784
|
+
overlay.setWidgetGroup(widgetGroup);
|
|
733
785
|
overlay.setProjectRoot(ctx.cwd);
|
|
734
786
|
overlay.setSessionId(ctx.sessionManager.getSessionId());
|
|
735
|
-
await overlay.refresh();
|
|
736
|
-
overlay.startPolling(TASK_WIDGET_POLL_INTERVAL_MS);
|
|
737
787
|
|
|
738
788
|
noteOverlay ??= new NoteOverlay();
|
|
739
|
-
noteOverlay.
|
|
789
|
+
noteOverlay.setWidgetGroup(widgetGroup);
|
|
740
790
|
noteOverlay.setProjectRoot(ctx.cwd);
|
|
791
|
+
|
|
792
|
+
widgetGroup.setOverlays(overlay, noteOverlay);
|
|
793
|
+
|
|
794
|
+
await overlay.refresh();
|
|
795
|
+
overlay.startPolling(TASK_WIDGET_POLL_INTERVAL_MS);
|
|
741
796
|
await noteOverlay.refresh();
|
|
742
797
|
noteOverlay.startPolling(NOTE_WIDGET_POLL_INTERVAL_MS);
|
|
743
798
|
});
|
|
@@ -756,6 +811,8 @@ export default async function (pi: ExtensionAPI) {
|
|
|
756
811
|
overlay = undefined;
|
|
757
812
|
noteOverlay?.dispose();
|
|
758
813
|
noteOverlay = undefined;
|
|
814
|
+
widgetGroup?.dispose();
|
|
815
|
+
widgetGroup = undefined;
|
|
759
816
|
try {
|
|
760
817
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
761
818
|
await callService("session.release", { session_id: sessionId, ...sessionSecretField(sessionId) });
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { vehicleWidgetTitle } from "@danypops/vehicle-client-pi/widget-header";
|
|
3
|
-
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
1
|
+
import type { WidgetSection } from "malevich-tui-components";
|
|
5
2
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Notes has no body rows of its own -- the open count lives on the section's own label. undefined
|
|
5
|
+
* (hide the section entirely) at 0, matching TaskOverlay's own "nothing open" hiding rule.
|
|
6
|
+
* Deliberately one uniform label string, not an embedded differently-colored count: the caller's
|
|
7
|
+
* own section `style` wraps this whole branch line, and nesting a second color inside it here
|
|
8
|
+
* would risk incorrect ANSI reset composition -- every other section label in this widget group
|
|
9
|
+
* stays one tone for the same reason.
|
|
10
|
+
*/
|
|
11
|
+
export function buildNoteWidgetSection(openCount: number): WidgetSection | undefined {
|
|
12
|
+
if (openCount === 0) return undefined;
|
|
13
|
+
return { label: `Notes ${openCount}`, render: () => [] };
|
|
11
14
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-papyrus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.1",
|
|
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.17.1",
|
|
25
25
|
"@danypops/vehicle-server": "^0.25.2",
|
|
26
26
|
"beautiful-mermaid": "1.1.3",
|
|
27
|
-
"malevich-tui-components": "^0.
|
|
27
|
+
"malevich-tui-components": "^0.31.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@danypops/pi-tui-harness": "^0.0.2",
|