pi-one-ui 0.1.1 → 0.2.0

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 (113) hide show
  1. package/README.md +66 -39
  2. package/extensions/{shell → app/commands}/settings-previews.ts +7 -7
  3. package/extensions/app/config/renderer.ts +13 -63
  4. package/extensions/app/config/shell.ts +24 -138
  5. package/extensions/app/config/store.ts +290 -0
  6. package/extensions/app/host/pi-extension-port.ts +25 -0
  7. package/extensions/app/host/pi-ui-port.ts +52 -0
  8. package/extensions/app/host/tui-capabilities.ts +27 -0
  9. package/extensions/app/overlay/input-router.ts +63 -0
  10. package/extensions/app/overlay/overlay-manager.ts +39 -0
  11. package/extensions/{shell → app/overlay}/selector-border.ts +3 -3
  12. package/extensions/app/overlay/selector-controller.ts +104 -0
  13. package/extensions/app/ownership/layout-registry.ts +65 -0
  14. package/extensions/app/ownership.ts +21 -7
  15. package/extensions/app/panel.ts +261 -140
  16. package/extensions/app/runtime/event-coordinator.ts +84 -0
  17. package/extensions/app/runtime/render-scheduler.ts +56 -0
  18. package/extensions/app/runtime/runtime-state.ts +83 -0
  19. package/extensions/app/runtime/tui-runtime.ts +546 -0
  20. package/extensions/features/{context.ts → context-inspector/index.ts} +30 -23
  21. package/extensions/features/{shell/flush-docked-bash.ts → flush-docked-bash.ts} +1 -4
  22. package/extensions/index.ts +2 -48
  23. package/extensions/layouts/context/index.ts +188 -0
  24. package/extensions/{shell → layouts/context/message}/user-message-styles.ts +2 -2
  25. package/extensions/{shell → layouts/context/message}/user-message.ts +2 -2
  26. package/extensions/{transcript → layouts/context}/renderer/compact-mode.ts +17 -13
  27. package/extensions/layouts/context/renderer/context-refresh.ts +79 -0
  28. package/extensions/{transcript → layouts/context}/renderer/default-mode.ts +3 -3
  29. package/extensions/{transcript → layouts/context}/renderer/index.ts +14 -14
  30. package/extensions/{transcript → layouts/context}/renderer/mouse/hover.ts +2 -2
  31. package/extensions/{transcript → layouts/context}/renderer/mouse/interaction.ts +26 -7
  32. package/extensions/{transcript → layouts/context}/renderer/mouse/layout.ts +1 -1
  33. package/extensions/{transcript → layouts/context}/renderer/mouse/packets.ts +3 -3
  34. package/extensions/{transcript → layouts/context}/renderer/mouse/scroll.ts +12 -7
  35. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-component.ts +2 -2
  36. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-edit-render.ts +1 -1
  37. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-highlight.ts +1 -1
  38. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-layout.ts +1 -1
  39. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-limits.ts +1 -1
  40. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-palette.ts +1 -1
  41. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-presentation.ts +1 -1
  42. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-renderer.ts +1 -1
  43. package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-write-render.ts +1 -1
  44. package/extensions/{transcript → layouts/context}/renderer/tool/diff/index.ts +2 -2
  45. package/extensions/{transcript → layouts/context}/renderer/tool/grouping.ts +4 -4
  46. package/extensions/{transcript → layouts/context}/renderer/tool/message-display.ts +6 -4
  47. package/extensions/{transcript → layouts/context}/renderer/tool/names.ts +2 -2
  48. package/extensions/{transcript → layouts/context}/renderer/tool/result.ts +3 -3
  49. package/extensions/{features/agent-summary → layouts/context/summary}/core.ts +1 -1
  50. package/extensions/{features → layouts/context/thinking}/compact-thinking.ts +7 -7
  51. package/extensions/{shell → layouts/editor}/accent-rail-editor.ts +8 -8
  52. package/extensions/{shell → layouts/editor}/completion-menu.ts +3 -6
  53. package/extensions/layouts/editor/controller.ts +613 -0
  54. package/extensions/{shell → layouts/editor}/editor-metadata-format.ts +6 -3
  55. package/extensions/layouts/editor/factory.ts +159 -0
  56. package/extensions/layouts/editor/index.ts +16 -0
  57. package/extensions/{shell → layouts/editor}/minimalist-editor.ts +4 -4
  58. package/extensions/layouts/editor/ownership.ts +103 -0
  59. package/extensions/{shell → layouts/editor}/ui.ts +2 -2
  60. package/extensions/layouts/footer/controller.ts +392 -0
  61. package/extensions/layouts/footer/data.ts +41 -0
  62. package/extensions/{shell → layouts/footer}/extension-status.ts +2 -2
  63. package/extensions/{shell → layouts/footer}/footer-layout.ts +1 -1
  64. package/extensions/{shell → layouts/footer}/footer.ts +11 -8
  65. package/extensions/layouts/footer/index.ts +24 -0
  66. package/extensions/layouts/header/index.ts +5 -0
  67. package/extensions/{features/shell → layouts/header}/startup-header.ts +105 -19
  68. package/extensions/layouts/working-line/controller.ts +291 -0
  69. package/extensions/{shell → layouts/working-line}/interaction-summary.ts +3 -3
  70. package/extensions/{shell → layouts/working-line}/working-line-spinners.ts +1 -1
  71. package/extensions/{shell → layouts/working-line}/working-line.ts +4 -4
  72. package/extensions/{shell → services}/live-context.ts +29 -1
  73. package/extensions/services/project-refresh.ts +374 -0
  74. package/extensions/{shell → services}/project-state.ts +5 -5
  75. package/extensions/{shell/state.ts → services/session-state.ts} +75 -5
  76. package/extensions/{shell → shared}/format.ts +4 -4
  77. package/extensions/tools/component-tree.ts +6 -6
  78. package/package.json +4 -4
  79. package/extensions/shell/index.ts +0 -1659
  80. package/extensions/shell/project-refresh.ts +0 -130
  81. package/extensions/shell/settings-command.ts +0 -2144
  82. package/extensions/transcript/index.ts +0 -65
  83. package/extensions/transcript/renderer/transcript-refresh.ts +0 -79
  84. /package/extensions/{shell → app/ownership}/prototype-patch-registry.ts +0 -0
  85. /package/extensions/{shell → app/runtime}/session-lifecycle.ts +0 -0
  86. /package/extensions/features/{shell/aliases.ts → aliases.ts} +0 -0
  87. /package/extensions/features/{shell → legacy}/working-message.ts +0 -0
  88. /package/extensions/features/{reference → session-reference}/index.ts +0 -0
  89. /package/extensions/features/{reference → session-reference}/session.ts +0 -0
  90. /package/extensions/features/{reference/subagent.ts → subagent-autocomplete.ts} +0 -0
  91. /package/extensions/{shell → layouts/context/message}/user-message-osc.ts +0 -0
  92. /package/extensions/{transcript → layouts/context}/renderer/markdown-enhance.ts +0 -0
  93. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/ATTRIBUTION.md +0 -0
  94. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/ansi-utils.ts +0 -0
  95. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-header.ts +0 -0
  96. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-inline.ts +0 -0
  97. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-parse.ts +0 -0
  98. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/diff-text.ts +0 -0
  99. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/line-width-safety.ts +0 -0
  100. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/shiki-highlight.ts +0 -0
  101. /package/extensions/{transcript → layouts/context}/renderer/tool/diff/write-execution.ts +0 -0
  102. /package/extensions/{transcript → layouts/context}/renderer/tool/show-more-hint.ts +0 -0
  103. /package/extensions/{features/agent-summary → layouts/context/summary}/index.ts +0 -0
  104. /package/extensions/{shell → layouts/editor}/accent-rail-layout-patch.ts +0 -0
  105. /package/extensions/{shell → layouts/editor}/editor-transfer.ts +0 -0
  106. /package/extensions/{shell → layouts/footer}/footer-format.ts +0 -0
  107. /package/extensions/{shell → layouts/working-line}/working-line-messages.ts +0 -0
  108. /package/extensions/{shell/git.ts → services/git-data.ts} +0 -0
  109. /package/extensions/{shell/package-version.ts → services/package-data.ts} +0 -0
  110. /package/extensions/{shell/runtime.ts → services/runtime-data.ts} +0 -0
  111. /package/extensions/{shell → services}/telemetry.ts +0 -0
  112. /package/extensions/{shell → shared}/icons.ts +0 -0
  113. /package/extensions/{shell → shared}/style.ts +0 -0
@@ -0,0 +1,392 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ hasUnsupportedComponentStyle,
4
+ type FooterComponentConfig,
5
+ type PolishedTuiConfig,
6
+ } from "../../app/config/shell.ts";
7
+ import type { SessionLifecycle } from "../../app/runtime/session-lifecycle.ts";
8
+ import { buildSessionDurationLabel } from "../../shared/format.ts";
9
+ import type { LiveContextOverride } from "../../services/live-context.ts";
10
+ import type { FooterState } from "../../services/session-state.ts";
11
+ import { installFooter, installHiddenFooter } from "./footer.ts";
12
+ import { activeFooterReferences } from "./data.ts";
13
+
14
+ const ZENTUI_FOOTER_OWNER = Symbol.for("pi-zentui.footer-owner");
15
+
16
+ type FooterKind = "starship" | "hidden";
17
+
18
+ export type FooterControllerContext = {
19
+ readonly getConfig: () => PolishedTuiConfig;
20
+ readonly saveComponent: (
21
+ patch: Partial<FooterComponentConfig>,
22
+ ) => PolishedTuiConfig;
23
+ readonly state: FooterState;
24
+ readonly sessionLifecycle: SessionLifecycle;
25
+ readonly refresh: () => void;
26
+ readonly scheduleProjectRefresh: (ctx: ExtensionContext) => void;
27
+ readonly getLiveContext: () => LiveContextOverride | undefined;
28
+ readonly onProjectRequirementChanged: (
29
+ ctx: ExtensionContext,
30
+ force?: boolean,
31
+ ) => void;
32
+ readonly onModelLabelChanged: (ctx: ExtensionContext) => void;
33
+ };
34
+
35
+ /**
36
+ * Owns Footer installation, ownership markers, status callbacks, and clocks.
37
+ */
38
+ export class FooterLayoutController {
39
+ private readonly context: FooterControllerContext;
40
+ private installedKind: FooterKind | undefined;
41
+ private installedToken: symbol | undefined;
42
+ private requestFooterRender: (() => void) | undefined;
43
+ private getActiveExtensionStatuses: () => ReadonlyMap<string, string> = () =>
44
+ new Map();
45
+ private stopSessionTimer: () => void = () => {};
46
+ private sessionTimerRequirements = "";
47
+ private lastDurationLabel = "";
48
+
49
+ /**
50
+ * Creates a Footer controller with shared state and project selectors.
51
+ *
52
+ * @param context Runtime services consumed by Footer installation.
53
+ */
54
+ constructor(context: FooterControllerContext) {
55
+ this.context = context;
56
+ }
57
+
58
+ /**
59
+ * Installs the configured Footer and starts required session data updates.
60
+ *
61
+ * @param ctx Active Pi extension context.
62
+ */
63
+ install(ctx: ExtensionContext): void {
64
+ if (ctx.mode !== "tui" && ctx.mode !== undefined) return;
65
+ const staleOwner = this.footerOwner(ctx);
66
+ if (typeof staleOwner === "symbol") this.installedToken = staleOwner;
67
+ this.reconcile(ctx);
68
+ }
69
+
70
+ /**
71
+ * Applies a Footer configuration patch and reconciles its host seam.
72
+ *
73
+ * @param patch Footer configuration changes to persist.
74
+ * @param ctx Active Pi extension context.
75
+ */
76
+ setComponent(
77
+ patch: Partial<FooterComponentConfig>,
78
+ ctx: ExtensionContext,
79
+ ): void {
80
+ const previousStyle = this.context.getConfig().components.footer.style;
81
+ const nextConfig = this.context.saveComponent(patch);
82
+ const styleChanged = nextConfig.components.footer.style !== previousStyle;
83
+ if (patch.style !== undefined) this.reconcile(ctx);
84
+ if (patch.modelLabel !== undefined) this.context.onModelLabelChanged(ctx);
85
+ this.context.onProjectRequirementChanged(ctx, styleChanged);
86
+ this.reconcileSessionTimer();
87
+ this.context.refresh();
88
+ }
89
+
90
+ /**
91
+ * Reconciles native, Starship, and hidden Footer ownership.
92
+ *
93
+ * @param ctx Active Pi extension context.
94
+ */
95
+ reconcile(ctx: ExtensionContext): void {
96
+ switch (this.effectiveFooterStyle()) {
97
+ case "starship":
98
+ this.installStarship(ctx);
99
+ break;
100
+ case "hidden":
101
+ this.installHidden(ctx);
102
+ break;
103
+ case "native":
104
+ this.uninstall(ctx);
105
+ break;
106
+ }
107
+ this.reconcileSessionTimer();
108
+ }
109
+
110
+ /**
111
+ * Removes the owned Footer while leaving third-party ownership untouched.
112
+ *
113
+ * @param ctx Active Pi extension context.
114
+ * @param forceLocalCleanup Whether shutdown should clear local bookkeeping.
115
+ */
116
+ uninstall(ctx: ExtensionContext, forceLocalCleanup = false): void {
117
+ const token = this.ownsStatusLine(ctx) ? this.installedToken : undefined;
118
+ if (!token) return;
119
+ try {
120
+ ctx.ui.setFooter(undefined);
121
+ } catch {
122
+ if (!forceLocalCleanup) return;
123
+ }
124
+ this.clearOwnership(ctx, token);
125
+ }
126
+
127
+ /**
128
+ * Stops Footer timers and clears host ownership during session shutdown.
129
+ *
130
+ * @param ctx Active Pi extension context, when available.
131
+ */
132
+ cleanup(ctx?: ExtensionContext): void {
133
+ this.stopSessionTimer();
134
+ if (ctx && this.isTuiContext(ctx)) this.uninstall(ctx, true);
135
+ this.installedKind = undefined;
136
+ this.installedToken = undefined;
137
+ this.requestFooterRender = undefined;
138
+ this.getActiveExtensionStatuses = () => new Map();
139
+ }
140
+
141
+ /**
142
+ * Reconciles the session timer after a Footer configuration update.
143
+ */
144
+ reconcileSessionTimer(): void {
145
+ const references = this.installedFooterReferences();
146
+ const needsTime = references.has("time");
147
+ const needsDuration = references.has("session_duration");
148
+ const requirements =
149
+ needsTime || needsDuration ? `${needsTime}:${needsDuration}` : "";
150
+ if (
151
+ !this.context.sessionLifecycle.isCurrent() ||
152
+ this.installedKind !== "starship" ||
153
+ !requirements
154
+ ) {
155
+ this.stopSessionTimer();
156
+ this.sessionTimerRequirements = "";
157
+ this.lastDurationLabel = "";
158
+ return;
159
+ }
160
+ if (requirements === this.sessionTimerRequirements) return;
161
+ this.stopSessionTimer();
162
+ this.sessionTimerRequirements = requirements;
163
+ this.lastDurationLabel = "";
164
+ const timer = setInterval(() => {
165
+ if (!this.context.sessionLifecycle.isCurrent()) return;
166
+ if (needsTime) {
167
+ this.context.refresh();
168
+ return;
169
+ }
170
+ const label = this.context.state.sessionStartEpoch
171
+ ? buildSessionDurationLabel(this.context.state.sessionStartEpoch)
172
+ : "";
173
+ if (label === this.lastDurationLabel) return;
174
+ this.lastDurationLabel = label;
175
+ this.context.refresh();
176
+ }, 1000);
177
+ this.stopSessionTimer = () => {
178
+ clearInterval(timer);
179
+ this.sessionTimerRequirements = "";
180
+ this.stopSessionTimer = () => {};
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Returns whether the owned Starship Footer needs project data.
186
+ *
187
+ * @returns Whether project refreshes are required.
188
+ */
189
+ needsProjectRefresh(): boolean {
190
+ return (
191
+ this.installedKind === "starship" && this.installedToken !== undefined
192
+ );
193
+ }
194
+
195
+ /**
196
+ * Returns the active extension status getter supplied by the Footer.
197
+ *
198
+ * @returns Read-only extension status map.
199
+ */
200
+ getExtensionStatuses(): ReadonlyMap<string, string> {
201
+ return this.getActiveExtensionStatuses();
202
+ }
203
+
204
+ /**
205
+ * Returns the format data dependencies of the owned Footer.
206
+ *
207
+ * @returns Active Footer dependency names.
208
+ */
209
+ installedFooterReferences(): Set<string> {
210
+ return this.installedKind === "starship" && this.installedToken
211
+ ? activeFooterReferences(this.context.getConfig())
212
+ : new Set<string>();
213
+ }
214
+
215
+ /**
216
+ * Requests a redraw from the mounted Footer component.
217
+ */
218
+ requestRender(): void {
219
+ this.requestFooterRender?.();
220
+ }
221
+
222
+ /**
223
+ * Reports whether a context is an interactive TUI context.
224
+ *
225
+ * @param ctx Candidate Pi extension context.
226
+ * @returns Whether Footer host APIs are available.
227
+ */
228
+ private isTuiContext(ctx: ExtensionContext): boolean {
229
+ return ctx.hasUI && (ctx.mode === undefined || ctx.mode === "tui");
230
+ }
231
+
232
+ /**
233
+ * Returns the currently marked Footer owner on Pi's UI object.
234
+ *
235
+ * @param ctx Active Pi extension context.
236
+ * @returns Existing owner token, when marked.
237
+ */
238
+ private footerOwner(ctx: ExtensionContext): unknown {
239
+ return (ctx.ui as unknown as Record<PropertyKey, unknown>)[
240
+ ZENTUI_FOOTER_OWNER
241
+ ];
242
+ }
243
+
244
+ /**
245
+ * Tests whether this controller still owns the host Footer marker.
246
+ *
247
+ * @param ctx Active Pi extension context.
248
+ * @returns Whether the local owner token remains installed.
249
+ */
250
+ private ownsStatusLine(ctx: ExtensionContext): boolean {
251
+ return (
252
+ this.installedToken !== undefined &&
253
+ this.footerOwner(ctx) === this.installedToken
254
+ );
255
+ }
256
+
257
+ /**
258
+ * Writes or removes the host Footer ownership marker.
259
+ *
260
+ * @param ctx Active Pi extension context.
261
+ * @param token Owner token, or undefined to remove it.
262
+ */
263
+ private setOwnership(ctx: ExtensionContext, token: symbol | undefined): void {
264
+ const ui = ctx.ui as unknown as Record<PropertyKey, unknown>;
265
+ try {
266
+ if (token) ui[ZENTUI_FOOTER_OWNER] = token;
267
+ else delete ui[ZENTUI_FOOTER_OWNER];
268
+ } catch {
269
+ // Failure to mark ownership prevents destructive native restoration.
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Clears local Footer state after the host has released an owned Footer.
275
+ *
276
+ * @param ctx Active Pi extension context.
277
+ * @param token Owner token being released.
278
+ */
279
+ private clearOwnership(ctx: ExtensionContext, token: symbol): void {
280
+ if (this.installedToken !== token) return;
281
+ this.installedKind = undefined;
282
+ this.installedToken = undefined;
283
+ if (this.footerOwner(ctx) === token) this.setOwnership(ctx, undefined);
284
+ this.requestFooterRender = undefined;
285
+ this.getActiveExtensionStatuses = () => new Map();
286
+ this.stopSessionTimer();
287
+ if (this.context.sessionLifecycle.isCurrent())
288
+ this.context.onProjectRequirementChanged(ctx, true);
289
+ }
290
+
291
+ /**
292
+ * Installs the Starship Footer with transactional local bookkeeping.
293
+ *
294
+ * @param ctx Active Pi extension context.
295
+ */
296
+ private installStarship(ctx: ExtensionContext): void {
297
+ if (this.installedKind === "starship" && this.ownsStatusLine(ctx)) return;
298
+ const token = Symbol("pi-one-ui-starship-footer");
299
+ const previous = this.snapshotBookkeeping(ctx);
300
+ try {
301
+ installFooter(ctx, this.context.state, this.context.getConfig, {
302
+ setRequestRender: (requestRender) => {
303
+ this.requestFooterRender = requestRender;
304
+ },
305
+ scheduleProjectRefresh: (refreshContext) =>
306
+ this.context.scheduleProjectRefresh(refreshContext),
307
+ setExtensionStatusesGetter: (getter) => {
308
+ this.getActiveExtensionStatuses = getter ?? (() => new Map());
309
+ },
310
+ getLiveContext: () => this.context.getLiveContext(),
311
+ onDispose: () => this.clearOwnership(ctx, token),
312
+ });
313
+ this.installedKind = "starship";
314
+ this.installedToken = token;
315
+ this.setOwnership(ctx, token);
316
+ this.context.refresh();
317
+ } catch {
318
+ this.restoreFailedInstallation(ctx, token, previous);
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Installs the hidden Footer while retaining ownership cleanup semantics.
324
+ *
325
+ * @param ctx Active Pi extension context.
326
+ */
327
+ private installHidden(ctx: ExtensionContext): void {
328
+ if (this.installedKind === "hidden" && this.ownsStatusLine(ctx)) return;
329
+ const token = Symbol("pi-one-ui-hidden-footer");
330
+ const previous = this.snapshotBookkeeping(ctx);
331
+ try {
332
+ installHiddenFooter(ctx, () => this.clearOwnership(ctx, token));
333
+ this.installedKind = "hidden";
334
+ this.installedToken = token;
335
+ this.setOwnership(ctx, token);
336
+ this.requestFooterRender = undefined;
337
+ this.getActiveExtensionStatuses = () => new Map();
338
+ this.stopSessionTimer();
339
+ } catch {
340
+ this.restoreFailedInstallation(ctx, token, previous);
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Snapshots callbacks needed to retain a live Footer after a failed swap.
346
+ *
347
+ * @param ctx Active Pi extension context.
348
+ * @returns Previous Footer bookkeeping.
349
+ */
350
+ private snapshotBookkeeping(ctx: ExtensionContext) {
351
+ return {
352
+ token: this.ownsStatusLine(ctx) ? this.installedToken : undefined,
353
+ requestRender: this.requestFooterRender,
354
+ getExtensionStatuses: this.getActiveExtensionStatuses,
355
+ };
356
+ }
357
+
358
+ /**
359
+ * Restores predecessor callbacks after a non-transactional Footer failure.
360
+ *
361
+ * @param ctx Active Pi extension context.
362
+ * @param token Failed replacement token.
363
+ * @param previous Predecessor bookkeeping snapshot.
364
+ */
365
+ private restoreFailedInstallation(
366
+ ctx: ExtensionContext,
367
+ token: symbol,
368
+ previous: ReturnType<FooterLayoutController["snapshotBookkeeping"]>,
369
+ ): void {
370
+ if (
371
+ previous.token !== undefined &&
372
+ this.installedToken === previous.token &&
373
+ this.footerOwner(ctx) === previous.token
374
+ ) {
375
+ this.requestFooterRender = previous.requestRender;
376
+ this.getActiveExtensionStatuses = previous.getExtensionStatuses;
377
+ return;
378
+ }
379
+ this.clearOwnership(ctx, token);
380
+ }
381
+
382
+ /**
383
+ * Resolves the configured Footer style with unsupported-style fallback.
384
+ *
385
+ * @returns Effective Footer style.
386
+ */
387
+ private effectiveFooterStyle(): PolishedTuiConfig["components"]["footer"]["style"] {
388
+ return hasUnsupportedComponentStyle(this.context.getConfig(), "footer")
389
+ ? "native"
390
+ : this.context.getConfig().components.footer.style;
391
+ }
392
+ }
@@ -0,0 +1,41 @@
1
+ import {
2
+ FOOTER_FORMAT_ALIASES,
3
+ type ZentuiConfig,
4
+ } from "../../app/config/shell.ts";
5
+ import {
6
+ collectFooterFormatReferences,
7
+ parseFooterFormat,
8
+ } from "./footer-format.ts";
9
+
10
+ /**
11
+ * Returns the footer data dependencies referenced by the active formats.
12
+ *
13
+ * @param config Current normalized plugin configuration.
14
+ * @returns Names of project, session, runtime, and clock data dependencies.
15
+ */
16
+ export function activeFooterReferences(config: ZentuiConfig): Set<string> {
17
+ const starship = config.components.footer.styles.starship;
18
+ const references = starship.format
19
+ ? collectFooterFormatReferences(
20
+ parseFooterFormat(starship.format),
21
+ FOOTER_FORMAT_ALIASES,
22
+ )
23
+ : new Set<string>([
24
+ ...(starship.segments.sessionName ? ["session_name"] : []),
25
+ ...(starship.segments.runtime ? ["runtime"] : []),
26
+ ...(starship.segments.gitCommit ? ["git_commit"] : []),
27
+ ...(starship.segments.gitMetrics ? ["git_metrics"] : []),
28
+ ...(starship.segments.packageVersion ? ["package"] : []),
29
+ ...(starship.segments.sessionDuration ? ["session_duration"] : []),
30
+ ...(starship.segments.time ? ["time"] : []),
31
+ ]);
32
+ if (starship.responsive) {
33
+ for (const name of collectFooterFormatReferences(
34
+ parseFooterFormat(starship.compactFormat),
35
+ FOOTER_FORMAT_ALIASES,
36
+ )) {
37
+ references.add(name);
38
+ }
39
+ }
40
+ return references;
41
+ }
@@ -3,12 +3,12 @@ import type {
3
3
  ExtensionStatusColorMode,
4
4
  ExtensionStatusPlacement,
5
5
  ZentuiConfig,
6
- } from "../app/config/shell.ts";
6
+ } from "../../app/config/shell.ts";
7
7
  import {
8
8
  getExtensionStatusColorMode,
9
9
  getExtensionStatusPlacement,
10
10
  isExtensionStatusPlacement,
11
- } from "../app/config/shell.ts";
11
+ } from "../../app/config/shell.ts";
12
12
 
13
13
  export type ExtensionStatusSegment = {
14
14
  key: string;
@@ -1,5 +1,5 @@
1
1
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
- import type { CompactFooterMaxLines } from "../app/config/shell.ts";
2
+ import type { CompactFooterMaxLines } from "../../app/config/shell.ts";
3
3
  import type { CompactBoundaryKind } from "./footer-format.ts";
4
4
 
5
5
  export type FooterZones = {
@@ -1,8 +1,8 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
- import type { SeparatorStyle, ZentuiConfig } from "../app/config/shell.ts";
4
- import { FOOTER_FORMAT_ALIASES } from "../app/config/shell.ts";
5
- import { sanitizeEditorMetadataText } from "./editor-metadata-format.ts";
3
+ import type { SeparatorStyle, ZentuiConfig } from "../../app/config/shell.ts";
4
+ import { FOOTER_FORMAT_ALIASES } from "../../app/config/shell.ts";
5
+ import { sanitizeEditorMetadataText } from "../editor/editor-metadata-format.ts";
6
6
  import {
7
7
  collectExtensionStatusSegments,
8
8
  type ExtensionStatusSegment,
@@ -35,11 +35,14 @@ import {
35
35
  formatRuntimeSegment,
36
36
  formatTimeLabel,
37
37
  formatUsernameHostLabel,
38
- } from "./format.ts";
39
- import { resolveRuntimeSymbol } from "./icons.ts";
40
- import type { LiveContextOverride } from "./live-context.ts";
41
- import { type FooterState, modelLabelFor } from "./state.ts";
42
- import { renderStyleForSource } from "./style.ts";
38
+ } from "../../shared/format.ts";
39
+ import { resolveRuntimeSymbol } from "../../shared/icons.ts";
40
+ import type { LiveContextOverride } from "../../services/live-context.ts";
41
+ import {
42
+ type FooterState,
43
+ modelLabelFor,
44
+ } from "../../services/session-state.ts";
45
+ import { renderStyleForSource } from "../../shared/style.ts";
43
46
 
44
47
  const separatorText: Record<SeparatorStyle, string> = {
45
48
  pipe: " | ",
@@ -0,0 +1,24 @@
1
+ export {
2
+ FooterLayoutController,
3
+ type FooterControllerContext,
4
+ } from "./controller.ts";
5
+ export { activeFooterReferences } from "./data.ts";
6
+ export { installFooter, installHiddenFooter } from "./footer.ts";
7
+ export {
8
+ collectExtensionStatusSegments,
9
+ sanitizeExtensionStatusOriginalText,
10
+ sanitizeExtensionStatusText,
11
+ } from "./extension-status.ts";
12
+ export {
13
+ collectFooterFormatReferences,
14
+ compileCompactFormat,
15
+ parseFooterFormat,
16
+ renderFormatSplit,
17
+ renderFormatTokens,
18
+ } from "./footer-format.ts";
19
+ export {
20
+ createInitialState,
21
+ modelLabelFor,
22
+ syncState,
23
+ } from "../../services/session-state.ts";
24
+ export type { FooterState } from "../../services/session-state.ts";
@@ -0,0 +1,5 @@
1
+ export {
2
+ applyStartupHeader,
3
+ renderHeaderLines,
4
+ } from "./startup-header.ts";
5
+ export { default } from "./startup-header.ts";