Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions scripts/mobile-showcase-environment.ts
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export const SHOWCASE_THREADS = [
"Keep hydration errors precise, but make the development copy unexpectedly delightful.",
response:
"The diagnostics still lead with the exact mismatch and component stack. A tiny optional haiku now closes the expanded explanation.",
snoozeMinutes: 90,
},
{
id: "beautiful-boot",
Expand All @@ -201,6 +202,17 @@ export const SHOWCASE_THREADS = [
response:
"The plan groups milestones without changing the underlying log stream, preserves plain-text output, and adds zero work to the hot path.",
},
{
id: "patient-penguins",
projectId: "linux",
title: "Teach penguins to wait patiently",
branch: "feat/patient-penguins",
minutesAgo: 52,
request: "Make delayed work easier to follow without adding noise to the scheduler trace.",
response:
"Delayed work now carries a concise reason through the trace, so the wait is legible without changing scheduling behavior.",
snoozeMinutes: 8 * 60,
},
// Finished work, settled by hand: the list keeps it as a receded tail so
// the active block above reads as everything still in flight. The active
// block stays small enough that the settled tail begins above the fold —
Expand Down Expand Up @@ -327,20 +339,29 @@ function insertThread(
readonly minutesAgo: number;
readonly state?: "working" | "approval" | "plan";
readonly settled?: boolean;
readonly snoozeMinutes?: number;
readonly workspaceRoot: string;
},
): void {
const turnId = `${input.id}-turn`;
const updatedAt = minutesBefore(now, input.minutesAgo);
const isWorking = input.state === "working";
const snoozedUntil =
input.snoozeMinutes === undefined
? null
: new Date(now + input.snoozeMinutes * 60_000).toISOString();
const snoozedAt =
input.snoozeMinutes === undefined
? null
: minutesBefore(now, Math.max(1, Math.floor(input.minutesAgo / 2)));
database
.prepare(
`INSERT INTO projection_threads (
thread_id, project_id, title, model_selection_json, runtime_mode, interaction_mode,
branch, worktree_path, latest_turn_id, latest_user_message_at, pending_approval_count,
pending_user_input_count, has_actionable_proposed_plan, created_at, updated_at,
archived_at, deleted_at, settled_override, settled_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, NULL, NULL, ?, ?)`,
archived_at, deleted_at, settled_override, settled_at, snoozed_until, snoozed_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, NULL, NULL, ?, ?, ?, ?)`,
)
.run(
input.id,
Expand All @@ -359,6 +380,8 @@ function insertThread(
updatedAt,
input.settled ? "settled" : null,
input.settled ? updatedAt : null,
snoozedUntil,
snoozedAt,
);
database
.prepare(
Expand Down Expand Up @@ -399,6 +422,8 @@ const SEEDED_PROJECTION_TABLES = [
"projection_state",
] as const;

const SEEDED_THREAD_COLUMNS = ["snoozed_until", "snoozed_at"] as const;

function hasSeedableSchema(dbPath: string): boolean {
let database: NodeSqlite.DatabaseSync;
try {
Expand All @@ -407,12 +432,18 @@ function hasSeedableSchema(dbPath: string): boolean {
return false;
}
try {
const row = database
const tableCount = database
.prepare(
`SELECT COUNT(*) AS count FROM sqlite_master WHERE type = 'table' AND name IN (${SEEDED_PROJECTION_TABLES.map(() => "?").join(", ")})`,
)
.get(...SEEDED_PROJECTION_TABLES) as { count: number };
return row.count === SEEDED_PROJECTION_TABLES.length;
if (tableCount.count !== SEEDED_PROJECTION_TABLES.length) return false;

const threadColumns = database.prepare("PRAGMA table_info(projection_threads)").all() as Array<{
name: string;
}>;
const threadColumnNames = new Set(threadColumns.map((column) => column.name));
return SEEDED_THREAD_COLUMNS.every((column) => threadColumnNames.has(column));
} catch {
return false;
} finally {
Expand Down
17 changes: 16 additions & 1 deletion scripts/mobile-showcase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,23 @@ it("seeds a playful multi-environment project spectrum", () => {
SHOWCASE_ENVIRONMENTS.map((environment) => environment.label),
["Moonbase Terminal", "Suspense Station", "Kernel Cabin"],
);
assert.equal(SHOWCASE_THREADS.length, 8);
assert.equal(SHOWCASE_THREADS.length, 9);
assert.equal(new Set(SHOWCASE_THREADS.map((thread) => thread.projectId)).size, 3);
const snoozedThreads = SHOWCASE_THREADS.filter((thread) => "snoozeMinutes" in thread);
assert.equal(snoozedThreads.length, 2);
assert.deepStrictEqual(
snoozedThreads.map((thread) => thread.id),
["hydration-haikus", "patient-penguins"],
);
assert.equal(new Set(snoozedThreads.map((thread) => thread.snoozeMinutes)).size, 2);
for (const thread of snoozedThreads) {
assert.equal(thread.response !== null, true, `${thread.title} is not completed`);
assert.equal("state" in thread, false, `${thread.title} is blocked or working`);
assert.equal("settled" in thread, false, `${thread.title} is settled`);
assert.equal(thread.snoozeMinutes > 60, true, `${thread.title} wakes too soon`);
}
const primaryThread = SHOWCASE_THREADS.find((thread) => thread.id === "remote-command-center");
assert.equal(primaryThread !== undefined && !("snoozeMinutes" in primaryThread), true);
// Every project contributes to both the active block and the settled tail,
// so each list scope screenshots with the same two-part structure.
for (const project of SHOWCASE_PROJECTS) {
Expand Down
Loading