@linxiraos/pi-tui 1.1.16 → 1.1.17
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Terminal } from "../terminal.js";
|
|
2
2
|
import { type Component, type ResizeScrollbackMode, type TerminalFramePlan, type TerminalFrameProvider, TUI, type TUIOptions, type ViewportSize } from "../tui.js";
|
|
3
3
|
import { CustomEditor } from "./custom-editor.js";
|
|
4
|
-
import { type LspServerInfo, type RecentSession, WelcomeComponent } from "./welcome.js";
|
|
4
|
+
import { type LspServerInfo, type RecentSession, WelcomeComponent, type WelcomeStrings } from "./welcome.js";
|
|
5
5
|
/** Live settings that affect the composer before and after session adoption. */
|
|
6
6
|
export interface ComposerPreferences {
|
|
7
7
|
readonly quiet: boolean;
|
|
@@ -24,6 +24,8 @@ export interface ComposerWelcomeUpdate {
|
|
|
24
24
|
readonly providerName?: string;
|
|
25
25
|
readonly recentSessions?: readonly RecentSession[];
|
|
26
26
|
readonly lspServers?: readonly LspServerInfo[];
|
|
27
|
+
/** Localizable panel strings; pass getters so `/language` switches apply on the next render. */
|
|
28
|
+
readonly strings?: WelcomeStrings;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* Placeholder-only status chrome replayed on the next first frame so the
|
|
@@ -13,7 +13,7 @@ export declare const WELCOME_LSP_SLOTS = 4;
|
|
|
13
13
|
* `r` is a uniform sample in [0, 1). Returns "" when `tips` is empty.
|
|
14
14
|
* Exported for tests. */
|
|
15
15
|
export declare function pickWeightedTip(tips: readonly string[], r: number): string;
|
|
16
|
-
export declare function renderWelcomeTip(tip: string, boxWidth: number, phase?: number): string[];
|
|
16
|
+
export declare function renderWelcomeTip(tip: string, boxWidth: number, phase?: number, tipLabel?: string, newTag?: string): string[];
|
|
17
17
|
export interface RecentSession {
|
|
18
18
|
name: string;
|
|
19
19
|
timeAgo: string;
|
|
@@ -26,6 +26,29 @@ export interface LspServerInfo {
|
|
|
26
26
|
/**
|
|
27
27
|
* Premium welcome screen with block-based OMP logo and two-column layout.
|
|
28
28
|
*/
|
|
29
|
+
/**
|
|
30
|
+
* Localizable strings for the welcome panel. The panel lives in pi-tui, which
|
|
31
|
+
* cannot import the host's i18n catalogue, so the host injects the strings.
|
|
32
|
+
* Every field is optional and falls back to the historical English literal.
|
|
33
|
+
* Fields may be plain strings OR zero-arg getters returning the current value;
|
|
34
|
+
* getters are re-read on every render so `/language` switches apply live.
|
|
35
|
+
*/
|
|
36
|
+
export type WelcomeStringSource = string | (() => string);
|
|
37
|
+
export interface WelcomeStrings {
|
|
38
|
+
back?: WelcomeStringSource;
|
|
39
|
+
tipsTitle?: WelcomeStringSource;
|
|
40
|
+
lspServersTitle?: WelcomeStringSource;
|
|
41
|
+
recentSessionsTitle?: WelcomeStringSource;
|
|
42
|
+
noRecentSessions?: WelcomeStringSource;
|
|
43
|
+
noLspServers?: WelcomeStringSource;
|
|
44
|
+
tipLabel?: WelcomeStringSource;
|
|
45
|
+
newTag?: WelcomeStringSource;
|
|
46
|
+
promptActionsHint?: WelcomeStringSource;
|
|
47
|
+
commandsHint?: WelcomeStringSource;
|
|
48
|
+
runBashHint?: WelcomeStringSource;
|
|
49
|
+
runPythonHint?: WelcomeStringSource;
|
|
50
|
+
nagNerdfont?: WelcomeStringSource;
|
|
51
|
+
}
|
|
29
52
|
export declare class WelcomeComponent implements Component {
|
|
30
53
|
#private;
|
|
31
54
|
private version;
|
|
@@ -33,6 +56,8 @@ export declare class WelcomeComponent implements Component {
|
|
|
33
56
|
private providerName;
|
|
34
57
|
private recentSessions;
|
|
35
58
|
private lspServers;
|
|
59
|
+
/** Inject localized strings; clears the render cache so the next frame uses them. */
|
|
60
|
+
setStrings(strings: WelcomeStrings): void;
|
|
36
61
|
constructor(version: string, modelName: string, providerName: string, recentSessions?: RecentSession[], lspServers?: LspServerInfo[]);
|
|
37
62
|
get tip(): string | undefined;
|
|
38
63
|
invalidate(): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@linxiraos/pi-tui",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.17",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://linxira-os.github.io/zeta/",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@linxiraos/pi-natives": "1.1.
|
|
41
|
-
"@linxiraos/pi-utils": "1.1.
|
|
42
|
-
"@linxiraos/pi-omptype": "1.1.
|
|
43
|
-
"@linxiraos/pi-agent-core": "1.1.
|
|
44
|
-
"@linxiraos/pi-ai": "1.1.
|
|
45
|
-
"@linxiraos/pi-catalog": "1.1.
|
|
46
|
-
"@linxiraos/pi-wire": "1.1.
|
|
47
|
-
"@linxiraos/pi-snapcompact": "1.1.
|
|
40
|
+
"@linxiraos/pi-natives": "1.1.17",
|
|
41
|
+
"@linxiraos/pi-utils": "1.1.17",
|
|
42
|
+
"@linxiraos/pi-omptype": "1.1.17",
|
|
43
|
+
"@linxiraos/pi-agent-core": "1.1.17",
|
|
44
|
+
"@linxiraos/pi-ai": "1.1.17",
|
|
45
|
+
"@linxiraos/pi-catalog": "1.1.17",
|
|
46
|
+
"@linxiraos/pi-wire": "1.1.17",
|
|
47
|
+
"@linxiraos/pi-snapcompact": "1.1.17"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"kitty-vt-wasm": "^0.2.0"
|
package/src/prompt/composer.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { postmortem } from "@linxiraos/pi-utils";
|
|
2
|
+
import { type AnimationFrame, TranscriptContainer } from "../chrome/transcript-container";
|
|
1
3
|
import type { EditorTopBorder } from "../components/composer/types";
|
|
2
4
|
import { Spacer } from "../components/spacer";
|
|
3
|
-
import { isInsideTerminalMultiplexer } from "../terminal-multiplexer";
|
|
4
5
|
import { ProcessTerminal, type Terminal } from "../terminal";
|
|
6
|
+
import { isInsideTerminalMultiplexer } from "../terminal-multiplexer";
|
|
7
|
+
import { ensureThemeSync, getEditorTheme, theme } from "../theme/theme";
|
|
5
8
|
import {
|
|
6
9
|
type Component,
|
|
7
10
|
Container,
|
|
@@ -13,11 +16,8 @@ import {
|
|
|
13
16
|
type ViewportSize,
|
|
14
17
|
} from "../tui";
|
|
15
18
|
import { sliceWithWidth, truncateToWidth, visibleWidth } from "../utils";
|
|
16
|
-
import { postmortem } from "@linxiraos/pi-utils";
|
|
17
19
|
import { CustomEditor } from "./custom-editor";
|
|
18
|
-
import { type
|
|
19
|
-
import { type LspServerInfo, type RecentSession, WelcomeComponent } from "./welcome";
|
|
20
|
-
import { ensureThemeSync, getEditorTheme, theme } from "../theme/theme";
|
|
20
|
+
import { type LspServerInfo, type RecentSession, WelcomeComponent, type WelcomeStrings } from "./welcome";
|
|
21
21
|
|
|
22
22
|
const DOUBLE_INTERRUPT_MS = 500;
|
|
23
23
|
|
|
@@ -56,6 +56,8 @@ export interface ComposerWelcomeUpdate {
|
|
|
56
56
|
readonly providerName?: string;
|
|
57
57
|
readonly recentSessions?: readonly RecentSession[];
|
|
58
58
|
readonly lspServers?: readonly LspServerInfo[];
|
|
59
|
+
/** Localizable panel strings; pass getters so `/language` switches apply on the next render. */
|
|
60
|
+
readonly strings?: WelcomeStrings;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
/**
|
|
@@ -884,12 +886,17 @@ export class Composer implements TerminalFrameProvider {
|
|
|
884
886
|
this.#stopped = true;
|
|
885
887
|
}
|
|
886
888
|
|
|
889
|
+
#welcomeStrings: WelcomeStrings | undefined;
|
|
887
890
|
#applyWelcomeUpdate(update: ComposerWelcomeUpdate): void {
|
|
888
891
|
if (update.version !== undefined) this.#version = update.version;
|
|
889
892
|
if (update.modelName !== undefined) this.#modelName = update.modelName;
|
|
890
893
|
if (update.providerName !== undefined) this.#providerName = update.providerName;
|
|
891
894
|
if (update.recentSessions !== undefined) this.#recentSessions = [...update.recentSessions];
|
|
892
895
|
if (update.lspServers !== undefined) this.#lspServers = [...update.lspServers];
|
|
896
|
+
if (update.strings !== undefined) {
|
|
897
|
+
this.#welcomeStrings = update.strings;
|
|
898
|
+
this.#welcome?.setStrings(update.strings);
|
|
899
|
+
}
|
|
893
900
|
}
|
|
894
901
|
|
|
895
902
|
#ensureWelcome(): void {
|
|
@@ -900,6 +907,7 @@ export class Composer implements TerminalFrameProvider {
|
|
|
900
907
|
this.#recentSessions,
|
|
901
908
|
this.#lspServers,
|
|
902
909
|
);
|
|
910
|
+
if (this.#welcomeStrings) this.#welcome.setStrings(this.#welcomeStrings);
|
|
903
911
|
}
|
|
904
912
|
|
|
905
913
|
#rebuildHeader(): void {
|
package/src/prompt/welcome.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { APP_NAME } from "@linxiraos/pi-utils/dirs";
|
|
1
2
|
import { TERMINAL } from "../terminal-capabilities";
|
|
3
|
+
import { theme } from "../theme/theme";
|
|
2
4
|
import type { Component } from "../tui";
|
|
3
5
|
import { padding, replaceTabs, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../utils";
|
|
4
|
-
import { APP_NAME } from "@linxiraos/pi-utils/dirs";
|
|
5
|
-
import { theme } from "../theme/theme";
|
|
6
6
|
import tipsText from "./tips.txt" with { type: "text" };
|
|
7
7
|
|
|
8
8
|
/** Tips embedded at build time, one per line; blanks dropped. */
|
|
@@ -24,12 +24,12 @@ export const WELCOME_SESSION_SLOTS = 4;
|
|
|
24
24
|
export const WELCOME_LSP_SLOTS = 4;
|
|
25
25
|
|
|
26
26
|
/** Trailing marker that flags a tip as a "what's new" callout. Stripped before
|
|
27
|
-
* wrapping (with any preceding whitespace) and replaced by
|
|
27
|
+
* wrapping (with any preceding whitespace) and replaced by the NEW tag
|
|
28
28
|
* painted as a shimmering rainbow. Non-global so `.test` stays stateless. */
|
|
29
29
|
const NEW_TIP_MARKER = /\s*\[NEW\]\s*$/;
|
|
30
30
|
|
|
31
31
|
/** Visible text rendered in place of {@link NEW_TIP_MARKER}. */
|
|
32
|
-
const
|
|
32
|
+
const NEW_TAG_TEXT_DEFAULT = "NEW!";
|
|
33
33
|
|
|
34
34
|
/** Milliseconds for one full hue rotation of the rainbow "NEW!" tag. */
|
|
35
35
|
const NEW_GLOW_PERIOD_MS = 1500;
|
|
@@ -55,14 +55,14 @@ export function pickWeightedTip(tips: readonly string[], r: number): string {
|
|
|
55
55
|
|
|
56
56
|
type ColorEncoding = "ansi-16m" | "ansi-256";
|
|
57
57
|
|
|
58
|
-
/** Paint each glyph of
|
|
58
|
+
/** Paint each glyph of the NEW tag on a moving HSL rainbow. `phase`
|
|
59
59
|
* rotates the hue offset cyclically; successive renders with increasing phase
|
|
60
60
|
* shimmer, while a fixed phase yields a still rainbow. */
|
|
61
|
-
function renderNewTag(phase: number, encoding: ColorEncoding): string {
|
|
61
|
+
function renderNewTag(tag: string, phase: number, encoding: ColorEncoding): string {
|
|
62
62
|
const bold = "\x1b[1m";
|
|
63
63
|
const reset = "\x1b[0m";
|
|
64
64
|
const wrapped = ((phase % 1) + 1) % 1;
|
|
65
|
-
const chars = [...
|
|
65
|
+
const chars = [...tag];
|
|
66
66
|
let out = bold;
|
|
67
67
|
let prev = "";
|
|
68
68
|
for (let i = 0; i < chars.length; i++) {
|
|
@@ -76,8 +76,14 @@ function renderNewTag(phase: number, encoding: ColorEncoding): string {
|
|
|
76
76
|
}
|
|
77
77
|
return out + reset;
|
|
78
78
|
}
|
|
79
|
-
export function renderWelcomeTip(
|
|
80
|
-
|
|
79
|
+
export function renderWelcomeTip(
|
|
80
|
+
tip: string,
|
|
81
|
+
boxWidth: number,
|
|
82
|
+
phase = 0,
|
|
83
|
+
tipLabel = "Tip: ",
|
|
84
|
+
newTag = "NEW!",
|
|
85
|
+
): string[] {
|
|
86
|
+
const label = tipLabel;
|
|
81
87
|
const labelWidth = visibleWidth(label);
|
|
82
88
|
const bodyBudget = boxWidth - 1 - labelWidth; // 1 = leading indent
|
|
83
89
|
if (bodyBudget < 8) return [];
|
|
@@ -105,8 +111,8 @@ export function renderWelcomeTip(tip: string, boxWidth: number, phase = 0): stri
|
|
|
105
111
|
// box; otherwise drop it onto its own indented continuation line so the
|
|
106
112
|
// styled glyphs never overflow or reflow the wrapped body.
|
|
107
113
|
const encoding: ColorEncoding = TERMINAL.trueColor ? "ansi-16m" : "ansi-256";
|
|
108
|
-
const tag = renderNewTag(phase, encoding);
|
|
109
|
-
const tagWidth = 1 + visibleWidth(
|
|
114
|
+
const tag = renderNewTag(newTag, phase, encoding);
|
|
115
|
+
const tagWidth = 1 + visibleWidth(newTag); // 1 = space separator
|
|
110
116
|
const lastLine = lines[lines.length - 1];
|
|
111
117
|
if (lastLine !== undefined && visibleWidth(lastLine) + tagWidth <= boxWidth) {
|
|
112
118
|
lines[lines.length - 1] = `${lastLine} ${tag}`;
|
|
@@ -132,6 +138,35 @@ export interface LspServerInfo {
|
|
|
132
138
|
/**
|
|
133
139
|
* Premium welcome screen with block-based OMP logo and two-column layout.
|
|
134
140
|
*/
|
|
141
|
+
/**
|
|
142
|
+
* Localizable strings for the welcome panel. The panel lives in pi-tui, which
|
|
143
|
+
* cannot import the host's i18n catalogue, so the host injects the strings.
|
|
144
|
+
* Every field is optional and falls back to the historical English literal.
|
|
145
|
+
* Fields may be plain strings OR zero-arg getters returning the current value;
|
|
146
|
+
* getters are re-read on every render so `/language` switches apply live.
|
|
147
|
+
*/
|
|
148
|
+
export type WelcomeStringSource = string | (() => string);
|
|
149
|
+
export interface WelcomeStrings {
|
|
150
|
+
back?: WelcomeStringSource;
|
|
151
|
+
tipsTitle?: WelcomeStringSource;
|
|
152
|
+
lspServersTitle?: WelcomeStringSource;
|
|
153
|
+
recentSessionsTitle?: WelcomeStringSource;
|
|
154
|
+
noRecentSessions?: WelcomeStringSource;
|
|
155
|
+
noLspServers?: WelcomeStringSource;
|
|
156
|
+
tipLabel?: WelcomeStringSource;
|
|
157
|
+
newTag?: WelcomeStringSource;
|
|
158
|
+
promptActionsHint?: WelcomeStringSource;
|
|
159
|
+
commandsHint?: WelcomeStringSource;
|
|
160
|
+
runBashHint?: WelcomeStringSource;
|
|
161
|
+
runPythonHint?: WelcomeStringSource;
|
|
162
|
+
nagNerdfont?: WelcomeStringSource;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function read(source: WelcomeStringSource | undefined, fallback: string): string {
|
|
166
|
+
if (source === undefined) return fallback;
|
|
167
|
+
return typeof source === "function" ? source() : source;
|
|
168
|
+
}
|
|
169
|
+
|
|
135
170
|
export class WelcomeComponent implements Component {
|
|
136
171
|
#animStart: number | null = null;
|
|
137
172
|
#animTimer: Timer | null = null;
|
|
@@ -147,6 +182,13 @@ export class WelcomeComponent implements Component {
|
|
|
147
182
|
// Bypassed while the intro animation runs (every frame differs).
|
|
148
183
|
#cachedWidth = -1;
|
|
149
184
|
#cachedLines: string[] | undefined;
|
|
185
|
+
#strings: WelcomeStrings = {};
|
|
186
|
+
|
|
187
|
+
/** Inject localized strings; clears the render cache so the next frame uses them. */
|
|
188
|
+
setStrings(strings: WelcomeStrings): void {
|
|
189
|
+
this.#strings = strings;
|
|
190
|
+
this.#cachedLines = undefined;
|
|
191
|
+
}
|
|
150
192
|
|
|
151
193
|
constructor(
|
|
152
194
|
private version: string,
|
|
@@ -159,7 +201,7 @@ export class WelcomeComponent implements Component {
|
|
|
159
201
|
this.#nagRoll ??= Math.random();
|
|
160
202
|
this.#tipRoll ??= Math.random();
|
|
161
203
|
if (theme.getSymbolPreset() === "unicode" && this.#nagRoll < 0.1) {
|
|
162
|
-
return "Please use nerdfont 😭.";
|
|
204
|
+
return read(this.#strings.nagNerdfont, "Please use nerdfont 😭.");
|
|
163
205
|
}
|
|
164
206
|
return pickWeightedTip(TIPS, this.#tipRoll) || undefined;
|
|
165
207
|
}
|
|
@@ -271,7 +313,7 @@ export class WelcomeComponent implements Component {
|
|
|
271
313
|
// Dynamic model/provider labels are truncated inside the fixed column.
|
|
272
314
|
// Letting them influence the responsive breakpoint changes the box height
|
|
273
315
|
// when authoritative session data replaces the empty prepaint labels.
|
|
274
|
-
const leftMinContentWidth = Math.max(minLeftCol, visibleWidth("Welcome back!"));
|
|
316
|
+
const leftMinContentWidth = Math.max(minLeftCol, visibleWidth(read(this.#strings.back, "Welcome back!")));
|
|
275
317
|
const desiredLeftCol = Math.max(
|
|
276
318
|
Math.min(preferredLeftCol, Math.max(minLeftCol, Math.floor(dualContentWidth * 0.35))),
|
|
277
319
|
leftMinContentWidth,
|
|
@@ -291,7 +333,7 @@ export class WelcomeComponent implements Component {
|
|
|
291
333
|
// Left column - centered content
|
|
292
334
|
const leftLines = [
|
|
293
335
|
"",
|
|
294
|
-
this.#centerText(theme.bold("Welcome back!"), leftCol),
|
|
336
|
+
this.#centerText(theme.bold(read(this.#strings.back, "Welcome back!")), leftCol),
|
|
295
337
|
"",
|
|
296
338
|
...logoColored.map(l => this.#centerText(l, leftCol)),
|
|
297
339
|
"",
|
|
@@ -306,7 +348,7 @@ export class WelcomeComponent implements Component {
|
|
|
306
348
|
// Recent sessions content
|
|
307
349
|
const sessionLines: string[] = [];
|
|
308
350
|
if (this.recentSessions.length === 0) {
|
|
309
|
-
sessionLines.push(` ${theme.fg("dim", "No recent sessions")}`);
|
|
351
|
+
sessionLines.push(` ${theme.fg("dim", read(this.#strings.noRecentSessions, "No recent sessions"))}`);
|
|
310
352
|
} else {
|
|
311
353
|
// Reserve width for the bullet prefix (" • ") and the trailing " (timeAgo)"
|
|
312
354
|
// so the relative time is never the part that gets truncated. The name
|
|
@@ -332,7 +374,7 @@ export class WelcomeComponent implements Component {
|
|
|
332
374
|
// LSP servers content
|
|
333
375
|
const lspLines: string[] = [];
|
|
334
376
|
if (this.lspServers.length === 0) {
|
|
335
|
-
lspLines.push(` ${theme.fg("dim", "No LSP servers")}`);
|
|
377
|
+
lspLines.push(` ${theme.fg("dim", read(this.#strings.noLspServers, "No LSP servers"))}`);
|
|
336
378
|
} else {
|
|
337
379
|
for (const server of this.lspServers.slice(0, WELCOME_LSP_SLOTS)) {
|
|
338
380
|
const icon =
|
|
@@ -354,16 +396,16 @@ export class WelcomeComponent implements Component {
|
|
|
354
396
|
|
|
355
397
|
// Right column
|
|
356
398
|
const rightLines = [
|
|
357
|
-
` ${theme.bold(theme.fg("accent", "Tips"))}`,
|
|
358
|
-
` ${theme.fg("dim", "#")}${theme.fg("muted", " for prompt actions")}`,
|
|
359
|
-
` ${theme.fg("dim", "/")}${theme.fg("muted", " for commands")}`,
|
|
360
|
-
` ${theme.fg("dim", "!")}${theme.fg("muted", " to run bash")}`,
|
|
361
|
-
` ${theme.fg("dim", "$")}${theme.fg("muted", " to run python")}`,
|
|
399
|
+
` ${theme.bold(theme.fg("accent", read(this.#strings.tipsTitle, "Tips")))}`,
|
|
400
|
+
` ${theme.fg("dim", "#")}${theme.fg("muted", read(this.#strings.promptActionsHint, " for prompt actions"))}`,
|
|
401
|
+
` ${theme.fg("dim", "/")}${theme.fg("muted", read(this.#strings.commandsHint, " for commands"))}`,
|
|
402
|
+
` ${theme.fg("dim", "!")}${theme.fg("muted", read(this.#strings.runBashHint, " to run bash"))}`,
|
|
403
|
+
` ${theme.fg("dim", "$")}${theme.fg("muted", read(this.#strings.runPythonHint, " to run python"))}`,
|
|
362
404
|
separator,
|
|
363
|
-
` ${theme.bold(theme.fg("accent", "LSP Servers"))}`,
|
|
405
|
+
` ${theme.bold(theme.fg("accent", read(this.#strings.lspServersTitle, "LSP Servers")))}`,
|
|
364
406
|
...lspLines,
|
|
365
407
|
separator,
|
|
366
|
-
` ${theme.bold(theme.fg("accent", "Recent sessions"))}`,
|
|
408
|
+
` ${theme.bold(theme.fg("accent", read(this.#strings.recentSessionsTitle, "Recent sessions")))}`,
|
|
367
409
|
...sessionLines,
|
|
368
410
|
"",
|
|
369
411
|
];
|
|
@@ -429,7 +471,13 @@ export class WelcomeComponent implements Component {
|
|
|
429
471
|
// intro's re-render frames, then settles into a still rainbow once the box
|
|
430
472
|
// caches its resting frame. Non-"[NEW]" tips ignore the phase entirely.
|
|
431
473
|
const phase = NEW_TIP_MARKER.test(tip) ? performance.now() / NEW_GLOW_PERIOD_MS : 0;
|
|
432
|
-
return renderWelcomeTip(
|
|
474
|
+
return renderWelcomeTip(
|
|
475
|
+
tip,
|
|
476
|
+
boxWidth,
|
|
477
|
+
phase,
|
|
478
|
+
read(this.#strings.tipLabel, "Tip: "),
|
|
479
|
+
read(this.#strings.newTag, NEW_TAG_TEXT_DEFAULT),
|
|
480
|
+
);
|
|
433
481
|
}
|
|
434
482
|
|
|
435
483
|
/** Center text within a given width */
|