Skip to content

Commit 3bebe9e

Browse files
committed
feat(tui): rename the dance easter egg to hatch
1 parent 8ed8f69 commit 3bebe9e

6 files changed

Lines changed: 148 additions & 148 deletions

File tree

apps/pythinker-code/src/tui/commands/dispatch.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { AuthFlowController } from '../controllers/auth-flow';
99
import type { BtwPanelController } from '../controllers/btw-panel';
1010
import type { StreamingUIController } from '../controllers/streaming-ui';
1111
import type { TasksBrowserController } from '../controllers/tasks-browser';
12-
import { tryHandleDanceCommand } from '../easter-eggs/dance';
12+
import { tryHandleHatchCommand } from '../easter-eggs/hatch';
1313
import type { ResolvedTheme } from '../theme/colors';
1414
import type { TUIState } from '../tui-state';
1515
import type {
@@ -378,10 +378,10 @@ async function executeSlashCommand(host: SlashCommandHost, input: string): Promi
378378
return;
379379
}
380380
case 'message':
381-
// Unknown slash command: let /dance claim it before it falls through to
381+
// Unknown slash command: let /hatch claim it before it falls through to
382382
// the model as a normal message. This runs *after* builtin and skill
383383
// resolution, so a real command or a same-named skill always wins.
384-
if (parsedCommand !== null && tryHandleDanceCommand(host, parsedCommand)) {
384+
if (parsedCommand !== null && tryHandleHatchCommand(host, parsedCommand)) {
385385
return;
386386
}
387387
host.sendNormalUserInput(intent.input);

apps/pythinker-code/src/tui/components/chrome/welcome.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
import type { Component } from '@pymodel/pi-tui';
77

88
import {
9-
isRainbowDancing,
10-
renderDanceWelcomeLogo,
11-
renderDanceWelcomeText,
12-
} from '#/tui/easter-eggs/dance';
9+
isRainbowHatching,
10+
renderHatchWelcomeLogo,
11+
renderHatchWelcomeText,
12+
} from '#/tui/easter-eggs/hatch';
1313
import type { AppState } from '#/tui/types';
1414
import type { GitStatusCache } from '#/utils/git/git-status';
1515

@@ -45,7 +45,7 @@ export class WelcomeComponent implements Component, WelcomeLogoAnimationHost {
4545
) {
4646
this.state = state;
4747
this.gitCache = createWelcomeGitCache(state.workDir);
48-
if (requestRender !== undefined && welcomeLogoAnimationEnabled() && !isRainbowDancing()) {
48+
if (requestRender !== undefined && welcomeLogoAnimationEnabled() && !isRainbowHatching()) {
4949
this.eyeAnimator = new WelcomeLogoAnimator(this, requestRender);
5050
queueMicrotask(() => this.eyeAnimator?.start());
5151
}
@@ -70,21 +70,21 @@ export class WelcomeComponent implements Component, WelcomeLogoAnimationHost {
7070

7171
render(width: number): string[] {
7272
const isLoggedOut = !this.state.model;
73-
const copy = isRainbowDancing()
73+
const copy = isRainbowHatching()
7474
? (() => {
7575
const text = buildWelcomeCopyText(isLoggedOut);
7676
return {
77-
head: renderDanceWelcomeText(text.head, 2, true),
78-
strapline: renderDanceWelcomeText(text.strapline, 5),
79-
prompt: renderDanceWelcomeText(text.prompt),
77+
head: renderHatchWelcomeText(text.head, 2, true),
78+
strapline: renderHatchWelcomeText(text.strapline, 5),
79+
prompt: renderHatchWelcomeText(text.prompt),
8080
};
8181
})()
8282
: buildWelcomeCopy(isLoggedOut);
8383
const logoLines = renderPythinkerLogoWithEyes(
8484
this.eyeBlinkState,
8585
this.antennaFrame ?? undefined,
8686
);
87-
const renderedLogo = isRainbowDancing() ? renderDanceWelcomeLogo(logoLines) : logoLines;
87+
const renderedLogo = isRainbowHatching() ? renderHatchWelcomeLogo(logoLines) : logoLines;
8888

8989
return renderWelcomeBanner({
9090
width,

apps/pythinker-code/src/tui/constant/tips.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const WORKING_TIPS: readonly ToolbarTip[] = [
1919
{ text: 'ctrl-s to add guidance without waiting for the turn to finish', priority: 2, solo: true },
2020
{ text: '/tasks to check progress and status for background tasks', priority: 2 },
2121
{ text: '/init: generate AGENTS.md', priority: 2 },
22-
{ text: 'Try /dance for a hidden Easter egg' },
22+
{ text: 'Try /hatch for a hidden Easter egg' },
2323
{
2424
text: '/plugins: manage plugins — try the "Pythinker Datasource" for reliable financial, economic, and academic data',
2525
solo: true,

apps/pythinker-code/src/tui/easter-eggs/dance.ts renamed to apps/pythinker-code/src/tui/easter-eggs/hatch.ts

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* `/dance` easter egg — everything it needs lives in this one file: the
2+
* `/hatch` easter egg — everything it needs lives in this one file: the
33
* rainbow text coloring, the animation state machine, and the command handler.
44
* Removing the feature is "delete this file + its import sites".
55
*
@@ -16,9 +16,9 @@ import type { ParsedSlashInput } from '../commands/types';
1616
import { currentTheme } from '../theme';
1717

1818
/** Frame interval for the rainbow flow animation. */
19-
export const DANCE_FRAME_MS = 110;
19+
export const HATCH_FRAME_MS = 110;
2020
/** How long the rainbow flows before settling (fading out, or freezing). */
21-
export const DANCE_FLOW_MS = 3000;
21+
export const HATCH_FLOW_MS = 3000;
2222

2323
const DARK_RAINBOW = [
2424
'#4FA8FF',
@@ -43,7 +43,7 @@ const LIGHT_RAINBOW = [
4343
'#354CB5',
4444
] as const;
4545

46-
function getDanceRainbowPalette(): readonly [string, ...string[]] {
46+
function getHatchRainbowPalette(): readonly [string, ...string[]] {
4747
return currentTheme.palette.text === '#1A1A1A' ? LIGHT_RAINBOW : DARK_RAINBOW;
4848
}
4949

@@ -66,69 +66,69 @@ export function rainbowText(
6666
.join('');
6767
}
6868

69-
/** Read-only view of the dance state for components that only render it. */
70-
export interface RainbowDanceView {
69+
/** Read-only view of the hatch state for components that only render it. */
70+
export interface RainbowHatchView {
7171
/** Whether consumers should paint themselves in rainbow at all. */
7272
readonly colored: boolean;
7373
/** Palette offset, advancing while the rainbow flows. */
7474
readonly phase: number;
7575
}
7676

77-
export interface RainbowDanceController extends RainbowDanceView {
77+
export interface RainbowHatchController extends RainbowHatchView {
7878
start(opts: { hold: boolean }): void;
7979
stop(): void;
8080
dispose(): void;
8181
}
8282

83-
let currentDanceController: RainbowDanceController | undefined;
84-
let currentDanceView: RainbowDanceView | undefined;
83+
let currentHatchController: RainbowHatchController | undefined;
84+
let currentHatchView: RainbowHatchView | undefined;
8585

86-
export function setRainbowDance(dance: RainbowDanceController | undefined): void {
87-
currentDanceController = dance;
88-
currentDanceView = dance;
86+
export function setRainbowHatch(hatch: RainbowHatchController | undefined): void {
87+
currentHatchController = hatch;
88+
currentHatchView = hatch;
8989
}
9090

91-
export function installRainbowDance(requestRender: () => void): () => void {
92-
currentDanceController?.dispose();
93-
const dance = new RainbowDance(requestRender);
94-
setRainbowDance(dance);
91+
export function installRainbowHatch(requestRender: () => void): () => void {
92+
currentHatchController?.dispose();
93+
const hatch = new RainbowHatch(requestRender);
94+
setRainbowHatch(hatch);
9595
return () => {
96-
dance.dispose();
97-
if (currentDanceController === dance) {
98-
setRainbowDance(undefined);
96+
hatch.dispose();
97+
if (currentHatchController === hatch) {
98+
setRainbowHatch(undefined);
9999
}
100100
};
101101
}
102102

103-
export function getRainbowDanceView(): RainbowDanceView | undefined {
104-
return currentDanceView;
103+
export function getRainbowHatchView(): RainbowHatchView | undefined {
104+
return currentHatchView;
105105
}
106106

107-
export function isRainbowDancing(): boolean {
108-
return currentDanceView?.colored === true;
107+
export function isRainbowHatching(): boolean {
108+
return currentHatchView?.colored === true;
109109
}
110110

111-
export function renderDanceWelcomeText(
111+
export function renderHatchWelcomeText(
112112
text: string,
113113
offset = 0,
114114
bold = false,
115115
): string {
116116
return rainbowText(
117117
text,
118-
getDanceRainbowPalette(),
119-
(currentDanceView?.phase ?? 0) + offset,
118+
getHatchRainbowPalette(),
119+
(currentHatchView?.phase ?? 0) + offset,
120120
bold,
121121
);
122122
}
123123

124-
export function renderDanceWelcomeLogo(logoLines: readonly string[]): string[] {
125-
const phase = currentDanceView?.phase ?? 0;
126-
const palette = getDanceRainbowPalette();
124+
export function renderHatchWelcomeLogo(logoLines: readonly string[]): string[] {
125+
const phase = currentHatchView?.phase ?? 0;
126+
const palette = getHatchRainbowPalette();
127127
return logoLines.map((line, index) => rainbowText(line, palette, phase + index * 3));
128128
}
129129

130-
export function renderDanceFooterModel(modelLabel: string): string {
131-
return rainbowText(modelLabel, getDanceRainbowPalette(), currentDanceView?.phase ?? 0);
130+
export function renderHatchFooterModel(modelLabel: string): string {
131+
return rainbowText(modelLabel, getHatchRainbowPalette(), currentHatchView?.phase ?? 0);
132132
}
133133

134134
/**
@@ -137,7 +137,7 @@ export function renderDanceFooterModel(modelLabel: string): string {
137137
* scrolling away or being rebuilt never disturbs the animation. Three states:
138138
* off (default), flowing, and a frozen static rainbow.
139139
*/
140-
export class RainbowDance implements RainbowDanceController {
140+
export class RainbowHatch implements RainbowHatchController {
141141
private currentPhase = 0;
142142
private isColored = false;
143143
private frameTimer: ReturnType<typeof setInterval> | null = null;
@@ -157,7 +157,7 @@ export class RainbowDance implements RainbowDanceController {
157157
}
158158

159159
/**
160-
* Flow the rainbow for `DANCE_FLOW_MS`, then settle:
160+
* Flow the rainbow for `HATCH_FLOW_MS`, then settle:
161161
* - `hold: false` → fade back to the default (uncolored) banner.
162162
* - `hold: true` → freeze into a static rainbow that stays on.
163163
*/
@@ -166,13 +166,13 @@ export class RainbowDance implements RainbowDanceController {
166166
this.isColored = true;
167167
this.frameTimer = setInterval(() => {
168168
// Phase just increments; rainbowText() takes it modulo the *current*
169-
// palette length, so the dance never needs to know the palette size.
169+
// palette length, so the hatch never needs to know the palette size.
170170
this.currentPhase += 1;
171171
this.requestRender();
172-
}, DANCE_FRAME_MS);
172+
}, HATCH_FRAME_MS);
173173
this.flowStopTimer = setTimeout(() => {
174174
this.settle(opts.hold);
175-
}, DANCE_FLOW_MS);
175+
}, HATCH_FLOW_MS);
176176
this.requestRender();
177177
}
178178

@@ -215,16 +215,16 @@ export class RainbowDance implements RainbowDanceController {
215215
}
216216

217217
/**
218-
* Handle `/dance`:
219-
* /dance flow for a few seconds, then fade back to the default colors
220-
* /dance on flow, then freeze into a static rainbow that stays on
221-
* /dance off turn the rainbow off
218+
* Handle `/hatch`:
219+
* /hatch flow for a few seconds, then fade back to the default colors
220+
* /hatch on flow, then freeze into a static rainbow that stays on
221+
* /hatch off turn the rainbow off
222222
*
223223
* Returns true when it claimed the input.
224224
*/
225-
export function tryHandleDanceCommand(host: SlashCommandHost, parsed: ParsedSlashInput): boolean {
226-
if (parsed.name !== 'dance') return false;
227-
if (currentDanceController === undefined) return false;
225+
export function tryHandleHatchCommand(host: SlashCommandHost, parsed: ParsedSlashInput): boolean {
226+
if (parsed.name !== 'hatch') return false;
227+
if (currentHatchController === undefined) return false;
228228

229229
// The status line dims the whole message, which buried the command in the
230230
// hint. Paint just the command in the brand color (bold) so it reads as a
@@ -233,13 +233,13 @@ export function tryHandleDanceCommand(host: SlashCommandHost, parsed: ParsedSlas
233233

234234
const sub = parsed.args.trim().toLowerCase();
235235
if (sub === 'off') {
236-
currentDanceController.stop();
236+
currentHatchController.stop();
237237
} else if (sub === 'on') {
238-
currentDanceController.start({ hold: true });
239-
host.showStatus(`Dancing — use ${cmd('/dance off')} to turn it off.`);
238+
currentHatchController.start({ hold: true });
239+
host.showStatus(`Hatching — use ${cmd('/hatch off')} to turn it off.`);
240240
} else {
241-
currentDanceController.start({ hold: false });
242-
host.showStatus(`Use ${cmd('/dance on')} to keep the rainbow on.`);
241+
currentHatchController.start({ hold: false });
242+
host.showStatus(`Use ${cmd('/hatch on')} to keep the rainbow on.`);
243243
}
244244
return true;
245245
}

apps/pythinker-code/test/tui/components/chrome/welcome.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
renderWelcomeBanner,
99
} from '#/tui/components/chrome/welcome-banner';
1010
import { WelcomeComponent } from '#/tui/components/chrome/welcome';
11-
import { setRainbowDance, type RainbowDanceController } from '#/tui/easter-eggs/dance';
11+
import { setRainbowHatch, type RainbowHatchController } from '#/tui/easter-eggs/hatch';
1212
import type { AppState } from '#/tui/types';
1313

1414
const TRUECOLOR_PATTERN = /\u001B\[38;2;(\d+);(\d+);(\d+)m/g;
@@ -59,15 +59,15 @@ function headerOf(lines: string[]): string {
5959
return [lines[3], lines[4]].join('\n');
6060
}
6161

62-
function setDanceView(colored: boolean, phase: number): void {
63-
const dance: RainbowDanceController = {
62+
function setHatchView(colored: boolean, phase: number): void {
63+
const hatch: RainbowHatchController = {
6464
colored,
6565
phase,
6666
start: () => {},
6767
stop: () => {},
6868
dispose: () => {},
6969
};
70-
setRainbowDance(dance);
70+
setRainbowHatch(hatch);
7171
}
7272

7373
describe('WelcomeComponent', () => {
@@ -79,27 +79,27 @@ describe('WelcomeComponent', () => {
7979

8080
afterEach(() => {
8181
chalk.level = previousChalkLevel;
82-
setRainbowDance(undefined);
82+
setRainbowHatch(undefined);
8383
});
8484

8585
it('renders the branded banner with semantic logo colors by default', () => {
8686
const codes = truecolorCodes(headerOf(new WelcomeComponent(appState).render(80)));
8787

8888
// The static logo uses themed accent, body, and border tokens; rainbow is
89-
// still off until /dance is activated.
89+
// still off until /hatch is activated.
9090
expect(codes.size).toBeGreaterThanOrEqual(3);
9191
});
9292

9393
it('paints the banner in rainbow while colored', () => {
94-
setDanceView(true, 0);
94+
setHatchView(true, 0);
9595
const codes = truecolorCodes(headerOf(new WelcomeComponent(appState).render(80)));
9696

9797
expect(codes.size).toBeGreaterThanOrEqual(5);
9898
});
9999

100100
it('renders exactly the default banner when not colored', () => {
101101
const base = headerOf(new WelcomeComponent(appState).render(80));
102-
setDanceView(false, 5);
102+
setHatchView(false, 5);
103103
const off = headerOf(new WelcomeComponent(appState).render(80));
104104

105105
expect(off).toBe(base);

0 commit comments

Comments
 (0)