authorgravatar for me@pixp.ccAlex <me@pixp.cc> 2026-08-12 13:43:18-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-12 16:43:18-04:00
loge3a9c2518d5f71ca24872cb3238c1b921c5ae5a1
treeda1a0919c6015ae7ceb362ed146e117b2dfd6c0f
parent18918d1c4d0933b565d1336a75cc5069547ff5e6
signature Signed by PGP key B5690EEEBB952194

test(mobile): seed snoozed showcase threads (#5155)


2 files changed, 51 insertions(+), 5 deletions(-)

scripts/mobile-showcase-environment.ts+35-4
......@@ -198,6 +198,7 @@ export const SHOWCASE_THREADS = [
198198 "Keep hydration errors precise, but make the development copy unexpectedly delightful.",
199199 response:
200200 "The diagnostics still lead with the exact mismatch and component stack. A tiny optional haiku now closes the expanded explanation.",
201 snoozeMinutes: 90,
201202 },
202203 {
203204 id: "beautiful-boot",
......@@ -211,6 +212,17 @@ export const SHOWCASE_THREADS = [
211212 response:
212213 "The plan groups milestones without changing the underlying log stream, preserves plain-text output, and adds zero work to the hot path.",
213214 },
215 {
216 id: "patient-penguins",
217 projectId: "linux",
218 title: "Teach penguins to wait patiently",
219 branch: "feat/patient-penguins",
220 minutesAgo: 52,
221 request: "Make delayed work easier to follow without adding noise to the scheduler trace.",
222 response:
223 "Delayed work now carries a concise reason through the trace, so the wait is legible without changing scheduling behavior.",
224 snoozeMinutes: 8 * 60,
225 },
214226 // Finished work, settled by hand: the list keeps it as a receded tail so
215227 // the active block above reads as everything still in flight. The active
216228 // block stays small enough that the settled tail begins above the fold —
......@@ -337,20 +349,29 @@ function insertThread(
337349 readonly minutesAgo: number;
338350 readonly state?: "working" | "approval" | "plan";
339351 readonly settled?: boolean;
352 readonly snoozeMinutes?: number;
340353 readonly workspaceRoot: string;
341354 },
342355): void {
343356 const turnId = `${input.id}-turn`;
344357 const updatedAt = minutesBefore(now, input.minutesAgo);
345358 const isWorking = input.state === "working";
359 const snoozedUntil =
360 input.snoozeMinutes === undefined
361 ? null
362 : new Date(now + input.snoozeMinutes * 60_000).toISOString();
363 const snoozedAt =
364 input.snoozeMinutes === undefined
365 ? null
366 : minutesBefore(now, Math.max(1, Math.floor(input.minutesAgo / 2)));
346367 database
347368 .prepare(
348369 `INSERT INTO projection_threads (
349370 thread_id, project_id, title, model_selection_json, runtime_mode, interaction_mode,
350371 branch, worktree_path, latest_turn_id, latest_user_message_at, pending_approval_count,
351372 pending_user_input_count, has_actionable_proposed_plan, created_at, updated_at,
352 archived_at, deleted_at, settled_override, settled_at
353 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, NULL, NULL, ?, ?)`,
373 archived_at, deleted_at, settled_override, settled_at, snoozed_until, snoozed_at
374 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, NULL, NULL, ?, ?, ?, ?)`,
354375 )
355376 .run(
356377 input.id,
......@@ -369,6 +390,8 @@ function insertThread(
369390 updatedAt,
370391 input.settled ? "settled" : null,
371392 input.settled ? updatedAt : null,
393 snoozedUntil,
394 snoozedAt,
372395 );
373396 database
374397 .prepare(
......@@ -409,6 +432,8 @@ const SEEDED_PROJECTION_TABLES = [
409432 "projection_state",
410433] as const;
411434
435const SEEDED_THREAD_COLUMNS = ["snoozed_until", "snoozed_at"] as const;
436
412437function hasSeedableSchema(dbPath: string): boolean {
413438 let database: NodeSqlite.DatabaseSync;
414439 try {
......@@ -417,12 +442,18 @@ function hasSeedableSchema(dbPath: string): boolean {
417442 return false;
418443 }
419444 try {
420 const row = database
445 const tableCount = database
421446 .prepare(
422447 `SELECT COUNT(*) AS count FROM sqlite_master WHERE type = 'table' AND name IN (${SEEDED_PROJECTION_TABLES.map(() => "?").join(", ")})`,
423448 )
424449 .get(...SEEDED_PROJECTION_TABLES) as { count: number };
425 return row.count === SEEDED_PROJECTION_TABLES.length;
450 if (tableCount.count !== SEEDED_PROJECTION_TABLES.length) return false;
451
452 const threadColumns = database.prepare("PRAGMA table_info(projection_threads)").all() as Array<{
453 name: string;
454 }>;
455 const threadColumnNames = new Set(threadColumns.map((column) => column.name));
456 return SEEDED_THREAD_COLUMNS.every((column) => threadColumnNames.has(column));
426457 } catch {
427458 return false;
428459 } finally {
scripts/mobile-showcase.test.ts+16-1
......@@ -270,8 +270,23 @@ it("seeds a playful multi-environment project spectrum", () => {
270270 SHOWCASE_ENVIRONMENTS.map((environment) => environment.label),
271271 ["Moonbase Terminal", "Suspense Station", "Kernel Cabin"],
272272 );
273 assert.equal(SHOWCASE_THREADS.length, 8);
273 assert.equal(SHOWCASE_THREADS.length, 9);
274274 assert.equal(new Set(SHOWCASE_THREADS.map((thread) => thread.projectId)).size, 3);
275 const snoozedThreads = SHOWCASE_THREADS.filter((thread) => "snoozeMinutes" in thread);
276 assert.equal(snoozedThreads.length, 2);
277 assert.deepStrictEqual(
278 snoozedThreads.map((thread) => thread.id),
279 ["hydration-haikus", "patient-penguins"],
280 );
281 assert.equal(new Set(snoozedThreads.map((thread) => thread.snoozeMinutes)).size, 2);
282 for (const thread of snoozedThreads) {
283 assert.equal(thread.response !== null, true, `${thread.title} is not completed`);
284 assert.equal("state" in thread, false, `${thread.title} is blocked or working`);
285 assert.equal("settled" in thread, false, `${thread.title} is settled`);
286 assert.equal(thread.snoozeMinutes > 60, true, `${thread.title} wakes too soon`);
287 }
288 const primaryThread = SHOWCASE_THREADS.find((thread) => thread.id === "remote-command-center");
289 assert.equal(primaryThread !== undefined && !("snoozeMinutes" in primaryThread), true);
275290 // Every project contributes to both the active block and the settled tail,
276291 // so each list scope screenshots with the same two-part structure.
277292 for (const project of SHOWCASE_PROJECTS) {