@underactive/pi-topping-statusline 0.2.1 → 0.2.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.
@@ -3,6 +3,8 @@ import { easeFade } from "./theme.js";
3
3
 
4
4
  export type StatusIndicatorKind = NonNullable<Parameters<CustomEditor["setWorkingStatusIndicator"]>[0]>["kind"];
5
5
 
6
+ /** Slide-out time for a status that appears while the top-left group shows none. */
7
+ export const STATUS_SLIDE_MS = 300;
6
8
  /** Briefly retain a cleared status so handoffs between host indicators do not flash. */
7
9
  export const STATUS_HOLD_MS = 150;
8
10
  /** Cross-fade budget when the embedded working status disappears: half out, half in. */
@@ -13,13 +15,15 @@ export function isMessageKind(kind: StatusIndicatorKind | undefined): boolean {
13
15
  return kind !== undefined && kind !== "working";
14
16
  }
15
17
 
16
- /** Which status the top-left group shows this frame and how far it has faded. */
18
+ /** Which status the top-left group shows this frame and how far it has slid out or faded. */
17
19
  export interface StatusFrame {
18
20
  status: string | undefined;
19
21
  leftFade?: number;
22
+ /** How far the status has slid out (0..1); absent once it is fully out. */
23
+ reveal?: number;
20
24
  /** Kind of the status this frame shows, for truncation styling. */
21
25
  kind: StatusIndicatorKind | undefined;
22
- /** True while a hold or fade still needs repaints. */
26
+ /** True while a slide, hold, or fade still needs repaints. */
23
27
  pending: boolean;
24
28
  }
25
29
 
@@ -31,24 +35,29 @@ interface StatusExit {
31
35
  /**
32
36
  * Resolve host status-indicator handoffs into stable top-bar frames.
33
37
  *
34
- * pi 0.86 routes working, retry, compaction, and branch-summary indicators
35
- * through the editor border and may clear the slot between them. A cleared
36
- * status is held briefly to bridge that gap. Only the working indicator — the
37
- * one that genuinely ends a stream — then cross-fades back to user segments.
38
+ * A status that appears while the top-left group shows none slides out over
39
+ * STATUS_SLIDE_MS. pi 0.86 routes working, retry, compaction, and
40
+ * branch-summary indicators through the editor border and may clear the slot
41
+ * between them. A cleared status is held briefly to bridge that gap, and a
42
+ * status arriving while the previous one is still drawn takes its place
43
+ * without sliding again. Only the working indicator — the one that genuinely
44
+ * ends a stream — then cross-fades back to user segments.
38
45
  */
39
46
  export class StatusTransition {
40
47
  #liveShown = false;
41
48
  #lastStatus: string | undefined;
42
49
  #lastKind: StatusIndicatorKind | undefined;
43
50
  #exit: StatusExit | undefined;
51
+ #slideFrom: number | undefined;
44
52
 
45
53
  resolve(live: string | undefined, kind: StatusIndicatorKind | undefined, now: number): StatusFrame {
46
54
  if (live !== undefined) {
55
+ if (!this.#liveShown && !this.#exitDrawsStatus(now)) this.#slideFrom = now;
47
56
  this.#liveShown = true;
48
57
  this.#lastStatus = live;
49
58
  this.#lastKind = kind;
50
59
  this.#exit = undefined;
51
- return { status: live, kind, pending: false };
60
+ return this.#withSlide({ status: live, kind, pending: false }, now);
52
61
  }
53
62
 
54
63
  if (this.#liveShown) {
@@ -62,7 +71,7 @@ export class StatusTransition {
62
71
  const exit = this.#exit;
63
72
  if (!exit) return { status: undefined, kind: undefined, pending: false };
64
73
  if (now < exit.holdUntil) {
65
- return { status: this.#lastStatus, kind: this.#lastKind, pending: true };
74
+ return this.#withSlide({ status: this.#lastStatus, kind: this.#lastKind, pending: true }, now);
66
75
  }
67
76
  const elapsed = now - exit.holdUntil;
68
77
  if (elapsed >= exit.fadeMs) {
@@ -72,17 +81,14 @@ export class StatusTransition {
72
81
  const half = exit.fadeMs / 2;
73
82
  const outgoing = elapsed < half;
74
83
  const leftFade = easeFade(outgoing ? 1 - elapsed / half : (elapsed - half) / half);
75
- return {
76
- status: outgoing ? this.#lastStatus : undefined,
77
- kind: outgoing ? this.#lastKind : undefined,
78
- leftFade,
79
- pending: true,
80
- };
84
+ if (!outgoing) return { status: undefined, kind: undefined, leftFade, pending: true };
85
+ return this.#withSlide({ status: this.#lastStatus, kind: this.#lastKind, leftFade, pending: true }, now);
81
86
  }
82
87
 
83
88
  pending(now: number): boolean {
84
89
  const exit = this.#exit;
85
- return exit !== undefined && now < exit.holdUntil + exit.fadeMs;
90
+ if (exit) return now < exit.holdUntil + exit.fadeMs;
91
+ return this.#slideFrom !== undefined && now < this.#slideFrom + STATUS_SLIDE_MS;
86
92
  }
87
93
 
88
94
  reset(): void {
@@ -90,9 +96,30 @@ export class StatusTransition {
90
96
  this.#clearExit();
91
97
  }
92
98
 
99
+ /** A cleared status is still drawn through its hold and the fade's outgoing half. */
100
+ #exitDrawsStatus(now: number): boolean {
101
+ const exit = this.#exit;
102
+ return exit !== undefined && now < exit.holdUntil + exit.fadeMs / 2;
103
+ }
104
+
105
+ /**
106
+ * Add slide-out progress to a frame that draws a status. A status cleared
107
+ * mid-slide keeps sliding through its exit rather than snapping to full width.
108
+ */
109
+ #withSlide(frame: StatusFrame, now: number): StatusFrame {
110
+ if (this.#slideFrom === undefined) return frame;
111
+ const progress = (now - this.#slideFrom) / STATUS_SLIDE_MS;
112
+ if (progress >= 1) {
113
+ this.#slideFrom = undefined;
114
+ return frame;
115
+ }
116
+ return { ...frame, reveal: easeFade(progress), pending: true };
117
+ }
118
+
93
119
  #clearExit(): void {
94
120
  this.#lastStatus = undefined;
95
121
  this.#lastKind = undefined;
96
122
  this.#exit = undefined;
123
+ this.#slideFrom = undefined;
97
124
  }
98
125
  }