@bismawy/pi-arnative 0.5.2 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.3] - 2026-10-14
4
+
5
+ ### Fixed
6
+ - A new session showed the previous session's token counts. The footer's 2s `getUsage` cache is keyed on branch length, and the module is not re-imported by `/new`, so a fresh session whose branch happened to reach the same length read the cached sums instead of its own. The cache is now cleared in the non-reload `session_start` branch (`extensions/footer.ts`).
7
+ - **The cell was invisible: the footer still matched the value on `jev-eye`.** The value became `Eye: ● ON (Clef Flash)`, which contains no `jev-eye` anywhere, so `value.includes("jev-eye")` went false and the whole cell fell through to the generic path — no icon, no dim backend suffix, just grey text. The router now recognises the cell by the label its value carries (`isJevEyeStatus`, `^\s*Eye\s*:`), and the self-check pins that rule from both sides: every mode's real value is recognised, `Jev: ● ON` and `MCP: 2 servers enabled` are not, and the value is asserted to contain no internal key name. Proved negative: reverting the rule to the substring test makes both `node extensions/footer.ts` and pi-jev-eye's `render-check` fail.
8
+ - **One space after the glyph, not two.** The two spaces existed because the previous glyph's ink overran its cell (`\uedcf`); with `\uf21b` in place they read as a gap in ` Eye: ● ON (Clef Flash)`. Pinned in pi-jev-eye's render check as one space, with an explicit assert that the glyph is not followed by two.
9
+
10
+ ### Changed
11
+ - **The cell glyph is U+F21B (`fa-user_secret`), chosen by the operator.** U+EDCF (`fa-user_shield`) was the previous one; both exist in JetBrainsMono Nerd Font, and the codepoint is pinned by number in pi-jev-eye's render check because a Nerd glyph that renders blank on another platform is the bug the ponytail cell had (`\uf51` -> `\uef04`).
12
+ - **The pi-jev-eye status cell renders `Eye: ● ON (Backend)`, parsed from the value instead of hardcoded.** The format is `Eye: ● ON (Clef Flash)` when a Clef model answers and `Eye: ● ON (Jev)` when TypeSafe or OpenRouter does. `jevEyeStatusLine()` reads the label before the first colon and the backend from the trailing parentheses — so nothing about the cell is a literal in this repo, and an older pi-jev-eye value with no suffix gains no guessed one.
13
+ - **The backend suffix takes `dim`, while `Eye:` and the state keep their tiers.** A backend painted like the label would bury `Eye: ● ON`, which is the case this cell exists for; the spaced `Clef Flash` (not a nested `clef (flash)`) reads as one label on a single line.
14
+ - **The status line moved out of the render closure**, next to `ponytailStatusLine`, so the self-check in `node extensions/footer.ts` drives the exact path the footer uses instead of a copy of it.
15
+ - Dropped two unused imports (`CustomMessageComponent`, `isPointerOrFocusInput`) and corrected three stale statements: the usage estimate comment named `prices.json` where the code reads `models.json`, the footer header listed two of the three footer presets, and the README's local-test snippet ran one extension where `npm run dev` runs all eight (`extensions/ui-render-tweaks.ts`, `lib/usage-store.ts`, `extensions/footer.ts`, `test/footer-minimal.test.ts`, `README.md`).
16
+
17
+ ### Added
18
+ - Self-check for the pi-jev-eye cell: both requested forms (`Eye: ● ON (Clef Flash)`, `Eye: ● ON (Jev)`), the tierless `(Clef)`, an upstream `(Clef Omni)`, `OFF` taking the dim bullet, `REVIEW` keeping its warning bullet, and a suffix-less value drawing no parenthesis.
19
+
3
20
  ## [0.5.2] - 2026-10-13
4
21
 
5
22
  ### Fixed
package/README.md CHANGED
@@ -46,7 +46,7 @@ Apply a theme via `/settings` → **Theme** → `arnative` (or variant). Require
46
46
 
47
47
  To test locally:
48
48
  ```bash
49
- pi --extension ./extensions/footer.ts
49
+ npm run dev
50
50
  ```
51
51
 
52
52
  ## Shortcuts
@@ -2,7 +2,8 @@
2
2
  * Arnative footer (fixed 3-row grid). Row 1: cwd | active time | branch — model + thinking;
3
3
  * rows 2-3: extension status | tok/s · cache · token, each side wrapped in its half of the
4
4
  * width. The clock counts active time, not wall-clock age — see {@link ActiveClock}.
5
- * Preset (`/arnative footers`), live: "Arnative (Full)" (default) or "Pi (system)".
5
+ * Preset (`/arnative footers`), live: "Arnative (Full)" (default), "Arnative (Minimal)"
6
+ * or "Pi (system)".
6
7
  */
7
8
  import type { AssistantMessage } from "@earendil-works/pi-ai";
8
9
  import { CustomEditor, FooterComponent, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
@@ -210,6 +211,55 @@ export function currentToolVerb(active: ReadonlyMap<string, string>): string {
210
211
  return verb;
211
212
  }
212
213
 
214
+ /**
215
+ * The pi-jev-eye status cell: `Eye: ● MODE (Backend)`. pi-jev-eye writes the value plain because a status
216
+ * is a fact, not a layout; this decides the painting.
217
+ *
218
+ * The trailing parenthesised backend is a modifier, so it takes `dim` while the `Eye:` label and the state
219
+ * keep the soft and state tiers — that is what makes `(Clef Flash)` recede and `Eye: ● ON` stay readable
220
+ * at a glance. The backend name itself is read out of the value, never hardcoded here: the extension
221
+ * reports the active backend from `/jev-eye models`, so the footer cannot know it in advance.
222
+ */
223
+ /**
224
+ * Is this status value pi-jev-eye's own cell? It is matched on the LABEL the value carries (`Eye:`), not on
225
+ * the internal key name: the value is `Eye: ● ON (Clef Flash)` and contains no `jev-eye` anywhere, so the
226
+ * old `value.includes("jev-eye")` test went false and the whole cell fell through to the generic path —
227
+ * which is exactly how it silently lost its icon and its dim backend suffix.
228
+ *
229
+ * Recognising the label keeps the rule honest: if pi-jev-eye renames its label, this test fails loudly in
230
+ * the self-check below instead of degrading to a plain grey cell.
231
+ */
232
+ export const isJevEyeStatus = (status: string): boolean => /^\s*Eye\s*:/i.test(stripAnsi(status));
233
+
234
+ export function jevEyeStatusLine(
235
+ status: string,
236
+ acc: (text: string) => string,
237
+ dim: (text: string) => string,
238
+ soft: (text: string) => string,
239
+ theme?: { fg?: (color: string, text: string) => string },
240
+ ): string {
241
+ const clean = stripAnsi(status);
242
+ const isOff = clean.includes("OFF") || clean.includes("\u25CB");
243
+ const isReview = /REVIEW/i.test(clean);
244
+ let bullet = isOff ? dim("\u25CB") : acc("\u25CF");
245
+ if (isReview && typeof theme?.fg === "function") {
246
+ try {
247
+ bullet = theme.fg("warning", "\u25CF");
248
+ } catch {
249
+ bullet = acc("\u25CF");
250
+ }
251
+ }
252
+ const label = isOff ? "OFF" : isReview ? "REVIEW" : "ON";
253
+ // `Eye: ● ON (Clef Flash)` -> the base before the first colon, the backend in the trailing parentheses.
254
+ const base = clean.split(":")[0]!.trim() || "Eye";
255
+ const at = clean.lastIndexOf("(");
256
+ const backend = at >= 0 && clean.trimEnd().endsWith(")") ? clean.slice(at, clean.lastIndexOf(")") + 1).trim() : "";
257
+ // One space, not the two the previous glyph needed: \uf21b's ink fits its own cell, so the label sits
258
+ // right after it. The space before the backend stays outside the dim run, so the modifier does not
259
+ // start with a painted blank.
260
+ return `${acc("\uf21b")} ${soft(`${base}:`)} ${bullet} ${soft(label)}${backend ? ` ${dim(backend)}` : ""}`;
261
+ }
262
+
213
263
  /**
214
264
  * The ponytail status cell. The horse lives at U+EF04 (fa-horse). U+EF51
215
265
  * (fa-circle_radiation) carries no outline in the Windows Nerd Font build, so it
@@ -848,6 +898,10 @@ export default function (pi: ExtensionAPI) {
848
898
  currentModel = ctx.model;
849
899
  currentThinkingLevel = ctx.thinkingLevel;
850
900
  git = null;
901
+ // A new session reuses this module (no re-import), so the 2s cache would serve the
902
+ // previous session's sums whenever the fresh branch happens to be the same length.
903
+ usageScanAt = 0;
904
+ usageScanLen = -1;
851
905
  } else {
852
906
  currentModel = (globalThis as Record<symbol, any>)[MODEL_KEY] ?? ctx.model;
853
907
  currentThinkingLevel = (globalThis as Record<symbol, any>)[THINKING_KEY] ?? ctx.thinkingLevel;
@@ -948,22 +1002,7 @@ export default function (pi: ExtensionAPI) {
948
1002
  return `${acc("\uf233")} ${secondarySoft(m ? m[0] : rest || "MCP")}`;
949
1003
  }
950
1004
  if (s.includes("ponytail")) return ponytailStatusLine(s, acc, dim, secondarySoft);
951
- if (s.includes("jev-eye")) {
952
- const isOff = s.includes("OFF") || s.includes("○");
953
- const isReview = /REVIEW/i.test(s);
954
- let bullet = isOff ? dim("○") : acc("●");
955
- if (isReview) {
956
- try {
957
- bullet = theme.fg("warning", "●");
958
- } catch {
959
- bullet = acc("●");
960
- }
961
- }
962
- const label = isOff ? "OFF" : isReview ? "REVIEW" : "ON";
963
- // Two spaces: \uedcf's ink is wider than MCP's \uf233 or ponytail's \uef04,
964
- // so it needs the extra cell to clear the label.
965
- return `${acc("\uedcf")} ${secondarySoft("Jev:")} ${bullet} ${secondarySoft(label)}`;
966
- }
1005
+ if (isJevEyeStatus(s)) return jevEyeStatusLine(s, acc, dim, secondarySoft, theme);
967
1006
  let clean = stripAnsi(s);
968
1007
  clean = clean.replace(/\uFFFD/g, "").replace(/\?{1,2}\s*/g, "");
969
1008
  return secondarySoft(clean.trim());
@@ -1438,6 +1477,58 @@ if (isMain(import.meta.url)) {
1438
1477
  assert(!ponyLine.includes("\uef51"), "the blank \\uef51 glyph must never come back");
1439
1478
  assert(ponytailStatusLine("ponytail: LITE", (s) => s, (s) => s, (s) => s).includes("LITE"), "the ponytail mode is read from the status text");
1440
1479
 
1480
+ // Self-check: the status router must recognise the pi-jev-eye cell by the label its VALUE carries.
1481
+ // The old test was `value.includes("jev-eye")`, and the value is `Eye: ● ON (Clef Flash)` — no
1482
+ // `jev-eye` anywhere — so the branch went false and the cell silently lost its icon and dim suffix.
1483
+ // This pins the recognition rule itself, at the same place the router uses it.
1484
+ assert(isJevEyeStatus("Eye: ● ON (Clef Flash)"), "the value pi-jev-eye publishes is recognised as its own cell");
1485
+ assert(isJevEyeStatus("Eye: ○ OFF (Jev)"), "and so is every other mode's value");
1486
+ assert(!isJevEyeStatus("Jev: ● ON"), "the previous format is not claimed as this cell");
1487
+ assert(!isJevEyeStatus("MCP: 2 servers enabled"), "and neither is another extension's status");
1488
+ assert(
1489
+ !stripAnsi("Eye: ● ON (Clef Flash)").includes("jev-eye"),
1490
+ "the value carries no internal key name, which is what broke the old matcher",
1491
+ );
1492
+
1493
+ // Self-check: the pi-jev-eye status cell. The value is `Eye: ● MODE (Backend)`; the backend is read out
1494
+ // of that string rather than hardcoded, and it takes `dim` while the base label and the state stay
1495
+ // readable — a backend painted like the label would bury `Eye: ● ON`, which is the case this cell
1496
+ // exists for. Both footer formats the operator asked for are pinned here.
1497
+ const jevPlain = (s: string) => s;
1498
+ const jevDim = (s: string) => `[dim]${s}[/dim]`;
1499
+ assert(
1500
+ jevEyeStatusLine("Eye: ● ON (Clef Flash)", jevPlain, jevDim, jevPlain, undefined) ===
1501
+ "\uf21b" + " Eye: ● ON [dim](Clef Flash)[/dim]",
1502
+ "Clef Flash is dim while `Eye:` and the state are not",
1503
+ );
1504
+ assert(
1505
+ jevEyeStatusLine("Eye: ● ON (Jev)", jevPlain, jevDim, jevPlain, undefined) === "\uf21b" + " Eye: ● ON [dim](Jev)[/dim]",
1506
+ "TypeSafe and OpenRouter both read as (Jev)",
1507
+ );
1508
+ assert(
1509
+ jevEyeStatusLine("Eye: ● ON (Clef)", jevPlain, jevDim, jevPlain, undefined) === "\uf21b" + " Eye: ● ON [dim](Clef)[/dim]",
1510
+ "the untiered Clef model draws no tier word",
1511
+ );
1512
+ assert(
1513
+ jevEyeStatusLine("Eye: ● ON (Clef Omni)", jevPlain, jevDim, jevPlain, undefined) === "\uf21b" + " Eye: ● ON [dim](Clef Omni)[/dim]",
1514
+ "a model added upstream draws its own tier",
1515
+ );
1516
+ assert(
1517
+ jevEyeStatusLine("Eye: ○ OFF (Jev)", jevPlain, jevDim, jevPlain, undefined) === "\uf21b" + " Eye: [dim]○[/dim] OFF [dim](Jev)[/dim]",
1518
+ "OFF takes the dim bullet",
1519
+ );
1520
+ assert(
1521
+ jevEyeStatusLine("Eye: ● REVIEW (Clef Flash)", jevPlain, jevDim, jevPlain, { fg: (color, text) => `<${color}>${text}</${color}>` }) ===
1522
+ "\uf21b" + " Eye: <warning>●</warning> REVIEW [dim](Clef Flash)[/dim]",
1523
+ "REVIEW keeps the warning bullet",
1524
+ );
1525
+ // A value with no parenthesised backend (an older pi-jev-eye) must not gain a stray `(`: the suffix is
1526
+ // drawn only when the value actually carries one.
1527
+ assert(
1528
+ jevEyeStatusLine("Eye: ● ON", jevPlain, jevDim, jevPlain, undefined) === "\uf21b" + " Eye: ● ON",
1529
+ "no backend suffix means no parenthesis, never a guessed one",
1530
+ );
1531
+
1441
1532
  // Self-check: session age survives /resume (header timestamp wins over now).
1442
1533
  const NOW = Date.parse("2026-10-08T12:00:00.000Z");
1443
1534
  assert(resolveSessionStartMs("resume", "2026-10-08T09:00:00.000Z", NOW) === Date.parse("2026-10-08T09:00:00.000Z"), "resume restores the session age from the header");
@@ -16,7 +16,6 @@
16
16
  */
17
17
  import {
18
18
  CompactionSummaryMessageComponent,
19
- CustomMessageComponent,
20
19
  ExtensionSelectorComponent,
21
20
  getMarkdownTheme,
22
21
  initTheme,
@@ -117,7 +117,7 @@ export interface UsageSummary {
117
117
  cost: number;
118
118
  /** Provider-reported cost only (never includes the estimate). */
119
119
  reportedCost: number;
120
- /** Cost reconstructed from `prices.json`. Shown with a `~`, never folded into `cost`. */
120
+ /** Cost reconstructed from `models.json` rates. Shown with a `~`, never folded into `cost`. */
121
121
  estimatedCost: number;
122
122
  };
123
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bismawy/pi-arnative",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Refined aesthetics. Cohesive tools. Built for Pi. Warm themes, rounded tool boxes, tabbed header, compact footer and a /usage dashboard.",
5
5
  "keywords": [
6
6
  "pi-package",