@bismawy/pi-arnative 0.2.0 → 0.2.2
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/README.md +39 -61
- package/extensions/footer.ts +23 -1
- package/extensions/section-headers.ts +29 -35
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,89 +1,69 @@
|
|
|
1
|
-
|
|
1
|
+
# Arnative
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A visual layer and tool styling tailored to your vibe coding.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://pi.dev/packages/@bismawy/pi-arnative)
|
|
6
|
+
[](https://www.npmjs.com/package/@bismawy/pi-arnative)
|
|
7
|
+
[](https://github.com/bismawy/pi-arnative)
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## Overview
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-

|
|
11
|
+
pi-arnative replaces pi’s default terminal chrome with a unified, cohesive visual aesthetic.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- **11 themes, one generator:** `arnative` plus 10 variants (`sun`, `zinc`, `violet`, `emerald`, `matrix`, `cyberpunk`, `synthwave`, `gruvbox`, `nord`, `dracula`), all generated from a single base file so their structure can never drift.
|
|
19
|
-
- **Header box with tabs:** replaces pi's stacked resource list with a 5-line box — logo, version + shortcuts, and a clickable `Model / Directory / Context / Skills / Extensions / Themes` tab menu. The `Directory` tab shows the full working directory (wrapped at `/`, never truncated).
|
|
20
|
-
- **Tool boxes:** every tool call renders in its own rounded box — spinner while running, then `✓`/`x`, right-aligned duration, and a one-line summary. Click or `ctrl+e` for the full output.
|
|
21
|
-
- **2-line footer:** `cwd | duration | branch/tag/status` on the first line, extension status + tok/s + cache + tokens on the second, plus a full-box editor with a `> ` prompt.
|
|
22
|
-
- **Transcript clock:** user and assistant messages get a right-aligned time, and the user bubble background is repaired where Markdown resets would otherwise leave black blocks.
|
|
23
|
-
- **Render fixes:** hidden reload box, readable drag selection, single-line compaction messages, and a higher-contrast `↓ Jump to latest message` pill.
|
|
24
|
-
- **Contrast-checked themes:** the self-check measures every `themes/*.json` against its own canvas, so no variant ships an unreadable box.
|
|
13
|
+
- Warm Themes: Cohesive, contrast-checked color palettes generated without drift.
|
|
14
|
+
- Tabbed Header: Compact box menu with quick access to Directory, Model, Skills, Extensions, and Themes.
|
|
15
|
+
- Rounded Tool Boxes: Clear status, spinner, and duration for every tool call.
|
|
16
|
+
- Enhanced Footer: 2-line layout showing directory path, git branch, model, and token metrics.
|
|
17
|
+
- Transcript Clock: Timestamps for messages with clean bubble backgrounds.
|
|
18
|
+
- Usage Dashboard: /usage command to track tokens and session costs.
|
|
25
19
|
|
|
26
20
|
## Install
|
|
27
21
|
|
|
28
|
-
**Install [JetBrains Mono Nerd Font](https://www.nerdfonts.com/font-downloads) first.** Every icon in this package (tab icons ` `, tool icons ` ✓`, footer icons ` `, and the rounded box corners `╭─╮`) is a Nerd Font glyph — without the font they render as empty boxes (□) or random characters.
|
|
29
|
-
|
|
30
|
-
1. Download **JetBrainsMono Nerd Font** from [nerdfonts.com/font-downloads](https://www.nerdfonts.com/font-downloads).
|
|
31
|
-
2. Extract and install it (Linux: copy into `~/.local/share/fonts/` then `fc-cache -f`; Windows: right click → Install).
|
|
32
|
-
3. Set your terminal to **JetBrainsMono NF** (or another Nerd Font) as the primary font.
|
|
33
|
-
|
|
34
22
|
```bash
|
|
35
23
|
pi install npm:@bismawy/pi-arnative
|
|
36
24
|
```
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
To try it without installing, from this directory:
|
|
26
|
+
Select a theme via `/settings` → **Theme** → `arnative` (or any `arnative-*` variant).
|
|
41
27
|
|
|
28
|
+
To test locally without installing:
|
|
42
29
|
```bash
|
|
43
30
|
pi --extension ./extensions/footer.ts
|
|
44
31
|
```
|
|
32
|
+
> **Prerequisite:** Requires a [Nerd Font](https://www.nerdfonts.com/font-downloads) (e.g. JetBrains Mono Nerd Font) for icons and rounded box borders.
|
|
45
33
|
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
| Command | Action |
|
|
49
|
-
| :--- | :--- |
|
|
50
|
-
| `/usage` | Token, message & cache usage across all local Pi sessions |
|
|
34
|
+
## Shortcuts
|
|
51
35
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
| `
|
|
55
|
-
|
|
|
56
|
-
|
|
|
36
|
+
| Key / Action | Result |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `/usage` | Show session usage & token metrics |
|
|
39
|
+
| `ctrl+alt+t` | Cycle header tabs (Directory → Model → Context → Skills → Extensions → Themes) |
|
|
40
|
+
| `ctrl+alt+r` | Quick reload runtime (`/reload`) |
|
|
41
|
+
| Click tab | Open selected header tab |
|
|
42
|
+
| Click tool / `ctrl+e` | Toggle tool output box |
|
|
57
43
|
|
|
58
|
-
##
|
|
44
|
+
## Architecture
|
|
59
45
|
|
|
60
46
|
<details>
|
|
61
47
|
<summary><b>Extensions</b></summary>
|
|
62
48
|
|
|
63
49
|
| File | Role |
|
|
64
|
-
|
|
|
65
|
-
| `extensions/tools.ts` |
|
|
66
|
-
| `extensions/section-headers.ts` |
|
|
67
|
-
| `extensions/footer.ts` | 2-line footer and
|
|
68
|
-
| `extensions/timestamps.ts` |
|
|
69
|
-
| `extensions/ui-render-tweaks.ts` |
|
|
70
|
-
| `extensions/usage.ts` | `/usage`
|
|
71
|
-
|
|
72
|
-
`lib/box.ts` and `lib/ansi.ts` hold the single definitions shared by all of them. They live outside `extensions/` so pi never loads them as extensions.
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `extensions/tools.ts` | Rounded tool boxes, spinners, and duration |
|
|
52
|
+
| `extensions/section-headers.ts` | Tabbed header and resource box |
|
|
53
|
+
| `extensions/footer.ts` | 2-line status footer and boxed editor |
|
|
54
|
+
| `extensions/timestamps.ts` | Message clock and bubble background fixes |
|
|
55
|
+
| `extensions/ui-render-tweaks.ts` | Contrast tweaks, selection style, and UI polish |
|
|
56
|
+
| `extensions/usage.ts` | `/usage` token dashboard |
|
|
73
57
|
|
|
74
58
|
</details>
|
|
75
59
|
|
|
76
60
|
<details>
|
|
77
61
|
<summary><b>Themes</b></summary>
|
|
78
62
|
|
|
79
|
-
- `themes/arnative.json`
|
|
80
|
-
- `themes/gen-themes.mjs`
|
|
81
|
-
- `npm run themes` rewrites every variant; `npm test` runs the same generator in `--check` mode, so a hand-edited variant file cannot drift.
|
|
82
|
-
- Every variant fills all `vars`/`colors`/`export` keys, so no color can silently inherit the base.
|
|
63
|
+
- `themes/arnative.json` serves as the structure source of truth.
|
|
64
|
+
- `themes/gen-themes.mjs` generates variants with automated contrast checks.
|
|
83
65
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Contrast rules enforced by the self-check (measured against each theme's own `userMessageBg` canvas): content ≥ 3.0, secondary text ≥ 2.0, `thinking*` ramp 1.3–4.0, accent pill ≥ 4.5, tool box text ≥ 4.5 on both success and error backgrounds, error box weight matched to the success box, and `scrollbarThumb` / `searchMatchBg` distinguishable from their track / selection.
|
|
66
|
+
**Variants:** `sun` · `zinc` · `violet` · `emerald` · `matrix` · `cyberpunk` · `synthwave` · `gruvbox` · `nord` · `dracula`
|
|
87
67
|
|
|
88
68
|
</details>
|
|
89
69
|
|
|
@@ -91,18 +71,16 @@ Contrast rules enforced by the self-check (measured against each theme's own `us
|
|
|
91
71
|
<summary><b>Development</b></summary>
|
|
92
72
|
|
|
93
73
|
```bash
|
|
94
|
-
npm test #
|
|
95
|
-
npm run themes #
|
|
74
|
+
npm test # Run self-checks & theme generator check
|
|
75
|
+
npm run themes # Rebuild all theme variants
|
|
96
76
|
```
|
|
97
77
|
|
|
98
|
-
Each extension is also a standalone self-check: `node extensions/tools.ts`.
|
|
99
|
-
|
|
100
78
|
</details>
|
|
101
79
|
|
|
102
80
|
## License
|
|
103
81
|
|
|
104
82
|
Distributed under the **MIT** license.
|
|
105
83
|
|
|
106
|
-
##
|
|
84
|
+
## Author
|
|
107
85
|
|
|
108
|
-
|
|
86
|
+
[Bisma](https://github.com/bismawy)
|
package/extensions/footer.ts
CHANGED
|
@@ -9,10 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
11
11
|
import { CustomEditor, FooterComponent, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
12
|
+
import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
13
13
|
import { execFile } from "node:child_process";
|
|
14
14
|
import { formatTokens } from "../lib/usage-store.ts";
|
|
15
15
|
|
|
16
|
+
// The TUI drops key-release events before handleInput, so press+release cannot
|
|
17
|
+
// fire this twice even when the terminal reports event types (Kitty flag 2).
|
|
18
|
+
const RELOAD_SHORTCUT = Key.ctrlAlt("r");
|
|
19
|
+
|
|
16
20
|
// Intercept the built-in FooterComponent to force a pure 2-line footer
|
|
17
21
|
const PATCHED_KEY = Symbol.for("pi-arnative.footer2LinesPatched");
|
|
18
22
|
if (FooterComponent?.prototype?.render && !(globalThis as Record<symbol, boolean>)[PATCHED_KEY]) {
|
|
@@ -471,6 +475,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
471
475
|
return super.renderTopBorder(width, hiddenLineCount);
|
|
472
476
|
}
|
|
473
477
|
|
|
478
|
+
handleInput(data: string) {
|
|
479
|
+
if (matchesKey(data, RELOAD_SHORTCUT)) {
|
|
480
|
+
this.setText("");
|
|
481
|
+
if (this.onSubmit) {
|
|
482
|
+
this.onSubmit("/reload");
|
|
483
|
+
}
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
super.handleInput(data);
|
|
487
|
+
}
|
|
488
|
+
|
|
474
489
|
render(width: number): string[] {
|
|
475
490
|
// The box eats 5 columns: 2 sides + the 3-column " > " prompt, so the
|
|
476
491
|
// content is rendered 5 columns narrower.
|
|
@@ -643,5 +658,12 @@ if (isMain) {
|
|
|
643
658
|
assert(getToolWorkingMessage("mcp", { tool: "fetch_repo" }) === "Fetching", "mcp fetch -> Fetching");
|
|
644
659
|
assert(boxed.slice(0, 4).every((l) => visibleWidth(l) === visibleWidth(D) + 5), "lebar kotak seragam (sisi+prompt)");
|
|
645
660
|
assert(boxEditorLines(["\u2500\u2500\u2500\u2500"], 1, plain).length === 1, "terlalu sempit: tak diubah");
|
|
661
|
+
|
|
662
|
+
// Guard: the reload shortcut must match every encoding a terminal can deliver,
|
|
663
|
+
// so a typo in the key id fails here instead of silently doing nothing.
|
|
664
|
+
assert(matchesKey("\x1b\x12", RELOAD_SHORTCUT), "shortcut cocok dgn ctrl+alt+r legacy (ESC + 0x12)");
|
|
665
|
+
assert(matchesKey("\x1b[114;7u", RELOAD_SHORTCUT), "shortcut cocok dgn ctrl+alt+r Kitty CSI-u");
|
|
666
|
+
assert(!matchesKey("\x12", RELOAD_SHORTCUT), "ctrl+r polos tidak memicu reload");
|
|
667
|
+
assert(!matchesKey("r", RELOAD_SHORTCUT), "huruf r tidak memicu reload");
|
|
646
668
|
console.log("footer.ts self-check OK");
|
|
647
669
|
}
|
|
@@ -34,7 +34,7 @@ import { formatTokens, getModelAllTimeUsage } from "../lib/usage-store.ts";
|
|
|
34
34
|
export type TabKey = "Model" | "Directory" | "Context" | "Skills" | "Extensions" | "Themes";
|
|
35
35
|
|
|
36
36
|
// Tab order for Ctrl+Alt+T and for the menu line.
|
|
37
|
-
export const TAB_ORDER: TabKey[] = ["
|
|
37
|
+
export const TAB_ORDER: TabKey[] = ["Directory", "Model", "Context", "Skills", "Extensions", "Themes"];
|
|
38
38
|
|
|
39
39
|
export const SECTION_ICONS: Record<TabKey, string> = {
|
|
40
40
|
Model: "\uf1b2",
|
|
@@ -276,7 +276,7 @@ export function getCwd(ctx?: { cwd?: string }): string {
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
export class ArnativeHeader implements Component {
|
|
279
|
-
public activeTab: TabKey = "
|
|
279
|
+
public activeTab: TabKey = "Directory";
|
|
280
280
|
private renderedTabLineY = -1;
|
|
281
281
|
private renderedTabRegions: Array<{ key: TabKey; startX: number; endX: number }> = [];
|
|
282
282
|
|
|
@@ -303,27 +303,25 @@ export class ArnativeHeader implements Component {
|
|
|
303
303
|
|
|
304
304
|
getTabsData(width: number): Array<{ key: TabKey; name: string; icon: string; count: number; label: string }> {
|
|
305
305
|
const modelObj = this.ctx?.model ?? (globalThis as Record<symbol, any>)[MODEL_SNAPSHOT_KEY];
|
|
306
|
-
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
const dirBase = cwd.replace(/\\/g, "/").replace(/\/+$/, "").split("/").pop() || cwd;
|
|
310
|
-
const dirLabel = dirBase ? `Directory: ${dirBase}` : "Directory";
|
|
306
|
+
// Tab row stays compact: the Directory tab shows just its name, the full path
|
|
307
|
+
// lives in the content line below.
|
|
308
|
+
const dirLabel = "Directory";
|
|
311
309
|
const fullModelName = formatModelDisplayName(modelObj);
|
|
312
310
|
// Drop bracketed details from the tail of the name ("... Free (1M) [OpenCode] (Freeflow)"
|
|
313
311
|
// -> "Space Bunny Free"); the details still show in the Model tab data line.
|
|
314
|
-
let modelLabel = `Model
|
|
312
|
+
let modelLabel = `Model [${fullModelName.replace(/(\s*\([^()]*\)|\s*\[[^[\]]*\])+$/, "").trim()}]`;
|
|
315
313
|
// Responsive on medium screens: drop the provider when columns < 112
|
|
316
314
|
if (width < 112 && modelLabel.includes(" (")) {
|
|
317
|
-
modelLabel = `Model
|
|
315
|
+
modelLabel = `Model [${fullModelName.slice(0, fullModelName.indexOf(" (")).trim()}]`;
|
|
318
316
|
}
|
|
319
317
|
// Below 88 columns: shorten the model name
|
|
320
318
|
if (width < 88 && modelLabel.length > 18) {
|
|
321
|
-
modelLabel = `Model
|
|
319
|
+
modelLabel = `Model [${modelLabel.slice(7, 18).trim()}…]`;
|
|
322
320
|
}
|
|
323
321
|
|
|
324
322
|
return [
|
|
325
|
-
{ key: "Model", name: "Model", icon: SECTION_ICONS.Model, count: 0, label: modelLabel },
|
|
326
323
|
{ key: "Directory", name: "Directory", icon: SECTION_ICONS.Directory, count: 0, label: dirLabel },
|
|
324
|
+
{ key: "Model", name: "Model", icon: SECTION_ICONS.Model, count: 0, label: modelLabel },
|
|
327
325
|
{
|
|
328
326
|
key: "Context",
|
|
329
327
|
name: "Context",
|
|
@@ -418,6 +416,7 @@ export class ArnativeHeader implements Component {
|
|
|
418
416
|
`${fgFirst(th, ["tint"], "[!]")} ${fgFirst(th, ["dim"], "Bash")}`,
|
|
419
417
|
`${fgFirst(th, ["tint"], "[Ctrl+o]")} ${fgFirst(th, ["dim"], "More/expand")}`,
|
|
420
418
|
`${fgFirst(th, ["tint"], "[Ctrl+alt+t]")} ${fgFirst(th, ["dim"], "Next tab")}`,
|
|
419
|
+
`${fgFirst(th, ["tint"], "[Ctrl+alt+r]")} ${fgFirst(th, ["dim"], "Reload")}`,
|
|
421
420
|
];
|
|
422
421
|
// Shortcut list is trimmed from the right (longest prefix that still fits) so
|
|
423
422
|
// narrow screens never clip the tabs or the data.
|
|
@@ -434,7 +433,8 @@ export class ArnativeHeader implements Component {
|
|
|
434
433
|
const sepTab = fgFirst(th, ["dim"], sepTabPlain);
|
|
435
434
|
|
|
436
435
|
const tabParts: Array<{ key: TabKey; plain: string; formatted: string }> = [];
|
|
437
|
-
const makeTabPart = (t: { key: TabKey; icon: string; label: string }
|
|
436
|
+
const makeTabPart = (t: { key: TabKey; icon: string; label: string }) => {
|
|
437
|
+
const { label } = t;
|
|
438
438
|
const plain = `${t.icon} ${label}`;
|
|
439
439
|
const isActive = t.key === this.activeTab;
|
|
440
440
|
const iconStyled = isActive ? fgFirst(th, ["accent"], t.icon) : fgFirst(th, ["dim"], t.icon);
|
|
@@ -455,22 +455,9 @@ export class ArnativeHeader implements Component {
|
|
|
455
455
|
usedWidth += w;
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
|
-
// All tabs fit? Then the Directory tab may show the whole path instead of the
|
|
459
|
-
// basename. Skipped when it would push another tab off the menu.
|
|
460
|
-
if (kept.length === tabParts.length) {
|
|
461
|
-
const i = kept.findIndex((t) => t.key === "Directory");
|
|
462
|
-
const cwdFull = getCwd(this.ctx);
|
|
463
|
-
const dirTab = tabs[i];
|
|
464
|
-
if (i >= 0 && dirTab && cwdFull) {
|
|
465
|
-
const full = makeTabPart(dirTab, `Directory: ${cwdFull}`);
|
|
466
|
-
if (usedWidth - visibleWidth(kept[i]!.plain) + visibleWidth(full.plain) <= innerW) {
|
|
467
|
-
kept[i] = full;
|
|
468
|
-
usedWidth += visibleWidth(full.plain) - visibleWidth(tabParts[i]!.plain);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
458
|
this.renderedTabRegions = [];
|
|
473
|
-
|
|
459
|
+
// The tab row has its own left border + a leading space before the first tab.
|
|
460
|
+
let curX = INFO_X + 2;
|
|
474
461
|
for (const t of kept) {
|
|
475
462
|
const w = visibleWidth(t.plain);
|
|
476
463
|
this.renderedTabRegions.push({ key: t.key, startX: curX, endX: curX + w - 1 });
|
|
@@ -723,8 +710,13 @@ if (isMain) {
|
|
|
723
710
|
// Only "dim" is colored, so the box line colors can be asserted.
|
|
724
711
|
const ansiTheme = { fg: (c: string, t: string) => (c === "dim" ? `\x1b[2m${t}\x1b[22m` : t) };
|
|
725
712
|
const hdr = new ArnativeHeader(fakeTui, ansiTheme, fakeCtx);
|
|
726
|
-
assert(hdr.activeTab === "
|
|
713
|
+
assert(hdr.activeTab === "Directory", "default tab adalah Directory");
|
|
714
|
+
|
|
715
|
+
// Default view: the working directory path, shown on open
|
|
716
|
+
const linesDefault = hdr.render(100);
|
|
717
|
+
assert(stripAnsi(linesDefault[6]!).includes("/run/media/bisma/DATA/Pi/pi-arnative"), "path direktori tampil sejak tab pertama dibuka");
|
|
727
718
|
|
|
719
|
+
hdr.activeTab = "Model";
|
|
728
720
|
const lines100 = hdr.render(100);
|
|
729
721
|
assert(lines100.length === 8, "render 100 = border + 6 baris logo + border (3 baris info mengisi 3 baris logo)");
|
|
730
722
|
assert(lines100.every((l) => visibleWidth(l) === 100), "semua baris render 100 tepat 100 kolom");
|
|
@@ -737,11 +729,13 @@ if (isMain) {
|
|
|
737
729
|
"ascii Pi asli di kolom kiri, 6 baris",
|
|
738
730
|
);
|
|
739
731
|
assert(stripAnsi(lines100[1]!).includes("pi v") && stripAnsi(lines100[1]!).includes("Arnative"), "baris versi + merek");
|
|
740
|
-
assert(stripAnsi(lines100[4]!).includes("Model
|
|
741
|
-
// Directory tab: whole path in the content line
|
|
732
|
+
assert(stripAnsi(lines100[4]!).includes("Model [Gemini 3.8 Flash]"), "menu tab Model tampil (Model [nama])");
|
|
733
|
+
// Directory tab: name only in the menu, whole path in the content line below
|
|
742
734
|
hdr.activeTab = "Directory";
|
|
743
735
|
hdr.render(100);
|
|
744
|
-
assert(stripAnsi(lines100[4]!).includes("Directory
|
|
736
|
+
assert(stripAnsi(lines100[4]!).includes("Directory"), "tab Directory tampil di menu");
|
|
737
|
+
assert(!stripAnsi(lines100[4]!).includes("Directory: /"), "menu Directory tanpa path (detail hanya saat diklik)");
|
|
738
|
+
assert(stripAnsi(lines100[4]!).indexOf("Directory") < stripAnsi(lines100[4]!).indexOf("Model ["), "Directory tab berada paling kiri");
|
|
745
739
|
hdr.activeTab = "Model";
|
|
746
740
|
hdr.render(100);
|
|
747
741
|
assert(stripAnsi(lines100[6]!).includes("Gemini 3.8 Flash (Antigravity)"), "data tab aktif Model tampil di baris info terakhir");
|
|
@@ -801,20 +795,20 @@ if (isMain) {
|
|
|
801
795
|
if (skillsRegion.key === "Skills") {
|
|
802
796
|
assert(linesSkills[6]!.includes("agents-sdk, cloudflare"), "data tab Skills tampil di baris info terakhir");
|
|
803
797
|
assert(!linesSkills[6]!.includes("Gemini 3.8 Flash (Antigravity)"), "data Model tidak tampil di konten tab Skills");
|
|
804
|
-
assert(linesSkills[4]!.includes("Model
|
|
798
|
+
assert(linesSkills[4]!.includes("Model [Gemini 3.8 Flash]"), "menu tab tetap tampil saat tab lain aktif");
|
|
805
799
|
}
|
|
806
800
|
|
|
807
801
|
// The Y of a tab must point at the tab menu line, not the shortcut line above
|
|
808
802
|
const rowsPlain = lines100.map((l) => stripAnsi(l));
|
|
809
|
-
assert(rowsPlain[tabY]!.includes("Model
|
|
810
|
-
assert(tabRegions.every((r) => rowsPlain[tabY]!.slice(r.startX, r.endX + 1).includes(r.key === "Model" ? "Model
|
|
803
|
+
assert(rowsPlain[tabY]!.includes("Model ["), "renderedTabLineY menunjuk baris menu tab");
|
|
804
|
+
assert(tabRegions.every((r) => rowsPlain[tabY]!.slice(r.startX, r.endX + 1).includes(r.key === "Model" ? "Model [" : r.key)), "region tab rata dengan teks di baris itu");
|
|
811
805
|
|
|
812
806
|
// Narrow screen: version + tabs + data stay intact, shortcuts get trimmed
|
|
813
807
|
const narrow = hdr.render(60);
|
|
814
808
|
assert(narrow.every((l) => visibleWidth(l) === 60), "baris 60 kolom tetap 60");
|
|
815
809
|
assert(stripAnsi(narrow[1]!).includes("pi v"), "versi tetap utuh di 60 kolom");
|
|
816
810
|
assert(stripAnsi(narrow[1]!).includes("More/expand") === false, "shortcut panjang dibuang di 60 kolom");
|
|
817
|
-
assert(stripAnsi(narrow[4]!).includes("Model
|
|
811
|
+
assert(stripAnsi(narrow[4]!).includes("Model ["), "tab Model tetap tampil di 60 kolom");
|
|
818
812
|
|
|
819
813
|
// Intercept addChild: capture the data and suppress the child
|
|
820
814
|
const proto = Object.getPrototypeOf(UserMessageComponent.prototype) as { addChild?: unknown };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bismawy/pi-arnative",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
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.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"@earendil-works/pi-tui": "*"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
|
+
"dev": "pi --extension ./extensions/tools.ts --extension ./extensions/section-headers.ts --extension ./extensions/footer.ts --extension ./extensions/timestamps.ts --extension ./extensions/ui-render-tweaks.ts --extension ./extensions/usage.ts",
|
|
40
41
|
"test": "node lib/box.ts && node lib/ansi.ts && node themes/gen-themes.mjs --check && node extensions/tools.ts && node extensions/footer.ts && node extensions/timestamps.ts && node extensions/section-headers.ts && node extensions/ui-render-tweaks.ts && node extensions/usage.ts",
|
|
41
42
|
"themes": "node themes/gen-themes.mjs"
|
|
42
43
|
}
|