Skip to content

feat(widgets): add editable label prefix to text-label widgets - #601

Open
tough-griff wants to merge 3 commits into
sirmalloc:mainfrom
tough-griff:feat/widget-label-override
Open

tough-griff wants to merge 3 commits into
sirmalloc:mainfrom
tough-griff:feat/widget-label-override

Conversation

@tough-griff

Copy link
Copy Markdown
Contributor

Closes #600

Adds a per-widget label override for widgets that show a text label (Model: , Ctx: , In: , …). Select the widget in the line editor and press b (la(b)el…).

Changes

  • Storage: the override is saved as metadata.label and used exactly as typed. An empty string removes the label, and a value equal to the default isn't stored, so untouched items stay minimal. No schema change or migration.
  • Rendering: formatRawOrLabeledValue now uses the override, so every widget that already went through it gets the feature from one change. Widgets that built their label inline now use the helper too: Model, Session ID, Account Email, Context Length/Window/Bar, Memory Usage, Output Style, Session Clock/Name/Cost, Thinking Effort, Terminal Width, Git Staged/Unstaged/Untracked Files, and Current Working Dir (its optional glyph still comes before the label).
  • Opt-in: widgets declare a new optional Widget.getLabelPrefix(item), which returns their default label for the item's current mode (Context % gives Ctx Used: or Ctx Left: ). The usage and speed families get theirs from their shared config.
  • One override for every mode: widgets with more than one label variant (Block: vs Block before a bar) use the same override in all modes.
  • TUI:
    • The keybind is added centrally in ItemsEditor, the same way (h)ide… is, and is hidden while raw value is on.
    • It opens a new LabelEditor. The value is shown in quotes so trailing spaces are visible, and Tab restores the default.
    • Rows show (label: "M ") while an override is set.
    • l would be the natural key, but the reset timers already use it for (l)ocale.
  • Shared text cursor: CustomText's grapheme-aware cursor code is moved into useTextCursor (src/widgets/shared/text-cursor.ts), which the label editor and Custom Text now both use. This also makes Ctrl+←/→ jump to the start/end as Custom Text's hint says. The old checks compared input to 'ArrowLeft', which Ink never sends, and ran after the plain-arrow branch, so they never matched.
  • Out of scope: glyph widgets keep (g)lyph. Widgets whose prefix is really part of the value (Version, Git PR, Sandbox/Voice/Remote Control status, Git Is Fork, Link) aren't changed.
  • Docs: docs/USAGE.md covers the keybind in the raw value section and in the editor keybind list.

Testing

  • Unit tests for the label helper and the text cursor (grapheme movement/deletion with emoji, edges, Ctrl jumps, cursor rendering)
  • Override cases for Model and Session Usage (text and slider modes), plus Context %'s mode-dependent default
  • A registry-wide test that no label widget binds b itself in any mode, next to the existing h check
  • An end-to-end ItemsEditor test covering b → edit → save → (label: "M "), with the keybind and row tag hidden in raw value mode
  • bun test and bun run lint pass
  • Manually checked in the TUI and with piped input:
$ echo '{"model":{"id":"claude-sonnet-4-5","display_name":"Sonnet 4.5"}}' | bun run src/ccstatusline.ts --config settings.json
M Sonnet 4.5 | Model: Sonnet 4.5 | 80
settings.json
{
  "version": 4,
  "lines": [
    [
      { "id": "1", "type": "model", "metadata": { "label": "M " } },
      { "id": "2", "type": "separator" },
      { "id": "3", "type": "model" },
      { "id": "4", "type": "separator" },
      { "id": "5", "type": "terminal-width", "metadata": { "label": "" } }
    ]
  ]
}

🤖 Generated with Claude Code

tough-griff and others added 3 commits September 24, 2026 11:05
Widgets that prepend a text label when raw value is off ("Model: ",
"Ctx: ", "In: ", ...) now accept a per-item override stored in
metadata.label. The override replaces every label variant of a widget
verbatim; an empty override drops the label.

Widgets opt in via Widget.getLabelPrefix(item). Rendering goes through
formatRawOrLabeledValue, which inline-label widgets now use too. The
items editor offers a shared la(b)el… keybind (hidden in raw value
mode) that opens a new LabelEditor, and lists overrides as
(label: "...").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extract CustomText's inline cursor handling into useTextCursor
(src/widgets/shared/text-cursor.ts) and use it from both the Custom
Text and label editors, giving the label editor cursor movement and
forward delete.

Ctrl+Left/Right now jump to the start/end as the editor hint says.
The old checks compared input to 'ArrowLeft', which Ink never sends,
and ran after the plain-arrow branch, so they were unreachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
axisrow added a commit to axisrow/ccstatusline that referenced this pull request Sep 26, 2026
…es (#10)

* feat(widgets): compact label presets with global and per-widget toggles

Add abbreviated label presets for labeled widgets, complementary to the
free-form label editing proposed upstream (sirmalloc#601):

- New `compactLabels` settings flag (default off). Additive v4 key: old
  configs parse unchanged and render identically, no version bump needed.
- Presets live in one map (`Model:` -> `M:`, `Context:` -> `Ctx:`,
  `Cost:` -> `` — the value's own `$` is the glyph) inside
  `formatRawOrLabeledValue`, so every widget routed through the helper
  picks them up with no per-widget changes. Model, Session Cost, and
  Context Bar moved onto the helper.
- One-click global toggle in Global Overrides `(j)`; per-widget override
  via `(j) compact label` in the items editor (metadata `compactLabel`),
  which wins over the global setting in both directions. Capability for
  the editor keybind is derived from a cached preview probe of the
  widget registry.
- Extend the Git Branch width-cap pattern to Current Working Dir and
  Session Name (`w` max-width keybind).
- Document per-widget padding/separator/label width overhead in
  docs/USAGE.md; defaults unchanged.

Tests: preset resolution, toggle metadata, global injection precedence,
migration parity (v1->v4 and schema-less v4 parse), registry-wide `j`
keybind reservation, capable-type probe sanity, Global Overrides `(j)`
keystroke test. bun test 2400 pass; bun run lint clean.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* fix(tui): compact-label toggle cycles over the effective state

The per-widget (j) toggle only wrote/removed 'true', so with the global
Compact Labels setting on it became a visual no-op: the render-time
injection re-compactified the widget after the key was removed, and no
row marker showed the effective state.

- toggleCompactLabel now takes the settings and cycles the EFFECTIVE
  state: compact (saved 'true' or inherited global on) -> write 'false';
  forced off -> drop the key and inherit; otherwise -> write 'true'.
- getCompactLabelModifierText reports the effective state:
  '(compact label)' / '(compact label: on)' / '(compact label: off)'.
- ItemsEditor/input-handlers pass settings through to both.
- docs/USAGE.md: fix preset savings arithmetic (Model -4, Context -4).

Co-Authored-By: Claude Code <noreply@anthropic.com>

* test(renderer): cover (j) toggle forcing labels off while global compact is on

Co-Authored-By: Claude Code <noreply@anthropic.com>

* style: fix import order in compact-labels renderer test

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: axisrow <axisrow@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow customizing the text label on labeled widgets (e.g. "Model: ", "Ctx: ")

1 participant