quadwork 1.9.0 → 1.10.1

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.
Files changed (75) hide show
  1. package/README.md +4 -0
  2. package/bridges/discord/__pycache__/discord_bridge.cpython-314.pyc +0 -0
  3. package/bridges/discord/discord_bridge.py +58 -14
  4. package/out/404.html +1 -1
  5. package/out/__next.__PAGE__.txt +3 -3
  6. package/out/__next._full.txt +13 -13
  7. package/out/__next._head.txt +4 -4
  8. package/out/__next._index.txt +7 -7
  9. package/out/__next._tree.txt +2 -2
  10. package/out/_next/static/chunks/0a4.d381szseh.css +2 -0
  11. package/out/_next/static/chunks/{0q71bcdksran1.js → 0aldkx8l9xukk.js} +1 -1
  12. package/out/_next/static/chunks/{04jznxp9-kut_.js → 0gaekhrfy94vz.js} +1 -1
  13. package/out/_next/static/chunks/{0m439m2ljf2gz.js → 0hirada7763yr.js} +14 -14
  14. package/out/_next/static/chunks/0rxi-m9onh_sa.js +1 -0
  15. package/out/_not-found/__next._full.txt +12 -12
  16. package/out/_not-found/__next._head.txt +4 -4
  17. package/out/_not-found/__next._index.txt +7 -7
  18. package/out/_not-found/__next._not-found.__PAGE__.txt +2 -2
  19. package/out/_not-found/__next._not-found.txt +3 -3
  20. package/out/_not-found/__next._tree.txt +2 -2
  21. package/out/_not-found.html +1 -1
  22. package/out/_not-found.txt +12 -12
  23. package/out/app-shell/__next._full.txt +12 -12
  24. package/out/app-shell/__next._head.txt +4 -4
  25. package/out/app-shell/__next._index.txt +7 -7
  26. package/out/app-shell/__next._tree.txt +2 -2
  27. package/out/app-shell/__next.app-shell.__PAGE__.txt +2 -2
  28. package/out/app-shell/__next.app-shell.txt +3 -3
  29. package/out/app-shell.html +1 -1
  30. package/out/app-shell.txt +12 -12
  31. package/out/index.html +1 -1
  32. package/out/index.txt +13 -13
  33. package/out/project/_/__next._full.txt +13 -13
  34. package/out/project/_/__next._head.txt +4 -4
  35. package/out/project/_/__next._index.txt +7 -7
  36. package/out/project/_/__next._tree.txt +2 -2
  37. package/out/project/_/__next.project.$d$id.__PAGE__.txt +3 -3
  38. package/out/project/_/__next.project.$d$id.txt +3 -3
  39. package/out/project/_/__next.project.txt +3 -3
  40. package/out/project/_/queue/__next._full.txt +13 -13
  41. package/out/project/_/queue/__next._head.txt +4 -4
  42. package/out/project/_/queue/__next._index.txt +7 -7
  43. package/out/project/_/queue/__next._tree.txt +2 -2
  44. package/out/project/_/queue/__next.project.$d$id.queue.__PAGE__.txt +3 -3
  45. package/out/project/_/queue/__next.project.$d$id.queue.txt +3 -3
  46. package/out/project/_/queue/__next.project.$d$id.txt +3 -3
  47. package/out/project/_/queue/__next.project.txt +3 -3
  48. package/out/project/_/queue.html +1 -1
  49. package/out/project/_/queue.txt +13 -13
  50. package/out/project/_.html +1 -1
  51. package/out/project/_.txt +13 -13
  52. package/out/settings/__next._full.txt +13 -13
  53. package/out/settings/__next._head.txt +4 -4
  54. package/out/settings/__next._index.txt +7 -7
  55. package/out/settings/__next._tree.txt +2 -2
  56. package/out/settings/__next.settings.__PAGE__.txt +3 -3
  57. package/out/settings/__next.settings.txt +3 -3
  58. package/out/settings.html +1 -1
  59. package/out/settings.txt +13 -13
  60. package/out/setup/__next._full.txt +13 -13
  61. package/out/setup/__next._head.txt +4 -4
  62. package/out/setup/__next._index.txt +7 -7
  63. package/out/setup/__next._tree.txt +2 -2
  64. package/out/setup/__next.setup.__PAGE__.txt +3 -3
  65. package/out/setup/__next.setup.txt +3 -3
  66. package/out/setup.html +1 -1
  67. package/out/setup.txt +13 -13
  68. package/package.json +1 -1
  69. package/server/index.js +187 -0
  70. package/server/routes.js +71 -50
  71. package/out/_next/static/chunks/0d4fb920~o_t9.css +0 -2
  72. package/out/_next/static/chunks/0yt_bs94icoma.js +0 -1
  73. /package/out/_next/static/{8W6cFlsHLq6GQp5eCnKNU → MA2-1YByee5M0-bbLgqQD}/_buildManifest.js +0 -0
  74. /package/out/_next/static/{8W6cFlsHLq6GQp5eCnKNU → MA2-1YByee5M0-bbLgqQD}/_clientMiddlewareManifest.js +0 -0
  75. /package/out/_next/static/{8W6cFlsHLq6GQp5eCnKNU → MA2-1YByee5M0-bbLgqQD}/_ssgManifest.js +0 -0
package/server/index.js CHANGED
@@ -1298,9 +1298,121 @@ Dev: Work on assigned ticket or address review feedback.
1298
1298
  RE1/RE2: Review open PRs. If Dev pushed fixes, re-review. Post verdict on PR AND notify here.
1299
1299
  ALL: Communicate via this chat by tagging agents. Your terminal is NOT visible.`;
1300
1300
 
1301
+ // #518: server-side bridge lifecycle helpers. Stop and start Telegram +
1302
+ // Discord bridges so they respond to batch transitions even when the
1303
+ // operator is on a different project page.
1304
+
1305
+ async function autoStopBridges(projectId, project, qwPort) {
1306
+ if (project?.telegram_auto) {
1307
+ try {
1308
+ await fetch(`http://127.0.0.1:${qwPort}/api/telegram?action=stop`, {
1309
+ method: "POST",
1310
+ headers: { "Content-Type": "application/json" },
1311
+ body: JSON.stringify({ project_id: projectId }),
1312
+ signal: AbortSignal.timeout(5000),
1313
+ });
1314
+ console.log(`[auto-bridge] ${projectId}: telegram bridge auto-stopped`);
1315
+ } catch { /* non-fatal */ }
1316
+ }
1317
+ if (project?.discord_auto) {
1318
+ try {
1319
+ await fetch(`http://127.0.0.1:${qwPort}/api/discord?action=stop`, {
1320
+ method: "POST",
1321
+ headers: { "Content-Type": "application/json" },
1322
+ body: JSON.stringify({ project_id: projectId }),
1323
+ signal: AbortSignal.timeout(5000),
1324
+ });
1325
+ console.log(`[auto-bridge] ${projectId}: discord bridge auto-stopped`);
1326
+ } catch { /* non-fatal */ }
1327
+ }
1328
+ }
1329
+
1330
+ async function autoStartBridges(projectId, project, qwPort) {
1331
+ if (project?.telegram_auto) {
1332
+ try {
1333
+ // Check if already running before starting
1334
+ const st = await fetch(
1335
+ `http://127.0.0.1:${qwPort}/api/telegram?project=${encodeURIComponent(projectId)}`,
1336
+ { signal: AbortSignal.timeout(5000) }
1337
+ );
1338
+ if (st.ok) {
1339
+ const data = await st.json();
1340
+ if (data.running) return; // already running
1341
+ if (!data.configured) return; // not configured — can't start
1342
+ }
1343
+ await fetch(`http://127.0.0.1:${qwPort}/api/telegram?action=start`, {
1344
+ method: "POST",
1345
+ headers: { "Content-Type": "application/json" },
1346
+ body: JSON.stringify({ project_id: projectId }),
1347
+ signal: AbortSignal.timeout(10000),
1348
+ });
1349
+ console.log(`[auto-bridge] ${projectId}: telegram bridge auto-started`);
1350
+ } catch { /* non-fatal */ }
1351
+ }
1352
+ if (project?.discord_auto) {
1353
+ try {
1354
+ const st = await fetch(
1355
+ `http://127.0.0.1:${qwPort}/api/discord?project=${encodeURIComponent(projectId)}`,
1356
+ { signal: AbortSignal.timeout(5000) }
1357
+ );
1358
+ if (st.ok) {
1359
+ const data = await st.json();
1360
+ if (data.running) return;
1361
+ if (!data.configured) return;
1362
+ }
1363
+ await fetch(`http://127.0.0.1:${qwPort}/api/discord?action=start`, {
1364
+ method: "POST",
1365
+ headers: { "Content-Type": "application/json" },
1366
+ body: JSON.stringify({ project_id: projectId }),
1367
+ signal: AbortSignal.timeout(10000),
1368
+ });
1369
+ console.log(`[auto-bridge] ${projectId}: discord bridge auto-started`);
1370
+ } catch { /* non-fatal */ }
1371
+ }
1372
+ }
1373
+
1374
+ // Track previous batch state per project for bridge auto-start detection
1375
+ const _bridgeBatchPrev = new Map();
1376
+
1301
1377
  async function sendTriggerMessage(projectId) {
1302
1378
  const cfg = readConfig();
1303
1379
  const project = cfg.projects && cfg.projects.find((p) => p.id === projectId);
1380
+
1381
+ // #516: server-side auto-stop — check batch progress before sending.
1382
+ // When trigger_auto is enabled, skip the message and stop the trigger
1383
+ // (plus caffeinate) if the batch is already complete. This covers the
1384
+ // case where the operator is on a different page and the client-side
1385
+ // ScheduledTriggerWidget is not mounted to detect completion.
1386
+ if (project && project.trigger_auto) {
1387
+ const qwPort = cfg.port || 8400;
1388
+ try {
1389
+ const bpRes = await fetch(
1390
+ `http://127.0.0.1:${qwPort}/api/batch-progress?project=${encodeURIComponent(projectId)}`
1391
+ );
1392
+ if (bpRes.ok) {
1393
+ const bp = await bpRes.json();
1394
+ if (bp && bp.complete) {
1395
+ console.log(`[auto-trigger] ${projectId}: batch complete, auto-stopped`);
1396
+ stopTrigger(projectId);
1397
+ // Also stop caffeinate if no other triggers remain running
1398
+ // (#441 companion fix). caffeinateProcess is global (not
1399
+ // project-scoped), so only kill it when all work is done.
1400
+ if (caffeinateProcess.process && triggers.size === 0) {
1401
+ try { caffeinateProcess.process.kill("SIGTERM"); } catch {}
1402
+ caffeinateProcess = { process: null, pid: null, startedAt: null, duration: null };
1403
+ console.log(`[auto-trigger] ${projectId}: caffeinate auto-stopped (no active triggers remain)`);
1404
+ }
1405
+ // #518: also stop bridges when batch completes
1406
+ await autoStopBridges(projectId, project, qwPort);
1407
+ return;
1408
+ }
1409
+ }
1410
+ } catch (err) {
1411
+ // Non-fatal — if batch-progress fails, proceed with the message
1412
+ console.error(`[auto-trigger] ${projectId}: batch-progress check failed:`, err.message);
1413
+ }
1414
+ }
1415
+
1304
1416
  const message = (project && project.trigger_message) || DEFAULT_MESSAGE;
1305
1417
 
1306
1418
  // #401 / quadwork#277: route trigger sends through the local
@@ -1721,6 +1833,67 @@ function syncTriggersFromConfig() {
1721
1833
  }
1722
1834
  }
1723
1835
 
1836
+ // #516: server-side batch-completion poller. Checks every 30s whether
1837
+ // any trigger_auto project's batch is complete, and auto-stops the
1838
+ // trigger (plus caffeinate when no triggers remain). This runs
1839
+ // independently of the trigger tick interval, so completion is
1840
+ // detected within 30s even if the operator is on a different page.
1841
+ // #518: also handles telegram_auto / discord_auto bridge lifecycle
1842
+ // (both start and stop) so bridges respond to batch transitions
1843
+ // even when the operator is viewing a different project page.
1844
+
1845
+ const AUTO_STOP_POLL_INTERVAL_MS = 30_000;
1846
+
1847
+ async function autoStopPollingTick() {
1848
+ const cfg = readConfig();
1849
+ if (!cfg.projects) return;
1850
+
1851
+ for (const project of cfg.projects) {
1852
+ const hasTriggerAuto = project.trigger_auto && triggers.has(project.id);
1853
+ const hasBridgeAuto = project.telegram_auto || project.discord_auto;
1854
+ if (!hasTriggerAuto && !hasBridgeAuto) continue;
1855
+ const qwPort = cfg.port || 8400;
1856
+ try {
1857
+ const res = await fetch(
1858
+ `http://127.0.0.1:${qwPort}/api/batch-progress?project=${encodeURIComponent(project.id)}`
1859
+ );
1860
+ if (!res.ok) continue;
1861
+ const bp = await res.json();
1862
+ const hasItems = bp.items && bp.items.length > 0;
1863
+ const prev = _bridgeBatchPrev.get(project.id);
1864
+ _bridgeBatchPrev.set(project.id, { complete: bp.complete, hasItems });
1865
+
1866
+ if (bp && bp.complete) {
1867
+ if (hasTriggerAuto) {
1868
+ console.log(`[auto-trigger] ${project.id}: batch complete, auto-stopped (poller)`);
1869
+ stopTrigger(project.id);
1870
+ if (caffeinateProcess.process && triggers.size === 0) {
1871
+ try { caffeinateProcess.process.kill("SIGTERM"); } catch {}
1872
+ caffeinateProcess = { process: null, pid: null, startedAt: null, duration: null };
1873
+ console.log(`[auto-trigger] ${project.id}: caffeinate auto-stopped (no active triggers remain)`);
1874
+ }
1875
+ }
1876
+ // #518: also stop bridges when batch completes
1877
+ if (hasBridgeAuto) {
1878
+ await autoStopBridges(project.id, project, qwPort);
1879
+ }
1880
+ }
1881
+
1882
+ // #518: detect batch-start transition → auto-start bridges
1883
+ if (hasBridgeAuto && hasItems && !bp.complete) {
1884
+ const isNewBatch = !prev || prev.complete || !prev.hasItems;
1885
+ if (isNewBatch) {
1886
+ await autoStartBridges(project.id, project, qwPort);
1887
+ }
1888
+ }
1889
+ } catch {
1890
+ // Non-fatal — retry on next tick
1891
+ }
1892
+ }
1893
+ }
1894
+
1895
+ setInterval(autoStopPollingTick, AUTO_STOP_POLL_INTERVAL_MS);
1896
+
1724
1897
  // #422 / quadwork#310: auto-continue after loop guard.
1725
1898
  //
1726
1899
  // Per opted-in project, poll AC's /api/status every 10s. When we see
@@ -1983,6 +2156,20 @@ server.listen(PORT, "127.0.0.1", () => {
1983
2156
  }
1984
2157
  } catch {}
1985
2158
  }
2159
+ // #506: refresh Discord bridge script from the npm package on startup.
2160
+ // The Telegram bridge uses git-fetch + pin, but Discord uses a file-copy
2161
+ // pattern. Without this, upgrading QuadWork leaves a stale on-disk script
2162
+ // missing fixes shipped in newer versions.
2163
+ const DISCORD_BRIDGE_SRC = path.join(__dirname, "..", "bridges", "discord", "discord_bridge.py");
2164
+ const DISCORD_BRIDGE_DEST = path.join(os.homedir(), ".quadwork", "agentchattr-discord", "discord_bridge.py");
2165
+ if (fs.existsSync(DISCORD_BRIDGE_SRC) && fs.existsSync(path.dirname(DISCORD_BRIDGE_DEST))) {
2166
+ try {
2167
+ fs.copyFileSync(DISCORD_BRIDGE_SRC, DISCORD_BRIDGE_DEST);
2168
+ console.log("[bridge-refresh] refreshed Discord bridge script from package");
2169
+ } catch (err) {
2170
+ console.warn(`[bridge-refresh] failed to refresh Discord bridge script: ${err.message || err}`);
2171
+ }
2172
+ }
1986
2173
  // #470: patch stale bridge_sender defaults in on-disk bridge scripts.
1987
2174
  // The AC config migration (#457) renames the agent sections, but the
1988
2175
  // bridge scripts themselves may still have old defaults if the operator
package/server/routes.js CHANGED
@@ -1069,17 +1069,17 @@ router.get("/api/uploads/:project/:filename", (req, res) => {
1069
1069
 
1070
1070
  // ─── Projects (dashboard aggregation) ──────────────────────────────────────
1071
1071
 
1072
- function ghJson(args) {
1073
- try {
1074
- const out = execFileSync("gh", args, { encoding: "utf-8", timeout: 15000 });
1075
- const parsed = JSON.parse(out);
1076
- return Array.isArray(parsed) ? parsed : [];
1077
- } catch {
1078
- return [];
1079
- }
1080
- }
1072
+ // #512: cache /api/projects results for 60s to eliminate repeated
1073
+ // slow gh CLI calls on every dashboard poll.
1074
+ let _projectsCache = null;
1075
+ let _projectsCacheTs = 0;
1076
+ const PROJECTS_CACHE_TTL = 60_000;
1081
1077
 
1082
1078
  router.get("/api/projects", async (req, res) => {
1079
+ if (_projectsCache && Date.now() - _projectsCacheTs < PROJECTS_CACHE_TTL) {
1080
+ return res.json(_projectsCache);
1081
+ }
1082
+
1083
1083
  const cfg = readConfigFile();
1084
1084
 
1085
1085
  // Fetch active sessions from our own in-memory state (only running PTYs)
@@ -1113,24 +1113,34 @@ router.get("/api/projects", async (req, res) => {
1113
1113
  .slice(-10)
1114
1114
  .reverse();
1115
1115
 
1116
- const numberToProject = {};
1117
- const projectResults = (cfg.projects || []).map((p) => {
1116
+ // #512: build project-id-to-name map from config and a reverse
1117
+ // lookup from chat message to project name via chatMsgsByProject
1118
+ // (which already knows which AC instance each message came from).
1119
+ // This replaces the expensive allPrs/allIssues gh CLI calls that
1120
+ // were only used for the numberToProject mapping.
1121
+ const projectIdToName = {};
1122
+ for (const p of cfg.projects || []) projectIdToName[p.id] = p.name;
1123
+ const msgToProject = new Map();
1124
+ for (const [pid, msgs] of Object.entries(chatMsgsByProject)) {
1125
+ for (const m of msgs) msgToProject.set(m, projectIdToName[pid]);
1126
+ }
1127
+
1128
+ // #512: parallelize gh CLI calls across projects using async exec.
1129
+ // Only fetch open PR count and most recent PR activity — drop the
1130
+ // allPrs/allIssues calls that were only used for numberToProject.
1131
+ async function fetchProjectGhData(p) {
1118
1132
  let openPrs = 0;
1119
1133
  let lastActivity = null;
1120
-
1121
1134
  if (REPO_RE.test(p.repo)) {
1122
- const prs = ghJson(["pr", "list", "-R", p.repo, "--json", "number", "--limit", "100"]);
1123
- openPrs = prs.length;
1124
-
1125
- const recentPrs = ghJson(["pr", "list", "-R", p.repo, "--state", "all", "--json", "updatedAt", "--limit", "1"]);
1126
- lastActivity = recentPrs[0]?.updatedAt || null;
1127
-
1128
- const allPrs = ghJson(["pr", "list", "-R", p.repo, "--state", "all", "--json", "number", "--limit", "100"]);
1129
- for (const pr of allPrs) numberToProject[pr.number] = p.name;
1130
- const allIssues = ghJson(["issue", "list", "-R", p.repo, "--state", "all", "--json", "number", "--limit", "100"]);
1131
- for (const issue of allIssues) numberToProject[issue.number] = p.name;
1135
+ try {
1136
+ const [prs, recentPrs] = await Promise.allSettled([
1137
+ ghJsonExecAsync(["pr", "list", "-R", p.repo, "--json", "number", "--limit", "100"]),
1138
+ ghJsonExecAsync(["pr", "list", "-R", p.repo, "--state", "all", "--json", "updatedAt", "--limit", "1"]),
1139
+ ]);
1140
+ if (prs.status === "fulfilled") openPrs = prs.value.length;
1141
+ if (recentPrs.status === "fulfilled") lastActivity = recentPrs.value[0]?.updatedAt || null;
1142
+ } catch {}
1132
1143
  }
1133
-
1134
1144
  const hasAgents = p.agents && Object.keys(p.agents).length > 0;
1135
1145
  return {
1136
1146
  id: p.id,
@@ -1141,20 +1151,21 @@ router.get("/api/projects", async (req, res) => {
1141
1151
  state: hasAgents && activeProjectIds.has(p.id) ? "active" : "idle",
1142
1152
  lastActivity,
1143
1153
  };
1144
- });
1154
+ }
1155
+
1156
+ const projectResults = await Promise.all(
1157
+ (cfg.projects || []).map((p) => fetchProjectGhData(p))
1158
+ );
1145
1159
 
1146
- // Build activity feed
1160
+ // Build activity feed — use chat-based project association instead
1161
+ // of the dropped numberToProject gh lookup.
1147
1162
  const recentEvents = [];
1148
1163
  for (const m of workflowMsgs) {
1164
+ // First: try text match against repo/project name
1149
1165
  let projectName = (cfg.projects || []).find((p) => m.text.includes(p.repo) || m.text.includes(p.name))?.name;
1150
- if (!projectName) {
1151
- const numMatch = m.text.match(/#(\d+)/);
1152
- if (numMatch) projectName = numberToProject[parseInt(numMatch[1], 10)];
1153
- }
1154
- if (!projectName) {
1155
- const branchMatch = m.text.match(/task\/(\d+)/);
1156
- if (branchMatch) projectName = numberToProject[parseInt(branchMatch[1], 10)];
1157
- }
1166
+ // Second: use the AC instance the message came from
1167
+ if (!projectName) projectName = msgToProject.get(m);
1168
+ // Fallback: single-project installs
1158
1169
  if (!projectName && cfg.projects && cfg.projects.length === 1) {
1159
1170
  projectName = cfg.projects[0].name;
1160
1171
  }
@@ -1169,7 +1180,10 @@ router.get("/api/projects", async (req, res) => {
1169
1180
  if (recentEvents.length >= 10) break;
1170
1181
  }
1171
1182
 
1172
- res.json({ projects: projectResults, recentEvents });
1183
+ const result = { projects: projectResults, recentEvents };
1184
+ _projectsCache = result;
1185
+ _projectsCacheTs = Date.now();
1186
+ res.json(result);
1173
1187
  });
1174
1188
 
1175
1189
  // ─── GitHub Issues / PRs ───────────────────────────────────────────────────
@@ -2301,7 +2315,7 @@ router.post("/api/rename", (req, res) => {
2301
2315
  const BRIDGE_DIR = path.join(CONFIG_DIR, "agentchattr-telegram");
2302
2316
  // #444: pin agentchattr-telegram to a known commit (same pattern as
2303
2317
  // AGENTCHATTR_PIN in bin/quadwork.js for bcurts/agentchattr).
2304
- const AGENTCHATTR_TELEGRAM_PIN = "03753c5e4f4497fb7a4a4da639faf31a61d9a4ac";
2318
+ const AGENTCHATTR_TELEGRAM_PIN = "045ee18f6d5dbcd0bd45d5ab29f06e2a27382aaf";
2305
2319
 
2306
2320
  function telegramPidFile(projectId) {
2307
2321
  return path.join(CONFIG_DIR, `tg-bridge-${projectId}.pid`);
@@ -2350,7 +2364,8 @@ function buildTelegramBridgeToml(tg, projectId) {
2350
2364
  `bot_token = "${tg.bot_token}"\n` +
2351
2365
  `chat_id = "${tg.chat_id}"\n` +
2352
2366
  `agentchattr_url = "${tg.agentchattr_url}"\n` +
2353
- `cursor_file = "${cursorFile}"\n`
2367
+ `cursor_file = "${cursorFile}"\n` +
2368
+ `project_id = "${projectId}"\n`
2354
2369
  );
2355
2370
  }
2356
2371
 
@@ -2816,6 +2831,9 @@ router.post("/api/telegram", async (req, res) => {
2816
2831
  if (pid) process.kill(pid, "SIGTERM");
2817
2832
  fs.unlinkSync(pf);
2818
2833
  }
2834
+ // #522: clear bridge log so last_error doesn't show stale
2835
+ // connection-refused messages after an intentional stop.
2836
+ try { fs.writeFileSync(telegramBridgeLog(projectId), ""); } catch {}
2819
2837
  return res.json({ ok: true, running: false });
2820
2838
  } catch (err) {
2821
2839
  return res.json({ ok: false, error: err.message || "Stop failed" });
@@ -2909,7 +2927,8 @@ function buildDiscordBridgeToml(dc, projectId) {
2909
2927
  `bot_token = "${dc.bot_token}"\n` +
2910
2928
  `channel_id = "${dc.channel_id}"\n` +
2911
2929
  `agentchattr_url = "${dc.agentchattr_url}"\n` +
2912
- `cursor_file = "${cursorFile}"\n`
2930
+ `cursor_file = "${cursorFile}"\n` +
2931
+ `project_id = "${projectId}"\n`
2913
2932
  );
2914
2933
  }
2915
2934
 
@@ -3066,20 +3085,19 @@ router.post("/api/discord", async (req, res) => {
3066
3085
  const venvPip = path.join(venvDir, "bin", "pip");
3067
3086
  let pipOutput = "";
3068
3087
  try {
3069
- // Copy from bundled package dir (not clone #397 decision)
3070
- if (!fs.existsSync(path.join(DISCORD_BRIDGE_DIR, "discord_bridge.py"))) {
3071
- fs.cpSync(DISCORD_BRIDGE_SRC, DISCORD_BRIDGE_DIR, { recursive: true });
3072
- } else {
3073
- // On upgrade: overwrite script, keep venv
3074
- fs.cpSync(
3075
- path.join(DISCORD_BRIDGE_SRC, "discord_bridge.py"),
3076
- path.join(DISCORD_BRIDGE_DIR, "discord_bridge.py"),
3077
- );
3078
- fs.cpSync(
3079
- path.join(DISCORD_BRIDGE_SRC, "requirements.txt"),
3080
- path.join(DISCORD_BRIDGE_DIR, "requirements.txt"),
3081
- );
3088
+ // #506: always copy bundled bridge files (not just on first install)
3089
+ // so re-installing after a QuadWork upgrade refreshes the script.
3090
+ if (!fs.existsSync(DISCORD_BRIDGE_DIR)) {
3091
+ fs.mkdirSync(DISCORD_BRIDGE_DIR, { recursive: true });
3082
3092
  }
3093
+ fs.cpSync(
3094
+ path.join(DISCORD_BRIDGE_SRC, "discord_bridge.py"),
3095
+ path.join(DISCORD_BRIDGE_DIR, "discord_bridge.py"),
3096
+ );
3097
+ fs.cpSync(
3098
+ path.join(DISCORD_BRIDGE_SRC, "requirements.txt"),
3099
+ path.join(DISCORD_BRIDGE_DIR, "requirements.txt"),
3100
+ );
3083
3101
  if (!fs.existsSync(venvPython)) {
3084
3102
  execFileSync("python3", ["-m", "venv", venvDir], { encoding: "utf-8", timeout: 60000 });
3085
3103
  }
@@ -3225,6 +3243,9 @@ router.post("/api/discord", async (req, res) => {
3225
3243
  if (pid) process.kill(pid, "SIGTERM");
3226
3244
  fs.unlinkSync(pf);
3227
3245
  }
3246
+ // #522: clear bridge log so last_error doesn't show stale
3247
+ // connection-refused messages after an intentional stop.
3248
+ try { fs.writeFileSync(discordBridgeLog(projectId), ""); } catch {}
3228
3249
  return res.json({ ok: true, running: false });
3229
3250
  } catch (err) {
3230
3251
  return res.json({ ok: false, error: err.message || "Stop failed" });
@@ -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;--tw-duration:initial;--tw-ease: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-amber-200:#fee685;--color-amber-300:#ffd236;--color-amber-400:#fcbb00;--color-amber-500:#f99c00;--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-700:#404040;--color-neutral-900:#171717;--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;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-lg:.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--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-amber-200:lab(91.7203% -.505269 49.9084);--color-amber-300:lab(86.4156% 6.13147 78.3961);--color-amber-400:lab(80.1641% 16.6016 99.2089);--color-amber-500:lab(72.7183% 31.8672 97.9407);--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-700:lab(27.036% 0 0);--color-neutral-900:lab(7.78201% -.0000149012 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-1{top:calc(var(--spacing) * -1)}.-top-1\.5{top:calc(var(--spacing) * -1.5)}.top-0{top:calc(var(--spacing) * 0)}.top-3{top:calc(var(--spacing) * 3)}.top-4{top:calc(var(--spacing) * 4)}.top-5{top:calc(var(--spacing) * 5)}.top-6{top:calc(var(--spacing) * 6)}.-right-1{right:calc(var(--spacing) * -1)}.-right-1\.5{right:calc(var(--spacing) * -1.5)}.right-0{right:calc(var(--spacing) * 0)}.right-3{right:calc(var(--spacing) * 3)}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-5{bottom:calc(var(--spacing) * 5)}.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-30{z-index:30}.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-1{margin-block:calc(var(--spacing) * 1)}.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)}.mr-0{margin-right:calc(var(--spacing) * 0)}.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}.contents{display:contents}.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{height:calc(var(--spacing) * 1)}.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-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-16{height:calc(var(--spacing) * 16)}.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-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}.max-h-\[150px\]{max-height:150px}.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-4{width:calc(var(--spacing) * 4)}.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-52{width:calc(var(--spacing) * 52)}.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-\[200px\]{max-width:200px}.max-w-\[520px\]{max-width:520px}.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-\[140px\]{min-width:140px}.min-w-\[220px\]{min-width:220px}.min-w-\[280px\]{min-width:280px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.-rotate-90{rotate:-90deg}.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}.list-disc{list-style-type:disc}.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}.items-stretch{align-items:stretch}.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-center{align-self:center}.self-end{align-self:flex-end}.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-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-\[\#ffcc00\]\/40{border-color:#fc06;border-color:lab(84.7597% 8.24091 84.7906/.4)}.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-amber-500\/40{border-color:#f99c0066}@supports (color:color-mix(in lab, red, red)){.border-amber-500\/40{border-color:color-mix(in oklab, var(--color-amber-500) 40%, 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\/40{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.border-error\/40{border-color:color-mix(in oklab, var(--error) 40%, 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-amber-500\/5{background-color:#f99c000d}@supports (color:color-mix(in lab, red, red)){.bg-amber-500\/5{background-color:color-mix(in oklab, var(--color-amber-500) 5%, 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-900{background-color:var(--color-neutral-900)}.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)}}.object-cover{object-fit:cover}.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)}.py-\[1px\]{padding-block:1px}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-1\.5{padding-top:calc(var(--spacing) * 1.5)}.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-6{padding-left:calc(var(--spacing) * 6)}.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}.text-\[16px\]{font-size:16px}.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)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.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-amber-200\/90{color:#fee685e6}@supports (color:color-mix(in lab, red, red)){.text-amber-200\/90{color:color-mix(in oklab, var(--color-amber-200) 90%, transparent)}}.text-amber-300{color:var(--color-amber-300)}.text-amber-400{color:var(--color-amber-400)}.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-neutral-700{color:var(--color-neutral-700)}.text-red-400{color:var(--color-red-400)}.text-text{color:var(--text)}.text-text-muted,.text-text-muted\/60{color:var(--text-muted)}@supports (color:color-mix(in lab, red, red)){.text-text-muted\/60{color:color-mix(in oklab, var(--text-muted) 60%, transparent)}}.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{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px 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)}.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-\[width\]{transition-property:width;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))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.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\:text-text:is(:where(.group):hover *){color:var(--text)}.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\:flex{display:flex}.md\:h-auto{height:auto}.md\:w-px{width:1px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-start{align-items:flex-start}.md\:gap-6{gap:calc(var(--spacing) * 6)}.md\:self-stretch{align-self:stretch}.md\:border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.md\:bg-border{background-color:var(--border)}}@media (min-width:64rem){.lg\:mb-0{margin-bottom:calc(var(--spacing) * 0)}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:min-h-0{min-height:calc(var(--spacing) * 0)}.lg\:min-w-\[280px\]{min-width:280px}.lg\:flex-1{flex:1}.lg\:grid-cols-\[1fr_340px\]{grid-template-columns:1fr 340px}.lg\:flex-col{flex-direction:column}.lg\:flex-row{flex-direction:row}.lg\:gap-6{gap:calc(var(--spacing) * 6)}.lg\:overflow-hidden{overflow:hidden}.lg\:overflow-y-auto{overflow-y:auto}}@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}@keyframes qw-name-shimmer{0%,to{color:var(--accent)}50%{color:color-mix(in srgb, var(--accent) 55%, #e0e0e0)}}.animate-name-shimmer{animation:1.6s ease-in-out infinite qw-name-shimmer}@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}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{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,98183,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n={assign:function(){return i},searchParamsToUrlQuery:function(){return o},urlQueryToSearchParams:function(){return l}};for(var s in n)Object.defineProperty(r,s,{enumerable:!0,get:n[s]});function o(e){let t={};for(let[r,n]of e.entries()){let e=t[r];void 0===e?t[r]=n:Array.isArray(e)?e.push(n):t[r]=[e,n]}return t}function a(e){return"string"==typeof e?e:("number"!=typeof e||isNaN(e))&&"boolean"!=typeof e?"":String(e)}function l(e){let t=new URLSearchParams;for(let[r,n]of Object.entries(e))if(Array.isArray(n))for(let e of n)t.append(r,a(e));else t.set(r,a(n));return t}function i(e,...t){for(let r of t){for(let t of r.keys())e.delete(t);for(let[t,n]of r.entries())e.append(t,n)}return e}},18967,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n={DecodeError:function(){return g},MiddlewareNotFoundError:function(){return y},MissingStaticPage:function(){return v},NormalizeError:function(){return b},PageNotFoundError:function(){return j},SP:function(){return x},ST:function(){return m},WEB_VITALS:function(){return o},execOnce:function(){return a},getDisplayName:function(){return d},getLocationOrigin:function(){return c},getURL:function(){return u},isAbsoluteUrl:function(){return i},isResSent:function(){return f},loadGetInitialProps:function(){return p},normalizeRepeatedSlashes:function(){return h},stringifyError:function(){return w}};for(var s in n)Object.defineProperty(r,s,{enumerable:!0,get:n[s]});let o=["CLS","FCP","FID","INP","LCP","TTFB"];function a(e){let t,r=!1;return(...n)=>(r||(r=!0,t=e(...n)),t)}let l=/^[a-zA-Z][a-zA-Z\d+\-.]*?:/,i=e=>l.test(e);function c(){let{protocol:e,hostname:t,port:r}=window.location;return`${e}//${t}${r?":"+r:""}`}function u(){let{href:e}=window.location,t=c();return e.substring(t.length)}function d(e){return"string"==typeof e?e:e.displayName||e.name||"Unknown"}function f(e){return e.finished||e.headersSent}function h(e){let t=e.split("?");return t[0].replace(/\\/g,"/").replace(/\/\/+/g,"/")+(t[1]?`?${t.slice(1).join("?")}`:"")}async function p(e,t){let r=t.res||t.ctx&&t.ctx.res;if(!e.getInitialProps)return t.ctx&&t.Component?{pageProps:await p(t.Component,t.ctx)}:{};let n=await e.getInitialProps(t);if(r&&f(r))return n;if(!n)throw Object.defineProperty(Error(`"${d(e)}.getInitialProps()" should resolve to an object. But found "${n}" instead.`),"__NEXT_ERROR_CODE",{value:"E1025",enumerable:!1,configurable:!0});return n}let x="u">typeof performance,m=x&&["mark","measure","getEntriesByName"].every(e=>"function"==typeof performance[e]);class g extends Error{}class b extends Error{}class j extends Error{constructor(e){super(),this.code="ENOENT",this.name="PageNotFoundError",this.message=`Cannot find module for page: ${e}`}}class v extends Error{constructor(e,t){super(),this.message=`Failed to load static file for page: ${e} ${t}`}}class y extends Error{constructor(){super(),this.code="ENOENT",this.message="Cannot find the middleware module"}}function w(e){return JSON.stringify({message:e.message,stack:e.stack})}},33525,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"warnOnce",{enumerable:!0,get:function(){return n}});let n=e=>{}},18566,(e,t,r)=>{t.exports=e.r(76562)},67449,e=>{"use strict";let t=[{value:"soft-chime",label:"Soft Chime"},{value:"warm-bell",label:"Warm Bell"},{value:"click",label:"Click"},{value:"alert-tone",label:"Alert Tone"},{value:"pluck",label:"Pluck"}],r="quadwork_notification_sound",n="quadwork_notification_sound_choice",s="quadwork_notification_sound_background_only";function o(e){try{return window.localStorage.getItem(e)}catch{return null}}function a(e,t){try{window.localStorage.setItem(e,t)}catch{}}function l(){return"off"!==o(r)}function i(){let e=o(n);return e&&t.some(t=>t.value===e)?e:"soft-chime"}function c(){let e=o(s);return null===e||"off"!==e}e.s(["NOTIFICATION_SOUND_OPTIONS",0,t,"getNotificationBackgroundOnly",0,c,"getNotificationChoice",0,i,"getNotificationEnabled",0,l,"playNotificationSound",0,function(){if(!l()||c()&&document.hasFocus())return;let e=i();try{let t=new Audio(`/sounds/${e}.mp3`);t.volume=.6,t.play().catch(()=>{})}catch{}},"setNotificationBackgroundOnly",0,function(e){a(s,e?"on":"off")},"setNotificationChoice",0,function(e){a(n,e)},"setNotificationEnabled",0,function(e){a(r,e?"on":"off")}])},95057,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n={formatUrl:function(){return l},formatWithValidation:function(){return c},urlObjectKeys:function(){return i}};for(var s in n)Object.defineProperty(r,s,{enumerable:!0,get:n[s]});let o=e.r(90809)._(e.r(98183)),a=/https?|ftp|gopher|file/;function l(e){let{auth:t,hostname:r}=e,n=e.protocol||"",s=e.pathname||"",l=e.hash||"",i=e.query||"",c=!1;t=t?encodeURIComponent(t).replace(/%3A/i,":")+"@":"",e.host?c=t+e.host:r&&(c=t+(~r.indexOf(":")?`[${r}]`:r),e.port&&(c+=":"+e.port)),i&&"object"==typeof i&&(i=String(o.urlQueryToSearchParams(i)));let u=e.search||i&&`?${i}`||"";return n&&!n.endsWith(":")&&(n+=":"),e.slashes||(!n||a.test(n))&&!1!==c?(c="//"+(c||""),s&&"/"!==s[0]&&(s="/"+s)):c||(c=""),l&&"#"!==l[0]&&(l="#"+l),u&&"?"!==u[0]&&(u="?"+u),s=s.replace(/[?#]/g,encodeURIComponent),u=u.replace("#","%23"),`${n}${c}${s}${u}${l}`}let i=["auth","hash","host","hostname","href","path","pathname","port","protocol","query","search","slashes"];function c(e){return l(e)}},18581,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"useMergedRef",{enumerable:!0,get:function(){return s}});let n=e.r(71645);function s(e,t){let r=(0,n.useRef)(null),s=(0,n.useRef)(null);return(0,n.useCallback)(n=>{if(null===n){let e=r.current;e&&(r.current=null,e());let t=s.current;t&&(s.current=null,t())}else e&&(r.current=o(e,n)),t&&(s.current=o(t,n))},[e,t])}function o(e,t){if("function"!=typeof e)return e.current=t,()=>{e.current=null};{let r=e(t);return"function"==typeof r?r:()=>e(null)}}("function"==typeof r.default||"object"==typeof r.default&&null!==r.default)&&void 0===r.default.__esModule&&(Object.defineProperty(r.default,"__esModule",{value:!0}),Object.assign(r.default,r),t.exports=r.default)},73668,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"isLocalURL",{enumerable:!0,get:function(){return o}});let n=e.r(18967),s=e.r(52817);function o(e){if(!(0,n.isAbsoluteUrl)(e))return!0;try{let t=(0,n.getLocationOrigin)(),r=new URL(e,t);return r.origin===t&&(0,s.hasBasePath)(r.pathname)}catch(e){return!1}}},84508,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0}),Object.defineProperty(r,"errorOnce",{enumerable:!0,get:function(){return n}});let n=e=>{}},22016,(e,t,r)=>{"use strict";Object.defineProperty(r,"__esModule",{value:!0});var n={default:function(){return g},useLinkStatus:function(){return j}};for(var s in n)Object.defineProperty(r,s,{enumerable:!0,get:n[s]});let o=e.r(90809),a=e.r(43476),l=o._(e.r(71645)),i=e.r(95057),c=e.r(8372),u=e.r(18581),d=e.r(18967),f=e.r(5550);e.r(33525);let h=e.r(88540),p=e.r(91949),x=e.r(73668),m=e.r(9396);function g(t){var r,n;let s,o,g,[j,v]=(0,l.useOptimistic)(p.IDLE_LINK_STATUS),y=(0,l.useRef)(null),{href:w,as:k,children:N,prefetch:C=null,passHref:S,replace:_,shallow:E,scroll:P,onClick:L,onMouseEnter:O,onTouchStart:T,legacyBehavior:I=!1,onNavigate:M,transitionTypes:$,ref:R,unstable_dynamicOnHover:A,...B}=t;s=N,I&&("string"==typeof s||"number"==typeof s)&&(s=(0,a.jsx)("a",{children:s}));let U=l.default.useContext(c.AppRouterContext),F=!1!==C,W=!1!==C?null===(n=C)||"auto"===n?m.FetchStrategy.PPR:m.FetchStrategy.Full:m.FetchStrategy.PPR,z="string"==typeof(r=k||w)?r:(0,i.formatUrl)(r);if(I){if(s?.$$typeof===Symbol.for("react.lazy"))throw Object.defineProperty(Error("`<Link legacyBehavior>` received a direct child that is either a Server Component, or JSX that was loaded with React.lazy(). This is not supported. Either remove legacyBehavior, or make the direct child a Client Component that renders the Link's `<a>` tag."),"__NEXT_ERROR_CODE",{value:"E863",enumerable:!1,configurable:!0});o=l.default.Children.only(s)}let q=I?o&&"object"==typeof o&&o.ref:R,D=l.default.useCallback(e=>(null!==U&&(y.current=(0,p.mountLinkInstance)(e,z,U,W,F,v)),()=>{y.current&&((0,p.unmountLinkForCurrentNavigation)(y.current),y.current=null),(0,p.unmountPrefetchableInstance)(e)}),[F,z,U,W,v]),K={ref:(0,u.useMergedRef)(D,q),onClick(t){I||"function"!=typeof L||L(t),I&&o.props&&"function"==typeof o.props.onClick&&o.props.onClick(t),!U||t.defaultPrevented||function(t,r,n,s,o,a,i){if("u">typeof window){let c,{nodeName:u}=t.currentTarget;if("A"===u.toUpperCase()&&((c=t.currentTarget.getAttribute("target"))&&"_self"!==c||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||t.nativeEvent&&2===t.nativeEvent.which)||t.currentTarget.hasAttribute("download"))return;if(!(0,x.isLocalURL)(r)){s&&(t.preventDefault(),location.replace(r));return}if(t.preventDefault(),a){let e=!1;if(a({preventDefault:()=>{e=!0}}),e)return}let{dispatchNavigateAction:d}=e.r(99781);l.default.startTransition(()=>{d(r,s?"replace":"push",!1===o?h.ScrollBehavior.NoScroll:h.ScrollBehavior.Default,n.current,i)})}}(t,z,y,_,P,M,$)},onMouseEnter(e){I||"function"!=typeof O||O(e),I&&o.props&&"function"==typeof o.props.onMouseEnter&&o.props.onMouseEnter(e),U&&F&&(0,p.onNavigationIntent)(e.currentTarget,!0===A)},onTouchStart:function(e){I||"function"!=typeof T||T(e),I&&o.props&&"function"==typeof o.props.onTouchStart&&o.props.onTouchStart(e),U&&F&&(0,p.onNavigationIntent)(e.currentTarget,!0===A)}};return(0,d.isAbsoluteUrl)(z)?K.href=z:I&&!S&&("a"!==o.type||"href"in o.props)||(K.href=(0,f.addBasePath)(z)),g=I?l.default.cloneElement(o,K):(0,a.jsx)("a",{...B,...K,children:s}),(0,a.jsx)(b.Provider,{value:j,children:g})}e.r(84508);let b=(0,l.createContext)(p.IDLE_LINK_STATUS),j=()=>(0,l.useContext)(b);("function"==typeof r.default||"object"==typeof r.default&&null!==r.default)&&void 0===r.default.__esModule&&(Object.defineProperty(r.default,"__esModule",{value:!0}),Object.assign(r.default,r),t.exports=r.default)},22140,e=>{"use strict";var t=e.i(43476),r=e.i(22016),n=e.i(18566),s=e.i(71645);function o(){return(0,t.jsxs)("svg",{width:"20",height:"20",viewBox:"0 0 20 20",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:[(0,t.jsx)("path",{d:"M3 10L10 3l7 7"}),(0,t.jsx)("path",{d:"M5 8.5V16h3.5v-4h3v4H15V8.5"})]})}function a(){return(0,t.jsxs)("svg",{width:"18",height:"18",viewBox:"0 0 18 18",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:[(0,t.jsx)("circle",{cx:"9",cy:"9",r:"2.5"}),(0,t.jsx)("path",{d:"M7.5 1.5h3l.4 2.1a5.5 5.5 0 011.3.7l2-.8 1.5 2.6-1.6 1.3a5.5 5.5 0 010 1.5l1.6 1.3-1.5 2.6-2-.8a5.5 5.5 0 01-1.3.7l-.4 2.1h-3l-.4-2.1a5.5 5.5 0 01-1.3-.7l-2 .8-1.5-2.6 1.6-1.3a5.5 5.5 0 010-1.5L2.3 6.1l1.5-2.6 2 .8a5.5 5.5 0 011.3-.7z"})]})}function l(){return(0,t.jsx)("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",children:(0,t.jsx)("path",{d:"M8 3v10M3 8h10"})})}function i(){return(0,t.jsx)("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:(0,t.jsx)("path",{d:"M10 3L5 8l5 5"})})}function c(){return(0,t.jsx)("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",children:(0,t.jsx)("path",{d:"M6 3l5 5-5 5"})})}function u({collapsed:e}){return(0,t.jsx)("svg",{width:"12",height:"12",viewBox:"0 0 12 12",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round",className:`transition-transform duration-150 ${e?"-rotate-90":""}`,children:(0,t.jsx)("path",{d:"M3 4.5l3 3 3-3"})})}function d({size:e=10}){return(0,t.jsxs)("svg",{width:e,height:e,viewBox:"0 0 16 16",fill:"currentColor",stroke:"none",children:[(0,t.jsx)("path",{d:"M10.5 1.5L14.5 5.5L10 7.5L8.5 12.5L3.5 7.5L8.5 6L10.5 1.5Z"}),(0,t.jsx)("path",{d:"M3.5 7.5L1 15L8.5 12.5"})]})}function f({project:e,isActive:n,expanded:o,pinned:a,onContextMenu:l}){let[i,c]=(0,s.useState)(null),u=(0,s.useRef)(null);return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(r.default,{ref:u,href:`/project/${e.id}`,className:`flex items-center gap-2 ${o?"w-full px-2":""} rounded-sm transition-colors ${!o?"":n?"bg-[#1a1a1a]":"hover:bg-[#1a1a1a]"}`,onMouseEnter:()=>{if(o)return;let e=u.current?.getBoundingClientRect();e&&c({top:e.top+e.height/2})},onMouseLeave:()=>c(null),onContextMenu:t=>l(t,e.id),children:[(0,t.jsxs)("div",{className:"relative shrink-0",children:[(0,t.jsx)("div",{className:`w-10 h-10 flex items-center justify-center rounded-full text-[11px] font-semibold uppercase tracking-tight transition-colors ${n?"border-2 border-accent text-accent":"border border-border text-text-muted hover:text-text"}`,children:e.name.slice(0,2)||"?"}),a&&!o&&(0,t.jsx)("div",{className:"absolute -top-1 -right-1 text-accent",children:(0,t.jsx)(d,{size:8})})]}),o&&(0,t.jsxs)("span",{className:`text-xs truncate flex items-center gap-1 ${n?"text-accent":"text-text-muted"}`,children:[e.name,a&&(0,t.jsx)(d,{size:10})]})]}),!o&&i&&(0,t.jsxs)("div",{className:"fixed px-2 py-1 bg-bg-surface border border-border text-text text-xs whitespace-nowrap pointer-events-none z-50",style:{left:72,top:i.top,transform:"translateY(-50%)"},children:[a&&"📌 ",e.name]})]})}let h="qw-sidebar-expanded",p="qw-sidebar-collapsed-groups";e.s(["default",0,function(){let e=(0,n.usePathname)(),[d,x]=(0,s.useState)([]),[m,g]=(0,s.useState)([]),[b,j]=(0,s.useState)([]),[v,y]=(0,s.useState)(new Set),[w,k]=(0,s.useState)("online"),[N,C]=(0,s.useState)(!1),[S,_]=(0,s.useState)(null),E=(0,s.useRef)(null);(0,s.useEffect)(()=>{try{if(window.innerWidth>=768){let e=localStorage.getItem(h);"true"===e&&C(!0)}let e=localStorage.getItem(p);e&&y(new Set(JSON.parse(e)))}catch{}},[]),(0,s.useEffect)(()=>{let e=window.matchMedia("(max-width: 767px)"),t=e=>{e.matches&&C(!1)};return e.addEventListener("change",t),()=>e.removeEventListener("change",t)},[]),(0,s.useEffect)(()=>{fetch("/api/config").then(e=>{if(!e.ok)throw Error(`Config fetch failed: ${e.status}`);return e.json()}).then(e=>{E.current=e,x((e.projects||[]).filter(e=>!e.archived)),g(e.pinned_projects||[]),j(e.sidebar_groups||[])}).catch(()=>{})},[]),(0,s.useEffect)(()=>{let e,t=!1,r=async()=>{try{let e=await fetch("/api/health",{signal:AbortSignal.timeout(3e3)});if(t)return;e.ok?k(e=>"offline"===e?"recovering":"online"):k("offline")}catch{if(t)return;k("offline")}t||(e=setTimeout(r,5e3))};return r(),()=>{t=!0,clearTimeout(e)}},[]),(0,s.useEffect)(()=>{if("recovering"===w){let e=setTimeout(()=>k("online"),1500);return()=>clearTimeout(e)}},[w]);let P="/"===e,L="/settings"===e,O=e.startsWith("/project/")?e.split("/")[2]:null,T=(0,s.useCallback)(e=>{g(e),fetch("/api/config").then(e=>e.json()).then(t=>{let r={...t,pinned_projects:e};return E.current=r,fetch("/api/config",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)})}).catch(()=>{})},[]),I=(0,s.useCallback)(e=>{j(e),fetch("/api/config").then(e=>e.json()).then(t=>{let r={...t,sidebar_groups:e};return E.current=r,fetch("/api/config",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)})}).catch(()=>{})},[]),M=(e,t)=>{let r=b.map(t=>({...t,projects:t.projects.filter(t=>t!==e)}));if("__ungrouped__"===t)I(r.filter(e=>e.projects.length>0));else{let n=r.find(e=>e.name===t);n?n.projects.push(e):r.push({name:t,projects:[e]}),I(r.filter(e=>e.projects.length>0))}_(null)},$=(e,t)=>{e.preventDefault(),_({x:e.clientX,y:e.clientY,projectId:t})};(0,s.useEffect)(()=>{if(!S)return;let e=()=>_(null);return window.addEventListener("click",e),()=>window.removeEventListener("click",e)},[S]);let R=new Set(m),A=m.map(e=>d.find(t=>t.id===e)).filter(e=>!!e),B=new Set(b.flatMap(e=>e.projects)),U=d.filter(e=>!R.has(e.id)),F=U.filter(e=>!B.has(e.id));return(0,t.jsxs)("aside",{className:`shrink-0 h-full border-r border-border bg-bg-surface flex flex-col py-3 transition-[width] duration-200 ease-in-out overflow-hidden ${N?"w-52 items-stretch px-2":"w-16 items-center"}`,children:[(0,t.jsx)("button",{onClick:()=>{C(e=>{let t=!e;try{localStorage.setItem(h,String(t))}catch{}return t})},className:`hidden md:flex shrink-0 items-center justify-center w-8 h-8 rounded-sm text-text-muted hover:text-text hover:bg-[#1a1a1a] transition-colors ${N?"self-end mr-0":"self-center"}`,title:N?"Collapse sidebar":"Expand sidebar",children:N?(0,t.jsx)(i,{}):(0,t.jsx)(c,{})}),(0,t.jsx)("div",{className:"h-1"}),(0,t.jsxs)(r.default,{href:"/",className:`flex items-center gap-2 rounded-sm transition-colors ${N?"px-2 py-2":"w-10 h-10 justify-center self-center"} ${P?"text-accent":"text-text-muted hover:text-text hover:bg-[#1a1a1a]"}`,title:"Home",children:[(0,t.jsx)(o,{}),N&&(0,t.jsx)("span",{className:"text-xs",children:"Home"})]}),(0,t.jsx)("div",{className:`h-px bg-border my-2 ${N?"":"w-6 self-center"}`}),(0,t.jsxs)("div",{className:`flex-1 flex flex-col gap-2 overflow-y-auto min-h-0 ${N?"":"items-center"}`,children:[A.length>0&&(0,t.jsxs)(t.Fragment,{children:[N&&(0,t.jsx)("span",{className:"text-[10px] uppercase tracking-widest text-text-muted px-2",children:"Pinned"}),A.map(e=>(0,t.jsx)(f,{project:e,isActive:O===e.id,expanded:N,pinned:!0,onContextMenu:$},e.id)),(0,t.jsx)("div",{className:`h-px bg-border ${N?"":"w-6"}`})]}),b.map(e=>{let r=e.projects.map(e=>U.find(t=>t.id===e)).filter(e=>!!e);if(0===r.length)return null;let n=v.has(e.name);return(0,t.jsxs)("div",{className:"flex flex-col gap-1",children:[(0,t.jsx)("button",{onClick:()=>{var t;return t=e.name,void y(e=>{let r=new Set(e);r.has(t)?r.delete(t):r.add(t);try{localStorage.setItem(p,JSON.stringify([...r]))}catch{}return r})},className:`flex items-center gap-1 text-text-muted hover:text-text transition-colors ${N?"px-2 py-0.5":"justify-center w-full"}`,title:`${n?"Expand":"Collapse"} ${e.name}`,children:N?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(u,{collapsed:n}),(0,t.jsx)("span",{className:"text-[10px] uppercase tracking-widest truncate",children:e.name})]}):(0,t.jsx)("div",{className:`w-6 h-px ${n?"bg-text-muted":"bg-border"}`})}),!n&&r.map(e=>(0,t.jsx)(f,{project:e,isActive:O===e.id,expanded:N,pinned:!1,onContextMenu:$},e.id))]},e.name)}),F.length>0&&b.length>0&&(0,t.jsxs)(t.Fragment,{children:[N&&(0,t.jsx)("span",{className:"text-[10px] uppercase tracking-widest text-text-muted px-2",children:"Ungrouped"}),b.length>0&&!N&&(0,t.jsx)("div",{className:"w-6 h-px bg-border"})]}),F.map(e=>(0,t.jsx)(f,{project:e,isActive:O===e.id,expanded:N,pinned:!1,onContextMenu:$},e.id)),(0,t.jsxs)(r.default,{href:"/setup",className:`flex items-center gap-2 rounded-full transition-colors ${N?"px-2 py-2 border border-dashed border-border text-text-muted hover:text-text hover:bg-[#1a1a1a] rounded-sm":"w-10 h-10 justify-center border border-dashed border-border text-text-muted hover:text-text hover:bg-[#1a1a1a]"}`,title:"Add project",children:[(0,t.jsx)(l,{}),N&&(0,t.jsx)("span",{className:"text-xs text-text-muted",children:"New Project"})]})]}),S&&(0,t.jsxs)("div",{className:"fixed bg-bg-surface border border-border py-1 z-50 text-xs",style:{left:S.x,top:S.y},onClick:e=>e.stopPropagation(),children:[R.has(S.projectId)?(0,t.jsx)("button",{className:"w-full px-3 py-1.5 text-left text-text hover:bg-[#1a1a1a] transition-colors",onClick:()=>{var e;return e=S.projectId,void(T(m.filter(t=>t!==e)),_(null))},children:"Unpin"}):(0,t.jsx)("button",{className:"w-full px-3 py-1.5 text-left text-text hover:bg-[#1a1a1a] transition-colors",onClick:()=>{var e;return e=S.projectId,void(!m.includes(e)&&(T([e,...m]),_(null)))},children:"Pin to top"}),(0,t.jsx)("div",{className:"h-px bg-border my-1"}),S.showGroupMenu?(0,t.jsxs)("div",{className:"flex flex-col",children:[b.map(e=>(0,t.jsx)("button",{className:"w-full px-3 py-1.5 text-left text-text hover:bg-[#1a1a1a] transition-colors",onClick:()=>M(S.projectId,e.name),children:e.name},e.name)),(0,t.jsx)("button",{className:"w-full px-3 py-1.5 text-left text-text hover:bg-[#1a1a1a] transition-colors",onClick:()=>{let e=prompt("New group name:");if(!e?.trim())return;let t=e.trim();b.some(e=>e.name.toLowerCase()===t.toLowerCase())?alert(`Group "${t}" already exists.`):M(S.projectId,t)},children:"+ New group"}),B.has(S.projectId)&&(0,t.jsx)("button",{className:"w-full px-3 py-1.5 text-left text-text-muted hover:bg-[#1a1a1a] transition-colors",onClick:()=>M(S.projectId,"__ungrouped__"),children:"Remove from group"})]}):(0,t.jsx)("button",{className:"w-full px-3 py-1.5 text-left text-text hover:bg-[#1a1a1a] transition-colors",onClick:()=>_({...S,showGroupMenu:!0}),children:"Move to group..."})]}),(0,t.jsx)("div",{className:`h-px bg-border my-2 ${N?"":"w-6 self-center"}`}),"online"!==w&&(0,t.jsxs)("div",{className:`mb-2 relative group ${N?"px-2":"self-center"}`,children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("div",{className:`w-3 h-3 shrink-0 rounded-full ${"offline"===w?"bg-red-500 animate-pulse":"bg-green-500"}`}),N&&(0,t.jsx)("span",{className:"text-xs text-text-muted",children:"offline"===w?"Backend offline":"Reconnected"})]}),!N&&(0,t.jsx)("div",{className:"fixed left-16 ml-2 px-2 py-1 bg-bg-surface border border-border text-xs whitespace-nowrap z-50 hidden group-hover:block",style:{transform:"translateY(-50%)",top:"auto"},children:"offline"===w?"Backend offline — run quadwork start":"Backend reconnected"})]}),(0,t.jsxs)(r.default,{href:"/settings",className:`flex items-center gap-2 rounded-sm transition-colors ${N?"px-2 py-2":"w-10 h-10 justify-center self-center"} ${L?"text-accent":"text-text-muted hover:text-text hover:bg-[#1a1a1a]"}`,title:"Settings",children:[(0,t.jsx)(a,{}),N&&(0,t.jsx)("span",{className:"text-xs",children:"Settings"})]})]})}])},26704,e=>{"use strict";var t=e.i(43476),r=e.i(22016),n=e.i(71645);function s({open:e,onClose:r}){return((0,n.useEffect)(()=>{if(!e)return;let t=e=>{"Escape"===e.key&&r()};return window.addEventListener("keydown",t),()=>window.removeEventListener("keydown",t)},[e,r]),e)?(0,t.jsx)("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm",onClick:r,role:"dialog","aria-modal":"true","aria-labelledby":"about-title",children:(0,t.jsxs)("div",{className:"relative mx-4 max-w-lg w-full 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:r,"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:"about-title",className:"text-lg font-semibold text-white",children:"What is QuadWork?"}),(0,t.jsx)("p",{className:"mt-3 text-sm leading-relaxed text-neutral-300",children:"QuadWork is a local dashboard that runs a team of 4 AI agents — Head, Dev, and two Reviewers — that code, review, and ship while you sleep."}),(0,t.jsx)("p",{className:"mt-3 text-sm leading-relaxed text-neutral-300",children:"Every task follows a strict GitHub workflow: Issue → Branch → Pull Request → 2 Reviews → Merge. Branch protection ensures no agent can skip the process."}),(0,t.jsx)("h3",{className:"mt-5 text-sm font-semibold text-white",children:"Why QuadWork?"}),(0,t.jsxs)("ul",{className:"mt-2 space-y-1.5 text-sm text-neutral-300",children:[(0,t.jsxs)("li",{children:["🤖 ",(0,t.jsx)("b",{children:"Run 24/7"})," — agents work overnight while you rest"]}),(0,t.jsxs)("li",{children:["🛡️ ",(0,t.jsx)("b",{children:"Always reviewed"})," — every PR needs 2 independent approvals"]}),(0,t.jsxs)("li",{children:["🔒 ",(0,t.jsx)("b",{children:"Local-first"})," — runs entirely on your machine, no data leaves"]}),(0,t.jsxs)("li",{children:["🧰 ",(0,t.jsx)("b",{children:"Bring your own CLI"})," — works with Claude Code, Codex, or both"]}),(0,t.jsxs)("li",{children:["📦 ",(0,t.jsx)("b",{children:"One install"})," — ",(0,t.jsx)("code",{className:"rounded bg-white/5 px-1 py-0.5 text-[12px]",children:"npx quadwork init"})," and you're set"]})]}),(0,t.jsx)("div",{className:"mt-5",children:(0,t.jsx)("a",{href:"https://github.com/realproject7/quadwork",target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1 text-sm text-blue-400 hover:text-blue-300",children:"Read the full docs on GitHub →"})})]})}):null}let o=["sleep.","eat.","enjoy life.","touch grass.","spend time with people.","watch a movie.","take a vacation.","go for a run."],a="quadwork_tagline_animation";function l(e){return!Number.isFinite(e)||e<=0?"0h":e<1?`${(60*e).toFixed(0)}m`:`${e.toFixed(1)}h`}e.s(["default",0,function(){let[e,i]=(0,n.useState)(!1);(0,n.useEffect)(()=>{i(!0)},[]);let[c,u]=(0,n.useState)(!1),[d,f]=(0,n.useState)(null),[h,p]=(0,n.useState)(!1);(0,n.useEffect)(()=>{let e=!1,t=()=>{fetch("/api/activity/stats").then(e=>e.ok?e.json():null).then(t=>{!e&&t&&f(t)}).catch(()=>{})};t();let r=setInterval(t,6e4);return()=>{e=!0,clearInterval(r)}},[]);let[x,m]=(0,n.useState)(!0);(0,n.useEffect)(()=>{try{let e=window.localStorage.getItem(a);"off"===e&&m(!1)}catch{}},[]);let g=function(e,t){let[r,s]=(0,n.useState)(0),[o,a]=(0,n.useState)(""),[l,i]=(0,n.useState)("typing");return(0,n.useEffect)(()=>{let n;if(!t)return;let c=e[r];return"typing"===l?n=o.length<c.length?setTimeout(()=>a(c.slice(0,o.length+1)),70):setTimeout(()=>i("holding"),0):"holding"===l?n=setTimeout(()=>i("deleting"),2e3):o.length>0?n=setTimeout(()=>a(c.slice(0,o.length-1)),35):(s(t=>(t+1)%e.length),i("typing")),()=>clearTimeout(n)},[o,l,r,e,t]),o}(o,x),b=x?g:o[0]||"",[j,v]=(0,n.useState)(!1);(0,n.useEffect)(()=>{if(!x)return void v(!0);let e=setTimeout(()=>v(!0),5e3);return()=>clearTimeout(e)},[x]);let y=(0,n.useRef)(!1);return((0,n.useEffect)(()=>{x&&(g.length>0?y.current=!0:y.current&&v(!0))},[x,g]),e)?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("header",{className:"sticky top-0 z-40 flex h-12 items-center justify-between border-b border-white/10 bg-neutral-950/90 px-4 backdrop-blur",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3 min-w-0",children:[(0,t.jsxs)(r.default,{href:"/",className:"flex items-center gap-1.5 text-sm font-bold text-accent hover:text-blue-400 shrink-0",children:[(0,t.jsx)("img",{src:"/icon.svg",alt:"",width:18,height:18,className:"inline-block"}),"QuadWork"]}),(0,t.jsx)("span",{className:"hidden sm:inline text-neutral-600",children:"|"}),(0,t.jsxs)("span",{className:"hidden sm:inline text-[13px] text-neutral-400 truncate",children:["Your AI dev team while you"," ",(0,t.jsx)("span",{className:"text-neutral-200",children:b}),x&&(0,t.jsx)("span",{className:"ml-0.5 inline-block w-[1px] h-[12px] align-middle bg-neutral-400 animate-qw-blink"})]}),j&&(0,t.jsx)("button",{type:"button",onClick:()=>{m(e=>{let t=!e;try{window.localStorage.setItem(a,t?"on":"off")}catch{}return t})},"aria-label":x?"Pause tagline animation":"Resume tagline animation","aria-pressed":x,title:x?"Pause tagline animation":"Resume tagline animation",className:"hidden sm:inline-flex items-center justify-center w-3.5 h-3.5 ml-1 rounded-full border border-white/15 text-neutral-500 hover:text-white hover:border-white/40 transition-colors text-[8px]",children:x?"❚❚":"▶"})]}),(0,t.jsxs)("div",{className:"flex items-center gap-3 shrink-0",children:[d&&(0,t.jsxs)("div",{className:"relative hidden md:flex items-center gap-2 text-[10px] text-neutral-500",onMouseEnter:()=>p(!0),onMouseLeave:()=>p(!1),onFocus:()=>p(!0),onBlur:()=>p(!1),tabIndex:0,children:[(0,t.jsx)("span",{className:"text-neutral-200",children:"Your AI team worked:"}),(0,t.jsxs)("span",{children:["Today ",(0,t.jsx)("span",{className:"text-neutral-200",children:l(d.today)})]}),(0,t.jsx)("span",{className:"text-neutral-700",children:"·"}),(0,t.jsxs)("span",{children:["Week ",(0,t.jsx)("span",{className:"text-neutral-200",children:l(d.week)})]}),(0,t.jsx)("span",{className:"text-neutral-700",children:"·"}),(0,t.jsxs)("span",{children:["Month ",(0,t.jsx)("span",{className:"text-neutral-200",children:l(d.month)})]}),h&&(0,t.jsxs)("div",{className:"absolute top-6 right-0 z-50 min-w-[220px] p-2 text-[10px] leading-snug text-neutral-200 bg-neutral-900 border border-white/15 rounded shadow-lg",children:[(0,t.jsx)("div",{className:"mb-1 text-neutral-400 uppercase tracking-wider text-[9px]",children:"Per project"}),0===Object.entries(d.by_project).length&&(0,t.jsx)("div",{className:"text-neutral-500",children:"No activity logged yet"}),Object.entries(d.by_project).map(([e,r])=>(0,t.jsxs)("div",{className:"flex items-baseline gap-2",children:[(0,t.jsx)("span",{className:"text-neutral-400 truncate flex-1",children:e}),(0,t.jsx)("span",{className:"tabular-nums text-neutral-200",children:l(r.month)}),(0,t.jsx)("span",{className:"text-neutral-600 text-[9px]",children:"/ mo"})]},e)),(0,t.jsxs)("div",{className:"mt-1 pt-1 border-t border-white/10 text-neutral-500",children:["Lifetime: ",(0,t.jsx)("span",{className:"text-neutral-200",children:l(d.total)})]}),(0,t.jsx)("div",{className:"mt-1 pt-1 border-t border-white/10 text-neutral-500 leading-snug",children:"ⓘ Stats are best-effort. Server restarts may undercount in-flight sessions."})]})]}),(0,t.jsx)("button",{type:"button",onClick:()=>u(!0),"aria-label":"About QuadWork",className:"rounded p-1 text-neutral-400 hover:bg-white/5 hover:text-white",children:(0,t.jsxs)("svg",{width:"18",height:"18",viewBox:"0 0 20 20",fill:"none",stroke:"currentColor",strokeWidth:"1.5",children:[(0,t.jsx)("circle",{cx:"10",cy:"10",r:"8"}),(0,t.jsx)("path",{d:"M10 9v5",strokeLinecap:"round"}),(0,t.jsx)("circle",{cx:"10",cy:"6.5",r:"0.8",fill:"currentColor"})]})}),(0,t.jsx)("a",{href:"https://github.com/realproject7/quadwork",target:"_blank",rel:"noopener noreferrer",className:"text-[12px] text-neutral-400 hover:text-white",children:"QuadWork github"})]})]}),(0,t.jsx)(s,{open:c,onClose:()=>u(!1)})]}):(0,t.jsx)("header",{className:"sticky top-0 z-40 flex h-12 items-center justify-between border-b border-white/10 bg-neutral-950/90 px-4 backdrop-blur","aria-hidden":"true"})}],26704)},43688,e=>{"use strict";var t=e.i(71645),r=e.i(67449);e.s(["default",0,function(){let e=(0,t.useRef)({}),n=(0,t.useRef)("user"),s=(0,t.useRef)([]),o=(0,t.useRef)(!1),a=(0,t.useRef)(new Set),l=(0,t.useCallback)(t=>{null==e.current[t]&&(e.current[t]=0,a.current.add(t))},[]),i=(0,t.useCallback)(()=>{fetch("/api/config").then(e=>e.ok?e.json():null).then(t=>{if(!t)return;"string"==typeof t.operator_name&&t.operator_name&&(n.current=t.operator_name);let r=t.projects||[];for(let e of(s.current=r,r))l(e.id);let a=new Set(r.map(e=>e.id));for(let t of Object.keys(e.current))a.has(t)||delete e.current[t];o.current=!0}).catch(()=>{})},[l]);(0,t.useEffect)(()=>{i();let e=setInterval(i,3e4);return()=>clearInterval(e)},[i]);let c=(0,t.useCallback)(()=>{if(o.current)for(let t of s.current){let s=e.current[t.id];null!=s&&fetch(`/api/chat?path=/api/messages&channel=general&cursor=${s}&project=${encodeURIComponent(t.id)}`).then(e=>e.ok?e.json():null).then(s=>{if(!s)return;let o=Array.isArray(s)?s:s.messages||[];if(0===o.length)return void a.current.delete(t.id);let l=e.current[t.id]??0,i=Math.max(...o.map(e=>e.id));i>l&&(e.current[t.id]=i);let c=n.current;a.current.has(t.id)?a.current.delete(t.id):o.some(e=>e.id>l&&(void 0===e.type||"chat"===e.type)&&"user"!==e.sender&&e.sender!==c&&"system"!==e.sender)&&(0,r.playNotificationSound)()}).catch(()=>{})}},[]);return(0,t.useEffect)(()=>{let e=setInterval(c,3e3);return()=>clearInterval(e)},[c]),null}])}]);