@gonrocca/zero-pi 0.1.32 → 0.1.33

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.
@@ -134,18 +134,58 @@ export function bannerBlock(width: number): string[] {
134
134
  return [ornament(width), "", ...renderLogo(width), "", center(tag, width), ornament(width)];
135
135
  }
136
136
 
137
+ /** The slice of pi's UI surface this extension uses. */
138
+ interface PiUI {
139
+ setWidget(id: string, lines: string[]): void;
140
+ }
141
+ interface PiSessionContext {
142
+ ui?: PiUI;
143
+ }
144
+ interface PiAPI {
145
+ on(event: string, handler: (event: unknown, ctx: PiSessionContext) => void): void;
146
+ }
147
+
148
+ /** Stable widget id — re-installing with the same id replaces the previous lines. */
149
+ const WIDGET_ID = "zero-banner";
150
+
137
151
  /**
138
- * The pi extension entry point. Renders the banner exactly once, synchronously,
139
- * at load time. Any failure is swallowed — a banner must never break a session.
152
+ * Install (or refresh) the banner as a pi-managed widget above the editor.
153
+ *
154
+ * Using `ctx.ui.setWidget` instead of a one-shot `stdout.write` means pi owns
155
+ * the lines and redraws them on every resize, every reload, every session
156
+ * restart — so the banner survives maximize/minimize instead of vanishing into
157
+ * scrollback. The crash this file's earlier comment warned about was the
158
+ * animated 140 ms re-render loop; a static widget set once per `session_start`
159
+ * has no timer and re-uses the same id, so it never spams.
140
160
  */
141
- export default function register(_pi?: unknown): void {
161
+ function installWidget(ctx: PiSessionContext): void {
162
+ if (!ctx?.ui || typeof ctx.ui.setWidget !== "function") return;
163
+ if (process.env.NO_COLOR) return;
164
+ const cols = process.stdout?.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
165
+ ctx.ui.setWidget(WIDGET_ID, bannerBlock(cols));
166
+ }
167
+
168
+ /**
169
+ * The pi extension entry point.
170
+ *
171
+ * Hooks `session_start` (fires on startup, reload, new, resume, fork) and
172
+ * installs the banner as a pi-managed widget above the editor — so pi keeps it
173
+ * rendered and redraws it on resize. Disabled with `ZERO_HEADER=off`.
174
+ * Defensive: any failure of registration or the widget install is swallowed —
175
+ * a banner must never break a pi session.
176
+ */
177
+ export default function register(pi?: unknown): void {
142
178
  try {
143
179
  if ((process.env.ZERO_HEADER ?? "").trim().toLowerCase() === "off") return;
144
- const stream = process.stdout;
145
- if (!stream || !stream.isTTY || process.env.NO_COLOR) return;
146
- const width = stream.columns && stream.columns > 0 ? stream.columns : 80;
147
- stream.write("\n" + bannerBlock(width).join("\n") + "\n\n");
180
+ if (!pi || typeof (pi as PiAPI).on !== "function") return;
181
+ (pi as PiAPI).on("session_start", (_event, ctx) => {
182
+ try {
183
+ installWidget(ctx);
184
+ } catch {
185
+ // A widget install failure must never break a pi session.
186
+ }
187
+ });
148
188
  } catch {
149
- // A banner failure must never break a pi session.
189
+ // Registration itself must never break a pi session.
150
190
  }
151
191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonrocca/zero-pi",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "zero-pi — an installable layer for pi (pi.dev): the zero spec-driven development workflow, per-phase model autotune, and skill auto-learning. Adds capability to pi without modifying pi.",
5
5
  "type": "module",
6
6
  "keywords": [