Skip to content

Commit 7d2f720

Browse files
Merge pull request #29 from offendingcommit/feat/live-dream-progress
feat(web): add live dream progress panel with adaptive polling
2 parents aa46d47 + 247ab84 commit 7d2f720

14 files changed

Lines changed: 883 additions & 3 deletions

File tree

117 KB
Loading
83.9 KB
Loading
322 KB
Loading
131 KB
Loading
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Capture documentation screenshots for the Dream Progress panel.
4+
*
5+
* The dev server must already be running at the URL passed in via PREVIEW_URL
6+
* (defaults to http://localhost:5178). The /dream-progress showcase route is
7+
* DEV-only and renders three variants of the panel against mock data.
8+
*
9+
* Usage:
10+
* PREVIEW_URL=http://localhost:5178 OUT_DIR=../../docs/screenshots/live-dream-progress \
11+
* node scripts/screenshot-dream-progress.mjs
12+
*/
13+
import { mkdir } from "node:fs/promises";
14+
import path from "node:path";
15+
import { fileURLToPath } from "node:url";
16+
import { chromium } from "@playwright/test";
17+
18+
const __filename = fileURLToPath(import.meta.url);
19+
const __dirname = path.dirname(__filename);
20+
21+
const PREVIEW_URL = process.env.PREVIEW_URL ?? "http://localhost:5178";
22+
const OUT_DIR = path.resolve(
23+
__dirname,
24+
process.env.OUT_DIR ?? "../../../docs/screenshots/live-dream-progress",
25+
);
26+
27+
async function main() {
28+
await mkdir(OUT_DIR, { recursive: true });
29+
const browser = await chromium.launch();
30+
const context = await browser.newContext({
31+
viewport: { width: 1440, height: 900 },
32+
deviceScaleFactor: 2,
33+
colorScheme: "dark",
34+
});
35+
const page = await context.newPage();
36+
37+
// Seed localStorage with a fake instance so the root redirect doesn't kick
38+
// us to the settings page. The showcase doesn't actually make any network
39+
// requests — it renders against in-memory mock data.
40+
await page.addInitScript(() => {
41+
localStorage.setItem(
42+
"openconcho:instances",
43+
JSON.stringify({
44+
instances: [
45+
{
46+
id: "inst_dev_demo",
47+
name: "Demo (mock)",
48+
baseUrl: "http://localhost:9999",
49+
token: "",
50+
},
51+
],
52+
activeId: "inst_dev_demo",
53+
}),
54+
);
55+
});
56+
57+
await page.goto(`${PREVIEW_URL}/dream-progress`, { waitUntil: "networkidle" });
58+
await page.waitForSelector('[data-testid="dream-progress-panel"]');
59+
// Let framer-motion entrance animations settle.
60+
await page.waitForTimeout(600);
61+
62+
// Full showcase — top-to-bottom view of all three variants.
63+
await page.screenshot({
64+
path: path.join(OUT_DIR, "overview.png"),
65+
fullPage: true,
66+
});
67+
68+
// Variant: idle
69+
{
70+
const handle = await page.locator("section").nth(0);
71+
await handle.scrollIntoViewIfNeeded();
72+
await page.waitForTimeout(150);
73+
await handle.screenshot({ path: path.join(OUT_DIR, "idle.png") });
74+
}
75+
76+
// Variant: active (with per-session breakdown)
77+
{
78+
const handle = await page.locator("section").nth(1);
79+
await handle.scrollIntoViewIfNeeded();
80+
await page.waitForTimeout(150);
81+
await handle.screenshot({ path: path.join(OUT_DIR, "active.png") });
82+
}
83+
84+
// Variant: stalled (>30m without forward progress)
85+
{
86+
const handle = await page.locator("section").nth(2);
87+
await handle.scrollIntoViewIfNeeded();
88+
await page.waitForTimeout(150);
89+
await handle.screenshot({ path: path.join(OUT_DIR, "stalled.png") });
90+
}
91+
92+
await browser.close();
93+
console.log(`Saved screenshots to ${OUT_DIR}`);
94+
}
95+
96+
main().catch((err) => {
97+
console.error(err);
98+
process.exit(1);
99+
});

packages/web/src/api/queries.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,22 @@ export function useScheduleDream(workspaceId: string) {
8585
});
8686
}
8787

88+
import type { components } from "./schema.d.ts";
89+
90+
type QueueStatusBody = components["schemas"]["QueueStatus"];
91+
92+
// Poll faster while work is in flight so users can watch dreams/representations
93+
// progress; back off to a slow heartbeat when idle. The TanStack Query callback
94+
// form re-reads the cached value each cycle, so the interval adapts on its own.
95+
export const QUEUE_REFETCH_ACTIVE_MS = 2500;
96+
export const QUEUE_REFETCH_IDLE_MS = 10_000;
97+
98+
export function pickQueueRefetchInterval(data: QueueStatusBody | undefined): number {
99+
if (!data) return QUEUE_REFETCH_IDLE_MS;
100+
const active = (data.in_progress_work_units ?? 0) + (data.pending_work_units ?? 0);
101+
return active > 0 ? QUEUE_REFETCH_ACTIVE_MS : QUEUE_REFETCH_IDLE_MS;
102+
}
103+
88104
export function useQueueStatus(workspaceId: string) {
89105
return useQuery({
90106
queryKey: QK.queueStatus(workspaceId),
@@ -96,7 +112,7 @@ export function useQueueStatus(workspaceId: string) {
96112
return data ?? err(error);
97113
},
98114
enabled: Boolean(workspaceId),
99-
refetchInterval: 10_000,
115+
refetchInterval: (query) => pickQueueRefetchInterval(query.state.data),
100116
});
101117
}
102118

packages/web/src/components/dashboard/Dashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ export function Dashboard() {
209209
<Activity className="w-4 h-4" style={{ color: "var(--accent)" }} strokeWidth={1.5} />
210210
<SectionHeading className="mb-0">Queue Status</SectionHeading>
211211
<span className="text-xs ml-1" style={{ color: "var(--text-4)" }}>
212-
all workspaces · updates every 10s
212+
all workspaces · live polling
213213
</span>
214214
</div>
215215

0 commit comments

Comments
 (0)