pi-fold 4.1.1 → 4.1.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/README.md
CHANGED
|
@@ -37,10 +37,10 @@ In an interactive session one row sits directly above Pi's footer and shows the
|
|
|
37
37
|
|
|
38
38
|
<picture>
|
|
39
39
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/shaneconner/fold/main/media/fold-bar-dark.svg">
|
|
40
|
-
<img alt="The fold bar: forty cells of the serving budget, oldest on the left, with ticks at the aim and the commit point. Staged spans, raw material and fold placeholders fill it in three colours, and the label reads 42%, commit at 80%, 12 staged,
|
|
40
|
+
<img alt="The fold bar: forty cells of the serving budget, oldest on the left, with ticks at the aim and the commit point. Staged spans, raw material and fold placeholders fill it in three colours, and the label reads 42%, commit at 80%, 12 staged, 3 folds hide 412k tokens." src="https://raw.githubusercontent.com/shaneconner/fold/main/media/fold-bar-light.svg">
|
|
41
41
|
</picture>
|
|
42
42
|
|
|
43
|
-
The bar is the serving budget, forty cells wide, oldest material on the left. The two ticks are the band: the left one is `minTarget`, where a commit aims to land, and the right one is `maxTarget`, where the commit fires. The fill is the provider's own count of what is in the window, never an estimate, so before the first response the row says `not measured yet` and draws nothing. One cell is one fortieth of the budget, and the fill reads left to right in three colours: the placeholders of standing folds first, drawn as low slivers and small by design; then staged spans, the raw material the next commit collapses; then raw material the runtime has not claimed yet. The label repeats the percentage, names the commit point (or reads `COMMIT DUE` once the fill has crossed it and `commit held` once that count has been weighed), states how many spans are staged
|
|
43
|
+
The bar is the serving budget, forty cells wide, oldest material on the left. The two ticks are the band: the left one is `minTarget`, where a commit aims to land, and the right one is `maxTarget`, where the commit fires. The fill is the provider's own count of what is in the window, never an estimate, so before the first response the row says `not measured yet` and draws nothing. One cell is one fortieth of the budget, and the fill reads left to right in three colours: the placeholders of standing folds first, drawn as low slivers and small by design; then staged spans, the raw material the next commit collapses; then raw material the runtime has not claimed yet. The label repeats the percentage, names the commit point (or reads `COMMIT DUE` once the fill has crossed it and `commit held` once that count has been weighed), states how many spans are staged, and ends with what folding has already bought: the tokens the standing folds hide from the window. Every token figure prices a pasted image as one image rather than as its base64 text, the same way the occupancy estimate does. Right after a commit the percentage is still the provider's last count, so the label says it is from before the commit until the next response measures the window again. If automatic folding ever stops, the row reads `FOLDING STOPPED` in place of the bar. On a truecolor terminal the three fill classes take three steps of Crameri's batlow colour map; elsewhere they use the theme's own colours.
|
|
44
44
|
|
|
45
45
|
## Results
|
|
46
46
|
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
selectAutomaticRung,
|
|
36
36
|
setFoldProjectionState,
|
|
37
37
|
withExpandLease,
|
|
38
|
+
renderFoldParts,
|
|
38
39
|
} from "./lib/folding.ts";
|
|
39
40
|
import {
|
|
40
41
|
admissionVerdict,
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
parseNativeCompactionDecision,
|
|
55
56
|
parseProviderContextMeasurementReceipt,
|
|
56
57
|
persistenceProjection,
|
|
58
|
+
pricedBytes,
|
|
57
59
|
protectedStaleMass,
|
|
58
60
|
providerContextMeasurement,
|
|
59
61
|
providerTokens,
|
|
@@ -178,7 +180,6 @@ import {
|
|
|
178
180
|
peekedSourceFoldIds,
|
|
179
181
|
snapFoldCandidate,
|
|
180
182
|
snapToFoldBoundaries,
|
|
181
|
-
spanBytes,
|
|
182
183
|
} from "./lib/selection.ts";
|
|
183
184
|
import type { SpanCorrection } from "./lib/selection.ts";
|
|
184
185
|
import {
|
|
@@ -224,6 +225,7 @@ export * from "./lib/persistence.ts";
|
|
|
224
225
|
export * from "./lib/policy.ts";
|
|
225
226
|
export * from "./lib/rollback.ts";
|
|
226
227
|
export * from "./lib/scheduling.ts";
|
|
228
|
+
export * from "./lib/status-widget.ts";
|
|
227
229
|
export * from "./lib/selection.ts";
|
|
228
230
|
export * from "./lib/transcript.ts";
|
|
229
231
|
|
|
@@ -667,6 +669,12 @@ export function registerActiveContext(pi: any, options: {
|
|
|
667
669
|
|
|
668
670
|
const deliverReceipt = (receipt: ContextReceipt): void => {
|
|
669
671
|
curation.receipts = withReceipt(curation.receipts, receipt);
|
|
672
|
+
// THE BAR'S READING GOES STALE AT A COMMIT. Occupancy is the provider's last count,
|
|
673
|
+
// which does not move until the next response, so after a commit the bar went on
|
|
674
|
+
// showing the pre-commit number as if it were current (38% beside "10 folds hold
|
|
675
|
+
// 8.6M", Shane 2026-09-03). Remember which measurement the commit landed against;
|
|
676
|
+
// while it is still the latest, the bar says the number is from before the commit.
|
|
677
|
+
if (receipt.foldsCommitted > 0) foldBar.staleSince = measurements.lastProviderMeasurement;
|
|
670
678
|
// MONOTONIC, unlike `curation.receipts.length`, which saturates at MAX_CONTEXT_RECEIPTS
|
|
671
679
|
// and would stop distinguishing one approach from the next once the session is long
|
|
672
680
|
// enough to matter. This is the only counter the notice latch reads.
|
|
@@ -718,6 +726,11 @@ export function registerActiveContext(pi: any, options: {
|
|
|
718
726
|
model: null as FoldBarModel | null,
|
|
719
727
|
installed: false,
|
|
720
728
|
requestRender: null as (() => void) | null,
|
|
729
|
+
/** The provider measurement the last landed commit ran against; the reading is
|
|
730
|
+
* stale while it is still the latest one. */
|
|
731
|
+
staleSince: null as unknown,
|
|
732
|
+
/** Each standing fold's source priced by the image law, keyed by the fold object. */
|
|
733
|
+
pricedSource: new WeakMap<ActiveFold, number>(),
|
|
721
734
|
};
|
|
722
735
|
const installFoldBar = (ctx: any): void => {
|
|
723
736
|
if (foldBar.installed || typeof ctx.ui?.setWidget !== "function") return;
|
|
@@ -734,18 +747,38 @@ export function registerActiveContext(pi: any, options: {
|
|
|
734
747
|
};
|
|
735
748
|
}, { placement: "belowEditor" });
|
|
736
749
|
};
|
|
737
|
-
const foldBarModel = (input: {
|
|
750
|
+
const foldBarModel = (ctx: any, input: {
|
|
738
751
|
share: number | null; budgetTokens: number; staged: number; roots: number; weighed: boolean;
|
|
739
752
|
}): FoldBarModel => {
|
|
740
753
|
const state = persistence.state;
|
|
741
|
-
|
|
754
|
+
// THE SAME SNAPSHOT /fold-status PRICES AGAINST. `markFreedBytes` answers ZERO, silently,
|
|
755
|
+
// for a span it cannot locate contiguously in the snapshot it is handed, and the last
|
|
756
|
+
// context event's snapshot can trail the branch the marks were cut from; the bar read
|
|
757
|
+
// "5 staged, 0 to free" beside a status command pricing the same marks at 41,877
|
|
758
|
+
// tokens (Shane, 2026-09-02, session 01a06272). The authoritative snapshot is memoized
|
|
759
|
+
// on the branch length, so reading it here costs nothing while nothing has changed.
|
|
760
|
+
let snapshot = lifecycle.latestSnapshot;
|
|
761
|
+
try { snapshot = authoritativeSnapshotFor(ctx); } catch { }
|
|
742
762
|
let stagedTokens = 0;
|
|
743
763
|
let placeholderChars = 0;
|
|
744
764
|
let hiddenChars = 0;
|
|
745
765
|
if (state && snapshot) {
|
|
746
766
|
for (const mark of pendingMarks(state)) stagedTokens += estimatedTokens(markFreedBytes(snapshot, state, mark));
|
|
747
767
|
for (const root of orderedRoots(state, snapshot)) placeholderChars += root.fold.placeholderChars;
|
|
748
|
-
|
|
768
|
+
// WHAT FOLDING BOUGHT, IN WINDOW TERMS. `sourceChars` is the transcript count, base64
|
|
769
|
+
// and all, and read as tokens it said "8.6M" over a 1M window. Each standing fold's
|
|
770
|
+
// source is priced by the image law once per fold OBJECT (folds are immutable and
|
|
771
|
+
// replaced, never mutated, so a WeakMap memo is exact) and summed over the whole
|
|
772
|
+
// forest, which is additive because a parent's source is its children's placeholders.
|
|
773
|
+
for (const fold of state.folds) {
|
|
774
|
+
let priced = foldBar.pricedSource.get(fold);
|
|
775
|
+
if (priced === undefined) {
|
|
776
|
+
const source = renderFoldParts(fold.parts, state, snapshot);
|
|
777
|
+
priced = source ? pricedBytes(source) : fold.sourceChars;
|
|
778
|
+
foldBar.pricedSource.set(fold, priced);
|
|
779
|
+
}
|
|
780
|
+
hiddenChars += Math.max(0, priced - fold.placeholderChars);
|
|
781
|
+
}
|
|
749
782
|
}
|
|
750
783
|
const budget = input.budgetTokens > 0 ? input.budgetTokens : 1;
|
|
751
784
|
return {
|
|
@@ -760,6 +793,8 @@ export function registerActiveContext(pi: any, options: {
|
|
|
760
793
|
folds: input.roots,
|
|
761
794
|
hiddenTokens: estimatedTokens(hiddenChars),
|
|
762
795
|
weighed: input.weighed,
|
|
796
|
+
staleAfterCommit: input.share !== null && foldBar.staleSince !== null &&
|
|
797
|
+
foldBar.staleSince === measurements.lastProviderMeasurement,
|
|
763
798
|
stopped: ladder.automaticFailure?.message ?? null,
|
|
764
799
|
};
|
|
765
800
|
};
|
|
@@ -774,7 +809,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
774
809
|
try {
|
|
775
810
|
installFoldBar(ctx);
|
|
776
811
|
if (foldBar.installed) {
|
|
777
|
-
foldBar.model = foldBarModel({
|
|
812
|
+
foldBar.model = foldBarModel(ctx, {
|
|
778
813
|
share, budgetTokens: capacity.budgetTokens, staged, roots,
|
|
779
814
|
weighed: ladder.bandTopMeasurement !== null &&
|
|
780
815
|
ladder.bandTopMeasurement === measurements.lastProviderMeasurement,
|
|
@@ -2652,7 +2687,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
2652
2687
|
});
|
|
2653
2688
|
return null;
|
|
2654
2689
|
}
|
|
2655
|
-
const bytesBefore =
|
|
2690
|
+
const bytesBefore = pricedBytes(projectActiveContext(snapshot, state));
|
|
2656
2691
|
// THE COMMIT CUTS ONLY AS DEEP AS THE BUDGET ASKS (Shane, 2026-08-23). The frontier
|
|
2657
2692
|
// stages as material arrives, so by now the pending set is most of the window; without
|
|
2658
2693
|
// this bound the first commit would fold all of it. `freeingTarget` is a share of the
|
|
@@ -2735,7 +2770,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
2735
2770
|
}
|
|
2736
2771
|
}
|
|
2737
2772
|
persistence.state = result.state;
|
|
2738
|
-
const bytesAfter =
|
|
2773
|
+
const bytesAfter = pricedBytes(projectActiveContext(snapshot, persistence.state));
|
|
2739
2774
|
const freedBytes = Math.max(0, bytesBefore - bytesAfter);
|
|
2740
2775
|
const pinHeld = protectedStaleMass(snapshot, result.state);
|
|
2741
2776
|
const commitEvent = emit("context.commit", {
|
|
@@ -4499,7 +4534,11 @@ export function registerActiveContext(pi: any, options: {
|
|
|
4499
4534
|
safeNotify(
|
|
4500
4535
|
ctx,
|
|
4501
4536
|
committed
|
|
4502
|
-
|
|
4537
|
+
// THE EPOCH RECORD IS camelCase; `applied_marks` is the EVENT's spelling. Reading
|
|
4538
|
+
// the event's name off the record printed "Committed 0 mark(s)" after a /fold that
|
|
4539
|
+
// had just applied forty (Shane, 2026-09-03, session 01a06746: 378,216 tokens to
|
|
4540
|
+
// 97,577 with the notice saying nothing happened).
|
|
4541
|
+
? `Committed ${ownValue(committed, "appliedMarks") ?? 0} mark(s)${topUp ? " with automatic top-up" : ""}. ` +
|
|
4503
4542
|
"Exact source remains expandable."
|
|
4504
4543
|
: "Nothing to commit: no staged mark could apply (pinned or already-folded spans hold).",
|
|
4505
4544
|
"info",
|
|
@@ -4586,7 +4625,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
4586
4625
|
// what the staged mark answers with after it.
|
|
4587
4626
|
const spanCost = (from: number, to: number) => ({
|
|
4588
4627
|
entries: Math.abs(to - from) + 1,
|
|
4589
|
-
tokens: estimatedTokens(
|
|
4628
|
+
tokens: estimatedTokens(pricedBytes(snapshot.messages.slice(Math.min(from, to), Math.max(from, to) + 1))),
|
|
4590
4629
|
});
|
|
4591
4630
|
|
|
4592
4631
|
/**
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
ACTIVE_CONTEXT_POLICY,
|
|
25
25
|
DEFAULT_CONTEXT_WINDOW,
|
|
26
|
+
ESTIMATED_BYTES_PER_TOKEN,
|
|
27
|
+
IMAGE_ESTIMATED_TOKENS,
|
|
26
28
|
servingBudgetTokens,
|
|
27
29
|
} from "./policy.ts";
|
|
28
30
|
import type {
|
|
@@ -594,6 +596,25 @@ export function parseNativeCompactionCompletion(value: unknown, expectedSessionI
|
|
|
594
596
|
* needs to be the right order of magnitude instead of wrong by seventy times, and the
|
|
595
597
|
* provider anchor corrects the remainder the moment a real measurement lands.
|
|
596
598
|
*/
|
|
599
|
+
/**
|
|
600
|
+
* WHAT A SPAN COSTS THE WINDOW, in the byte unit every estimator already speaks.
|
|
601
|
+
*
|
|
602
|
+
* `bytes()` counts a message's serialized characters, and a pasted screenshot serializes
|
|
603
|
+
* as hundreds of thousands of base64 characters the provider prices as one image. The
|
|
604
|
+
* occupancy estimate has applied that law since gate 156; the pricing of what a fold
|
|
605
|
+
* FREES had not, so a session holding 83 screenshots (67.3M base64 characters against
|
|
606
|
+
* 1.5M of text) told its owner a commit had "5.8M to free" against a 1M window, and a
|
|
607
|
+
* commit that moved the window 280,000 tokens recorded 8.6M freed (Shane, 2026-09-03,
|
|
608
|
+
* session 01a06746). Text is counted as its bytes; each image is counted as the bytes
|
|
609
|
+
* that estimate to IMAGE_ESTIMATED_TOKENS, so `estimatedTokens(pricedBytes(m))` is the
|
|
610
|
+
* same reading gate 156 gives the projection. Every "frees N" figure derives from this.
|
|
611
|
+
*/
|
|
612
|
+
export function pricedBytes(messages: unknown[]): number {
|
|
613
|
+
const images = imageMass(messages);
|
|
614
|
+
return Math.max(0, bytes(messages) - images.base64Chars) +
|
|
615
|
+
images.images * IMAGE_ESTIMATED_TOKENS * ESTIMATED_BYTES_PER_TOKEN;
|
|
616
|
+
}
|
|
617
|
+
|
|
597
618
|
export function imageMass(projected: unknown[]): { base64Chars: number; images: number } {
|
|
598
619
|
let base64Chars = 0;
|
|
599
620
|
let images = 0;
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
orderedRoots,
|
|
28
28
|
protectedStaleMass,
|
|
29
29
|
refsProtected,
|
|
30
|
+
pricedBytes,
|
|
30
31
|
} from "./measurement.ts";
|
|
31
32
|
import {
|
|
32
33
|
childFoldIds,
|
|
@@ -179,13 +180,15 @@ export function markFreedBytes(
|
|
|
179
180
|
...state,
|
|
180
181
|
expanded: state.expanded.filter((id) => id !== mark.id),
|
|
181
182
|
}, snapshot);
|
|
182
|
-
return expanded && folded ? Math.max(0,
|
|
183
|
+
return expanded && folded ? Math.max(0, pricedBytes(expanded) - pricedBytes(folded)) : 0;
|
|
183
184
|
}
|
|
184
185
|
const source = renderFoldParts(mark.parts, state, snapshot);
|
|
185
186
|
if (!source) return 0;
|
|
186
187
|
const placeholders = mark.kind === "tool-result" ? mark.parts.length : 1;
|
|
187
188
|
const placeholder = placeholders * (mark.brief.length + ESTIMATED_PLACEHOLDER_OVERHEAD_BYTES);
|
|
188
|
-
|
|
189
|
+
// PRICED, NOT COUNTED: a span holding a screenshot is worth one image to the
|
|
190
|
+
// window, not its base64 length (measurement.ts, pricedBytes).
|
|
191
|
+
return Math.max(0, pricedBytes(source) - placeholder);
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
export function markSpanStart(
|
|
@@ -55,6 +55,8 @@ export interface FoldBarModel {
|
|
|
55
55
|
hiddenTokens: number;
|
|
56
56
|
/** Whether a band-top commit has already been weighed against the current count. */
|
|
57
57
|
weighed: boolean;
|
|
58
|
+
/** A commit landed after the count `share` reads; the number is from before it. */
|
|
59
|
+
staleAfterCommit: boolean;
|
|
58
60
|
/** The suspension message when automatic folding has stopped, else null. */
|
|
59
61
|
stopped: string | null;
|
|
60
62
|
}
|
|
@@ -64,11 +66,33 @@ export interface FoldBarTheme {
|
|
|
64
66
|
fg(color: "dim" | "muted" | "text" | "warning" | "error", text: string): string;
|
|
65
67
|
bold(text: string): string;
|
|
66
68
|
getColorMode?(): "truecolor" | "256color";
|
|
69
|
+
getFgAnsi?(color: "text"): string;
|
|
70
|
+
name?: string;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
// Crameri batlow10, classes 3, 6 and 8 (0-indexed)
|
|
70
|
-
//
|
|
71
|
-
|
|
73
|
+
// Crameri batlow10, three classes per background. Classes 3, 6 and 8 (0-indexed) read on
|
|
74
|
+
// a dark terminal; the lightest of them is 1.7:1 against white, so a light terminal steps
|
|
75
|
+
// two classes darker (2, 5 and 6), the same triple the README's light figure uses.
|
|
76
|
+
const BATLOW_ON_DARK = { raw: "#3C6D56", staged: "#D29343", folded: "#FDB7BC" } as const;
|
|
77
|
+
const BATLOW_ON_LIGHT = { raw: "#1C5A62", staged: "#9D892B", folded: "#D29343" } as const;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* WHICH WAY THE BACKGROUND GOES, read off the theme's own text colour rather than its
|
|
81
|
+
* name: a theme whose body text is dark is drawn on a light background. pi hands the
|
|
82
|
+
* text colour out as a truecolor escape, and a theme that does not (256-colour mode,
|
|
83
|
+
* or a host without the accessor) falls back to the name, then to dark.
|
|
84
|
+
*/
|
|
85
|
+
export function lightBackground(theme: FoldBarTheme): boolean {
|
|
86
|
+
try {
|
|
87
|
+
const ansi = theme.getFgAnsi?.("text") ?? "";
|
|
88
|
+
const match = /38;2;(\d+);(\d+);(\d+)/.exec(ansi);
|
|
89
|
+
if (match) {
|
|
90
|
+
const [r, g, b] = match.slice(1).map((v) => Number(v) / 255);
|
|
91
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.5;
|
|
92
|
+
}
|
|
93
|
+
} catch { }
|
|
94
|
+
return /light/i.test(theme.name ?? "");
|
|
95
|
+
}
|
|
72
96
|
|
|
73
97
|
const truecolor = (hex: string, text: string): string => {
|
|
74
98
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
@@ -138,6 +162,7 @@ export function renderFoldBar(model: FoldBarModel, width: number, theme: FoldBar
|
|
|
138
162
|
width, theme.fg("dim", "..."));
|
|
139
163
|
}
|
|
140
164
|
const rich = theme.getColorMode?.() === "truecolor";
|
|
165
|
+
const BATLOW = lightBackground(theme) ? BATLOW_ON_LIGHT : BATLOW_ON_DARK;
|
|
141
166
|
const ink: Record<Cell, (text: string) => string> = {
|
|
142
167
|
raw: (t) => rich ? truecolor(BATLOW.raw, t) : theme.fg("muted", t),
|
|
143
168
|
staged: (t) => rich ? truecolor(BATLOW.staged, t) : theme.fg("text", t),
|
|
@@ -154,19 +179,27 @@ export function renderFoldBar(model: FoldBarModel, width: number, theme: FoldBar
|
|
|
154
179
|
else bar += ink[cells[i]](GLYPH[cells[i]]);
|
|
155
180
|
}
|
|
156
181
|
const pct = Math.round(model.share * 100);
|
|
157
|
-
const parts: string[] = [
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
if (model.
|
|
164
|
-
parts.push(
|
|
165
|
-
|
|
166
|
-
|
|
182
|
+
const parts: string[] = [];
|
|
183
|
+
// A READING FROM BEFORE THE COMMIT SAYS SO. The count only moves when the provider
|
|
184
|
+
// answers, so right after a commit the bar showed the pre-commit percentage in the
|
|
185
|
+
// same type as a live one and read as a commit that did nothing. No estimate is
|
|
186
|
+
// drawn in its place: the freed bytes are transcript mass and the arithmetic would
|
|
187
|
+
// go negative against the window.
|
|
188
|
+
if (model.staleAfterCommit) {
|
|
189
|
+
parts.push(theme.fg("muted", `${pct}% before the commit`));
|
|
190
|
+
} else {
|
|
191
|
+
parts.push(theme.fg("text", `${pct}%`));
|
|
192
|
+
if (model.share < model.commitShare) parts.push(theme.fg("muted", `commit at ${Math.round(model.commitShare * 100)}%`));
|
|
193
|
+
else if (model.weighed) parts.push(theme.fg("muted", "commit held"));
|
|
194
|
+
else parts.push(theme.fg("warning", "COMMIT DUE"));
|
|
167
195
|
}
|
|
196
|
+
// A COUNT FOR WHAT IS STAGED, TOKENS FOR WHAT FOLDING BOUGHT. Both figures are priced
|
|
197
|
+
// by the image law now (measurement.ts pricedBytes): "5.8M to free" beside a 1M window
|
|
198
|
+
// was 83 screenshots' base64 counted as text (Shane 2026-09-03). The unit is written
|
|
199
|
+
// out because pi's k/M format read as megabytes.
|
|
200
|
+
if (model.stagedMarks > 0) parts.push(ink.staged(`${model.stagedMarks} staged`));
|
|
168
201
|
if (model.folds > 0) {
|
|
169
|
-
parts.push(ink.folded(`${model.folds} fold${model.folds === 1 ? "" : "s"}
|
|
202
|
+
parts.push(ink.folded(`${model.folds} fold${model.folds === 1 ? "" : "s"} hide ${formatTokens(model.hiddenTokens)} tokens`));
|
|
170
203
|
}
|
|
171
204
|
// NO BRAND IN FRONT OF THE BAR (Shane 2026-09-02): the bar identifies itself, and the
|
|
172
205
|
// two prose states above keep the brand because nothing else on them says whose they are.
|