quadwork 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/quadwork.js +53 -7
- package/out/404.html +1 -1
- package/out/__next.__PAGE__.txt +3 -3
- package/out/__next._full.txt +12 -12
- package/out/__next._head.txt +4 -4
- package/out/__next._index.txt +6 -6
- package/out/__next._tree.txt +2 -2
- package/out/_next/static/chunks/{0-y13tz~pmpno.js → 0-7v31f-nsgw-.js} +1 -1
- package/out/_next/static/chunks/{0za4cvk8.n0-y.js → 04_t39bv8y9pe.js} +1 -1
- package/out/_next/static/chunks/0ccoe1hsu70ql.css +2 -0
- package/out/_next/static/chunks/{0.9m84as-sc_r.js → 0m83k84.midd1.js} +11 -10
- package/out/_not-found/__next._full.txt +11 -11
- package/out/_not-found/__next._head.txt +4 -4
- package/out/_not-found/__next._index.txt +6 -6
- package/out/_not-found/__next._not-found.__PAGE__.txt +2 -2
- package/out/_not-found/__next._not-found.txt +3 -3
- package/out/_not-found/__next._tree.txt +2 -2
- package/out/_not-found.html +1 -1
- package/out/_not-found.txt +11 -11
- package/out/app-shell/__next._full.txt +11 -11
- package/out/app-shell/__next._head.txt +4 -4
- package/out/app-shell/__next._index.txt +6 -6
- package/out/app-shell/__next._tree.txt +2 -2
- package/out/app-shell/__next.app-shell.__PAGE__.txt +2 -2
- package/out/app-shell/__next.app-shell.txt +3 -3
- package/out/app-shell.html +1 -1
- package/out/app-shell.txt +11 -11
- package/out/index.html +1 -1
- package/out/index.txt +12 -12
- package/out/project/_/__next._full.txt +12 -12
- package/out/project/_/__next._head.txt +4 -4
- package/out/project/_/__next._index.txt +6 -6
- package/out/project/_/__next._tree.txt +2 -2
- package/out/project/_/__next.project.$d$id.__PAGE__.txt +3 -3
- package/out/project/_/__next.project.$d$id.txt +3 -3
- package/out/project/_/__next.project.txt +3 -3
- package/out/project/_/memory/__next._full.txt +12 -12
- package/out/project/_/memory/__next._head.txt +4 -4
- package/out/project/_/memory/__next._index.txt +6 -6
- package/out/project/_/memory/__next._tree.txt +2 -2
- package/out/project/_/memory/__next.project.$d$id.memory.__PAGE__.txt +3 -3
- package/out/project/_/memory/__next.project.$d$id.memory.txt +3 -3
- package/out/project/_/memory/__next.project.$d$id.txt +3 -3
- package/out/project/_/memory/__next.project.txt +3 -3
- package/out/project/_/memory.html +1 -1
- package/out/project/_/memory.txt +12 -12
- package/out/project/_/queue/__next._full.txt +12 -12
- package/out/project/_/queue/__next._head.txt +4 -4
- package/out/project/_/queue/__next._index.txt +6 -6
- package/out/project/_/queue/__next._tree.txt +2 -2
- package/out/project/_/queue/__next.project.$d$id.queue.__PAGE__.txt +3 -3
- package/out/project/_/queue/__next.project.$d$id.queue.txt +3 -3
- package/out/project/_/queue/__next.project.$d$id.txt +3 -3
- package/out/project/_/queue/__next.project.txt +3 -3
- package/out/project/_/queue.html +1 -1
- package/out/project/_/queue.txt +12 -12
- package/out/project/_.html +1 -1
- package/out/project/_.txt +12 -12
- package/out/settings/__next._full.txt +12 -12
- package/out/settings/__next._head.txt +4 -4
- package/out/settings/__next._index.txt +6 -6
- package/out/settings/__next._tree.txt +2 -2
- package/out/settings/__next.settings.__PAGE__.txt +3 -3
- package/out/settings/__next.settings.txt +3 -3
- package/out/settings.html +1 -1
- package/out/settings.txt +12 -12
- package/out/setup/__next._full.txt +12 -12
- package/out/setup/__next._head.txt +4 -4
- package/out/setup/__next._index.txt +6 -6
- package/out/setup/__next._tree.txt +2 -2
- package/out/setup/__next.setup.__PAGE__.txt +3 -3
- package/out/setup/__next.setup.txt +3 -3
- package/out/setup.html +1 -1
- package/out/setup.txt +12 -12
- package/package.json +1 -1
- package/server/index.js +5 -4
- package/server/routes.chatWsSend.test.js +161 -0
- package/server/routes.js +165 -34
- package/server/routes.telegramBridge.test.js +75 -2
- package/templates/seeds/head.AGENTS.md +1 -1
- package/out/_next/static/chunks/05.po0c1knrbu.css +0 -2
- /package/out/_next/static/{OzDK1Fplm2eUu23bzILlU → yMYfZ4LAn8Fy22suFUnOy}/_buildManifest.js +0 -0
- /package/out/_next/static/{OzDK1Fplm2eUu23bzILlU → yMYfZ4LAn8Fy22suFUnOy}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{OzDK1Fplm2eUu23bzILlU → yMYfZ4LAn8Fy22suFUnOy}/_ssgManifest.js +0 -0
package/server/routes.js
CHANGED
|
@@ -148,11 +148,41 @@ router.get("/api/chat", async (req, res) => {
|
|
|
148
148
|
const { WebSocket: NodeWebSocket } = require("ws");
|
|
149
149
|
const { syncChattrToken } = require("./config");
|
|
150
150
|
|
|
151
|
+
// #236: wait for AgentChattr to echo our message back over the same ws
|
|
152
|
+
// connection before resolving, instead of fire-and-forgetting. AC's
|
|
153
|
+
// /ws handler does this on every connect:
|
|
154
|
+
// 1. Replays history as N `{type:"message", data: msg}` frames.
|
|
155
|
+
// 2. Sends one `{type:"status", data: …}` frame (broadcast_status).
|
|
156
|
+
// 3. Enters the receive loop and accepts our outgoing frame.
|
|
157
|
+
// After our `type:"message"` is processed, AC calls `store.add()`
|
|
158
|
+
// which broadcasts the stored record back to all clients (including
|
|
159
|
+
// us) as another `{type:"message", data: msg}`.
|
|
160
|
+
//
|
|
161
|
+
// To get a race-free ack we therefore:
|
|
162
|
+
// A. Wait for the first `type:"status"` frame to confirm the
|
|
163
|
+
// history replay is done — any `type:"message"` frame seen
|
|
164
|
+
// BEFORE that is historical and must be ignored.
|
|
165
|
+
// B. Only then send our message and record the highest message
|
|
166
|
+
// id observed so far as a correlation baseline.
|
|
167
|
+
// C. Accept the first post-send `type:"message"` whose payload
|
|
168
|
+
// matches (sender, text, channel, reply_to) AND whose id is
|
|
169
|
+
// strictly greater than the baseline (AC ids are monotonically
|
|
170
|
+
// increasing from store.add). This eliminates the risk a
|
|
171
|
+
// reviewer flagged on #382 round 1: a historical identical
|
|
172
|
+
// message from <1.5s ago could have satisfied the old
|
|
173
|
+
// heuristic matcher.
|
|
174
|
+
// On timeout / early close / 4003, we surface a proper error so the
|
|
175
|
+
// /api/chat handler can return a 5xx (or 401) instead of a silent
|
|
176
|
+
// {ok:true}.
|
|
151
177
|
function sendViaWebSocket(baseUrl, sessionToken, message) {
|
|
152
178
|
return new Promise((resolve, reject) => {
|
|
153
179
|
const wsUrl = `${baseUrl.replace(/^http/, "ws")}/ws?token=${encodeURIComponent(sessionToken || "")}`;
|
|
154
180
|
const ws = new NodeWebSocket(wsUrl);
|
|
155
181
|
let settled = false;
|
|
182
|
+
let historyFlushed = false;
|
|
183
|
+
let sent = false;
|
|
184
|
+
let maxIdAtSend = -Infinity;
|
|
185
|
+
let maxHistoryId = -Infinity;
|
|
156
186
|
const finish = (err, value) => {
|
|
157
187
|
if (settled) return;
|
|
158
188
|
settled = true;
|
|
@@ -160,14 +190,56 @@ function sendViaWebSocket(baseUrl, sessionToken, message) {
|
|
|
160
190
|
if (err) reject(err); else resolve(value);
|
|
161
191
|
};
|
|
162
192
|
const giveUp = setTimeout(() => finish(new Error("websocket send timeout")), 4000);
|
|
163
|
-
|
|
193
|
+
const doSend = () => {
|
|
194
|
+
if (sent || settled) return;
|
|
164
195
|
try {
|
|
196
|
+
maxIdAtSend = maxHistoryId;
|
|
165
197
|
ws.send(JSON.stringify({ type: "message", ...message }));
|
|
166
|
-
|
|
167
|
-
// contract only needs to know the message was accepted. Wait
|
|
168
|
-
// ~250ms for the server to enqueue + close cleanly.
|
|
169
|
-
setTimeout(() => { clearTimeout(giveUp); finish(null, { ok: true }); }, 250);
|
|
198
|
+
sent = true;
|
|
170
199
|
} catch (err) { clearTimeout(giveUp); finish(err); }
|
|
200
|
+
};
|
|
201
|
+
ws.on("open", () => {
|
|
202
|
+
// Do NOT send yet. Wait for the status frame that marks the
|
|
203
|
+
// end of history replay so we have a clean correlation
|
|
204
|
+
// baseline. A safety timer covers the (unlikely) case of an
|
|
205
|
+
// AC build that doesn't emit status on connect — after 750ms
|
|
206
|
+
// we fall back to sending anyway, using whatever max id we
|
|
207
|
+
// collected from history so far as the baseline.
|
|
208
|
+
setTimeout(() => {
|
|
209
|
+
if (!historyFlushed) {
|
|
210
|
+
historyFlushed = true;
|
|
211
|
+
doSend();
|
|
212
|
+
}
|
|
213
|
+
}, 750);
|
|
214
|
+
});
|
|
215
|
+
ws.on("message", (raw) => {
|
|
216
|
+
if (settled) return;
|
|
217
|
+
let frame;
|
|
218
|
+
try { frame = JSON.parse(raw.toString()); } catch { return; }
|
|
219
|
+
if (!frame || !frame.type) return;
|
|
220
|
+
if (frame.type === "status" && !historyFlushed) {
|
|
221
|
+
historyFlushed = true;
|
|
222
|
+
doSend();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (frame.type !== "message" || !frame.data) return;
|
|
226
|
+
const d = frame.data;
|
|
227
|
+
// Track the highest message id we have observed, whether from
|
|
228
|
+
// history replay or from other live broadcasts. Used as the
|
|
229
|
+
// baseline for the post-send correlation check.
|
|
230
|
+
if (typeof d.id === "number" && d.id > maxHistoryId) {
|
|
231
|
+
maxHistoryId = d.id;
|
|
232
|
+
}
|
|
233
|
+
if (!sent) return; // anything before our send is history
|
|
234
|
+
if (typeof d.id !== "number" || d.id <= maxIdAtSend) return;
|
|
235
|
+
if (d.sender !== message.sender) return;
|
|
236
|
+
if (d.text !== message.text) return;
|
|
237
|
+
if ((d.channel || "general") !== (message.channel || "general")) return;
|
|
238
|
+
const wantReply = message.reply_to ?? null;
|
|
239
|
+
const gotReply = d.reply_to ?? null;
|
|
240
|
+
if (wantReply !== gotReply) return;
|
|
241
|
+
clearTimeout(giveUp);
|
|
242
|
+
finish(null, { ok: true, message: d });
|
|
171
243
|
});
|
|
172
244
|
ws.on("error", (err) => { clearTimeout(giveUp); finish(err); });
|
|
173
245
|
ws.on("close", (code, reason) => {
|
|
@@ -179,6 +251,15 @@ function sendViaWebSocket(baseUrl, sessionToken, message) {
|
|
|
179
251
|
const e = new Error(msg);
|
|
180
252
|
e.code = "EAGENTCHATTR_401";
|
|
181
253
|
finish(e);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// Any other premature close after we sent but before we saw
|
|
257
|
+
// the echo is an error — the old code path would have claimed
|
|
258
|
+
// success, silently swallowing a server-side reject.
|
|
259
|
+
if (!settled) {
|
|
260
|
+
clearTimeout(giveUp);
|
|
261
|
+
const r = (reason && reason.toString()) || "";
|
|
262
|
+
finish(new Error(`websocket closed before ack (code=${code}${r ? ", reason=" + r : ""})`));
|
|
182
263
|
}
|
|
183
264
|
});
|
|
184
265
|
});
|
|
@@ -861,8 +942,12 @@ router.post("/api/chat", async (req, res) => {
|
|
|
861
942
|
const attemptSend = () => sendViaWebSocket(base, sessionToken, message);
|
|
862
943
|
|
|
863
944
|
try {
|
|
864
|
-
|
|
865
|
-
|
|
945
|
+
// #236: sendViaWebSocket now waits for AC's broadcast echo and
|
|
946
|
+
// returns `{ok, message}` where `message` is the stored record
|
|
947
|
+
// (with server-assigned id/timestamp). Pass it through so
|
|
948
|
+
// callers regain parity with the old /api/send response body.
|
|
949
|
+
const result = await attemptSend();
|
|
950
|
+
return res.json({ ok: true, message: result.message });
|
|
866
951
|
} catch (err) {
|
|
867
952
|
// If the cached session_token is stale (AgentChattr regenerates
|
|
868
953
|
// one on every restart) the ws closes with code 4003 — re-sync
|
|
@@ -876,8 +961,8 @@ router.post("/api/chat", async (req, res) => {
|
|
|
876
961
|
const { token: refreshed } = getChattrConfig(projectId);
|
|
877
962
|
if (refreshed && refreshed !== sessionToken) {
|
|
878
963
|
try {
|
|
879
|
-
await sendViaWebSocket(base, refreshed, message);
|
|
880
|
-
return res.json({ ok: true, resynced: true });
|
|
964
|
+
const retry = await sendViaWebSocket(base, refreshed, message);
|
|
965
|
+
return res.json({ ok: true, resynced: true, message: retry.message });
|
|
881
966
|
} catch (retryErr) {
|
|
882
967
|
console.warn(`[chat] retry after token resync failed: ${retryErr.message}`);
|
|
883
968
|
return res.status(401).json({ error: "AgentChattr auth failed (token resync did not help)", detail: retryErr.message });
|
|
@@ -2339,7 +2424,12 @@ function readLastLines(filePath, n) {
|
|
|
2339
2424
|
// otherwise. Keep the import list small and close to what the
|
|
2340
2425
|
// bridge actually needs; add modules here if the bridge gains new
|
|
2341
2426
|
// hard deps.
|
|
2342
|
-
|
|
2427
|
+
// #380: `pythonPath` defaults to bare `python3` for backward-compat,
|
|
2428
|
+
// but the production call sites (install, start) MUST pass the
|
|
2429
|
+
// dedicated bridge venv's interpreter (`<BRIDGE_DIR>/.venv/bin/python3`)
|
|
2430
|
+
// so the import check runs against the same interpreter the spawn will
|
|
2431
|
+
// use. See #379 research ticket for root cause.
|
|
2432
|
+
function checkTelegramBridgePythonDeps(pythonPath = "python3") {
|
|
2343
2433
|
try {
|
|
2344
2434
|
// Only check the third-party module the bridge actually needs
|
|
2345
2435
|
// at import time — `requests`. Toml parsing differs between
|
|
@@ -2347,7 +2437,7 @@ function checkTelegramBridgePythonDeps() {
|
|
|
2347
2437
|
// genuine toml import failure will now be captured in the
|
|
2348
2438
|
// bridge log file on spawn, so this pre-flight stays narrow
|
|
2349
2439
|
// and avoids false negatives on older Python installs.
|
|
2350
|
-
execFileSync(
|
|
2440
|
+
execFileSync(pythonPath, ["-c", "import requests"], {
|
|
2351
2441
|
encoding: "utf-8",
|
|
2352
2442
|
timeout: 10000,
|
|
2353
2443
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -2503,34 +2593,47 @@ router.post("/api/telegram", async (req, res) => {
|
|
|
2503
2593
|
}
|
|
2504
2594
|
}
|
|
2505
2595
|
case "install": {
|
|
2506
|
-
// #
|
|
2507
|
-
//
|
|
2508
|
-
//
|
|
2509
|
-
//
|
|
2510
|
-
//
|
|
2511
|
-
//
|
|
2512
|
-
//
|
|
2596
|
+
// #380: create a dedicated bridge venv at
|
|
2597
|
+
// `<BRIDGE_DIR>/.venv` and install requirements into it using
|
|
2598
|
+
// that venv's pip. All bridge subprocesses then spawn with
|
|
2599
|
+
// `<BRIDGE_DIR>/.venv/bin/python3` by absolute path. See #379
|
|
2600
|
+
// research ticket for the root cause — bare `python3` / `pip3`
|
|
2601
|
+
// resolve to Homebrew Python on modern macOS where `requests`
|
|
2602
|
+
// is not available, producing a ModuleNotFoundError on Start.
|
|
2603
|
+
// Idempotent: existing installs missing a `.venv` get the venv
|
|
2604
|
+
// created on top of the existing clone without re-cloning.
|
|
2605
|
+
const venvDir = path.join(BRIDGE_DIR, ".venv");
|
|
2606
|
+
const venvPython = path.join(venvDir, "bin", "python3");
|
|
2607
|
+
const venvPip = path.join(venvDir, "bin", "pip");
|
|
2513
2608
|
let pipOutput = "";
|
|
2514
2609
|
try {
|
|
2515
2610
|
if (!fs.existsSync(BRIDGE_DIR)) {
|
|
2516
2611
|
execFileSync("gh", ["repo", "clone", "realproject7/agentchattr-telegram", BRIDGE_DIR], { encoding: "utf-8", timeout: 30000 });
|
|
2517
2612
|
}
|
|
2613
|
+
// #380: create the dedicated venv if missing. `python3 -m venv`
|
|
2614
|
+
// builds a fresh isolated environment that bypasses PEP 668
|
|
2615
|
+
// externally-managed markers, so this works even on Homebrew
|
|
2616
|
+
// Python where bare `pip3 install` would be blocked.
|
|
2617
|
+
if (!fs.existsSync(venvPython)) {
|
|
2618
|
+
execFileSync("python3", ["-m", "venv", venvDir], { encoding: "utf-8", timeout: 60000 });
|
|
2619
|
+
}
|
|
2518
2620
|
pipOutput = execFileSync(
|
|
2519
|
-
|
|
2621
|
+
venvPip,
|
|
2520
2622
|
["install", "-r", path.join(BRIDGE_DIR, "requirements.txt")],
|
|
2521
|
-
{ encoding: "utf-8", timeout:
|
|
2623
|
+
{ encoding: "utf-8", timeout: 120000 },
|
|
2522
2624
|
);
|
|
2523
2625
|
} catch (err) {
|
|
2524
|
-
|
|
2626
|
+
const stderr = (err && err.stderr && err.stderr.toString && err.stderr.toString()) || "";
|
|
2627
|
+
return res.json({ ok: false, error: (stderr.trim() || err.message || "Install failed") });
|
|
2525
2628
|
}
|
|
2526
|
-
const depCheck = checkTelegramBridgePythonDeps();
|
|
2629
|
+
const depCheck = checkTelegramBridgePythonDeps(venvPython);
|
|
2527
2630
|
if (!depCheck.ok) {
|
|
2528
2631
|
return res.json({
|
|
2529
2632
|
ok: false,
|
|
2530
2633
|
error:
|
|
2531
|
-
"
|
|
2532
|
-
"This
|
|
2533
|
-
|
|
2634
|
+
"pip reported success but the bridge venv's Python deps still fail to import. " +
|
|
2635
|
+
"This is unexpected for a freshly-created venv — check disk space and permissions " +
|
|
2636
|
+
`on ${venvDir}.\n\n` +
|
|
2534
2637
|
`Import error: ${depCheck.error}\n\n` +
|
|
2535
2638
|
`pip output tail:\n${pipOutput.split("\n").slice(-10).join("\n")}`,
|
|
2536
2639
|
});
|
|
@@ -2543,6 +2646,18 @@ router.post("/api/telegram", async (req, res) => {
|
|
|
2543
2646
|
if (isTelegramRunning(projectId)) return res.json({ ok: true, running: true, message: "Already running" });
|
|
2544
2647
|
const bridgeScript = path.join(BRIDGE_DIR, "telegram_bridge.py");
|
|
2545
2648
|
if (!fs.existsSync(bridgeScript)) return res.json({ ok: false, error: "Bridge not installed. Click Install Bridge first." });
|
|
2649
|
+
// #380: resolve the dedicated venv's python3 by absolute path.
|
|
2650
|
+
// Do NOT activate the venv or set VIRTUAL_ENV in the parent —
|
|
2651
|
+
// calling the venv's python3 directly is sufficient because
|
|
2652
|
+
// Python's sys.executable bootstrap resolves the venv
|
|
2653
|
+
// automatically. See #379 research ticket.
|
|
2654
|
+
const venvPython = path.join(BRIDGE_DIR, ".venv", "bin", "python3");
|
|
2655
|
+
if (!fs.existsSync(venvPython)) {
|
|
2656
|
+
return res.json({
|
|
2657
|
+
ok: false,
|
|
2658
|
+
error: "Bridge venv missing. Click \"Install Bridge\" to create it.",
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2546
2661
|
const tg = getProjectTelegram(projectId);
|
|
2547
2662
|
if (!tg || !tg.bot_token || !tg.chat_id) return res.json({ ok: false, error: "Save bot_token and chat_id in project settings first." });
|
|
2548
2663
|
const tomlPath = telegramConfigToml(projectId);
|
|
@@ -2553,15 +2668,25 @@ router.post("/api/telegram", async (req, res) => {
|
|
|
2553
2668
|
// `requests` module produces a readable error instead of the
|
|
2554
2669
|
// Start → Running → Stopped flicker that the v1 code path
|
|
2555
2670
|
// produced with `stdio: "ignore"`.
|
|
2556
|
-
const depCheck = checkTelegramBridgePythonDeps();
|
|
2671
|
+
const depCheck = checkTelegramBridgePythonDeps(venvPython);
|
|
2557
2672
|
if (!depCheck.ok) {
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2673
|
+
// #372: persist the pre-flight failure to the bridge log
|
|
2674
|
+
// file so the GET /api/telegram `last_error` tail picks it
|
|
2675
|
+
// up on the next status poll. Without this the widget only
|
|
2676
|
+
// sees the error for ~5s before the polling cycle clobbers
|
|
2677
|
+
// local error state, producing the "silent fail" symptom
|
|
2678
|
+
// (pill flips back to Stopped with no trace of why).
|
|
2679
|
+
const msg =
|
|
2680
|
+
"Bridge Python dependencies not installed in the dedicated venv. " +
|
|
2681
|
+
"Click \"Install Bridge\" to (re)create the venv and install them.\n\n" +
|
|
2682
|
+
`Import error: ${depCheck.error}`;
|
|
2683
|
+
try {
|
|
2684
|
+
fs.writeFileSync(
|
|
2685
|
+
telegramBridgeLog(projectId),
|
|
2686
|
+
`[${new Date().toISOString()}] pre-flight dep check failed\n${msg}\n`,
|
|
2687
|
+
);
|
|
2688
|
+
} catch {}
|
|
2689
|
+
return res.json({ ok: false, error: msg });
|
|
2565
2690
|
}
|
|
2566
2691
|
// #353: capture stdout + stderr to a per-project log file so
|
|
2567
2692
|
// bridge crashes (bad token, network failure, config parse
|
|
@@ -2585,7 +2710,7 @@ router.post("/api/telegram", async (req, res) => {
|
|
|
2585
2710
|
}
|
|
2586
2711
|
let child;
|
|
2587
2712
|
try {
|
|
2588
|
-
child = spawn(
|
|
2713
|
+
child = spawn(venvPython, [bridgeScript, "--config", tomlPath], {
|
|
2589
2714
|
detached: true,
|
|
2590
2715
|
stdio: ["ignore", outFd, errFd],
|
|
2591
2716
|
});
|
|
@@ -2770,3 +2895,9 @@ module.exports.buildNoPrRow = buildNoPrRow;
|
|
|
2770
2895
|
module.exports.summarizeItems = summarizeItems;
|
|
2771
2896
|
// #353: expose readLastLines for the telegram-bridge test.
|
|
2772
2897
|
module.exports.readLastLines = readLastLines;
|
|
2898
|
+
// #380: expose checkTelegramBridgePythonDeps so the bridge test can
|
|
2899
|
+
// exercise the venv-path interpreter argument round trip.
|
|
2900
|
+
module.exports.checkTelegramBridgePythonDeps = checkTelegramBridgePythonDeps;
|
|
2901
|
+
// #236: expose sendViaWebSocket so the chat-ws-send regression test
|
|
2902
|
+
// can verify the ack/body/error paths against a fake AC ws server.
|
|
2903
|
+
module.exports.sendViaWebSocket = sendViaWebSocket;
|
|
@@ -10,7 +10,8 @@ const assert = require("node:assert/strict");
|
|
|
10
10
|
const fs = require("node:fs");
|
|
11
11
|
const os = require("node:os");
|
|
12
12
|
const path = require("node:path");
|
|
13
|
-
const {
|
|
13
|
+
const { execFileSync } = require("node:child_process");
|
|
14
|
+
const { readLastLines, checkTelegramBridgePythonDeps } = require("./routes");
|
|
14
15
|
|
|
15
16
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "qw-bridge-log-"));
|
|
16
17
|
function write(name, content) {
|
|
@@ -64,7 +65,79 @@ try {
|
|
|
64
65
|
assert.match(got, /ModuleNotFoundError/);
|
|
65
66
|
assert.match(got, /No module named 'requests'/);
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
// 8) #372: pre-flight dep-check failures must be persisted to
|
|
69
|
+
// the bridge log file so a subsequent status poll can surface
|
|
70
|
+
// them via last_error. Without this the widget's local
|
|
71
|
+
// actionError got clobbered by the next 5s polling cycle and
|
|
72
|
+
// the failure appeared as a silent Start → Stopped flicker.
|
|
73
|
+
// Here we simulate the exact fs.writeFileSync the start
|
|
74
|
+
// handler now runs on pre-flight failure, then round-trip it
|
|
75
|
+
// through readLastLines to confirm the error text survives.
|
|
76
|
+
const preflightLog = path.join(tmp, "preflight.log");
|
|
77
|
+
const msg =
|
|
78
|
+
"Bridge Python dependencies not installed. Click \"Install Bridge\" to install them, " +
|
|
79
|
+
"or run: pip3 install -r /tmp/fake/requirements.txt\n\n" +
|
|
80
|
+
"Import error: Traceback (most recent call last):\n" +
|
|
81
|
+
" File \"<string>\", line 1, in <module>\n" +
|
|
82
|
+
" import requests\n" +
|
|
83
|
+
"ModuleNotFoundError: No module named 'requests'";
|
|
84
|
+
fs.writeFileSync(
|
|
85
|
+
preflightLog,
|
|
86
|
+
`[${new Date().toISOString()}] pre-flight dep check failed\n${msg}\n`,
|
|
87
|
+
);
|
|
88
|
+
const tail = readLastLines(preflightLog, 20);
|
|
89
|
+
assert.match(tail, /pre-flight dep check failed/);
|
|
90
|
+
assert.match(tail, /ModuleNotFoundError/);
|
|
91
|
+
assert.match(tail, /Install Bridge/);
|
|
92
|
+
|
|
93
|
+
// 9) #380: checkTelegramBridgePythonDeps accepts an explicit
|
|
94
|
+
// interpreter path. Passing a guaranteed-broken path must
|
|
95
|
+
// return { ok: false, error } without throwing.
|
|
96
|
+
const broken = checkTelegramBridgePythonDeps(path.join(tmp, "nope", "python3"));
|
|
97
|
+
assert.equal(broken.ok, false);
|
|
98
|
+
assert.ok(broken.error && broken.error.length > 0);
|
|
99
|
+
|
|
100
|
+
// 10) #380: start handler's missing-venv branch — we don't boot
|
|
101
|
+
// the server here, but the branch reduces to a plain
|
|
102
|
+
// fs.existsSync check on `<BRIDGE_DIR>/.venv/bin/python3`,
|
|
103
|
+
// so we verify the check returns false for a fixture dir
|
|
104
|
+
// that has no `.venv` subdir at all.
|
|
105
|
+
const fixtureBridgeDir = fs.mkdtempSync(path.join(tmp, "bridge-no-venv-"));
|
|
106
|
+
const missingVenvPython = path.join(fixtureBridgeDir, ".venv", "bin", "python3");
|
|
107
|
+
assert.equal(fs.existsSync(missingVenvPython), false);
|
|
108
|
+
|
|
109
|
+
// 11) #380: round-trip — build a real venv in a tmp dir, install
|
|
110
|
+
// a stdlib-only sentinel is trivially importable, and confirm
|
|
111
|
+
// checkTelegramBridgePythonDeps reports ok when `requests`
|
|
112
|
+
// is installed into that venv. Skipped gracefully on CI if
|
|
113
|
+
// `python3 -m venv` or network-backed pip install fails.
|
|
114
|
+
const venvDir = path.join(tmp, "case11-venv");
|
|
115
|
+
let venvSkipped = false;
|
|
116
|
+
try {
|
|
117
|
+
execFileSync("python3", ["-m", "venv", venvDir], { timeout: 30000, stdio: "pipe" });
|
|
118
|
+
const venvPython = path.join(venvDir, "bin", "python3");
|
|
119
|
+
const venvPip = path.join(venvDir, "bin", "pip");
|
|
120
|
+
// Without `requests` installed yet, the check must fail.
|
|
121
|
+
const before = checkTelegramBridgePythonDeps(venvPython);
|
|
122
|
+
assert.equal(before.ok, false);
|
|
123
|
+
try {
|
|
124
|
+
execFileSync(venvPip, ["install", "--quiet", "requests"], { timeout: 120000, stdio: "pipe" });
|
|
125
|
+
} catch {
|
|
126
|
+
venvSkipped = true;
|
|
127
|
+
}
|
|
128
|
+
if (!venvSkipped) {
|
|
129
|
+
const after = checkTelegramBridgePythonDeps(venvPython);
|
|
130
|
+
assert.equal(after.ok, true);
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
venvSkipped = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(
|
|
137
|
+
"routes.telegramBridge.test.js: all assertions passed (11 cases" +
|
|
138
|
+
(venvSkipped ? ", case 11 pip step skipped" : "") +
|
|
139
|
+
")",
|
|
140
|
+
);
|
|
68
141
|
} finally {
|
|
69
142
|
try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {}
|
|
70
143
|
}
|
|
@@ -74,7 +74,7 @@ When the operator asks you in chat to start a task or batch:
|
|
|
74
74
|
3. Reply in chat to confirm what you wrote to the queue file (issue numbers + which section).
|
|
75
75
|
4. **Tell the operator the queue is ready and how to kick it off.** Send a chat message like:
|
|
76
76
|
|
|
77
|
-
> Queue is ready. To begin,
|
|
77
|
+
> Queue is ready. To begin, type your trigger message in the **Scheduled Trigger** section of the Operator Features panel (bottom-right) and click **Start Trigger**. I will start assigning Dev as soon as the trigger fires.
|
|
78
78
|
|
|
79
79
|
Without this prompt the operator has no idea what to do next and the batch sits idle indefinitely. Always send it after step 3, even if the operator only asked for a single ticket.
|
|
80
80
|
5. **Wait for the operator to trigger the batch via the Scheduled Trigger widget** before assigning the first item to `@dev`. Do NOT start assignments the moment the queue file is written — the operator controls kickoff. The trigger fires the queue-check pulse to all agents and is your signal that the operator wants the batch to start.
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/4fa387ec64143e14-s.0q3udbd2bu5yp.woff2)format("woff2");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/bbc41e54d2fcbd21-s.0gw~uztddq1df.woff2)format("woff2");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Mono;font-style:normal;font-weight:100 900;font-display:swap;src:url(../media/797e433ab948586e-s.p.0.q-h669a_dqa.woff2)format("woff2");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Geist Mono Fallback;src:local(Arial);ascent-override:74.67%;descent-override:21.92%;line-gap-override:0.0%;size-adjust:134.59%}.geist_mono_8d43a2aa-module__8Li5zG__className{font-family:Geist Mono,Geist Mono Fallback;font-style:normal}.geist_mono_8d43a2aa-module__8Li5zG__variable{--font-geist-mono:"Geist Mono", "Geist Mono Fallback"}
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--color-red-400:#ff6568;--color-red-500:#fb2c36;--color-red-700:#bf000f;--color-red-900:#82181a;--color-green-500:#00c758;--color-blue-300:#90c5ff;--color-blue-400:#54a2ff;--color-neutral-200:#e5e5e5;--color-neutral-300:#d4d4d4;--color-neutral-400:#a1a1a1;--color-neutral-500:#737373;--color-neutral-600:#525252;--color-neutral-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;--leading-tight:1.25;--leading-snug:1.375;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-lg:.5rem;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-geist-mono)}@supports (color:lab(0% 0 0)){:root,:host{--color-red-400:lab(63.7053% 60.745 31.3109);--color-red-500:lab(55.4814% 75.0732 48.8528);--color-red-700:lab(40.4273% 67.2623 53.7441);--color-red-900:lab(28.5139% 44.5539 29.0463);--color-green-500:lab(70.5521% -66.5147 45.8073);--color-blue-300:lab(77.5052% -6.4629 -36.42);--color-blue-400:lab(65.0361% -1.42065 -56.9802);--color-neutral-200:lab(90.952% 0 -.0000119209);--color-neutral-300:lab(84.92% 0 -.0000119209);--color-neutral-400:lab(66.128% -.0000298023 .0000119209);--color-neutral-500:lab(48.496% 0 0);--color-neutral-600:lab(34.924% 0 0);--color-neutral-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-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-0{right:calc(var(--spacing) * 0)}.right-3{right:calc(var(--spacing) * 3)}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-full{bottom:100%}.left-0{left:calc(var(--spacing) * 0)}.left-16{left:calc(var(--spacing) * 16)}.left-\[14px\]{left:14px}.z-10{z-index:10}.z-20{z-index:20}.z-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-2{margin-block:calc(var(--spacing) * 2)}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-auto{margin-left:auto}.block{display:block}.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\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-\[12px\]{height:12px}.h-\[80vh\]{height:80vh}.h-\[calc\(100\%-80px\)\]{height:calc(100% - 80px)}.h-full{height:100%}.h-px{height:1px}.max-h-28{max-height:calc(var(--spacing) * 28)}.max-h-32{max-height:calc(var(--spacing) * 32)}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-\[88px\]{min-height:88px}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-44{width:calc(var(--spacing) * 44)}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-\[1px\]{width:1px}.w-full{width:100%}.w-px{width:1px}.max-w-3xl{max-width:var(--container-3xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-\[60\%\]{max-width:60%}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[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}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.cursor-col-resize{cursor:col-resize}.cursor-move{cursor:move}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.resize{resize:both}.resize-none{resize:none}.resize-y{resize:vertical}.list-decimal{list-style-type:decimal}.grid-flow-col{grid-auto-flow:column}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0{gap:calc(var(--spacing) * 0)}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-\[\#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-border,.border-border\/30{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/30{border-color:color-mix(in oklab, var(--border) 30%, transparent)}}.border-border\/40{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/40{border-color:color-mix(in oklab, var(--border) 40%, transparent)}}.border-border\/50{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/50{border-color:color-mix(in oklab, var(--border) 50%, transparent)}}.border-border\/60{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/60{border-color:color-mix(in oklab, var(--border) 60%, transparent)}}.border-error,.border-error\/30{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.border-error\/30{border-color:color-mix(in oklab, var(--error) 30%, transparent)}}.border-error\/60{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.border-error\/60{border-color:color-mix(in oklab, var(--error) 60%, transparent)}}.border-red-700\/40{border-color:#bf000f66}@supports (color:color-mix(in lab, red, red)){.border-red-700\/40{border-color:color-mix(in oklab, var(--color-red-700) 40%, transparent)}}.border-red-700\/50{border-color:#bf000f80}@supports (color:color-mix(in lab, red, red)){.border-red-700\/50{border-color:color-mix(in oklab, var(--color-red-700) 50%, transparent)}}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.border-white\/15{border-color:#ffffff26}@supports (color:color-mix(in lab, red, red)){.border-white\/15{border-color:color-mix(in oklab, var(--color-white) 15%, transparent)}}.bg-\[\#1a1a1a\]{background-color:#1a1a1a}.bg-\[\#ffcc00\]{background-color:#fc0}.bg-accent,.bg-accent\/5{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.bg-accent\/5{background-color:color-mix(in oklab, var(--accent) 5%, transparent)}}.bg-accent\/10{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.bg-accent\/10{background-color:color-mix(in oklab, var(--accent) 10%, transparent)}}.bg-accent\/30{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.bg-accent\/30{background-color:color-mix(in oklab, var(--accent) 30%, transparent)}}.bg-bg{background-color:var(--bg)}.bg-bg-surface,.bg-bg-surface\/50{background-color:var(--bg-surface)}@supports (color:color-mix(in lab, red, red)){.bg-bg-surface\/50{background-color:color-mix(in oklab, var(--bg-surface) 50%, transparent)}}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab, red, red)){.bg-black\/60{background-color:color-mix(in oklab, var(--color-black) 60%, transparent)}}.bg-border{background-color:var(--border)}.bg-error,.bg-error\/5{background-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.bg-error\/5{background-color:color-mix(in oklab, var(--error) 5%, transparent)}}.bg-error\/10{background-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.bg-error\/10{background-color:color-mix(in oklab, var(--error) 10%, transparent)}}.bg-green-500{background-color:var(--color-green-500)}.bg-neutral-400{background-color:var(--color-neutral-400)}.bg-neutral-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)}}.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-10{padding-left:calc(var(--spacing) * 10)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-geist-mono)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#ffcc00\]{color:#fc0}.text-accent,.text-accent\/70{color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.text-accent\/70{color:color-mix(in oklab, var(--accent) 70%, transparent)}}.text-bg{color:var(--bg)}.text-blue-400{color:var(--color-blue-400)}.text-error{color:var(--error)}.text-neutral-200{color:var(--color-neutral-200)}.text-neutral-300{color:var(--color-neutral-300)}.text-neutral-400{color:var(--color-neutral-400)}.text-neutral-500{color:var(--color-neutral-500)}.text-neutral-600{color:var(--color-neutral-600)}.text-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\/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-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-all{-webkit-user-select:all;user-select:all}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.group-hover\:block:is(:where(.group):hover *){display:block}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.placeholder\:text-text-muted::placeholder{color:var(--text-muted)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.last\:pb-0:last-child{padding-bottom:calc(var(--spacing) * 0)}@media (hover:hover){.hover\:border-accent:hover,.hover\:border-accent\/40:hover{border-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:border-accent\/40:hover{border-color:color-mix(in oklab, var(--accent) 40%, transparent)}}.hover\:border-error\/40:hover{border-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.hover\:border-error\/40:hover{border-color:color-mix(in oklab, var(--error) 40%, transparent)}}.hover\:border-text-muted:hover{border-color:var(--text-muted)}.hover\:border-white\/40:hover{border-color:#fff6}@supports (color:color-mix(in lab, red, red)){.hover\:border-white\/40:hover{border-color:color-mix(in oklab, var(--color-white) 40%, transparent)}}.hover\:bg-\[\#1a1a1a\]:hover{background-color:#1a1a1a}.hover\:bg-accent-dim:hover{background-color:var(--accent-dim)}.hover\:bg-accent\/5:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/5:hover{background-color:color-mix(in oklab, var(--accent) 5%, transparent)}}.hover\:bg-accent\/10:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/10:hover{background-color:color-mix(in oklab, var(--accent) 10%, transparent)}}.hover\:bg-accent\/20:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/20:hover{background-color:color-mix(in oklab, var(--accent) 20%, transparent)}}.hover\:bg-error\/20:hover{background-color:var(--error)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-error\/20:hover{background-color:color-mix(in oklab, var(--error) 20%, transparent)}}.hover\:bg-white\/5:hover{background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/5:hover{background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.hover\:text-accent:hover{color:var(--accent)}.hover\:text-accent-dim:hover{color:var(--accent-dim)}.hover\:text-blue-300:hover{color:var(--color-blue-300)}.hover\:text-blue-400:hover{color:var(--color-blue-400)}.hover\:text-error:hover{color:var(--error)}.hover\:text-text:hover{color:var(--text)}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}}.focus\:border-accent:focus{border-color:var(--accent)}.focus\:opacity-100:focus{opacity:1}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-accent:focus{--tw-ring-color:var(--accent)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:40rem){.sm\:inline{display:inline}.sm\:inline-flex{display:inline-flex}}@media (min-width:48rem){.md\:flex{display:flex}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:64rem){.lg\:block{display:block}.lg\:min-h-0{min-height:calc(var(--spacing) * 0)}.lg\:min-w-\[280px\]{min-width:280px}.lg\:flex-1{flex:1}.lg\:flex-row{flex-direction:row}.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}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|