@bismawy/pi-arnative 0.3.9 → 0.3.10

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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.10] - 2026-10-11
4
+
5
+ ### Fixed
6
+ - The ponytail icon rendered as a blank cell on Windows. The status drew `U+EF51` (`fa-circle_radiation`), a codepoint the Windows JetBrainsMono Nerd Font build lists in its cmap but rasterizes with no outline, so the footer left an empty gap before `ponytail:`. It now draws `U+EF04` (`fa-horse`), which renders on both platforms. The cell also spends one space after the icon instead of two, matching `MCP:`. A self-check pins the codepoint, so an edit back to an invisible glyph fails `bun run test` instead of shipping silently (`extensions/footer.ts`).
7
+
3
8
  ## [0.3.9] - 2026-10-08
4
9
 
5
10
  ### Fixed
@@ -145,6 +145,25 @@ export function currentToolVerb(active: ReadonlyMap<string, string>): string {
145
145
  return verb;
146
146
  }
147
147
 
148
+ /**
149
+ * The ponytail status cell. The horse lives at U+EF04 (fa-horse). U+EF51
150
+ * (fa-circle_radiation) carries no outline in the Windows Nerd Font build, so it
151
+ * renders as a blank cell there and the status silently loses its icon; U+EF04 does
152
+ * draw. Kept out of the render closure so the self-check drives this exact path.
153
+ */
154
+ export function ponytailStatusLine(
155
+ status: string,
156
+ acc: (text: string) => string,
157
+ dim: (text: string) => string,
158
+ soft: (text: string) => string,
159
+ ): string {
160
+ const bullet = status.includes("\u25CF") ? acc("\u25CF") : dim("\u25CB");
161
+ let mode = "FULL";
162
+ if (/LITE/i.test(status)) mode = "LITE";
163
+ else if (/ULTRA/i.test(status)) mode = "ULTRA";
164
+ return `${acc("\uef04")} ${soft("ponytail:")} ${bullet} ${soft(mode)}`;
165
+ }
166
+
148
167
  type RunResult = { ok: boolean; out: string };
149
168
  /** Distinguishes "git said nothing" from "git could not run" — the caller must not
150
169
  * invent a clean tree when git is absent or the repo is not ours. */
@@ -743,15 +762,7 @@ export default function (pi: ExtensionAPI) {
743
762
  const m = rest.match(/MCP:\s*\d+\s*servers?\s*enabled/i);
744
763
  return `${acc("\uf233")} ${soft(m ? m[0] : rest || "MCP")}`;
745
764
  }
746
- if (s.includes("ponytail")) {
747
- const isActive = s.includes("●");
748
- const bullet = isActive ? acc("●") : dim("○");
749
- let mode = "FULL";
750
- if (/LITE/i.test(s)) mode = "LITE";
751
- else if (/ULTRA/i.test(s)) mode = "ULTRA";
752
- else if (/FULL/i.test(s)) mode = "FULL";
753
- return `${acc("\uef51")} ${soft("ponytail:")} ${bullet} ${soft(mode)}`;
754
- }
765
+ if (s.includes("ponytail")) return ponytailStatusLine(s, acc, dim, soft);
755
766
  if (s.includes("jev-eye")) {
756
767
  const isOff = s.includes("OFF") || s.includes("○");
757
768
  const isReview = /REVIEW/i.test(s);
@@ -764,6 +775,8 @@ export default function (pi: ExtensionAPI) {
764
775
  }
765
776
  }
766
777
  const label = isOff ? "OFF" : isReview ? "REVIEW" : "ON";
778
+ // Two spaces: \uedcf's ink is wider than MCP's \uf233 or ponytail's \uef04,
779
+ // so it needs the extra cell to clear the label.
767
780
  return `${acc("\uedcf")} ${soft("Jev:")} ${bullet} ${soft(label)}`;
768
781
  }
769
782
  let clean = stripAnsi(s);
@@ -1216,6 +1229,14 @@ if (isMain(import.meta.url)) {
1216
1229
  const fmnRes = formatModelName({ id: "claude-3-7-sonnet", name: "Sonnet" }, "low", (s) => s, (s) => s, (s) => s);
1217
1230
  assert(fmnRes.includes("\uee9c Low"), "formatModelName includes \\uee9c Low thinking indicator");
1218
1231
 
1232
+ // Self-check: the ponytail glyph. \uef51 carries no outline in the Windows Nerd Font
1233
+ // build and renders blank there, so the status silently loses its icon (this is what
1234
+ // a past commit did). Pin the codepoint so a swap back to an invisible one fails here.
1235
+ const ponyLine = ponytailStatusLine("⚡ ponytail: FULL", (s) => s, (s) => s, (s) => s);
1236
+ assert(ponyLine.includes("\uef04"), "the ponytail status renders the \\uef04 horse, not the blank \\uef51");
1237
+ assert(!ponyLine.includes("\uef51"), "the blank \\uef51 glyph must never come back");
1238
+ assert(ponytailStatusLine("ponytail: LITE", (s) => s, (s) => s, (s) => s).includes("LITE"), "the ponytail mode is read from the status text");
1239
+
1219
1240
  // Self-check: session age survives /resume (header timestamp wins over now).
1220
1241
  const NOW = Date.parse("2026-10-08T12:00:00.000Z");
1221
1242
  assert(resolveSessionStartMs("resume", "2026-10-08T09:00:00.000Z", NOW, NOW) === Date.parse("2026-10-08T09:00:00.000Z"), "resume restores the session age from the header");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bismawy/pi-arnative",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
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",