quadwork 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -82
- package/out/404.html +1 -1
- package/out/__next.__PAGE__.txt +3 -3
- package/out/__next._full.txt +12 -12
- package/out/__next._head.txt +4 -4
- package/out/__next._index.txt +6 -6
- package/out/__next._tree.txt +2 -2
- package/out/_next/static/chunks/006g3lco-9xqf.js +1 -0
- package/out/_next/static/chunks/035rt-n0oid7d.js +1 -0
- package/out/_next/static/chunks/{0e~ue9ca5zrep.js → 05ok82hwk0x-c.js} +1 -1
- package/out/_next/static/chunks/0u~7e4fgf-u06.css +2 -0
- package/out/_next/static/chunks/{0swlbn4q4u71z.js → 0zqyw6q.jp~1i.js} +14 -14
- package/out/_next/static/chunks/17y2walb2um9w.js +1 -0
- package/out/_next/static/chunks/{134b1p_egmf1c.js → 18cmux34jwe.p.js} +1 -1
- package/out/_not-found/__next._full.txt +11 -11
- package/out/_not-found/__next._head.txt +4 -4
- package/out/_not-found/__next._index.txt +6 -6
- package/out/_not-found/__next._not-found.__PAGE__.txt +2 -2
- package/out/_not-found/__next._not-found.txt +3 -3
- package/out/_not-found/__next._tree.txt +2 -2
- package/out/_not-found.html +1 -1
- package/out/_not-found.txt +11 -11
- package/out/app-shell/__next._full.txt +11 -11
- package/out/app-shell/__next._head.txt +4 -4
- package/out/app-shell/__next._index.txt +6 -6
- package/out/app-shell/__next._tree.txt +2 -2
- package/out/app-shell/__next.app-shell.__PAGE__.txt +2 -2
- package/out/app-shell/__next.app-shell.txt +3 -3
- package/out/app-shell.html +1 -1
- package/out/app-shell.txt +11 -11
- package/out/index.html +1 -1
- package/out/index.txt +12 -12
- package/out/project/_/__next._full.txt +12 -12
- package/out/project/_/__next._head.txt +4 -4
- package/out/project/_/__next._index.txt +6 -6
- package/out/project/_/__next._tree.txt +2 -2
- package/out/project/_/__next.project.$d$id.__PAGE__.txt +3 -3
- package/out/project/_/__next.project.$d$id.txt +3 -3
- package/out/project/_/__next.project.txt +3 -3
- package/out/project/_/memory/__next._full.txt +12 -12
- package/out/project/_/memory/__next._head.txt +4 -4
- package/out/project/_/memory/__next._index.txt +6 -6
- package/out/project/_/memory/__next._tree.txt +2 -2
- package/out/project/_/memory/__next.project.$d$id.memory.__PAGE__.txt +3 -3
- package/out/project/_/memory/__next.project.$d$id.memory.txt +3 -3
- package/out/project/_/memory/__next.project.$d$id.txt +3 -3
- package/out/project/_/memory/__next.project.txt +3 -3
- package/out/project/_/memory.html +1 -1
- package/out/project/_/memory.txt +12 -12
- package/out/project/_/queue/__next._full.txt +12 -12
- package/out/project/_/queue/__next._head.txt +4 -4
- package/out/project/_/queue/__next._index.txt +6 -6
- package/out/project/_/queue/__next._tree.txt +2 -2
- package/out/project/_/queue/__next.project.$d$id.queue.__PAGE__.txt +3 -3
- package/out/project/_/queue/__next.project.$d$id.queue.txt +3 -3
- package/out/project/_/queue/__next.project.$d$id.txt +3 -3
- package/out/project/_/queue/__next.project.txt +3 -3
- package/out/project/_/queue.html +1 -1
- package/out/project/_/queue.txt +12 -12
- package/out/project/_.html +1 -1
- package/out/project/_.txt +12 -12
- package/out/settings/__next._full.txt +12 -12
- package/out/settings/__next._head.txt +4 -4
- package/out/settings/__next._index.txt +6 -6
- package/out/settings/__next._tree.txt +2 -2
- package/out/settings/__next.settings.__PAGE__.txt +3 -3
- package/out/settings/__next.settings.txt +3 -3
- package/out/settings.html +1 -1
- package/out/settings.txt +12 -12
- package/out/setup/__next._full.txt +12 -12
- package/out/setup/__next._head.txt +4 -4
- package/out/setup/__next._index.txt +6 -6
- package/out/setup/__next._tree.txt +2 -2
- package/out/setup/__next.setup.__PAGE__.txt +3 -3
- package/out/setup/__next.setup.txt +3 -3
- package/out/setup.html +1 -1
- package/out/setup.txt +12 -12
- package/package.json +5 -2
- package/server/index.js +248 -12
- package/server/routes.js +364 -10
- package/out/_next/static/chunks/06mbme.sc_26-.css +0 -2
- package/out/_next/static/chunks/0caq73v0knw_w.js +0 -1
- package/out/_next/static/chunks/0md7hgvwnovzq.js +0 -1
- package/out/_next/static/chunks/0omuxbg.tg-il.js +0 -1
- /package/out/_next/static/{na3L7KeOGKGsbamYVibRj → 6uvV3nUfwr_t_JKrZJSP8}/_buildManifest.js +0 -0
- /package/out/_next/static/{na3L7KeOGKGsbamYVibRj → 6uvV3nUfwr_t_JKrZJSP8}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{na3L7KeOGKGsbamYVibRj → 6uvV3nUfwr_t_JKrZJSP8}/_ssgManifest.js +0 -0
package/server/routes.js
CHANGED
|
@@ -267,6 +267,18 @@ router.put("/api/loop-guard", async (req, res) => {
|
|
|
267
267
|
if (!tomlPath || !fs.existsSync(tomlPath)) {
|
|
268
268
|
return res.status(404).json({ error: "config.toml not found for project" });
|
|
269
269
|
}
|
|
270
|
+
// Capture the previous value before rewriting so we can decide
|
|
271
|
+
// whether the /continue auto-resume should fire (only when the
|
|
272
|
+
// operator is RAISING the limit — lowering it means they want
|
|
273
|
+
// the runaway loop to stay paused).
|
|
274
|
+
let previousValue = null;
|
|
275
|
+
try {
|
|
276
|
+
const previousContent = fs.readFileSync(tomlPath, "utf-8");
|
|
277
|
+
const prevMatch = previousContent.match(/^\s*max_agent_hops\s*=\s*(\d+)/m);
|
|
278
|
+
if (prevMatch) previousValue = parseInt(prevMatch[1], 10);
|
|
279
|
+
} catch {
|
|
280
|
+
// fall through — previousValue stays null, auto-resume will skip
|
|
281
|
+
}
|
|
270
282
|
try {
|
|
271
283
|
let content = fs.readFileSync(tomlPath, "utf-8");
|
|
272
284
|
if (/^\s*max_agent_hops\s*=/m.test(content)) {
|
|
@@ -289,14 +301,77 @@ router.put("/api/loop-guard", async (req, res) => {
|
|
|
289
301
|
// /api/chat does (#230): re-sync the session token from AC and
|
|
290
302
|
// retry once. Other failures stay non-fatal — the persisted value
|
|
291
303
|
// still takes effect on next AC restart.
|
|
304
|
+
//
|
|
305
|
+
// #417 / quadwork#309: the update_settings ws event correctly
|
|
306
|
+
// updates router.max_hops in the running AC (verified in AC's
|
|
307
|
+
// app.py:1249), AND writes settings.json via _save_settings. But
|
|
308
|
+
// AC's router stays paused once it has tripped the guard — raising
|
|
309
|
+
// max_hops at runtime does NOT resurrect an already-paused channel
|
|
310
|
+
// (router.py:76-77 → `paused = True`). The operator typically
|
|
311
|
+
// raises the limit precisely BECAUSE the channel is stuck paused,
|
|
312
|
+
// so we immediately follow the update_settings event with a
|
|
313
|
+
// `/continue` chat message (the same path AC's own slash command
|
|
314
|
+
// handler uses at app.py:1106-1110) to resume routing. This is the
|
|
315
|
+
// whole fix: the previous version updated max_hops live but left
|
|
316
|
+
// the channel frozen, which made the widget look like a no-op.
|
|
292
317
|
let live = false;
|
|
318
|
+
let autoResumed = false;
|
|
319
|
+
// Only auto-resume when ALL of:
|
|
320
|
+
// (a) operator is RAISING the limit (lowering = "make it
|
|
321
|
+
// stricter", must leave a paused runaway alone)
|
|
322
|
+
// (b) the router is currently paused (AC's continue_routing
|
|
323
|
+
// resets hop_count + paused + guard_emitted unconditionally,
|
|
324
|
+
// so firing it on an actively-running chain would silently
|
|
325
|
+
// extend the chain beyond the new limit — t2a finding)
|
|
326
|
+
// (c) previousValue is known (null means we can't prove it's a
|
|
327
|
+
// raise, so err on the side of not touching router state)
|
|
328
|
+
const isRaising = previousValue !== null && value > previousValue;
|
|
329
|
+
const ensureLive = async (sessionToken) => {
|
|
330
|
+
await sendWsEvent(base, sessionToken, { type: "update_settings", data: { max_agent_hops: value } });
|
|
331
|
+
if (isRaising) {
|
|
332
|
+
// Check AC's /api/status before firing /continue so we don't
|
|
333
|
+
// reset hop_count on a running (unpaused) chain. The endpoint
|
|
334
|
+
// exposes `paused: true` iff ANY channel currently paused.
|
|
335
|
+
let isPaused = false;
|
|
336
|
+
try {
|
|
337
|
+
// AC's security middleware (app.py:212-224) only accepts
|
|
338
|
+
// bearer auth for /api/messages, /api/send, and /api/rules/*.
|
|
339
|
+
// /api/status requires x-session-token header (or ?token=),
|
|
340
|
+
// so pass that instead — a bearer header silently 403s and
|
|
341
|
+
// leaves isPaused stuck at false, defeating the gate.
|
|
342
|
+
const statusUrl = `${base}/api/status`;
|
|
343
|
+
const statusRes = await fetch(statusUrl, {
|
|
344
|
+
headers: sessionToken ? { "x-session-token": sessionToken } : {},
|
|
345
|
+
signal: AbortSignal.timeout(5000),
|
|
346
|
+
});
|
|
347
|
+
if (statusRes.ok) {
|
|
348
|
+
const statusJson = await statusRes.json();
|
|
349
|
+
isPaused = !!(statusJson && statusJson.paused);
|
|
350
|
+
}
|
|
351
|
+
} catch {
|
|
352
|
+
// Status fetch failed — err toward "don't auto-resume". The
|
|
353
|
+
// operator can always type /continue manually.
|
|
354
|
+
}
|
|
355
|
+
if (isPaused) {
|
|
356
|
+
// Resume paused channels. /continue is routed by AC's ws
|
|
357
|
+
// message handler when the buffer starts with /continue;
|
|
358
|
+
// the handler calls router.continue_routing() which
|
|
359
|
+
// unpauses AND resets hop_count — which is why we gate on
|
|
360
|
+
// isPaused to avoid wiping the counter on a live chain.
|
|
361
|
+
await sendWsEvent(base, sessionToken, { type: "message", text: "/continue", channel: "general", sender: "user" });
|
|
362
|
+
autoResumed = true;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
live = true;
|
|
366
|
+
};
|
|
367
|
+
let base = null;
|
|
293
368
|
try {
|
|
294
|
-
const
|
|
369
|
+
const chattr = getChattrConfig(projectId);
|
|
370
|
+
base = chattr.url;
|
|
371
|
+
const sessionToken = chattr.token;
|
|
295
372
|
if (base) {
|
|
296
|
-
const event = { type: "update_settings", data: { max_agent_hops: value } };
|
|
297
373
|
try {
|
|
298
|
-
await
|
|
299
|
-
live = true;
|
|
374
|
+
await ensureLive(sessionToken);
|
|
300
375
|
} catch (err) {
|
|
301
376
|
if (err && err.code === "EAGENTCHATTR_401") {
|
|
302
377
|
console.warn(`[loop-guard] ws auth failed for ${projectId}, re-syncing session token and retrying...`);
|
|
@@ -305,8 +380,7 @@ router.put("/api/loop-guard", async (req, res) => {
|
|
|
305
380
|
const { token: refreshed } = getChattrConfig(projectId);
|
|
306
381
|
if (refreshed && refreshed !== sessionToken) {
|
|
307
382
|
try {
|
|
308
|
-
await
|
|
309
|
-
live = true;
|
|
383
|
+
await ensureLive(refreshed);
|
|
310
384
|
} catch (retryErr) {
|
|
311
385
|
console.warn(`[loop-guard] retry after token resync failed: ${retryErr.message || retryErr}`);
|
|
312
386
|
}
|
|
@@ -320,7 +394,7 @@ router.put("/api/loop-guard", async (req, res) => {
|
|
|
320
394
|
console.warn(`[loop-guard] live update failed for ${projectId}: ${err.message || err}`);
|
|
321
395
|
}
|
|
322
396
|
|
|
323
|
-
res.json({ ok: true, value, live });
|
|
397
|
+
res.json({ ok: true, value, live, previousValue, resumed: autoResumed });
|
|
324
398
|
});
|
|
325
399
|
|
|
326
400
|
// #412 / quadwork#279: project history export + import.
|
|
@@ -532,6 +606,214 @@ router.post("/api/project-history", async (req, res) => {
|
|
|
532
606
|
res.json({ ok: errors.length === 0, imported, skipped, total: body.messages.length, errors });
|
|
533
607
|
});
|
|
534
608
|
|
|
609
|
+
// #424 / quadwork#304 Phase 4: list + restore auto-snapshots.
|
|
610
|
+
// snapshotProjectHistory() in server/index.js writes envelope
|
|
611
|
+
// files to ~/.quadwork/{id}/history-snapshots/{ISO}.json before
|
|
612
|
+
// destructive restart/update operations. These endpoints let the
|
|
613
|
+
// Project History widget surface them with a restore button so
|
|
614
|
+
// the operator can roll back a bad /clear or botched update.
|
|
615
|
+
router.get("/api/project-history/snapshots", (req, res) => {
|
|
616
|
+
const projectId = req.query.project;
|
|
617
|
+
if (!projectId) return res.status(400).json({ error: "Missing project" });
|
|
618
|
+
const snapDir = path.join(CONFIG_DIR, projectId, "history-snapshots");
|
|
619
|
+
if (!fs.existsSync(snapDir)) return res.json({ snapshots: [] });
|
|
620
|
+
try {
|
|
621
|
+
const entries = fs.readdirSync(snapDir)
|
|
622
|
+
.filter((f) => f.endsWith(".json"))
|
|
623
|
+
.map((f) => {
|
|
624
|
+
const st = fs.statSync(path.join(snapDir, f));
|
|
625
|
+
return { name: f, size: st.size, mtime: st.mtimeMs };
|
|
626
|
+
})
|
|
627
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
628
|
+
res.json({ snapshots: entries });
|
|
629
|
+
} catch (err) {
|
|
630
|
+
res.status(500).json({ error: "Failed to list snapshots", detail: err.message });
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
router.post("/api/project-history/restore", async (req, res) => {
|
|
635
|
+
const projectId = req.query.project;
|
|
636
|
+
const name = req.query.name || req.body?.name;
|
|
637
|
+
if (!projectId || !name) return res.status(400).json({ error: "Missing project or name" });
|
|
638
|
+
// Prevent path traversal — only allow basenames from the snapshot
|
|
639
|
+
// directory; reject anything with a separator or ".." segment.
|
|
640
|
+
if (name !== path.basename(name) || name.includes("..") || !name.endsWith(".json")) {
|
|
641
|
+
return res.status(400).json({ error: "Invalid snapshot name" });
|
|
642
|
+
}
|
|
643
|
+
const snapPath = path.join(CONFIG_DIR, projectId, "history-snapshots", name);
|
|
644
|
+
if (!fs.existsSync(snapPath)) {
|
|
645
|
+
return res.status(404).json({ error: "Snapshot not found" });
|
|
646
|
+
}
|
|
647
|
+
let body;
|
|
648
|
+
try {
|
|
649
|
+
const text = fs.readFileSync(snapPath, "utf-8");
|
|
650
|
+
body = JSON.parse(text);
|
|
651
|
+
} catch (err) {
|
|
652
|
+
return res.status(500).json({ error: "Failed to read snapshot", detail: err.message });
|
|
653
|
+
}
|
|
654
|
+
// Post the snapshot back through the existing import endpoint
|
|
655
|
+
// with both bypass flags — the snapshot contains real agent
|
|
656
|
+
// senders (so allow_agent_senders) and may match a previous
|
|
657
|
+
// restore's exported_at (so allow_duplicate). This is the
|
|
658
|
+
// legitimate disaster-recovery case the #297 denylist expected.
|
|
659
|
+
try {
|
|
660
|
+
const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
661
|
+
const qwPort = cfg.port || 8400;
|
|
662
|
+
const r = await fetch(`http://127.0.0.1:${qwPort}/api/project-history?project=${encodeURIComponent(projectId)}`, {
|
|
663
|
+
method: "POST",
|
|
664
|
+
headers: { "Content-Type": "application/json" },
|
|
665
|
+
body: JSON.stringify({ ...body, allow_agent_senders: true, allow_duplicate: true }),
|
|
666
|
+
});
|
|
667
|
+
const data = await r.json().catch(() => null);
|
|
668
|
+
if (!r.ok) {
|
|
669
|
+
return res.status(r.status).json(data || { error: `import returned ${r.status}` });
|
|
670
|
+
}
|
|
671
|
+
res.json({ ok: true, ...(data || {}) });
|
|
672
|
+
} catch (err) {
|
|
673
|
+
res.status(502).json({ error: "Restore failed", detail: err.message });
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// #430 / quadwork#312: AI team work-hours tracking.
|
|
678
|
+
//
|
|
679
|
+
// The frontend's TerminalGrid detects per-agent activity transitions
|
|
680
|
+
// (idle → active, active → idle) via the existing activity ref and
|
|
681
|
+
// POSTs them to /api/activity/log. We buffer `start` events in
|
|
682
|
+
// memory keyed by `${project}/${agent}`; an `end` event looks up the
|
|
683
|
+
// matching buffered start, computes the duration, and appends a
|
|
684
|
+
// complete session row to ~/.quadwork/{project}/activity.jsonl.
|
|
685
|
+
//
|
|
686
|
+
// /api/activity/stats aggregates across all projects with a 30s
|
|
687
|
+
// cache so the dashboard can poll it every minute without thrashing
|
|
688
|
+
// the filesystem.
|
|
689
|
+
|
|
690
|
+
const _activityStarts = new Map(); // `${project}/${agent}` → startTimestamp
|
|
691
|
+
const _activityStatsCache = { ts: 0, data: null };
|
|
692
|
+
const ACTIVITY_STATS_TTL_MS = 30000;
|
|
693
|
+
|
|
694
|
+
function activityLogPath(projectId) {
|
|
695
|
+
return path.join(CONFIG_DIR, projectId, "activity.jsonl");
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
router.post("/api/activity/log", (req, res) => {
|
|
699
|
+
const { project, agent, type, timestamp } = req.body || {};
|
|
700
|
+
if (typeof project !== "string" || !project) return res.status(400).json({ error: "Missing project" });
|
|
701
|
+
if (typeof agent !== "string" || !agent) return res.status(400).json({ error: "Missing agent" });
|
|
702
|
+
if (type !== "start" && type !== "end") return res.status(400).json({ error: "type must be start|end" });
|
|
703
|
+
const ts = typeof timestamp === "number" && Number.isFinite(timestamp) ? timestamp : Date.now();
|
|
704
|
+
const key = `${project}/${agent}`;
|
|
705
|
+
|
|
706
|
+
if (type === "start") {
|
|
707
|
+
// Only remember the first start per session — duplicate starts
|
|
708
|
+
// are possible if the frontend re-mounts mid-stream; ignore
|
|
709
|
+
// them so the session duration reflects the original onset.
|
|
710
|
+
if (!_activityStarts.has(key)) _activityStarts.set(key, ts);
|
|
711
|
+
return res.json({ ok: true });
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// type === "end"
|
|
715
|
+
const start = _activityStarts.get(key);
|
|
716
|
+
if (start === undefined) {
|
|
717
|
+
// Orphan end (missed start — probably happens on server
|
|
718
|
+
// restart while a session was live). Drop it silently so we
|
|
719
|
+
// don't write a row with an unknown start timestamp.
|
|
720
|
+
return res.json({ ok: true, dropped: "orphan" });
|
|
721
|
+
}
|
|
722
|
+
_activityStarts.delete(key);
|
|
723
|
+
const row = { agent, start, end: ts, duration_ms: Math.max(0, ts - start) };
|
|
724
|
+
try {
|
|
725
|
+
const p = activityLogPath(project);
|
|
726
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
727
|
+
fs.appendFileSync(p, JSON.stringify(row) + "\n");
|
|
728
|
+
// Invalidate the stats cache so the next read sees the new row.
|
|
729
|
+
_activityStatsCache.ts = 0;
|
|
730
|
+
} catch (err) {
|
|
731
|
+
console.warn(`[activity] failed to append ${project}/${agent}: ${err.message || err}`);
|
|
732
|
+
}
|
|
733
|
+
res.json({ ok: true, duration_ms: row.duration_ms });
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// Aggregate all activity.jsonl files under ~/.quadwork/*/activity.jsonl.
|
|
737
|
+
// `today`, `week`, `month` boundaries use the operator's local
|
|
738
|
+
// timezone rather than UTC — "this week" should mean the week the
|
|
739
|
+
// operator is living in, not a UTC-offset week that starts at
|
|
740
|
+
// 16:00 local time.
|
|
741
|
+
function computeActivityStats() {
|
|
742
|
+
if (Date.now() - _activityStatsCache.ts < ACTIVITY_STATS_TTL_MS && _activityStatsCache.data) {
|
|
743
|
+
return _activityStatsCache.data;
|
|
744
|
+
}
|
|
745
|
+
const now = new Date();
|
|
746
|
+
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
|
747
|
+
// Start of this week = local Monday 00:00. JS: getDay() → 0-Sun..6-Sat.
|
|
748
|
+
const day = now.getDay();
|
|
749
|
+
const mondayOffset = day === 0 ? -6 : 1 - day; // Sun → -6, Mon → 0, Tue → -1, …
|
|
750
|
+
const startOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() + mondayOffset).getTime();
|
|
751
|
+
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1).getTime();
|
|
752
|
+
|
|
753
|
+
const totals = { today_ms: 0, week_ms: 0, month_ms: 0, total_ms: 0 };
|
|
754
|
+
const byProject = {};
|
|
755
|
+
// #430 / quadwork#312: only count projects registered in
|
|
756
|
+
// config.json, not every directory under ~/.quadwork/. Stray
|
|
757
|
+
// folders from deleted / unconfigured projects must not inflate
|
|
758
|
+
// the stats — that's explicit in #312's acceptance.
|
|
759
|
+
let projectIds = [];
|
|
760
|
+
try {
|
|
761
|
+
const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
762
|
+
if (Array.isArray(cfg.projects)) {
|
|
763
|
+
projectIds = cfg.projects.map((p) => p && p.id).filter((id) => typeof id === "string" && id);
|
|
764
|
+
}
|
|
765
|
+
} catch {
|
|
766
|
+
// config unreadable → no projects → empty stats (safe fallback)
|
|
767
|
+
}
|
|
768
|
+
for (const projectId of projectIds) {
|
|
769
|
+
const p = activityLogPath(projectId);
|
|
770
|
+
if (!fs.existsSync(p)) continue;
|
|
771
|
+
const projectTotals = { today_ms: 0, week_ms: 0, month_ms: 0, total_ms: 0 };
|
|
772
|
+
let text;
|
|
773
|
+
try { text = fs.readFileSync(p, "utf-8"); } catch { continue; }
|
|
774
|
+
for (const line of text.split("\n")) {
|
|
775
|
+
if (!line.trim()) continue;
|
|
776
|
+
let row;
|
|
777
|
+
try { row = JSON.parse(line); } catch { continue; }
|
|
778
|
+
const d = row && typeof row.duration_ms === "number" ? row.duration_ms : 0;
|
|
779
|
+
const start = row && typeof row.start === "number" ? row.start : 0;
|
|
780
|
+
if (d <= 0 || !start) continue;
|
|
781
|
+
projectTotals.total_ms += d;
|
|
782
|
+
if (start >= startOfToday) projectTotals.today_ms += d;
|
|
783
|
+
if (start >= startOfWeek) projectTotals.week_ms += d;
|
|
784
|
+
if (start >= startOfMonth) projectTotals.month_ms += d;
|
|
785
|
+
}
|
|
786
|
+
byProject[projectId] = {
|
|
787
|
+
today: Math.round(projectTotals.today_ms / 3600) / 1000,
|
|
788
|
+
week: Math.round(projectTotals.week_ms / 3600) / 1000,
|
|
789
|
+
month: Math.round(projectTotals.month_ms / 3600) / 1000,
|
|
790
|
+
total: Math.round(projectTotals.total_ms / 3600) / 1000,
|
|
791
|
+
};
|
|
792
|
+
totals.today_ms += projectTotals.today_ms;
|
|
793
|
+
totals.week_ms += projectTotals.week_ms;
|
|
794
|
+
totals.month_ms += projectTotals.month_ms;
|
|
795
|
+
totals.total_ms += projectTotals.total_ms;
|
|
796
|
+
}
|
|
797
|
+
const data = {
|
|
798
|
+
today: Math.round(totals.today_ms / 3600) / 1000,
|
|
799
|
+
week: Math.round(totals.week_ms / 3600) / 1000,
|
|
800
|
+
month: Math.round(totals.month_ms / 3600) / 1000,
|
|
801
|
+
total: Math.round(totals.total_ms / 3600) / 1000,
|
|
802
|
+
by_project: byProject,
|
|
803
|
+
};
|
|
804
|
+
_activityStatsCache.ts = Date.now();
|
|
805
|
+
_activityStatsCache.data = data;
|
|
806
|
+
return data;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
router.get("/api/activity/stats", (_req, res) => {
|
|
810
|
+
try {
|
|
811
|
+
res.json(computeActivityStats());
|
|
812
|
+
} catch (err) {
|
|
813
|
+
res.status(500).json({ error: "Failed to compute activity stats", detail: err.message });
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
|
|
535
817
|
router.post("/api/chat", async (req, res) => {
|
|
536
818
|
const projectId = req.query.project || req.body.project;
|
|
537
819
|
const { url: base, token: sessionToken } = getChattrConfig(projectId);
|
|
@@ -845,6 +1127,68 @@ router.get("/api/github/merged-prs", (req, res) => {
|
|
|
845
1127
|
// Cached for 10s per project to avoid hammering gh on every poll.
|
|
846
1128
|
|
|
847
1129
|
const _batchProgressCache = new Map(); // projectId -> { ts, data }
|
|
1130
|
+
|
|
1131
|
+
// #429 / quadwork#316: persistent batch snapshot on disk so the
|
|
1132
|
+
// Batch Progress panel keeps showing merged items after Head moves
|
|
1133
|
+
// them from Active Batch to Done. The in-memory `_batchProgressCache`
|
|
1134
|
+
// above is a 10s TTL cache of the rendered rows; this new cache is
|
|
1135
|
+
// the *set of issue numbers* we currently consider "the active
|
|
1136
|
+
// batch", and it survives restarts + lives across polls.
|
|
1137
|
+
function batchSnapshotPath(projectId) {
|
|
1138
|
+
return path.join(CONFIG_DIR, projectId, "batch-progress-cache.json");
|
|
1139
|
+
}
|
|
1140
|
+
function readBatchSnapshot(projectId) {
|
|
1141
|
+
try {
|
|
1142
|
+
return JSON.parse(fs.readFileSync(batchSnapshotPath(projectId), "utf-8"));
|
|
1143
|
+
} catch {
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
function writeBatchSnapshot(projectId, snapshot) {
|
|
1148
|
+
try {
|
|
1149
|
+
const p = batchSnapshotPath(projectId);
|
|
1150
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
1151
|
+
fs.writeFileSync(p, JSON.stringify(snapshot));
|
|
1152
|
+
} catch {
|
|
1153
|
+
// Non-fatal — panel still works from the live parse.
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Decide which batch to render, combining the live parse of
|
|
1158
|
+
// OVERNIGHT-QUEUE.md with the persistent snapshot. The snapshot is
|
|
1159
|
+
// replaced whenever a new batch starts (explicit Batch: N bump OR
|
|
1160
|
+
// the live Active Batch contains items the snapshot doesn't); in
|
|
1161
|
+
// all other cases the snapshot wins, so items Head moved to Done
|
|
1162
|
+
// stay visible until the operator starts the next batch.
|
|
1163
|
+
function resolveDisplayedBatch(queueText, projectId, { queueReadOk = true } = {}) {
|
|
1164
|
+
// Queue file deleted / unreadable → fall back to empty state per
|
|
1165
|
+
// #316's edge case. Returning the snapshot here would "heal" a
|
|
1166
|
+
// genuinely missing file into stale data the operator can't
|
|
1167
|
+
// reconcile without nuking ~/.quadwork/{id}/batch-progress-cache.json
|
|
1168
|
+
// manually.
|
|
1169
|
+
if (!queueReadOk) return { batchNumber: null, issueNumbers: [] };
|
|
1170
|
+
const current = parseActiveBatch(queueText);
|
|
1171
|
+
const snapshot = readBatchSnapshot(projectId);
|
|
1172
|
+
const hasExplicitBump =
|
|
1173
|
+
current.batchNumber !== null &&
|
|
1174
|
+
(!snapshot || snapshot.batchNumber === null || current.batchNumber > snapshot.batchNumber);
|
|
1175
|
+
const hasNewItems =
|
|
1176
|
+
current.issueNumbers.length > 0 &&
|
|
1177
|
+
(!snapshot || current.issueNumbers.some((n) => !snapshot.issueNumbers.includes(n)));
|
|
1178
|
+
let next;
|
|
1179
|
+
if (hasExplicitBump || hasNewItems) {
|
|
1180
|
+
next = { batchNumber: current.batchNumber, issueNumbers: current.issueNumbers.slice() };
|
|
1181
|
+
} else if (snapshot && Array.isArray(snapshot.issueNumbers) && snapshot.issueNumbers.length > 0) {
|
|
1182
|
+
next = {
|
|
1183
|
+
batchNumber: snapshot.batchNumber ?? null,
|
|
1184
|
+
issueNumbers: snapshot.issueNumbers.slice(),
|
|
1185
|
+
};
|
|
1186
|
+
} else {
|
|
1187
|
+
next = { batchNumber: current.batchNumber, issueNumbers: current.issueNumbers.slice() };
|
|
1188
|
+
}
|
|
1189
|
+
if (next.issueNumbers.length > 0) writeBatchSnapshot(projectId, next);
|
|
1190
|
+
return next;
|
|
1191
|
+
}
|
|
848
1192
|
const BATCH_PROGRESS_TTL_MS = 10000;
|
|
849
1193
|
|
|
850
1194
|
function parseActiveBatch(queueText) {
|
|
@@ -1078,10 +1422,20 @@ router.get("/api/batch-progress", async (req, res) => {
|
|
|
1078
1422
|
|
|
1079
1423
|
const queuePath = path.join(CONFIG_DIR, projectId, "OVERNIGHT-QUEUE.md");
|
|
1080
1424
|
let queueText = "";
|
|
1081
|
-
|
|
1082
|
-
|
|
1425
|
+
let queueReadOk = false;
|
|
1426
|
+
try {
|
|
1427
|
+
queueText = fs.readFileSync(queuePath, "utf-8");
|
|
1428
|
+
queueReadOk = true;
|
|
1429
|
+
} catch {
|
|
1430
|
+
// Missing / unreadable file — pass queueReadOk=false so the
|
|
1431
|
+
// resolver bypasses the snapshot and returns the empty state
|
|
1432
|
+
// per #316's edge case.
|
|
1433
|
+
}
|
|
1083
1434
|
|
|
1084
|
-
|
|
1435
|
+
// #429 / quadwork#316: resolve the displayed batch through the
|
|
1436
|
+
// snapshot-aware helper so merged items stay visible after Head
|
|
1437
|
+
// moves them from Active Batch to Done, until a new batch starts.
|
|
1438
|
+
const { batchNumber, issueNumbers } = resolveDisplayedBatch(queueText, projectId, { queueReadOk });
|
|
1085
1439
|
if (issueNumbers.length === 0) {
|
|
1086
1440
|
const data = { batch_number: batchNumber, items: [], summary: "", complete: false };
|
|
1087
1441
|
_batchProgressCache.set(projectId, { ts: Date.now(), data });
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Mono Fallback;src:local(Arial);ascent-override:74.67%;descent-override:21.92%;line-gap-override:0.0%;size-adjust:134.59%}.geist_mono_8d43a2aa-module__8Li5zG__className{font-family:Geist Mono,Geist Mono Fallback;font-style:normal}.geist_mono_8d43a2aa-module__8Li5zG__variable{--font-geist-mono:"Geist Mono", "Geist Mono Fallback"}
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--color-red-400:#ff6568;--color-red-500:#fb2c36;--color-red-700:#bf000f;--color-red-900:#82181a;--color-green-500:#00c758;--color-blue-300:#90c5ff;--color-blue-400:#54a2ff;--color-neutral-200:#e5e5e5;--color-neutral-300:#d4d4d4;--color-neutral-400:#a1a1a1;--color-neutral-500:#737373;--color-neutral-600:#525252;--color-neutral-950:#0a0a0a;--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-3xl:48rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-lg:.5rem;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-geist-mono)}@supports (color:lab(0% 0 0)){:root,:host{--color-red-400:lab(63.7053% 60.745 31.3109);--color-red-500:lab(55.4814% 75.0732 48.8528);--color-red-700:lab(40.4273% 67.2623 53.7441);--color-red-900:lab(28.5139% 44.5539 29.0463);--color-green-500:lab(70.5521% -66.5147 45.8073);--color-blue-300:lab(77.5052% -6.4629 -36.42);--color-blue-400:lab(65.0361% -1.42065 -56.9802);--color-neutral-200:lab(90.952% 0 -.0000119209);--color-neutral-300:lab(84.92% 0 -.0000119209);--color-neutral-400:lab(66.128% -.0000298023 .0000119209);--color-neutral-500:lab(48.496% 0 0);--color-neutral-600:lab(34.924% 0 0);--color-neutral-950:lab(2.75381% 0 0)}}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.top-0{top:calc(var(--spacing) * 0)}.top-3{top:calc(var(--spacing) * 3)}.top-5{top:calc(var(--spacing) * 5)}.right-0{right:calc(var(--spacing) * 0)}.right-3{right:calc(var(--spacing) * 3)}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-full{bottom:100%}.left-0{left:calc(var(--spacing) * 0)}.left-16{left:calc(var(--spacing) * 16)}.left-\[14px\]{left:14px}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.z-50{z-index:50}.col-span-2{grid-column:span 2/span 2}.mx-4{margin-inline:calc(var(--spacing) * 4)}.my-2{margin-block:calc(var(--spacing) * 2)}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.list-item{display:list-item}.table{display:table}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-\[12px\]{height:12px}.h-\[80vh\]{height:80vh}.h-\[calc\(100\%-80px\)\]{height:calc(100% - 80px)}.h-full{height:100%}.h-px{height:1px}.max-h-28{max-height:calc(var(--spacing) * 28)}.max-h-32{max-height:calc(var(--spacing) * 32)}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-\[88px\]{min-height:88px}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-44{width:calc(var(--spacing) * 44)}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-\[1px\]{width:1px}.w-full{width:100%}.w-px{width:1px}.max-w-3xl{max-width:var(--container-3xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-\[60\%\]{max-width:60%}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[220px\]{min-width:220px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.cursor-col-resize{cursor:col-resize}.cursor-move{cursor:move}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.resize{resize:both}.resize-none{resize:none}.resize-y{resize:vertical}.list-decimal{list-style-type:decimal}.grid-flow-col{grid-auto-flow:column}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0{gap:calc(var(--spacing) * 0)}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-accent,.border-accent\/20{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.border-accent\/20{border-color:color-mix(in oklab, var(--accent) 20%, transparent)}}.border-accent\/30{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.border-accent\/30{border-color:color-mix(in oklab, var(--accent) 30%, transparent)}}.border-accent\/40{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.border-accent\/40{border-color:color-mix(in oklab, var(--accent) 40%, transparent)}}.border-accent\/50{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.border-accent\/50{border-color:color-mix(in oklab, var(--accent) 50%, transparent)}}.border-border,.border-border\/30{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/30{border-color:color-mix(in oklab, var(--border) 30%, transparent)}}.border-border\/40{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/40{border-color:color-mix(in oklab, var(--border) 40%, transparent)}}.border-border\/50{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/50{border-color:color-mix(in oklab, var(--border) 50%, transparent)}}.border-border\/60{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/60{border-color:color-mix(in oklab, var(--border) 60%, transparent)}}.border-error,.border-error\/30{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.border-error\/30{border-color:color-mix(in oklab, var(--error) 30%, transparent)}}.border-error\/60{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.border-error\/60{border-color:color-mix(in oklab, var(--error) 60%, transparent)}}.border-red-700\/40{border-color:#bf000f66}@supports (color:color-mix(in lab, red, red)){.border-red-700\/40{border-color:color-mix(in oklab, var(--color-red-700) 40%, transparent)}}.border-red-700\/50{border-color:#bf000f80}@supports (color:color-mix(in lab, red, red)){.border-red-700\/50{border-color:color-mix(in oklab, var(--color-red-700) 50%, transparent)}}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.border-white\/15{border-color:#ffffff26}@supports (color:color-mix(in lab, red, red)){.border-white\/15{border-color:color-mix(in oklab, var(--color-white) 15%, transparent)}}.bg-\[\#1a1a1a\]{background-color:#1a1a1a}.bg-\[\#ffcc00\]{background-color:#fc0}.bg-accent,.bg-accent\/5{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.bg-accent\/5{background-color:color-mix(in oklab, var(--accent) 5%, transparent)}}.bg-accent\/10{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.bg-accent\/10{background-color:color-mix(in oklab, var(--accent) 10%, transparent)}}.bg-accent\/30{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.bg-accent\/30{background-color:color-mix(in oklab, var(--accent) 30%, transparent)}}.bg-bg{background-color:var(--bg)}.bg-bg-surface,.bg-bg-surface\/50{background-color:var(--bg-surface)}@supports (color:color-mix(in lab, red, red)){.bg-bg-surface\/50{background-color:color-mix(in oklab, var(--bg-surface) 50%, transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab, red, red)){.bg-black\/60{background-color:color-mix(in oklab, var(--color-black) 60%, transparent)}}.bg-border{background-color:var(--border)}.bg-error,.bg-error\/5{background-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.bg-error\/5{background-color:color-mix(in oklab, var(--error) 5%, transparent)}}.bg-error\/10{background-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.bg-error\/10{background-color:color-mix(in oklab, var(--error) 10%, transparent)}}.bg-green-500{background-color:var(--color-green-500)}.bg-neutral-400{background-color:var(--color-neutral-400)}.bg-neutral-950{background-color:var(--color-neutral-950)}.bg-neutral-950\/90{background-color:#0a0a0ae6}@supports (color:color-mix(in lab, red, red)){.bg-neutral-950\/90{background-color:color-mix(in oklab, var(--color-neutral-950) 90%, transparent)}}.bg-red-500{background-color:var(--color-red-500)}.bg-red-900\/20{background-color:#82181a33}@supports (color:color-mix(in lab, red, red)){.bg-red-900\/20{background-color:color-mix(in oklab, var(--color-red-900) 20%, transparent)}}.bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab, red, red)){.bg-red-900\/30{background-color:color-mix(in oklab, var(--color-red-900) 30%, transparent)}}.bg-text-muted{background-color:var(--text-muted)}.bg-transparent{background-color:#0000}.bg-white\/5{background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.bg-white\/5{background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.p-1{padding:calc(var(--spacing) * 1)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0{padding-block:calc(var(--spacing) * 0)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pb-1{padding-bottom:calc(var(--spacing) * 1)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-10{padding-left:calc(var(--spacing) * 10)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-geist-mono)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#ffcc00\]{color:#fc0}.text-accent,.text-accent\/70{color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.text-accent\/70{color:color-mix(in oklab, var(--accent) 70%, transparent)}}.text-bg{color:var(--bg)}.text-blue-400{color:var(--color-blue-400)}.text-error{color:var(--error)}.text-neutral-200{color:var(--color-neutral-200)}.text-neutral-300{color:var(--color-neutral-300)}.text-neutral-400{color:var(--color-neutral-400)}.text-neutral-500{color:var(--color-neutral-500)}.text-neutral-600{color:var(--color-neutral-600)}.text-red-400{color:var(--color-red-400)}.text-text{color:var(--text)}.text-text-muted,.text-text-muted\/80{color:var(--text-muted)}@supports (color:color-mix(in lab, red, red)){.text-text-muted\/80{color:color-mix(in oklab, var(--text-muted) 80%, transparent)}}.text-white{color:var(--color-white)}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.accent-accent{accent-color:var(--accent)}.opacity-0{opacity:0}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:block:is(:where(.group):hover *){display:block}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.placeholder\:text-text-muted::placeholder{color:var(--text-muted)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.last\:pb-0:last-child{padding-bottom:calc(var(--spacing) * 0)}@media (hover:hover){.hover\:border-accent:hover,.hover\:border-accent\/40:hover{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:border-accent\/40:hover{border-color:color-mix(in oklab, var(--accent) 40%, transparent)}}.hover\:border-error\/40:hover{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.hover\:border-error\/40:hover{border-color:color-mix(in oklab, var(--error) 40%, transparent)}}.hover\:border-text-muted:hover{border-color:var(--text-muted)}.hover\:border-white\/40:hover{border-color:#fff6}@supports (color:color-mix(in lab, red, red)){.hover\:border-white\/40:hover{border-color:color-mix(in oklab, var(--color-white) 40%, transparent)}}.hover\:bg-\[\#1a1a1a\]:hover{background-color:#1a1a1a}.hover\:bg-accent-dim:hover{background-color:var(--accent-dim)}.hover\:bg-accent\/5:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/5:hover{background-color:color-mix(in oklab, var(--accent) 5%, transparent)}}.hover\:bg-accent\/10:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/10:hover{background-color:color-mix(in oklab, var(--accent) 10%, transparent)}}.hover\:bg-accent\/20:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/20:hover{background-color:color-mix(in oklab, var(--accent) 20%, transparent)}}.hover\:bg-error\/20:hover{background-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-error\/20:hover{background-color:color-mix(in oklab, var(--error) 20%, transparent)}}.hover\:bg-white\/5:hover{background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/5:hover{background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.hover\:text-accent:hover{color:var(--accent)}.hover\:text-accent-dim:hover{color:var(--accent-dim)}.hover\:text-blue-300:hover{color:var(--color-blue-300)}.hover\:text-blue-400:hover{color:var(--color-blue-400)}.hover\:text-error:hover{color:var(--error)}.hover\:text-text:hover{color:var(--text)}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}}.focus\:border-accent:focus{border-color:var(--accent)}.focus\:opacity-100:focus{opacity:1}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-accent:focus{--tw-ring-color:var(--accent)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:40rem){.sm\:inline{display:inline}.sm\:inline-flex{display:inline-flex}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:80rem){.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}.\[\&_a\]\:text-accent a{color:var(--accent)}.\[\&_a\]\:underline a{text-decoration-line:underline}.\[\&_blockquote\]\:border-l-2 blockquote{border-left-style:var(--tw-border-style);border-left-width:2px}.\[\&_blockquote\]\:border-border blockquote{border-color:var(--border)}.\[\&_blockquote\]\:pl-2 blockquote{padding-left:calc(var(--spacing) * 2)}.\[\&_blockquote\]\:text-text-muted blockquote{color:var(--text-muted)}.\[\&_code\]\:rounded code{border-radius:.25rem}.\[\&_code\]\:bg-bg-surface code{background-color:var(--bg-surface)}.\[\&_code\]\:px-1 code{padding-inline:calc(var(--spacing) * 1)}.\[\&_code\]\:text-\[11px\] code{font-size:11px}.\[\&_h1\]\:mt-3 h1{margin-top:calc(var(--spacing) * 3)}.\[\&_h1\]\:mb-2 h1{margin-bottom:calc(var(--spacing) * 2)}.\[\&_h1\]\:text-\[14px\] h1{font-size:14px}.\[\&_h1\]\:font-semibold h1{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.\[\&_h2\]\:mt-3 h2{margin-top:calc(var(--spacing) * 3)}.\[\&_h2\]\:mb-1\.5 h2{margin-bottom:calc(var(--spacing) * 1.5)}.\[\&_h2\]\:text-\[13px\] h2{font-size:13px}.\[\&_h2\]\:font-semibold h2{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.\[\&_h3\]\:mt-2 h3{margin-top:calc(var(--spacing) * 2)}.\[\&_h3\]\:mb-1 h3{margin-bottom:calc(var(--spacing) * 1)}.\[\&_h3\]\:text-\[12px\] h3{font-size:12px}.\[\&_h3\]\:font-semibold h3{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.\[\&_hr\]\:my-3 hr{margin-block:calc(var(--spacing) * 3)}.\[\&_hr\]\:border-border hr{border-color:var(--border)}.\[\&_li\]\:my-0\.5 li{margin-block:calc(var(--spacing) * .5)}.\[\&_ol\]\:my-1\.5 ol{margin-block:calc(var(--spacing) * 1.5)}.\[\&_ol\]\:list-decimal ol{list-style-type:decimal}.\[\&_ol\]\:pl-4 ol{padding-left:calc(var(--spacing) * 4)}.\[\&_p\]\:my-1\.5 p{margin-block:calc(var(--spacing) * 1.5)}.\[\&_strong\]\:text-text strong{color:var(--text)}.\[\&_ul\]\:my-1\.5 ul{margin-block:calc(var(--spacing) * 1.5)}.\[\&_ul\]\:list-disc ul{list-style-type:disc}.\[\&_ul\]\:pl-4 ul{padding-left:calc(var(--spacing) * 4)}}:root{--bg:#0a0a0a;--bg-surface:#111;--text:#e0e0e0;--text-muted:#737373;--accent:#0f8;--accent-dim:#00cc6a;--border:#2a2a2a;--error:#f44}::selection{background:var(--accent);color:var(--bg)}body{background:var(--bg);color:var(--text);font-family:var(--font-geist-mono), ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1,h2,h3,h4,h5,h6{font-family:var(--font-geist-mono), ui-monospace, monospace;letter-spacing:-.01em}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:0}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}:focus-visible{outline:1px solid var(--accent);outline-offset:1px}@keyframes qw-blink{0%,50%{opacity:1}51%,to{opacity:0}}.animate-qw-blink{animation:1s steps(2,start) infinite qw-blink}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,64618,e=>{"use strict";var t=e.i(24046),s=e.i(85899),a=e.i(4232),r=e.i(16353);let c=[{id:"name",label:"Project Name",subtitle:"Name your project",status:"active"},{id:"repo",label:"GitHub Repo",subtitle:"Connect a repository",status:"pending"},{id:"models",label:"Agent Models",subtitle:"Configure CLI backends",status:"pending"},{id:"workdir",label:"Working Directory",subtitle:"Set the local path",status:"pending"},{id:"workspaces",label:"Create Workspaces",subtitle:"Worktrees + seed files",status:"pending"},{id:"launch",label:"Ready to Launch",subtitle:"Review & start",status:"pending"}],n=[{value:"claude",label:"Claude Code"},{value:"codex",label:"Codex"}],o=[{key:"head",label:"T1 — Head",role:"Owner / Final Guard",desc:"Merges PRs, makes final calls"},{key:"reviewer1",label:"T2a — Reviewer 1",role:"Design Reviewer",desc:"Reviews architecture & design"},{key:"reviewer2",label:"T2b — Reviewer 2",role:"Code Reviewer",desc:"Reviews implementation quality"},{key:"dev",label:"T3 — Developer",role:"Full-Stack Builder",desc:"Implements features & fixes"}];function l({repo:e,workingDir:t,setWorkingDir:r,error:c,onNext:n}){let[o,i]=(0,a.useState)(!0),[x,d]=(0,a.useState)(null),[p,m]=(0,a.useState)(!1),u=e?e.split("/")[1]:"project";return(0,a.useEffect)(()=>{e?fetch(`/api/setup/detect-clone?repo=${encodeURIComponent(e)}`).then(e=>e.ok?e.json():null).then(e=>{d(e),e?.found&&e.path?r(e.path):e?.suggested&&r(e.suggested),i(!1)}).catch(()=>i(!1)):i(!1)},[e,r]),(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Where is your project?"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-3",children:"Your project's git repository on your local machine. QuadWork will create 4 agent workspaces next to this directory."}),o&&(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-3",children:"Scanning for existing clone..."}),!o&&x?.found&&(0,s.jsxs)("div",{className:"border border-accent/30 bg-accent/5 p-3 mb-4 text-[11px]",children:[(0,s.jsx)("p",{className:"text-accent font-semibold mb-1",children:"Found existing clone"}),(0,s.jsx)("p",{className:"text-text font-mono",children:x.path}),(0,s.jsxs)("div",{className:"flex gap-2 mt-2",children:[(0,s.jsx)("button",{onClick:n,className:"px-3 py-1 bg-accent text-bg text-[11px] font-semibold hover:bg-accent-dim transition-colors",children:"Use this"}),(0,s.jsx)("button",{onClick:()=>{m(!0),r("")},className:"px-3 py-1 text-[11px] text-text-muted border border-border hover:text-text transition-colors",children:"Choose different path"})]})]}),!o&&!x?.found&&!p&&(0,s.jsxs)("div",{className:"border border-border bg-bg-surface p-3 mb-4 text-[11px]",children:[(0,s.jsxs)("p",{className:"text-text-muted mb-1",children:["No local clone found for ",(0,s.jsx)("span",{className:"text-accent",children:e})]}),(0,s.jsx)("p",{className:"text-text-muted mb-2",children:"Setup will clone it to:"}),(0,s.jsx)("p",{className:"text-text font-mono mb-2",children:x?.suggested||`~/Projects/${u}`}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsx)("button",{onClick:n,disabled:!t.trim(),className:"px-3 py-1 bg-accent text-bg text-[11px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:"Clone here & continue"}),(0,s.jsx)("button",{onClick:()=>m(!0),className:"px-3 py-1 text-[11px] text-text-muted border border-border hover:text-text transition-colors",children:"Choose different path"})]})]}),(p||!o&&!x)&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("input",{value:t,onChange:e=>r(e.target.value),placeholder:`~/Projects/${u}`,className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent mb-2"}),(0,s.jsx)("button",{onClick:n,disabled:!t.trim(),className:"px-4 py-1.5 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:"Next"})]}),c&&(0,s.jsx)("p",{className:"text-[11px] text-error mt-2",children:c}),(0,s.jsxs)("div",{className:"border border-border bg-bg-surface p-3 mt-4 text-[11px] text-text-muted font-mono space-y-0.5",children:[(0,s.jsx)("p",{className:"text-[10px] uppercase tracking-wider text-text-muted mb-1 font-sans",children:"Workspace layout"}),(0,s.jsxs)("p",{className:"text-accent",children:[u,"/ ← your repo"]}),(0,s.jsxs)("p",{children:[u,"-head/ ← Head agent"]}),(0,s.jsxs)("p",{children:[u,"-dev/ ← Dev agent"]}),(0,s.jsxs)("p",{children:[u,"-reviewer1/ ← Reviewer1"]}),(0,s.jsxs)("p",{children:[u,"-reviewer2/ ← Reviewer2"]})]})]})}e.s(["default",0,function(){let e=(0,r.useRouter)(),[i,x]=(0,a.useState)(c),[d,p]=(0,a.useState)(0),[m,u]=(0,a.useState)(""),[h,b]=(0,a.useState)(""),[g,j]=(0,a.useState)(""),[f,v]=(0,a.useState)([]),[N,k]=(0,a.useState)(!1),[y,w]=(0,a.useState)(!1),[C,S]=(0,a.useState)(""),[_,R]=(0,a.useState)(!1),[T,P]=(0,a.useState)({head:"claude",reviewer1:"claude",reviewer2:"claude",dev:"claude"}),[$,q]=(0,a.useState)(!0),[H,I]=(0,a.useState)(!1),[A,L]=(0,a.useState)(""),[E,F]=(0,a.useState)("paste"),[U,W]=(0,a.useState)(""),[D,G]=(0,a.useState)("~/.quadwork/reviewer-token"),[O,M]=(0,a.useState)(""),[Y,B]=(0,a.useState)({}),[z,J]=(0,a.useState)(!1),[K,V]=(0,a.useState)([]),[X,Q]=(0,a.useState)("idle"),[Z,ee]=(0,a.useState)(!1),[et,es]=(0,a.useState)({chattr:0,mcpHttp:0,mcpSse:0}),[ea,er]=(0,a.useState)({chattr:0,mcpHttp:0,mcpSse:0}),[ec,en]=(0,a.useState)(null);(0,a.useEffect)(()=>{fetch("/api/cli-status").then(e=>e.json()).then(e=>{en(e);let t=e.claude&&!e.codex?"claude":!e.claude&&e.codex?"codex":null;t?P({head:t,reviewer1:t,reviewer2:t,dev:t}):e.claude&&e.codex&&P({head:"codex",dev:"claude",reviewer1:"codex",reviewer2:"claude"})}).catch(()=>{})},[]),(0,a.useEffect)(()=>{fetch("/api/github/user").then(e=>e.json()).then(e=>{e.login&&S(e.login)}).catch(()=>{})},[]),(0,a.useEffect)(()=>{C&&(k(!0),fetch(`/api/github/repos?owner=${encodeURIComponent(C)}`).then(e=>e.json()).then(e=>{Array.isArray(e)&&v(e)}).catch(()=>{}).finally(()=>k(!1)))},[C]);let eo=(0,a.useCallback)((e,t)=>{x(s=>s.map((s,a)=>a===e?{...s,...t}:s))},[]),el=(0,a.useCallback)(()=>{x(e=>e.map((e,t)=>t===d?{...e,status:"done"}:t===d+1?{...e,status:"active"}:e)),p(e=>e+1)},[d]),ei=async(e,t)=>{J(!0);try{let s=await fetch(`/api/setup?step=${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}),a=await s.json();return J(!1),a}catch{return J(!1),{ok:!1,error:"Request failed"}}},ex=async()=>{let e=await ei("verify-repo",{repo:h});e.ok?el():eo(d,{status:"error",error:e.error})},ed=async()=>{if(J(!0),V([]),H&&"paste"===E&&U){V(e=>[...e,"Saving reviewer token..."]);try{let e=await fetch("/api/setup/save-token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:U})}),t=await e.json();t.ok&&V(e=>[...e,`Token saved to ${t.path}`])}catch{}}V(e=>[...e,"Creating worktrees..."]);let e=await ei("create-worktrees",{workingDir:O,repo:h});if(!e.ok){V(t=>[...t,`Error: ${e.errors?.join(", ")||e.error}`]),eo(d,{status:"error",error:e.errors?.join(", ")||e.error}),J(!1);return}V(e=>[...e,"Worktrees created."]),V(e=>[...e,"Writing seed files..."]);let t=H?"file"===E?D:"~/.quadwork/reviewer-token":"",s=await ei("seed-files",{workingDir:O,projectName:m,repo:h,reviewerUser:H?A:"",reviewerTokenPath:t});if(!s.ok){V(e=>[...e,`Error: ${s.error}`]),eo(d,{status:"error",error:s.error}),J(!1);return}V(e=>[...e,"Seed files written."]),V(e=>[...e,"Done."]),J(!1),el()},ep=async()=>{let t,s,a;if(Q("running"),Z&&et.chattr>0){t=et.chattr,s=et.mcpHttp||et.chattr-100,a=et.mcpSse||s+1;let e=[t,s,a];try{let t=(await Promise.all(e.map(e=>fetch(`/api/port-check?port=${e}`).then(e=>e.json())))).filter(e=>!e.free).map(e=>e.port);if(t.length>0){Q("error"),eo(d,{status:"error",error:`Port${t.length>1?"s":""} ${t.join(", ")} already in use`});return}}catch{}}else if(ea.chattr)t=ea.chattr,s=ea.mcpHttp,a=ea.mcpSse;else try{let e=await fetch("/api/port-check/auto?start=8300&count=1"),r=await e.json(),c=await fetch("/api/port-check/auto?start=8200&count=2"),n=await c.json();t=r.ports?.[0]||8300,s=n.ports?.[0]||8200,a=n.ports?.[1]||8201}catch{t=8300,s=8200,a=8201}let r=await ei("agentchattr-config",{workingDir:O,projectName:m,repo:h,backends:T,agentchattr_port:t,mcp_http_port:s,mcp_sse_port:a});r.ok&&B({agentchattr_token:r.agentchattr_token,agentchattr_port:r.agentchattr_port,mcp_http_port:r.mcp_http_port,mcp_sse_port:r.mcp_sse_port});let c=O.split("/").pop()||m.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,""),n=await ei("add-config",{id:c,name:m,repo:h,workingDir:O,backends:T,auto_approve:$,...r.ok?{agentchattr_token:r.agentchattr_token,agentchattr_port:r.agentchattr_port,mcp_http_port:r.mcp_http_port,mcp_sse_port:r.mcp_sse_port}:Y});n.ok?(Q("done"),eo(d,{status:"done"}),setTimeout(()=>e.push(`/project/${c}`),1200)):(Q("error"),eo(d,{status:"error",error:n.error}))};(0,a.useEffect)(()=>{i[d]?.id==="launch"&&(async()=>{try{let e=await fetch("/api/port-check/auto?start=8300&count=1"),t=await e.json(),s=await fetch("/api/port-check/auto?start=8200&count=2"),a=await s.json(),r={chattr:t.ports?.[0]||8300,mcpHttp:a.ports?.[0]||8200,mcpSse:a.ports?.[1]||8201};er(r),et.chattr||es(r)}catch{}})()},[d,i]);let em=f.filter(e=>e.name.toLowerCase().includes(g.toLowerCase())),eu=i[d];return(0,s.jsxs)("div",{className:"h-full overflow-y-auto",children:[(0,s.jsxs)("div",{className:"px-6 pt-6 pb-4 border-b border-border",children:[(0,s.jsx)("h1",{className:"text-lg font-semibold text-text tracking-tight",children:"Set Up Your AI Dev Team"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mt-1",children:"Configure agents, connect your repo, and launch a multi-agent development workflow in minutes."})]}),(0,s.jsxs)("div",{className:"flex h-[calc(100%-80px)]",children:[(0,s.jsxs)("div",{className:"flex-1 flex gap-6 p-6 overflow-y-auto",children:[(0,s.jsx)("div",{className:"w-44 shrink-0",children:i.map((e,t)=>(0,s.jsxs)("div",{className:"flex items-start gap-2 py-2",children:[(0,s.jsx)("span",{className:`w-5 h-5 flex items-center justify-center text-[10px] border shrink-0 mt-0.5 ${"done"===e.status?"border-accent text-accent":"error"===e.status?"border-error text-error":"active"===e.status?"border-accent text-accent bg-accent/10":"skipped"===e.status?"border-border text-text-muted line-through":"border-border text-text-muted"}`,children:"done"===e.status?"✓":"error"===e.status?"!":t+1}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:`text-[11px] block leading-tight ${"active"===e.status?"text-text font-semibold":"done"===e.status?"text-accent":"text-text-muted"}`,children:e.label}),(0,s.jsx)("span",{className:"text-[10px] text-text-muted block",children:e.subtitle})]})]},e.id))}),(0,s.jsxs)("div",{className:"flex-1 border border-border p-5 min-h-0",children:[eu?.id==="name"&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Name your project"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-4",children:"This name identifies your project in the dashboard and agent configs."}),(0,s.jsx)("input",{value:m,onChange:e=>u(e.target.value),placeholder:"e.g. My DeFi App",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent mb-4",autoFocus:!0}),(0,s.jsx)("button",{onClick:el,disabled:!m.trim(),className:"px-4 py-1.5 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:"Next"})]}),eu?.id==="repo"&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Connect a GitHub repository"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-4",children:"Select an existing repo or enter one manually. Agents will work within this repo."}),!y&&(0,s.jsxs)(s.Fragment,{children:[C&&(0,s.jsxs)("p",{className:"text-[11px] text-text-muted mb-2",children:["Showing repos for ",(0,s.jsx)("span",{className:"text-accent",children:C})]}),(0,s.jsx)("input",{value:g,onChange:e=>j(e.target.value),placeholder:"Search repos...",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent mb-2"}),N&&(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-2",children:"Loading..."}),(0,s.jsxs)("div",{className:"max-h-40 overflow-y-auto border border-border mb-3",children:[em.map(e=>(0,s.jsxs)("button",{onClick:()=>b(`${C}/${e.name}`),className:`w-full text-left px-3 py-1.5 text-[11px] border-b border-border/50 last:border-b-0 hover:bg-accent/5 transition-colors ${h===`${C}/${e.name}`?"bg-accent/10 text-accent":"text-text"}`,children:[(0,s.jsx)("span",{className:"font-semibold",children:e.name}),e.isPrivate&&(0,s.jsx)("span",{className:"text-[10px] text-text-muted ml-2",children:"private"}),e.description&&(0,s.jsx)("span",{className:"text-[10px] text-text-muted ml-2",children:e.description})]},e.name)),!N&&0===em.length&&(0,s.jsx)("p",{className:"px-3 py-2 text-[11px] text-text-muted",children:"No repos found."})]}),(0,s.jsx)("button",{onClick:()=>w(!0),className:"text-[11px] text-text-muted hover:text-accent transition-colors mb-3 block",children:"Enter manually instead"})]}),y&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("input",{value:h,onChange:e=>b(e.target.value),placeholder:"owner/repo",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent mb-2"}),(0,s.jsx)("button",{onClick:()=>w(!1),className:"text-[11px] text-text-muted hover:text-accent transition-colors mb-3 block",children:"Back to repo list"})]}),(0,s.jsxs)("label",{className:"flex items-center gap-2 mb-4 cursor-pointer",children:[(0,s.jsx)("input",{type:"checkbox",checked:_,onChange:e=>R(e.target.checked),className:"accent-accent"}),(0,s.jsxs)("span",{className:"text-[11px] text-text-muted",children:["Enable branch protection on ",(0,s.jsx)("code",{className:"text-accent",children:"main"})]})]}),_&&(0,s.jsxs)("div",{className:"border border-border bg-bg-surface p-3 mb-4 text-[11px] space-y-2",children:[(0,s.jsx)("p",{className:"text-text-muted",children:"Run this after setup, or configure in GitHub UI:"}),(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("code",{className:"text-accent flex-1 select-all text-[10px] break-all",children:`gh api repos/${h||"owner/repo"}/branches/main/protection -X PUT -f "required_pull_request_reviews[required_approving_review_count]=1" -f "enforce_admins=false" -f "required_status_checks=null" -f "restrictions=null"`}),(0,s.jsx)("button",{onClick:()=>navigator.clipboard.writeText(`gh api repos/${h}/branches/main/protection -X PUT -f "required_pull_request_reviews[required_approving_review_count]=1" -f "enforce_admins=false" -f "required_status_checks=null" -f "restrictions=null"`),className:"text-[10px] text-text-muted hover:text-accent shrink-0",children:"copy"})]})]}),eu.error&&(0,s.jsx)("p",{className:"text-[11px] text-error mb-2",children:eu.error}),(0,s.jsx)("button",{onClick:ex,disabled:!h||z,className:"px-4 py-1.5 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:z?"Verifying...":"Verify & Continue"})]}),eu?.id==="models"&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Configure agent CLI backends"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-4",children:"Each agent runs its own CLI instance. Pick the backend for each role."}),ec&&!ec.claude&&ec.codex&&(0,s.jsxs)("div",{className:"border border-accent/20 bg-accent/5 p-3 mb-4 text-[11px]",children:[(0,s.jsx)("p",{className:"text-text",children:"You have Codex CLI installed — great! All 4 agents will use Codex."}),(0,s.jsx)("p",{className:"text-text-muted mt-1.5",children:"Tip: Installing Claude Code too gives your team different AI perspectives, which can improve code review quality. You can add it anytime:"}),(0,s.jsx)("p",{className:"text-accent mt-1 font-mono text-[10px]",children:"npm install -g @anthropic-ai/claude-code"}),(0,s.jsx)("p",{className:"text-text-muted mt-1.5",children:"For now, Codex CLI handles everything perfectly. Let's continue!"})]}),ec&&ec.claude&&!ec.codex&&(0,s.jsxs)("div",{className:"border border-accent/20 bg-accent/5 p-3 mb-4 text-[11px]",children:[(0,s.jsx)("p",{className:"text-text",children:"You have Claude Code installed — great! All 4 agents will use Claude."}),(0,s.jsx)("p",{className:"text-text-muted mt-1.5",children:"Tip: Installing Codex CLI too gives your team different AI perspectives, which can improve code review quality. You can add it anytime:"}),(0,s.jsx)("p",{className:"text-accent mt-1 font-mono text-[10px]",children:"npm install -g codex"}),(0,s.jsx)("p",{className:"text-text-muted mt-1.5",children:"For now, Claude Code handles everything perfectly. Let's continue!"})]}),(0,s.jsx)("div",{className:"border border-border mb-4",children:o.map(e=>(0,s.jsxs)("div",{className:"flex items-center justify-between px-3 py-2 border-b border-border/50 last:border-b-0",children:[(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsx)("span",{className:"text-[11px] text-text font-semibold block",children:e.label}),(0,s.jsx)("span",{className:"text-[10px] text-text-muted",children:e.desc})]}),(0,s.jsx)("select",{value:T[e.key],onChange:t=>P({...T,[e.key]:t.target.value}),className:"bg-transparent border border-border px-2 py-0.5 text-[11px] text-text outline-none focus:border-accent cursor-pointer ml-3",children:n.map(e=>(0,s.jsxs)("option",{value:e.value,className:"bg-bg-surface",disabled:!!ec&&!ec[e.value],children:[e.label,ec&&!ec[e.value]?" (not installed)":""]},e.value))})]},e.key))}),(0,s.jsxs)("label",{className:"flex items-center gap-2 mb-3 cursor-pointer",title:"Enable permission bypass flags so agents can work autonomously without prompting for approval on every action",children:[(0,s.jsx)("input",{type:"checkbox",checked:$,onChange:e=>q(e.target.checked),className:"accent-accent"}),(0,s.jsx)("span",{className:"text-[11px] text-text",children:"Auto-approve agent actions"}),(0,s.jsx)("span",{className:"text-[10px] text-text-muted",children:"(required for autonomous work)"})]}),(0,s.jsxs)("label",{className:"flex items-center gap-2 mb-3 cursor-pointer",children:[(0,s.jsx)("input",{type:"checkbox",checked:H,onChange:e=>I(e.target.checked),className:"accent-accent"}),(0,s.jsx)("span",{className:"text-[11px] text-text-muted",children:"Configure reviewer credentials (for GitHub PR reviews)"})]}),H&&(0,s.jsxs)("div",{className:"border border-border p-3 mb-4 space-y-3",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("label",{className:"text-[11px] text-text-muted block mb-1",children:"Reviewer GitHub username"}),(0,s.jsx)("input",{value:A,onChange:e=>L(e.target.value),placeholder:"github-username",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("label",{className:"text-[11px] text-text-muted block mb-2",children:"Token source"}),(0,s.jsxs)("div",{className:"flex gap-4 mb-2",children:[(0,s.jsxs)("label",{className:"flex items-center gap-1.5 cursor-pointer",children:[(0,s.jsx)("input",{type:"radio",name:"tokenMode",checked:"paste"===E,onChange:()=>F("paste"),className:"accent-accent"}),(0,s.jsx)("span",{className:"text-[11px] text-text",children:"Paste token"})]}),(0,s.jsxs)("label",{className:"flex items-center gap-1.5 cursor-pointer",children:[(0,s.jsx)("input",{type:"radio",name:"tokenMode",checked:"file"===E,onChange:()=>F("file"),className:"accent-accent"}),(0,s.jsx)("span",{className:"text-[11px] text-text",children:"Use existing file"})]})]}),"paste"===E?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("input",{value:U,onChange:e=>W(e.target.value),placeholder:"ghp_xxxxxxxxxxxxxxxxxxxx",type:"password",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent"}),(0,s.jsxs)("div",{className:"mt-2 text-[10px] text-text-muted leading-relaxed",children:[(0,s.jsxs)("p",{children:["Paste a GitHub ",(0,s.jsx)("span",{className:"text-text",children:"Personal Access Token (classic)"}),"."]}),(0,s.jsxs)("p",{className:"mt-1",children:["Create one at"," ",(0,s.jsx)("a",{href:"https://github.com/settings/tokens",target:"_blank",rel:"noopener noreferrer",className:"text-accent hover:underline",children:"github.com/settings/tokens"})," ","→ Generate new token (classic)"]}),(0,s.jsxs)("p",{className:"mt-1",children:["Required permission: ",(0,s.jsx)("span",{className:"text-accent",children:"repo"})," (Full control of private repositories)",(0,s.jsx)("br",{}),(0,s.jsx)("span",{className:"text-text-muted",children:"Needed for reading PRs, posting reviews, and approving/requesting changes"})]})]})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("input",{value:D,onChange:e=>G(e.target.value),placeholder:"~/.quadwork/reviewer-token",className:"w-full bg-transparent border border-border px-2 py-1.5 text-[12px] text-text outline-none focus:border-accent"}),D&&!D.startsWith("~/.quadwork")&&!D.startsWith(String.raw`${t.default.env.HOME}/.quadwork`)&&(0,s.jsx)("p",{className:"text-[10px] text-[#ffcc00] mt-1",children:"This path may be inside a git repository. Consider using the default ~/.quadwork/ location to avoid accidentally committing tokens."})]})]})]}),(0,s.jsx)("button",{onClick:el,className:"px-4 py-1.5 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors",children:"Next"})]}),eu?.id==="workdir"&&(0,s.jsx)(l,{repo:h,workingDir:O,setWorkingDir:M,error:eu.error,onNext:el}),eu?.id==="workspaces"&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Create workspaces"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-4",children:"This creates git worktrees for each agent and writes seed configuration files (AGENTS.md, CLAUDE.md) into each workspace."}),eu.error&&(0,s.jsx)("p",{className:"text-[11px] text-error mb-2",children:eu.error}),K.length>0&&(0,s.jsx)("div",{className:"border border-border bg-bg-surface p-3 mb-4 text-[11px] text-text-muted space-y-0.5 font-mono",children:K.map((e,t)=>(0,s.jsx)("p",{children:e},t))}),(0,s.jsx)("button",{onClick:ed,disabled:z,className:"px-4 py-1.5 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:z?"Creating...":"Create Worktrees & Seed Files"})]}),eu?.id==="launch"&&(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-sm font-semibold text-text mb-1",children:"Ready to launch"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mb-4",children:"Everything is configured. Review the summary and launch your AI dev team."}),(0,s.jsxs)("div",{className:"border border-border mb-4",children:[(0,s.jsx)("div",{className:"px-3 py-1.5 border-b border-border bg-bg-surface",children:(0,s.jsx)("span",{className:"text-[11px] text-text font-semibold",children:"Team Roster"})}),o.map(e=>(0,s.jsxs)("div",{className:"flex items-center justify-between px-3 py-1.5 border-b border-border/50 last:border-b-0",children:[(0,s.jsx)("span",{className:"text-[11px] text-text font-semibold",children:e.label}),(0,s.jsx)("span",{className:"text-[10px] text-text-muted",children:e.role}),(0,s.jsx)("span",{className:"text-[11px] text-accent",children:"claude"===T[e.key]?"Claude Code":"Codex"})]},e.key))]}),(0,s.jsxs)("div",{className:"mb-4",children:[(0,s.jsxs)("label",{className:"flex items-center gap-2 cursor-pointer mb-2",children:[(0,s.jsx)("input",{type:"checkbox",checked:Z,onChange:e=>ee(e.target.checked),className:"accent-accent"}),(0,s.jsx)("span",{className:"text-[11px] text-text-muted",children:"Custom ports"})]}),Z&&(0,s.jsx)("div",{className:"border border-border p-3 space-y-2",children:(0,s.jsxs)("div",{className:"grid grid-cols-3 gap-3",children:[(0,s.jsxs)("div",{className:"flex flex-col gap-1",children:[(0,s.jsx)("label",{className:"text-[10px] text-text-muted uppercase tracking-wider",children:"AgentChattr port"}),(0,s.jsx)("input",{type:"number",value:et.chattr||"",onChange:e=>es({...et,chattr:parseInt(e.target.value,10)||0}),placeholder:String(ea.chattr||8300),className:"bg-transparent border border-border px-2 py-1 text-[11px] text-text outline-none focus:border-accent"}),ea.chattr>0&&(0,s.jsxs)("span",{className:"text-[10px] text-text-muted",children:["auto-detected: ",ea.chattr]})]}),(0,s.jsxs)("div",{className:"flex flex-col gap-1",children:[(0,s.jsx)("label",{className:"text-[10px] text-text-muted uppercase tracking-wider",children:"MCP HTTP port"}),(0,s.jsx)("input",{type:"number",value:et.mcpHttp||"",onChange:e=>es({...et,mcpHttp:parseInt(e.target.value,10)||0}),placeholder:String(ea.mcpHttp||8200),className:"bg-transparent border border-border px-2 py-1 text-[11px] text-text outline-none focus:border-accent"}),ea.mcpHttp>0&&(0,s.jsxs)("span",{className:"text-[10px] text-text-muted",children:["auto-detected: ",ea.mcpHttp]})]}),(0,s.jsxs)("div",{className:"flex flex-col gap-1",children:[(0,s.jsx)("label",{className:"text-[10px] text-text-muted uppercase tracking-wider",children:"MCP SSE port"}),(0,s.jsx)("input",{type:"number",value:et.mcpSse||"",onChange:e=>es({...et,mcpSse:parseInt(e.target.value,10)||0}),placeholder:String(ea.mcpSse||8201),className:"bg-transparent border border-border px-2 py-1 text-[11px] text-text outline-none focus:border-accent"}),ea.mcpSse>0&&(0,s.jsxs)("span",{className:"text-[10px] text-text-muted",children:["auto-detected: ",ea.mcpSse]})]})]})})]}),eu.error&&(0,s.jsx)("p",{className:"text-[11px] text-error mb-2",children:eu.error}),"done"===X&&(0,s.jsx)("p",{className:"text-[11px] text-accent mb-2",children:"Project saved. Redirecting to dashboard..."}),(0,s.jsx)("button",{onClick:ep,disabled:"running"===X||"done"===X,className:"px-5 py-2 bg-accent text-bg text-[12px] font-semibold hover:bg-accent-dim transition-colors disabled:opacity-50",children:"running"===X?"Launching...":"done"===X?"Launched!":"Launch Project"})]}),d>=i.length&&(0,s.jsxs)("div",{className:"text-center py-8",children:[(0,s.jsx)("p",{className:"text-accent text-sm font-semibold",children:"Setup complete!"}),(0,s.jsx)("p",{className:"text-[11px] text-text-muted mt-2",children:"Redirecting to project dashboard..."})]})]})]}),(0,s.jsxs)("div",{className:"w-64 shrink-0 border-l border-border p-4 overflow-y-auto bg-bg-surface/50",children:[(0,s.jsx)("h3",{className:"text-[11px] font-semibold text-text-muted uppercase tracking-wider mb-3",children:"Configuration Preview"}),(0,s.jsxs)("div",{className:"space-y-3 text-[11px]",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Project"}),(0,s.jsx)("span",{className:"text-text",children:m||"—"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Repository"}),(0,s.jsx)("span",{className:"text-text",children:h||"—"}),_&&(0,s.jsx)("span",{className:"text-[10px] text-accent block",children:"+ branch protection"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Backends"}),Object.entries(T).map(([e,t])=>(0,s.jsxs)("div",{className:"flex justify-between",children:[(0,s.jsx)("span",{className:"text-text capitalize",children:e}),(0,s.jsx)("span",{className:"text-accent",children:t})]},e))]}),H&&A&&(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Reviewer"}),(0,s.jsxs)("span",{className:"text-text",children:["@",A]})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Directory"}),(0,s.jsx)("span",{className:"text-text font-mono text-[10px]",children:O||"—"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("span",{className:"text-text-muted block mb-0.5",children:"Status"}),(0,s.jsx)("div",{className:"space-y-0.5",children:i.map(e=>(0,s.jsxs)("div",{className:"flex items-center gap-1.5",children:[(0,s.jsx)("span",{className:`text-[10px] ${"done"===e.status?"text-accent":"error"===e.status?"text-error":"active"===e.status?"text-text":"text-text-muted"}`,children:"done"===e.status?"✓":"error"===e.status?"✗":"active"===e.status?"●":"○"}),(0,s.jsx)("span",{className:`text-[10px] ${"active"===e.status?"text-text":"text-text-muted"}`,children:e.label})]},e.id))})]})]})]})]})]})}])}]);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,18540,e=>{"use strict";var t=e.i(85899),r=e.i(4232);let s=[{title:"You assign a task in the chat",body:"Tell @head what to build. Be as specific or as vague as you like."},{title:"Head creates a GitHub issue",body:"Head opens an issue, adds it to the queue, and waits for your trigger."},{title:"Dev writes the code",body:"Dev clones a branch, implements the change, and opens a pull request."},{title:"Reviewers check the work",body:"Reviewer1 and Reviewer2 each review the PR independently. Both must approve before the PR is mergeable."},{title:"Head merges and continues",body:"Head merges the approved PR and assigns the next ticket from the queue. The cycle continues all night while you sleep."}];e.s(["default",0,function({open:e,onClose:a}){return((0,r.useEffect)(()=>{if(!e)return;let t=e=>{"Escape"===e.key&&a()};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[e,a]),e)?(0,t.jsx)("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm",onClick:a,role:"dialog","aria-modal":"true","aria-labelledby":"how-to-work-title",children:(0,t.jsxs)("div",{className:"relative mx-4 max-w-xl w-full max-h-[90vh] overflow-auto rounded-lg border border-white/10 bg-neutral-950 p-6 shadow-2xl",onClick:e=>e.stopPropagation(),children:[(0,t.jsx)("button",{type:"button",onClick:a,"aria-label":"Close",className:"absolute right-3 top-3 rounded p-1 text-neutral-400 hover:bg-white/5 hover:text-white",children:(0,t.jsx)("svg",{width:"18",height:"18",viewBox:"0 0 20 20",fill:"none",stroke:"currentColor",strokeWidth:"1.8",children:(0,t.jsx)("path",{d:"M4 4l12 12M16 4L4 16",strokeLinecap:"round"})})}),(0,t.jsx)("h2",{id:"how-to-work-title",className:"text-base font-semibold text-white",children:"How QuadWork builds your code"}),(0,t.jsx)("p",{className:"mt-2 text-[12px] text-neutral-400",children:"Five steps from your one-line request to a merged pull request."}),(0,t.jsxs)("ol",{className:"mt-5 relative",children:[(0,t.jsx)("span",{"aria-hidden":!0,className:"absolute left-[14px] top-3 bottom-3 w-px bg-accent/30"}),s.map((e,r)=>(0,t.jsxs)("li",{className:"relative pl-10 pb-5 last:pb-0",children:[(0,t.jsx)("span",{className:"absolute left-0 top-0 inline-flex items-center justify-center w-7 h-7 rounded-full border border-accent bg-neutral-950 text-accent text-[12px] font-semibold tabular-nums","aria-hidden":!0,children:r+1}),(0,t.jsx)("div",{className:"text-[13px] font-semibold text-white",children:e.title}),(0,t.jsx)("div",{className:"mt-1 text-[12px] leading-relaxed text-neutral-400",children:e.body})]},r))]})]})}):null}])},16348,e=>{"use strict";var t=e.i(85899),r=e.i(4232),s=e.i(2270),a=e.i(18540);function l({hasProjects:e}){let[i,o]=(0,r.useState)(!1);return(0,t.jsxs)("div",{className:"flex flex-col items-center justify-center text-center px-6 py-12 border border-border bg-bg-surface",children:[(0,t.jsxs)("svg",{width:"64",height:"64",viewBox:"0 0 64 64",fill:"none","aria-hidden":!0,className:"text-accent",children:[(0,t.jsx)("rect",{x:"6",y:"14",width:"18",height:"22",rx:"2",stroke:"currentColor",strokeWidth:"2"}),(0,t.jsx)("rect",{x:"40",y:"14",width:"18",height:"22",rx:"2",stroke:"currentColor",strokeWidth:"2"}),(0,t.jsx)("rect",{x:"6",y:"42",width:"18",height:"14",rx:"2",stroke:"currentColor",strokeWidth:"2"}),(0,t.jsx)("rect",{x:"40",y:"42",width:"18",height:"14",rx:"2",stroke:"currentColor",strokeWidth:"2"}),(0,t.jsx)("circle",{cx:"15",cy:"25",r:"2",fill:"currentColor"}),(0,t.jsx)("circle",{cx:"49",cy:"25",r:"2",fill:"currentColor"}),(0,t.jsx)("circle",{cx:"15",cy:"49",r:"2",fill:"currentColor"}),(0,t.jsx)("circle",{cx:"49",cy:"49",r:"2",fill:"currentColor"}),(0,t.jsx)("path",{d:"M24 25 L40 25 M24 49 L40 49 M15 36 L15 42 M49 36 L49 42",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round"})]}),(0,t.jsx)("h1",{className:"mt-5 text-lg font-semibold text-text max-w-md",children:e?"Pick a project from the sidebar to start working":"Welcome to QuadWork — let's set up your first AI dev team"}),(0,t.jsx)("p",{className:"mt-2 text-[12px] text-text-muted leading-relaxed max-w-md",children:e?"Each project has its own 4-agent team and chat. Click any chip in the left sidebar to open one.":"QuadWork runs Head, Dev, and two Reviewers as a team. They open issues, write code, review PRs, and merge — while you sleep."}),(0,t.jsxs)("div",{className:"mt-5 flex items-center gap-3",children:[e?(0,t.jsx)("span",{className:"text-[11px] text-text-muted italic",children:"← look at the left sidebar"}):(0,t.jsx)(s.default,{href:"/setup",className:"px-4 py-2 text-[12px] font-semibold text-bg bg-accent hover:bg-accent-dim transition-colors",children:"Add Your First Project →"}),(0,t.jsx)("button",{type:"button",onClick:()=>o(!0),className:"px-4 py-2 text-[12px] text-text-muted border border-border hover:text-text hover:border-text-muted transition-colors",children:"How to Work"})]}),(0,t.jsx)(a.default,{open:i,onClose:()=>o(!1)})]})}e.s(["default",0,function(){let[e,a]=(0,r.useState)([]),[i,o]=(0,r.useState)([]),[c,n]=(0,r.useState)("loading");return(0,r.useEffect)(()=>{fetch("/api/projects").then(e=>{if(!e.ok)throw Error(`${e.status}`);return e.json()}).then(e=>{e.projects&&Array.isArray(e.projects)&&a(e.projects.filter(e=>!e.archived)),e.recentEvents&&Array.isArray(e.recentEvents)&&o(e.recentEvents),n("loaded")}).catch(()=>{n("error")})},[]),(0,t.jsxs)("div",{className:"h-full overflow-y-auto p-6",children:["loaded"===c&&(0,t.jsx)("div",{className:"mb-6",children:(0,t.jsx)(l,{hasProjects:e.length>0})}),"error"===c&&(0,t.jsx)("div",{className:"mb-6 border border-error/30 bg-error/5 text-error text-[11px] px-3 py-2",children:"Could not load projects from /api/projects. The dashboard may be out of date — check the server logs and reload."}),(0,t.jsxs)("div",{className:"mb-6",children:[(0,t.jsx)("h1",{className:"text-lg font-semibold text-text tracking-tight",children:"Projects"}),(0,t.jsxs)("p",{className:"text-xs text-text-muted mt-1",children:[e.length," configured project",1!==e.length?"s":""]})]}),(0,t.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3 mb-8",children:[e.map(e=>(0,t.jsxs)(s.default,{href:`/project/${e.id}`,className:"block border border-border bg-bg-surface p-4 hover:bg-[#1a1a1a] transition-colors group",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:`w-1.5 h-1.5 rounded-full ${"active"===e.state?"bg-accent":"bg-text-muted"}`}),(0,t.jsx)("span",{className:"text-sm font-semibold text-text",children:e.name}),(0,t.jsx)("span",{className:"text-[10px] text-text-muted",children:e.state})]}),(0,t.jsx)("span",{className:"text-[10px] text-text-muted opacity-0 group-hover:opacity-100 transition-opacity",children:"open →"})]}),(0,t.jsxs)("div",{className:"flex gap-4 text-[11px] mb-2",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"text-text-muted",children:"agents"}),(0,t.jsx)("span",{className:"ml-1.5 text-text",children:e.agentCount})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"text-text-muted",children:"PRs"}),(0,t.jsx)("span",{className:"ml-1.5 text-text",children:e.openPrs})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"text-text-muted",children:"repo"}),(0,t.jsx)("span",{className:"ml-1.5 text-text",children:e.repo})]})]}),e.lastActivity&&(0,t.jsxs)("div",{className:"text-[10px] text-text-muted",children:["last activity: ",function(e){let t=Math.floor((Date.now()-new Date(e).getTime())/6e4);if(t<1)return"just now";if(t<60)return`${t}m ago`;let r=Math.floor(t/60);if(r<24)return`${r}h ago`;let s=Math.floor(r/24);return`${s}d ago`}(e.lastActivity)]})]},e.id)),(0,t.jsx)(s.default,{href:"/setup",className:"border border-dashed border-border p-4 flex items-center justify-center text-text-muted hover:text-text hover:border-text-muted transition-colors min-h-[88px]",children:(0,t.jsx)("span",{className:"text-sm",children:"+ New Project"})})]}),(0,t.jsxs)("div",{className:"mb-6",children:[(0,t.jsx)("h2",{className:"text-xs text-text-muted uppercase tracking-wider mb-3",children:"Recent Activity"}),(0,t.jsxs)("div",{className:"border border-border bg-bg-surface",children:[0===i.length&&(0,t.jsx)("div",{className:"px-3 py-3 text-[11px] text-text-muted",children:"No recent activity"}),i.map((e,r)=>(0,t.jsxs)("div",{className:"flex gap-3 px-3 py-1.5 border-b border-border/50 last:border-b-0 text-[11px]",children:[(0,t.jsx)("span",{className:"text-text-muted shrink-0 w-10 text-right tabular-nums",children:e.time?.slice(0,5)||""}),(0,t.jsx)("span",{className:"text-accent shrink-0 font-semibold w-12",children:e.projectName}),(0,t.jsx)("span",{className:"text-[#ffcc00] shrink-0 font-semibold w-6",children:e.actor}),(0,t.jsx)("span",{className:"text-text truncate min-w-0",children:e.text})]},`${e.time}-${r}`))]})]})]})}],16348)}]);
|