fix(tree): stop rendering bookkeeping entries as empty rows

Seven session entry types — title_change, credential_pin, mode_change,
service_tier_change, ttsr_injection, reset_boundary and session_init —
fell through #getEntryDisplayText's default branch to the empty string,
and none of them were in the default view's hidden set. Each one drew as
a bare bullet: a row you cannot read, cannot identify and cannot explain
the gap it leaves in the thread.

Hide them in the default and no-tools views, alongside the settings
entries they resemble, and give every one of them a label for `all`
mode. The default branch now falls back to the entry type rather than
the empty string, so a type added later degrades to a dull row instead
of an invisible one. Service tier renders its per-family map, and says
"(default)" when the tier was cleared instead of printing null.
This commit is contained in:
ParadaCarleton
2026-08-05 19:08:54 -07:00
parent 1e492d6ff9
commit ecaefd54d7
3 changed files with 153 additions and 3 deletions
@@ -286,12 +286,20 @@ class TreeList implements Component {
// Apply filter mode
let passesFilter = true;
// Entry types hidden in default view (settings/bookkeeping)
// Entry types hidden in default view (settings/bookkeeping). These carry
// no conversation content, so the tree only shows them in "all" mode.
const isSettingsEntry =
entry.type === "label" ||
entry.type === "custom" ||
entry.type === "model_change" ||
entry.type === "thinking_level_change";
entry.type === "thinking_level_change" ||
entry.type === "service_tier_change" ||
entry.type === "title_change" ||
entry.type === "credential_pin" ||
entry.type === "session_init" ||
entry.type === "ttsr_injection" ||
entry.type === "mode_change" ||
entry.type === "reset_boundary";
switch (this.#filterMode) {
case "user-only":
@@ -658,8 +666,31 @@ class TreeList implements Component {
case "label":
result = theme.fg("dim", `[label: ${entry.label ?? "(cleared)"}]`);
break;
case "service_tier_change": {
// Per-family map, or null when the session went back to the default.
const tiers = entry.serviceTier
? Object.entries(entry.serviceTier)
.map(([family, tier]) => `${family}:${tier}`)
.join(" ")
: "(default)";
result = theme.fg("dim", `[service tier: ${tiers}]`);
break;
}
case "title_change":
result = theme.fg("dim", `[title: ${normalize(entry.title)}]`);
break;
case "mode_change":
result = theme.fg("dim", `[mode: ${entry.mode}]`);
break;
case "credential_pin":
result = theme.fg("dim", `[credential pin: ${entry.provider}]`);
break;
default:
result = "";
// Bookkeeping entries with nothing worth spelling out still get their
// type. A row that renders to the empty string is worse than a
// useless one: it draws as a bare bullet with no way to tell what it
// is or why the tree has a gap in it.
result = theme.fg("dim", `[${entry.type.replaceAll("_", " ")}]`);
}
return isSelected ? theme.bold(result) : result;