Merge pull request #51 from Kaelio/demo-target-dir-explain

feat(cli): add intro step and project dir to demo tour
This commit is contained in:
Luca Martial 2026-05-12 20:54:46 -04:00 committed by GitHub
commit fefbabab5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 21 deletions

View file

@ -209,7 +209,7 @@ describe('runDemoTour', () => {
},
);
expect(result).toBe(0);
// Navigation called once for databases step, then exits
// Navigation called once for intro, then exits on back
expect(navigation).toHaveBeenCalledTimes(1);
});
@ -218,10 +218,11 @@ describe('runDemoTour', () => {
let callCount = 0;
const navigation = vi.fn().mockImplementation(() => {
callCount++;
// First call (databases): forward
// Second call (sources): back
// Third call (databases again): back (exit)
if (callCount === 1) return Promise.resolve('forward');
// First call (intro): forward
// Second call (databases): forward
// Third call (sources): back
// Fourth call (databases again): back (exit)
if (callCount <= 2) return Promise.resolve('forward');
return Promise.resolve('back');
});
@ -235,7 +236,7 @@ describe('runDemoTour', () => {
},
);
expect(result).toBe(0);
expect(navigation).toHaveBeenCalledTimes(3);
expect(navigation).toHaveBeenCalledTimes(4);
});
it('handles agent step returning back', async () => {
@ -243,10 +244,10 @@ describe('runDemoTour', () => {
let navCount = 0;
const navigation = vi.fn().mockImplementation(() => {
navCount++;
// Forward through databases, sources, context
// Forward through intro, databases, sources, context
// Then back from context (after agents returns back)
// Then back from sources, then back from databases (exit)
if (navCount <= 3) return Promise.resolve('forward');
if (navCount <= 4) return Promise.resolve('forward');
return Promise.resolve('back');
});

View file

@ -62,12 +62,15 @@ function createTargetState(target: KtxPublicIngestPlanTarget): ContextBuildTarge
// Pure rendering functions
// ---------------------------------------------------------------------------
export function renderDemoBanner(): string {
export function renderDemoBanner(projectDir?: string): string {
const lines = [
'',
`${cyan('Demo mode')} — data has been pre-processed and KTX context is already built.`,
'│ This walkthrough illustrates the setup steps. Selections are pre-filled and read-only.',
];
if (projectDir) {
lines.push(`│ Project directory: ${dim(projectDir)}`);
}
return lines.join('\n');
}
@ -144,16 +147,15 @@ export async function waitForDemoNavigation(
};
const onData = (data: Buffer) => {
const char = data.toString();
if (char === '\r' || char === '\n') {
cleanup();
resolve('forward');
} else if (char === '\x1b') {
cleanup();
resolve('back');
} else if (char === '\x03') {
if (data[0] === 0x03) {
cleanup();
reject(new KtxSetupExitError());
} else if (data[0] === 0x0d || data[0] === 0x0a) {
cleanup();
resolve('forward');
} else if (data[0] === 0x1b) {
cleanup();
resolve('back');
}
};
@ -171,8 +173,9 @@ export async function renderDemoCard(
io: KtxCliIo,
stdin?: NodeJS.ReadStream,
waitNav: (stdin?: NodeJS.ReadStream) => Promise<'forward' | 'back'> = waitForDemoNavigation,
projectDir?: string,
): Promise<'forward' | 'back'> {
io.stdout.write(renderDemoBanner() + '\n\n');
io.stdout.write(renderDemoBanner(projectDir) + '\n\n');
io.stdout.write(renderDemoCardContent(title, selections) + '\n');
return waitNav(stdin);
}
@ -337,6 +340,11 @@ export async function runDemoTour(
const projectDir = defaultDemoProjectDir();
await ensureProject({ projectDir, force: false });
io.stdout.write(renderDemoBanner(projectDir) + '\n');
io.stdout.write(`\n│ ${dim('Press Enter to continue, Escape to go back')}\n└\n`);
const introDirection = await waitNav();
if (introDirection === 'back') return 0;
let stepIndex = 0;
while (stepIndex < DEMO_STEPS.length) {
@ -344,11 +352,11 @@ export async function runDemoTour(
let direction: 'forward' | 'back';
if (step === 'databases') {
direction = await renderDemoCard('Database connection', ['PostgreSQL — Orbit Analytics (56 tables, 2 schemas)'], io, undefined, waitNav);
direction = await renderDemoCard('Database connection', ['PostgreSQL — Orbit Analytics (56 tables, 2 schemas)'], io, undefined, waitNav, projectDir);
} else if (step === 'sources') {
direction = await renderDemoCard('Context sources', ['dbt — 34 transformation models', 'Metabase — 80 dashboard cards', 'Notion — 9 knowledge pages'], io, undefined, waitNav);
direction = await renderDemoCard('Context sources', ['dbt — 34 transformation models', 'Metabase — 80 dashboard cards', 'Notion — 9 knowledge pages'], io, undefined, waitNav, projectDir);
} else if (step === 'context') {
io.stdout.write(renderDemoBanner() + '\n\n');
io.stdout.write(renderDemoBanner(projectDir) + '\n\n');
if (deps.skipReplayAnimation) {
direction = await waitNav();
} else {