@xzzpig/pi-starline 0.5.1 → 0.7.1

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.
Files changed (50) hide show
  1. package/FORK-CHANGELOG.md +19 -0
  2. package/README.md +68 -181
  3. package/extensions/starline/config.ts +156 -326
  4. package/extensions/starline/context-live.ts +33 -0
  5. package/extensions/starline/editor-cursor.ts +3 -0
  6. package/extensions/starline/extension-status.ts +9 -5
  7. package/extensions/starline/footer.ts +331 -528
  8. package/extensions/starline/fork-editor-cursor.ts +14 -0
  9. package/extensions/starline/format.ts +151 -20
  10. package/extensions/starline/git-live.ts +20 -0
  11. package/extensions/starline/git-probe.ts +40 -0
  12. package/extensions/starline/git-segments.ts +175 -0
  13. package/extensions/starline/git.ts +71 -0
  14. package/extensions/starline/icons.ts +17 -0
  15. package/extensions/starline/index.ts +188 -91
  16. package/extensions/starline/live-session.ts +164 -0
  17. package/extensions/starline/live-tps.ts +165 -0
  18. package/extensions/starline/mouse/bubble-dismiss.ts +82 -0
  19. package/extensions/starline/mouse/cursor-marker.ts +69 -0
  20. package/extensions/starline/mouse/editor-hit-test.ts +6 -3
  21. package/extensions/starline/mouse/external-layer.ts +42 -0
  22. package/extensions/starline/mouse/fork-native-clipboard.ts +57 -0
  23. package/extensions/starline/mouse/fork-pill-mouse.ts +19 -0
  24. package/extensions/starline/mouse/fork-pointer-coordination.ts +10 -0
  25. package/extensions/starline/mouse/fork-transcript-copy.ts +54 -0
  26. package/extensions/starline/mouse/fork-wheel-compat.ts +22 -0
  27. package/extensions/starline/mouse/hint.ts +4 -4
  28. package/extensions/starline/mouse/index.ts +54 -24
  29. package/extensions/starline/mouse/pointer-events.ts +73 -0
  30. package/extensions/starline/mouse/pointer-shape.ts +138 -0
  31. package/extensions/starline/mouse/transcript-copy.ts +76 -33
  32. package/extensions/starline/paste-collapse.ts +52 -13
  33. package/extensions/starline/pill-actions.ts +590 -0
  34. package/extensions/starline/pill-anchors.ts +26 -0
  35. package/extensions/starline/pill-api.ts +80 -0
  36. package/extensions/starline/pill-bubble.ts +607 -0
  37. package/extensions/starline/pill-config.ts +67 -9
  38. package/extensions/starline/pill.ts +102 -20
  39. package/extensions/starline/project-probe.ts +79 -0
  40. package/extensions/starline/prototype-patch-registry.ts +6 -1
  41. package/extensions/starline/queue-rows.ts +43 -0
  42. package/extensions/starline/settings-command.ts +124 -177
  43. package/extensions/starline/subagent-fleet.ts +246 -0
  44. package/extensions/starline/turn-clock.ts +81 -0
  45. package/extensions/starline/ui.ts +297 -156
  46. package/extensions/starline/working-indicator.ts +189 -0
  47. package/package.json +6 -5
  48. package/types/pill-api.d.ts +115 -0
  49. package/extensions/starline/editor-metadata-format.ts +0 -259
  50. package/extensions/starline/footer-format.ts +0 -279
@@ -15,7 +15,6 @@ import {
15
15
  type ContextStyle,
16
16
  type ExtensionStatusColorMode,
17
17
  type ExtensionStatusPlacement,
18
- type FooterSegmentsConfig,
19
18
  type GitBranchConfig,
20
19
  type GitBranchMaxLength,
21
20
  getExtensionStatusColorMode,
@@ -23,16 +22,23 @@ import {
23
22
  type IconMode,
24
23
  isExtensionStatusColorMode,
25
24
  isExtensionStatusPlacement,
26
- isSeparatorStyle,
27
25
  type MouseConfig,
28
26
  type PathDisplayConfig,
29
27
  type PathDisplayMode,
30
28
  type PolishedTuiConfig,
31
- type SeparatorStyle,
32
29
  type UiFeaturesConfig,
33
30
  } from "./config";
34
31
  import { sanitizeExtensionStatusText } from "./extension-status";
35
32
  import { isIconMode } from "./icons";
33
+ import { pillActionFor, runPillAction } from "./pill-actions";
34
+ import { getPillAnchor } from "./pill-anchors";
35
+ import {
36
+ EXTENSION_STATUS_PREFIX,
37
+ isKnownPillSegment,
38
+ movePillSegment,
39
+ PILL_SEGMENT_NAMES,
40
+ pillSegmentPosition,
41
+ } from "./pill-config";
36
42
  import type { SessionLifecycle } from "./session-lifecycle";
37
43
  import { EDITOR_BORDER_STYLE, renderChromeBorder, safeThemeFg } from "./style";
38
44
 
@@ -42,11 +48,9 @@ const extensionStatusPlacementValues: ExtensionStatusPlacement[] = [
42
48
  "left",
43
49
  "middle",
44
50
  "right",
45
- "editor",
46
51
  ];
47
52
  const extensionStatusColorModeValues: ExtensionStatusColorMode[] = ["themed", "original"];
48
53
  const contextStyleValues: ContextStyle[] = ["text", "gauge", "text+gauge"];
49
- const separatorStyleValues: SeparatorStyle[] = ["pipe", "dot", "chevron", "none"];
50
54
  const pathDisplayModeValues: PathDisplayMode[] = ["basename", "full"];
51
55
  const pathDepthValues = ["0", "1", "2", "3", "4", "5"] as const;
52
56
  const branchLengthPresetValues = ["full", "10", "20", "30", "40", "50"] as const;
@@ -54,25 +58,12 @@ const iconModeValues: IconMode[] = ["auto", "nerd", "ascii"];
54
58
  type FeatureState = "enabled" | "disabled";
55
59
 
56
60
  const featureStateValues: FeatureState[] = ["enabled", "disabled"];
57
- const settingsSections = [
58
- "coloring",
59
- "features",
60
- "layout",
61
- "builtinSegments",
62
- "extensionSegments",
63
- ] as const;
61
+ const settingsSections = ["coloring", "features", "layout", "pills", "extensionSegments"] as const;
64
62
 
65
63
  type ColorSettingId = "starship" | "editorMessages";
66
64
  type FeatureSettingId = keyof UiFeaturesConfig;
67
- type FooterSegmentSettingId = keyof FooterSegmentsConfig;
68
65
  type SettingsSection = (typeof settingsSections)[number];
69
- type LayoutSettingId =
70
- | "contextStyle"
71
- | "separator"
72
- | "pathDisplay"
73
- | "pathDepth"
74
- | "branchLength"
75
- | "iconMode";
66
+ type LayoutSettingId = "contextStyle" | "pathDisplay" | "pathDepth" | "branchLength" | "iconMode";
76
67
 
77
68
  type SettingsCommandDeps = {
78
69
  sessionLifecycle: SessionLifecycle;
@@ -82,13 +73,11 @@ type SettingsCommandDeps = {
82
73
  patch: Partial<UiFeaturesConfig>,
83
74
  ctx: ExtensionContext,
84
75
  ) => { applied: boolean; reason?: string };
85
- setFooterSegments: (patch: Partial<FooterSegmentsConfig>) => void;
86
- setFooterFormat: (value: string) => void;
87
76
  setIconMode: (mode: IconMode) => void;
88
77
  setContextStyle: (style: ContextStyle) => void;
89
- setSeparator: (separator: SeparatorStyle) => void;
90
78
  setPathDisplay: (patch: Partial<PathDisplayConfig>) => void;
91
79
  setGitBranch: (patch: Partial<GitBranchConfig>) => void;
80
+ setPillSegments: (segments: readonly string[]) => void;
92
81
  getActiveExtensionStatuses: () => ReadonlyMap<string, string>;
93
82
  setExtensionStatusPlacement: (key: string, placement: ExtensionStatusPlacement) => void;
94
83
  setExtensionStatusColorMode: (key: string, colorMode: ExtensionStatusColorMode) => void;
@@ -116,63 +105,74 @@ const featureSettingLabels: Record<FeatureSettingId, string> = {
116
105
  };
117
106
 
118
107
  const featureSettingDescriptions: Record<FeatureSettingId, string> = {
119
- editor:
120
- "Enable or disable Starline's custom editor, selector borders, and previous-message chrome.",
108
+ editor: "Enable or disable Starline's custom editor and selector borders.",
121
109
  statusLine: "Enable or disable Starline's custom footer/status line.",
122
110
  copyFriendly:
123
111
  "Hide editor and previous-message rail glyphs for cleaner native terminal selection.",
124
112
  };
125
113
 
126
- const footerSegmentSettingLabels: Record<FooterSegmentSettingId, string> = {
127
- model: "Model name",
128
- thinking: "Thinking level",
129
- cwd: "Current directory",
114
+ /** Row label per bar segment. The order rows are listed in is fixed. */
115
+ const pillSegmentLabels: Record<string, string> = {
116
+ model: "Model",
117
+ thinking: "Thinking",
118
+ queue: "Queue",
119
+ subagents: "Subagents",
120
+ cwd: "Directory",
130
121
  sessionName: "Session name",
131
- gitBranch: "Git branch",
132
- gitStatus: "Git status",
133
- gitCounts: "Git counts",
134
- sessionDuration: "Session duration",
135
- username: "Username@host",
136
- time: "Current time",
137
- os: "OS icon",
138
- runtime: "Runtime",
139
- context: "Context usage",
140
- tokens: "Token counts",
141
- cacheHit: "Cache hit rate",
142
- cost: "Session cost",
143
- packageVersion: "Package version",
122
+ git: "Git",
144
123
  gitCommit: "Git commit",
124
+ gitTag: "Git tag",
145
125
  gitMetrics: "Git line metrics",
126
+ runtime: "Runtime",
127
+ context: "Context",
128
+ cost: "Cost",
129
+ sessionDuration: "Session duration",
130
+ username: "user@host",
131
+ time: "Time",
132
+ os: "OS",
133
+ packageVersion: "Package version",
134
+ extensionStatus: "Other extensions",
146
135
  };
147
136
 
148
- const footerSegmentSettingDescriptions: Record<FooterSegmentSettingId, string> = {
149
- model:
150
- "Show the active model on the left. Off by default because the editor metadata line already shows it — drop $model from editorMetadataFormat to move it down here rather than showing it twice.",
151
- thinking:
152
- "Show the current thinking level on the left, coloured by level. Hidden while thinking is off. Off by default for the same reason as the model segment.",
153
- cwd: "Show or hide the current working directory segment on the left.",
154
- sessionName: "Show or hide the current Pi session name on the left, after the current directory.",
155
- gitBranch: "Show or hide the git branch name on the left.",
156
- gitStatus: "Show or hide git status icons and ahead/behind markers.",
157
- gitCounts:
158
- "Show numeric ahead/behind and stash counts (requires the Git status segment to be enabled).",
159
- sessionDuration: "Show session running time on the left, after the runtime.",
160
- username: "Show user@hostname on the left.",
161
- time: "Show the current time (HH:MM) on the right.",
162
- os: "Show an operating-system icon on the left.",
163
- runtime: "Show or hide the detected runtime/language segment on the left.",
164
- context: "Show or hide context usage on the right.",
165
- tokens: "Show or hide input/output token counts on the right.",
166
- cacheHit:
167
- 'Show the latest turn\'s cache hit rate as its own segment. Off by default; the tokens segment already carries it inline unless segmentOptions.tokens.cache is set to "off".',
168
- cost: "Show or hide session cost on the right.",
169
- packageVersion:
170
- "Show the project’s own manifest version (package.json, Cargo.toml, pyproject.toml, …). Distinct from the runtime segment, which shows the installed toolchain version.",
171
- gitCommit:
172
- "Show the current commit hash (and optional exact-match tag). On detached HEAD this provides context the branch segment can’t. Starship `git_commit`-style; default off.",
173
- gitMetrics:
174
- "Show aggregate added/deleted line counts (e.g. `+12 −3`) via `git diff HEAD --numstat`. Complements the git status counts. Starship `git_metrics`-style; default off.",
175
- };
137
+ const pillSegmentSettingPrefix = "pillSegment:";
138
+
139
+ function pillSegmentSettingId(name: string): string {
140
+ return `${pillSegmentSettingPrefix}${name}`;
141
+ }
142
+
143
+ function pillSegmentFromId(id: string): string | undefined {
144
+ if (!id.startsWith(pillSegmentSettingPrefix)) return undefined;
145
+ const name = id.slice(pillSegmentSettingPrefix.length);
146
+ // Per-status segments (`extensionStatus:<key>`) are rows too; the key is read
147
+ // from whatever the extension published this session, so any key is valid.
148
+ return isKnownPillSegment(name) ? name : undefined;
149
+ }
150
+
151
+ /** Row label for a segment, including the single-status rows. */
152
+ function pillSegmentLabel(name: string): string {
153
+ const builtin = pillSegmentLabels[name];
154
+ if (builtin) return builtin;
155
+ return name.startsWith(EXTENSION_STATUS_PREFIX)
156
+ ? name.slice(EXTENSION_STATUS_PREFIX.length)
157
+ : name;
158
+ }
159
+
160
+ /**
161
+ * Values a row cycles through: `off`, then every position the bar can hold.
162
+ *
163
+ * A segment that is not on the bar gets one extra value so it can be appended to
164
+ * the end. A segment that is already on the bar must not get it: appending it
165
+ * would put it back where it already is, so the cycle would bounce between the
166
+ * last position and a number that renders as that same position, and `off` would
167
+ * become unreachable — which is exactly how a segment at the end of the bar got
168
+ * stuck.
169
+ */
170
+ function pillSegmentValues(shown: number, onBar: boolean): string[] {
171
+ const positions = Array.from({ length: onBar ? shown : shown + 1 }, (_, index) =>
172
+ String(index + 1),
173
+ );
174
+ return ["off", ...positions];
175
+ }
176
176
 
177
177
  const directCommandSuggestions = [
178
178
  "editor enable",
@@ -187,21 +187,20 @@ const directCommandSuggestions = [
187
187
  "mouse enable",
188
188
  "mouse disable",
189
189
  "mouse toggle",
190
- "format clear",
191
- "format $cwd on $git_branch $fill $context",
192
- "format $cwd( on $git_branch)($git_status)$fill($context)( | $cost)",
190
+ "queue",
191
+ "subagents",
192
+ "thinking",
193
193
  ];
194
194
 
195
195
  const sectionLabels: Record<SettingsSection, string> = {
196
196
  coloring: "Coloring",
197
197
  features: "Features",
198
198
  layout: "Layout",
199
- builtinSegments: "Built-in segments",
199
+ pills: "Pills",
200
200
  extensionSegments: "Extension segments",
201
201
  };
202
202
 
203
203
  const thirdPartyStatusSettingPrefix = "thirdPartyStatus:";
204
- const footerSegmentSettingPrefix = "footerSegment:";
205
204
  type ThirdPartyStatusSettingKind = "placement" | "colorMode";
206
205
 
207
206
  function isColorSource(value: string): value is ColorSource {
@@ -216,27 +215,6 @@ function isFeatureSettingId(value: string): value is FeatureSettingId {
216
215
  return value === "editor" || value === "statusLine" || value === "copyFriendly";
217
216
  }
218
217
 
219
- function isFooterSegmentSettingId(value: string): value is FooterSegmentSettingId {
220
- return (
221
- value === "cwd" ||
222
- value === "sessionName" ||
223
- value === "gitBranch" ||
224
- value === "gitStatus" ||
225
- value === "gitCounts" ||
226
- value === "sessionDuration" ||
227
- value === "runtime" ||
228
- value === "context" ||
229
- value === "tokens" ||
230
- value === "cost" ||
231
- value === "username" ||
232
- value === "time" ||
233
- value === "os" ||
234
- value === "packageVersion" ||
235
- value === "gitCommit" ||
236
- value === "gitMetrics"
237
- );
238
- }
239
-
240
218
  function isFeatureState(value: string): value is FeatureState {
241
219
  return value === "enabled" || value === "disabled";
242
220
  }
@@ -269,7 +247,6 @@ function branchLengthValues(maxLength: GitBranchMaxLength): string[] {
269
247
  function isLayoutSettingId(value: string): value is LayoutSettingId {
270
248
  return (
271
249
  value === "contextStyle" ||
272
- value === "separator" ||
273
250
  value === "pathDisplay" ||
274
251
  value === "pathDepth" ||
275
252
  value === "branchLength" ||
@@ -295,25 +272,8 @@ function featurePatch(id: FeatureSettingId, value: FeatureState): Partial<UiFeat
295
272
  return { [id]: value === "enabled" } as Partial<UiFeaturesConfig>;
296
273
  }
297
274
 
298
- function footerSegmentSettingId(key: FooterSegmentSettingId): string {
299
- return `${footerSegmentSettingPrefix}${key}`;
300
- }
301
-
302
- function footerSegmentSettingFromId(id: string): FooterSegmentSettingId | undefined {
303
- if (!id.startsWith(footerSegmentSettingPrefix)) return undefined;
304
- const key = id.slice(footerSegmentSettingPrefix.length);
305
- return isFooterSegmentSettingId(key) ? key : undefined;
306
- }
307
-
308
- function footerSegmentPatch(
309
- id: FooterSegmentSettingId,
310
- value: FeatureState,
311
- ): Partial<FooterSegmentsConfig> {
312
- return { [id]: value === "enabled" } as Partial<FooterSegmentsConfig>;
313
- }
314
-
315
275
  function usageText(): string {
316
- return 'Usage: /starline [editor|statusline|copy-friendly] [enable|disable|toggle] or /starline format "<template>"';
276
+ return "Usage: /starline [editor|statusline|copy-friendly] [enable|disable|toggle], or /starline queue";
317
277
  }
318
278
 
319
279
  function featureNotification(
@@ -382,18 +342,6 @@ function parseMouseCommand(
382
342
  };
383
343
  }
384
344
 
385
- function parseFormatCommand(args: string): { value: string | undefined } | undefined {
386
- const trimmed = args.trim();
387
- if (!trimmed.toLowerCase().startsWith("format")) return undefined;
388
-
389
- const rest = trimmed.slice("format".length).trim();
390
- if (!rest || rest.toLowerCase() === "clear") return { value: undefined };
391
-
392
- const unquoted =
393
- rest.startsWith('"') && rest.endsWith('"') && rest.length >= 2 ? rest.slice(1, -1) : rest;
394
- return { value: unquoted };
395
- }
396
-
397
345
  function argumentCompletions(prefix: string): AutocompleteItem[] | null {
398
346
  const trimmedPrefix = prefix.trimStart().toLowerCase();
399
347
  const items = directCommandSuggestions.map((value) => ({ value, label: value }));
@@ -490,13 +438,6 @@ function buildItems(
490
438
  currentValue: config.contextStyle,
491
439
  values: contextStyleValues,
492
440
  },
493
- {
494
- id: "separator",
495
- label: "Separator",
496
- description: "Choose the separator between default footer segments.",
497
- currentValue: config.separator,
498
- values: separatorStyleValues,
499
- },
500
441
  {
501
442
  id: "pathDisplay",
502
443
  label: "Path display",
@@ -516,8 +457,8 @@ function buildItems(
516
457
  id: "branchLength",
517
458
  label: "Branch length",
518
459
  description: "Show the full branch name or truncate it to a preset visible width.",
519
- currentValue: String(config.gitBranch.maxLength),
520
- values: branchLengthValues(config.gitBranch.maxLength),
460
+ currentValue: String(config.git.branch.maxLength),
461
+ values: branchLengthValues(config.git.branch.maxLength),
521
462
  },
522
463
  {
523
464
  id: "iconMode",
@@ -529,14 +470,29 @@ function buildItems(
529
470
  ];
530
471
  }
531
472
 
532
- if (section === "builtinSegments") {
533
- return (Object.keys(footerSegmentSettingLabels) as FooterSegmentSettingId[]).map((key) => ({
534
- id: footerSegmentSettingId(key),
535
- label: footerSegmentSettingLabels[key],
536
- description: footerSegmentSettingDescriptions[key],
537
- currentValue: featureValue(config.footerSegments[key]),
538
- values: featureStateValues,
539
- }));
473
+ if (section === "pills") {
474
+ const segments = config.pill.segments;
475
+ const row = (name: string, label: string): SettingItem => {
476
+ const position = pillSegmentPosition(segments, name);
477
+ const onBar = position > 0;
478
+ const values = pillSegmentValues(segments.length, onBar);
479
+ return {
480
+ id: pillSegmentSettingId(name),
481
+ label,
482
+ description: `Position in the bar (1 is leftmost). Space cycles off → 1…${values.length - 1}.`,
483
+ currentValue: onBar ? String(position) : "off",
484
+ values,
485
+ };
486
+ };
487
+ const items = PILL_SEGMENT_NAMES.map((name) => row(name, pillSegmentLabel(name)));
488
+ // One row per status an extension published this session, so a single
489
+ // status can be placed on the bar without editing starline.json. Sorted,
490
+ // so the rows do not move under the cursor between changes.
491
+ const statusKeys = [...activeStatuses.keys()].sort();
492
+ for (const key of statusKeys) {
493
+ items.push(row(`${EXTENSION_STATUS_PREFIX}${key}`, key));
494
+ }
495
+ return items;
540
496
  }
541
497
 
542
498
  const statuses = Array.from(activeStatuses.entries()).sort(([a], [b]) =>
@@ -621,25 +577,6 @@ export function registerStarlineSettingsCommand(pi: ExtensionAPI, deps: Settings
621
577
  handler: async (_args, ctx) => {
622
578
  const args = typeof _args === "string" ? _args : "";
623
579
 
624
- const formatCommand = parseFormatCommand(args);
625
- if (formatCommand) {
626
- try {
627
- deps.setFooterFormat(formatCommand.value ?? "");
628
- deps.requestRender();
629
- if (ctx.hasUI) {
630
- if (formatCommand.value === undefined) {
631
- ctx.ui.notify("Footer format cleared (using default layout)", "info");
632
- } else {
633
- ctx.ui.notify(`Footer format: ${formatCommand.value}`, "info");
634
- }
635
- }
636
- } catch (error) {
637
- const message = error instanceof Error ? error.message : String(error);
638
- if (ctx.hasUI) ctx.ui.notify(`Could not update footer format: ${message}`, "error");
639
- }
640
- return;
641
- }
642
-
643
580
  const directCommand = parseDirectFeatureCommand(args, deps.getConfig());
644
581
  if (directCommand) {
645
582
  try {
@@ -680,6 +617,16 @@ export function registerStarlineSettingsCommand(pi: ExtensionAPI, deps: Settings
680
617
  return;
681
618
  }
682
619
 
620
+ // The keyboard half of a clickable pill: same action, same bubble, pointed at
621
+ // the pill the footer last drew. Only segments with an action resolve, so an
622
+ // unknown word still falls through to the usage warning below.
623
+ const actionKey = args.trim().toLowerCase();
624
+ const action = pillActionFor(actionKey);
625
+ if (action) {
626
+ runPillAction(action, ctx, getPillAnchor(actionKey), "command");
627
+ return;
628
+ }
629
+
683
630
  if (args.trim()) {
684
631
  if (ctx.hasUI) ctx.ui.notify(usageText(), "warning");
685
632
  return;
@@ -747,15 +694,6 @@ export function registerStarlineSettingsCommand(pi: ExtensionAPI, deps: Settings
747
694
  return;
748
695
  }
749
696
 
750
- if (id === "separator" && isSeparatorStyle(newValue)) {
751
- deps.setSeparator(newValue);
752
- settingsList.updateValue(id, newValue);
753
- deps.requestRender();
754
- ctx.ui.notify(`Separator: ${newValue}`, "info");
755
- tui.requestRender();
756
- return;
757
- }
758
-
759
697
  if (id === "pathDisplay" && isPathDisplayMode(newValue)) {
760
698
  deps.setPathDisplay({ mode: newValue });
761
699
  settingsList.updateValue(id, newValue);
@@ -795,13 +733,22 @@ export function registerStarlineSettingsCommand(pi: ExtensionAPI, deps: Settings
795
733
  return;
796
734
  }
797
735
 
798
- const footerSegmentSetting = footerSegmentSettingFromId(id);
799
- if (footerSegmentSetting && isFeatureState(newValue)) {
800
- deps.setFooterSegments(footerSegmentPatch(footerSegmentSetting, newValue));
801
- settingsList.updateValue(id, newValue);
736
+ const pillSegment = pillSegmentFromId(id);
737
+ if (pillSegment) {
738
+ const segments = movePillSegment(
739
+ deps.getConfig().pill.segments,
740
+ pillSegment,
741
+ newValue,
742
+ );
743
+ deps.setPillSegments(segments);
744
+ // Every row's position can change, so the list is rebuilt
745
+ // rather than patched — then the cursor is put back.
746
+ settingsList = makeSettingsList();
747
+ settingsList.selectItem(id);
802
748
  deps.requestRender();
749
+ const label = pillSegmentLabel(pillSegment);
803
750
  ctx.ui.notify(
804
- `${footerSegmentSettingLabels[footerSegmentSetting]}: ${newValue}`,
751
+ newValue === "off" ? `${label}: off` : `${label}: position ${newValue}`,
805
752
  "info",
806
753
  );
807
754
  tui.requestRender();