@bismawy/pi-arnative 0.2.8 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.9] - 2026-09-30
4
+
5
+ ### Fixed
6
+ - The startup `[Context]`, `[Skills]`, `[Extensions]` and `[Themes]` lists stacked under the header again on pi ≥ 0.99: pi replaced `ExpandableText`'s `getCollapsedText`/`getExpandedText` fields with a `build` callback plus a `state` object, so the section detector matched nothing and the tabs stayed at `[0]`. Both shapes are read now (`sectionBodyOf`), and the same applies to the `[Extension issues]` box, which read the stale `child.text` snapshot instead of the theme-aware `build()` output.
7
+ - Fixed NPM version badge in `README.md` to reference `@bismawy/pi-arnative` instead of placeholder `react`.
8
+
9
+ ---
10
+
3
11
  ## [0.2.8] - 2026-09-28
4
12
 
5
13
  ### Fixed
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Arnative
2
2
 
3
- A visual layer and tool styling tailored to your vibe coding.
3
+ Refined aesthetics. Cohesive tools. Built for Pi.
4
4
 
5
5
  [![Custom badge](https://shieldcn.dev/badge/pi-%20Packages.svg?variant=outline&size=xs&logo=ri%3APiPiBold)](https://pi.dev/packages/@bismawy/pi-arnative)
6
- [![badge](https://shieldcn.dev/npm/react.svg?variant=outline&size=xs)](https://www.npmjs.com/package/@bismawy/pi-arnative)
6
+ [![badge](https://shieldcn.dev/npm/@bismawy/pi-arnative.svg?variant=outline&size=xs)](https://www.npmjs.com/package/@bismawy/pi-arnative)
7
7
  [![license](https://shieldcn.dev/github/bismawy/pi-arnative/license.svg?variant=outline&size=xs)](https://github.com/bismawy/pi-arnative)
8
8
 
9
9
  ![Arnative: tabbed header, rounded tool boxes, transcript clock and /usage dashboard](https://raw.githubusercontent.com/bismawy/pi-arnative/main/assets/pi-arnative.webp)
@@ -35,32 +35,23 @@ pi --extension ./extensions/footer.ts
35
35
 
36
36
  ## Shortcuts
37
37
 
38
- | Key / Action | Result |
38
+ | Key (Linux / Win) | Action |
39
39
  | --- | --- |
40
+ | `ctrl+alt+t` · `alt+t` | Cycle header tabs |
41
+ | `ctrl+alt+r` · `alt+r` | Quick reload (`/reload`) |
42
+ | `ctrl+alt+n` · `alt+n` | New session (`/new`) — requires keybinding below |
40
43
  | `/usage` | Show session usage & token metrics |
41
- | `ctrl+alt+t` (`alt+t` on Windows/WSL) | Cycle header tabs (Directory → Model → Context → Skills → Extensions → Themes → Shortcut) |
42
- | `ctrl+alt+n` (`alt+n` on Windows/WSL) | Start a new session (`/new`) — see [Keybindings](#keybindings) |
43
- | `ctrl+alt+r` (`alt+r` on Windows/WSL) | Quick reload runtime (`/reload`) |
44
44
  | Click tab | Open selected header tab |
45
45
  | Click tool / `ctrl+e` | Toggle tool output box |
46
46
 
47
- Same cheatsheet in the header's **Shortcut** tab — it shows the variant your platform actually uses (on macOS `alt` reads `option`); on narrow terminals the rightmost tabs drop off first.
47
+ > **Notes:**
48
+ > - **Windows / WSL:** Windows Terminal aliases `Ctrl+Alt` to AltGr; use `alt+…` (or set `"altGrAliasing": false` in WT profile).
49
+ > - **macOS:** uses `ctrl+option+…` (plain Option composes special characters).
50
+ > - **Herdr / tmux glitch:** if shortcuts feel delayed or type stray letters, set `PI_TUI_ESC_TIMEOUT=100` in your shell profile.
48
51
 
49
- ### Platform notes
52
+ ### Keybindings
50
53
 
51
- - **Windows / WSL:** Windows Terminal ≥ 1.24 aliases every `Ctrl+Alt` combo to AltGr ([microsoft/terminal#20052](https://github.com/microsoft/terminal/pull/20052)), so the legacy `ESC` + control-byte encoding never leaves the terminal. Both variants are registered there; `alt+…` is what pi itself uses for the same reason (`alt+v` paste image, `alt+p` cycle model). Setting `"altGrAliasing": false` in your Windows Terminal profile brings `ctrl+alt+…` back.
52
- - **macOS:** only `ctrl+option+…` is registered — plain Option composes characters (`Option+r` = ®) unless “Use Option as Meta key” is on. Works in iTerm2 and Terminal.app as-is.
53
- - **Linux desktops:** `ctrl+alt+t` is “launch terminal” on Ubuntu/Fedora GNOME, which eats the key before pi sees it. Rebind it there if you want tab cycling.
54
- - **Multiplexers (Herdr/tmux) + slow PTYs:** every `ctrl+alt+…` / `alt+…` shortcut is sent as `ESC` followed by a byte. pi waits only ~10 ms for that second byte before treating a lone `ESC` as the Escape key — and an Escape mid-turn aborts it, leaving a stray `r`/`t`/`n` in the editor. That is why the shortcut can look “glitchy” and why it is intermittent. Raise the window with `PI_TUI_ESC_TIMEOUT=100` in the environment that launches pi (same knob pi auto-applies over SSH, at the cost of a ~100 ms delay before a real Escape registers):
55
-
56
- ```powershell
57
- $env:PI_TUI_ESC_TIMEOUT = 100; pi
58
- ```
59
-
60
- ## Keybindings
61
-
62
- `ctrl+alt+n` is a plain user keybinding, not a package shortcut — pi-arnative never registers it.
63
- Add it once to `~/.pi/agent/keybindings.json` (the list form keeps Linux and Windows working):
54
+ Add to `~/.pi/agent/keybindings.json` to bind `app.session.new`:
64
55
 
65
56
  ```json
66
57
  {
@@ -68,8 +59,6 @@ Add it once to `~/.pi/agent/keybindings.json` (the list form keeps Linux and Win
68
59
  }
69
60
  ```
70
61
 
71
- Restart pi to pick it up.
72
-
73
62
  ## Architecture
74
63
 
75
64
  <details>
@@ -72,6 +72,28 @@ export function fgFirst(th: Themeish, names: string[], text: string): string {
72
72
  return text;
73
73
  }
74
74
 
75
+ /**
76
+ * Collapsed body of a pi startup section ("[Skills]\n a, b"). pi 0.99 dropped
77
+ * ExpandableText's own getCollapsedText/getExpandedText fields for a `build` callback
78
+ * plus a `state` object, so both shapes are read here and older pi keeps working.
79
+ * Returns null when the child is not an expandable section.
80
+ */
81
+ export function sectionBodyOf(child: any): string | null {
82
+ if (typeof child?.getCollapsedText === "function") return String(child.getCollapsedText());
83
+ if (typeof child?.build === "function" && child?.state && typeof child.state === "object") {
84
+ return String(child.build());
85
+ }
86
+ return null;
87
+ }
88
+
89
+ /** Text of a themed text line, whichever pi version built it (pi 0.99 defers it to `build`). */
90
+ export function themedTextOf(child: any): string {
91
+ if (typeof child?.build === "function") return String(child.build());
92
+ const direct = child?.text ?? child?.content;
93
+ if (typeof direct === "string" && direct !== "") return direct;
94
+ return typeof child?.getText === "function" ? String(child.getText()) : "";
95
+ }
96
+
75
97
  export function sectionNameOf(text: string): "Context" | "Skills" | "Extensions" | "Themes" | null {
76
98
  const nl = text.indexOf("\n");
77
99
  const first = stripAnsi(nl === -1 ? text : text.slice(0, nl)).trim();
@@ -587,20 +609,19 @@ if (UserMessageComponent?.prototype && !(globalThis as Record<symbol, boolean>)[
587
609
  const origAddChild = containerProto.addChild;
588
610
  containerProto.addChild = function (child: any): unknown {
589
611
  try {
590
- const sectionName = sectionNameOf(String(child?.getCollapsedText?.() ?? ""));
612
+ const body = sectionBodyOf(child);
613
+ const sectionName = body === null ? null : sectionNameOf(body);
591
614
  const isSection =
592
- child &&
593
- typeof child.getCollapsedText === "function" &&
594
- typeof child.getExpandedText === "function" &&
615
+ body !== null &&
595
616
  typeof child.setText === "function" &&
617
+ typeof child.render === "function" &&
596
618
  (sectionName === null ? false : SECTION_ICONS[sectionName] !== undefined);
597
619
 
598
- if (isSection && sectionName) {
620
+ if (isSection && sectionName && body !== null) {
599
621
  // Mark this container as the loadedResourcesContainer
600
622
  (this as Record<string, unknown>)._isLoadedResourcesContainer = true;
601
623
 
602
- const origCollapsed = child.getCollapsedText.bind(child) as () => string;
603
- const rawBody = origCollapsed().split("\n").slice(1).join("\n");
624
+ const rawBody = body.split("\n").slice(1).join("\n");
604
625
  const items = extractItemsFromBody(rawBody, sectionName === "Extensions");
605
626
  tabStore.set(sectionName, items);
606
627
 
@@ -613,7 +634,7 @@ if (UserMessageComponent?.prototype && !(globalThis as Record<symbol, boolean>)[
613
634
  }
614
635
  } else if ((this as Record<string, unknown>)._isLoadedResourcesContainer) {
615
636
  // pi core's "[Extension issues]" -> wrap in an arnative box.
616
- const text = String(child?.text ?? child?.content ?? (typeof child?.getText === "function" ? child.getText() : ""));
637
+ const text = themedTextOf(child);
617
638
  if (text.includes("[Extension issues]") && typeof child.render === "function") {
618
639
  const origRender = child.render.bind(child);
619
640
  child.render = (w: number) => renderBoxLines(activeThemeProxy, w, origRender(w), undefined, "warning");
@@ -893,5 +914,22 @@ if (isMain(import.meta.url)) {
893
914
  assert(fakeSection.render(80).length === 0, "child of loadedResourcesContainer suppressed (render = [])");
894
915
  assert(tabStore.get("Skills")?.includes("new-skill-1") === true, "new data lands in tabStore");
895
916
 
917
+ // pi 0.99 ExpandableText shape: `build` callback + `state` object, no getters.
918
+ const modernSection = {
919
+ state: { expanded: false },
920
+ build: () => "[Themes]\n arnative-sun, arnative-dusk",
921
+ setText: () => {},
922
+ render: (_w: number) => ["should be hidden"],
923
+ };
924
+ container.addChild(modernSection);
925
+ assert(modernSection.render(80).length === 0, "pi 0.99 section suppressed (build + state shape)");
926
+ assert(tabStore.get("Themes")?.includes("arnative-sun") === true, "pi 0.99 section data lands in tabStore");
927
+
928
+ // Startup help block is not a resource section and must survive untouched.
929
+ const helpBlock = { build: () => "\u2580\u2580\u2588 v0.99.1\n\u2588\u2580 \u2588 escape interrupt", state: { expanded: false }, render: () => ["help"] };
930
+ container.addChild(helpBlock);
931
+ assert(sectionBodyOf(helpBlock) === "\u2580\u2580\u2588 v0.99.1\n\u2588\u2580 \u2588 escape interrupt", "sectionBodyOf reads the build shape");
932
+ assert(sectionNameOf(sectionBodyOf(helpBlock)!) === null, "startup help block is not a resource section");
933
+
896
934
  console.log("section-headers.ts self-check OK");
897
935
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bismawy/pi-arnative",
3
- "version": "0.2.8",
4
- "description": "Arnative look for Pi: 11 warm-neutral themes, rounded tool boxes, branded header with tabs, 2-line footer with git/model/status, transcript clock, and a /usage dashboard.",
3
+ "version": "0.2.9",
4
+ "description": "Arnative look for Pi: warm-neutral themes, rounded tool boxes, tabbed header, compact footer, transcript clock, and a /usage dashboard.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi-theme",