@suveren/gateway 0.6.7 → 0.7.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.
@@ -5,9 +5,9 @@ import {
5
5
 
6
6
  // src/index.ts
7
7
  import { randomBytes as randomBytes2 } from "crypto";
8
- import { existsSync as existsSync5, readFileSync as readFileSync4, renameSync } from "fs";
9
- import { dirname as dirname3, join as join4 } from "path";
10
- import { homedir as homedir3 } from "os";
8
+ import { existsSync as existsSync5, readFileSync as readFileSync5, renameSync } from "fs";
9
+ import { dirname as dirname4, join as join5 } from "path";
10
+ import { homedir as homedir4 } from "os";
11
11
  import express from "express";
12
12
  import { createProxyMiddleware } from "http-proxy-middleware";
13
13
 
@@ -1377,6 +1377,38 @@ function requireAuthQueryOrHeader(vault2) {
1377
1377
  };
1378
1378
  }
1379
1379
 
1380
+ // src/middleware/host-guard.ts
1381
+ var ALLOWED_HOSTNAMES = /* @__PURE__ */ new Set([
1382
+ "localhost",
1383
+ "127.0.0.1",
1384
+ "::1",
1385
+ "[::1]",
1386
+ "0.0.0.0"
1387
+ ]);
1388
+ function hostnameOf(hostHeader) {
1389
+ const trimmed = hostHeader.trim().toLowerCase();
1390
+ const bracketed = trimmed.match(/^\[([^\]]+)\](?::\d+)?$/);
1391
+ if (bracketed) return bracketed[1];
1392
+ return trimmed.replace(/:\d+$/, "");
1393
+ }
1394
+ function isAllowedHost(hostHeader) {
1395
+ if (!hostHeader) return false;
1396
+ const name = hostnameOf(hostHeader);
1397
+ if (ALLOWED_HOSTNAMES.has(name)) return true;
1398
+ if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(name)) return true;
1399
+ if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(name)) return true;
1400
+ if (/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(name)) return true;
1401
+ if (name === "localhost" || name.endsWith(".localhost")) return true;
1402
+ return false;
1403
+ }
1404
+ function requireAllowedHost(req, res, next) {
1405
+ if (!isAllowedHost(req.headers.host)) {
1406
+ res.status(403).json({ error: "Forbidden host" });
1407
+ return;
1408
+ }
1409
+ next();
1410
+ }
1411
+
1380
1412
  // src/lib/read-policy-defaults.ts
1381
1413
  async function backfillReadPolicyDefaults(data) {
1382
1414
  const defaults = /* @__PURE__ */ new Map();
@@ -1508,15 +1540,15 @@ import { promisify } from "util";
1508
1540
  var run = promisify(execFile);
1509
1541
  var SUPPORTED = /* @__PURE__ */ new Set(["darwin", "win32", "linux"]);
1510
1542
  var MODULE_DIR = pathDirname(fileURLToPath(import.meta.url));
1511
- function findCli(dirname4 = MODULE_DIR) {
1543
+ function findCli(dirname5 = MODULE_DIR) {
1512
1544
  const candidates = [
1513
1545
  // Bundled (the shipped layout): dist/control-plane → dist → root → bin
1514
- resolve(dirname4, "..", "..", "bin", "suveren-gateway.js"),
1546
+ resolve(dirname5, "..", "..", "bin", "suveren-gateway.js"),
1515
1547
  // Bundled, were the routes/ directory ever preserved.
1516
- resolve(dirname4, "..", "..", "..", "bin", "suveren-gateway.js"),
1548
+ resolve(dirname5, "..", "..", "..", "bin", "suveren-gateway.js"),
1517
1549
  // Dev, running from source: the repo's own bundle directory.
1518
- resolve(dirname4, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
1519
- resolve(dirname4, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
1550
+ resolve(dirname5, "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js"),
1551
+ resolve(dirname5, "..", "..", "..", "..", "..", "bundle", "bin", "suveren-gateway.js")
1520
1552
  // Deliberately NO process.cwd() fallback: it makes the result depend on
1521
1553
  // where the process happened to be launched from, so the same install
1522
1554
  // resolves differently between a shell and a login service — and it would
@@ -1961,9 +1993,8 @@ function createEventsHandler() {
1961
1993
  res.setHeader("X-Accel-Buffering", "no");
1962
1994
  res.flushHeaders();
1963
1995
  const unsubscribe = eventBus.subscribe((event) => {
1964
- const data = JSON.stringify(event.payload ?? null);
1965
1996
  res.write(`event: ${event.type}
1966
- data: ${data}
1997
+ data: null
1967
1998
 
1968
1999
  `);
1969
2000
  });
@@ -1977,18 +2008,193 @@ data: ${data}
1977
2008
  };
1978
2009
  }
1979
2010
 
2011
+ // src/routes/gateway-settings.ts
2012
+ import { Router as Router10 } from "express";
2013
+
2014
+ // src/lib/gateway-settings.ts
2015
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
2016
+ import { homedir as homedir3 } from "os";
2017
+ import { join as join3, dirname as dirname3 } from "path";
2018
+ var DEFAULT_SETTINGS = {
2019
+ desktopNotifications: true
2020
+ };
2021
+ function settingsPath(dataDir) {
2022
+ const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join3(homedir3(), ".suveren");
2023
+ return join3(dir, "settings.json");
2024
+ }
2025
+ function readSettings(dataDir) {
2026
+ try {
2027
+ const raw = JSON.parse(readFileSync3(settingsPath(dataDir), "utf8"));
2028
+ return {
2029
+ desktopNotifications: typeof raw.desktopNotifications === "boolean" ? raw.desktopNotifications : DEFAULT_SETTINGS.desktopNotifications
2030
+ };
2031
+ } catch {
2032
+ return { ...DEFAULT_SETTINGS };
2033
+ }
2034
+ }
2035
+ function writeSettings(patch, dataDir) {
2036
+ const next = {
2037
+ ...readSettings(dataDir),
2038
+ ...typeof patch.desktopNotifications === "boolean" ? { desktopNotifications: patch.desktopNotifications } : {}
2039
+ };
2040
+ const path = settingsPath(dataDir);
2041
+ mkdirSync3(dirname3(path), { recursive: true });
2042
+ writeFileSync3(path, JSON.stringify(next, null, 2) + "\n", "utf8");
2043
+ return next;
2044
+ }
2045
+
2046
+ // src/routes/gateway-settings.ts
2047
+ function createGatewaySettingsRouter() {
2048
+ const router = Router10();
2049
+ router.get("/", (_req, res) => {
2050
+ res.json(readSettings());
2051
+ });
2052
+ router.put("/", (req, res) => {
2053
+ const body = req.body ?? {};
2054
+ if ("desktopNotifications" in body && typeof body.desktopNotifications !== "boolean") {
2055
+ res.status(400).json({ error: "desktopNotifications must be a boolean" });
2056
+ return;
2057
+ }
2058
+ try {
2059
+ const saved = writeSettings({
2060
+ desktopNotifications: body.desktopNotifications
2061
+ });
2062
+ res.json(saved);
2063
+ } catch (err) {
2064
+ res.status(500).json({ error: `Could not save settings: ${err.message}` });
2065
+ }
2066
+ });
2067
+ return router;
2068
+ }
2069
+
2070
+ // src/routes/internal-events.ts
2071
+ import { Router as Router11 } from "express";
2072
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
2073
+ var ALLOWED = ["proposal-added", "action-approval-needed"];
2074
+ function secretMatches(provided, expected) {
2075
+ if (!expected) return false;
2076
+ if (!provided) return false;
2077
+ const a = Buffer.from(provided);
2078
+ const b = Buffer.from(expected);
2079
+ if (a.length !== b.length) return false;
2080
+ return timingSafeEqual2(a, b);
2081
+ }
2082
+ function createInternalEventsRouter(getSecret) {
2083
+ const router = Router11();
2084
+ router.post("/event", (req, res) => {
2085
+ if (!secretMatches(req.headers["x-internal-secret"], getSecret())) {
2086
+ res.status(401).json({ error: "Unauthorized" });
2087
+ return;
2088
+ }
2089
+ const type = (req.body ?? {}).type;
2090
+ if (typeof type !== "string" || !ALLOWED.includes(type)) {
2091
+ res.status(400).json({ error: "Unsupported event type" });
2092
+ return;
2093
+ }
2094
+ console.error(`[Control Plane] Internal event received: ${type}`);
2095
+ eventBus.emit(type);
2096
+ res.json({ ok: true });
2097
+ });
2098
+ return router;
2099
+ }
2100
+
2101
+ // src/lib/notification-dispatcher.ts
2102
+ var TRIGGER_EVENTS = ["proposal-added", "action-approval-needed"];
2103
+ var TITLE = "Suveren";
2104
+ var WAITING_MESSAGE = "Something is waiting for your review.";
2105
+ var FLOOD_THRESHOLD = 10;
2106
+ var floodMessage = (n) => `Unusual volume: ${n} requests in the last minute.`;
2107
+ var DEBOUNCE_MS = 5e3;
2108
+ var COOLDOWN_MS = 6e4;
2109
+ var NotificationDispatcher = class {
2110
+ debounceTimer = null;
2111
+ cooldownTimer = null;
2112
+ pendingWhileCoolingDown = false;
2113
+ /** Events observed since the current window opened — the flood signal only. */
2114
+ observedInWindow = 0;
2115
+ unsubscribe = null;
2116
+ now;
2117
+ setTimeoutFn;
2118
+ notifyFn;
2119
+ settingsFn;
2120
+ url;
2121
+ constructor(opts = {}) {
2122
+ this.now = opts.now ?? Date.now;
2123
+ this.setTimeoutFn = opts.setTimeoutFn ?? setTimeout;
2124
+ this.notifyFn = opts.notifyFn ?? notify;
2125
+ this.settingsFn = opts.settingsFn ?? readSettings;
2126
+ this.url = opts.url;
2127
+ }
2128
+ /** Subscribe to the bus. Returns a stop function. */
2129
+ start() {
2130
+ if (this.unsubscribe) return this.unsubscribe;
2131
+ const off = eventBus.subscribe((event) => {
2132
+ if (TRIGGER_EVENTS.includes(event.type)) this.onTrigger();
2133
+ });
2134
+ this.unsubscribe = () => {
2135
+ off();
2136
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
2137
+ if (this.cooldownTimer) clearTimeout(this.cooldownTimer);
2138
+ this.debounceTimer = null;
2139
+ this.cooldownTimer = null;
2140
+ this.unsubscribe = null;
2141
+ };
2142
+ return this.unsubscribe;
2143
+ }
2144
+ /** Exposed for tests; the bus subscription calls this. */
2145
+ onTrigger() {
2146
+ this.observedInWindow += 1;
2147
+ if (this.cooldownTimer) {
2148
+ this.pendingWhileCoolingDown = true;
2149
+ return;
2150
+ }
2151
+ if (this.debounceTimer) return;
2152
+ this.debounceTimer = this.setTimeoutFn(() => {
2153
+ this.debounceTimer = null;
2154
+ this.fire();
2155
+ }, DEBOUNCE_MS);
2156
+ }
2157
+ fire() {
2158
+ const count = this.observedInWindow;
2159
+ this.observedInWindow = 0;
2160
+ let enabled = true;
2161
+ try {
2162
+ enabled = this.settingsFn().desktopNotifications;
2163
+ } catch {
2164
+ enabled = true;
2165
+ }
2166
+ console.error(
2167
+ `[Control Plane] Review pending \u2014 desktop notification ${enabled ? "sent" : "suppressed (turned off)"}`
2168
+ );
2169
+ if (enabled) {
2170
+ const message = count >= FLOOD_THRESHOLD ? floodMessage(count) : WAITING_MESSAGE;
2171
+ this.notifyFn(TITLE, message, process.platform, this.url);
2172
+ }
2173
+ this.cooldownTimer = this.setTimeoutFn(() => {
2174
+ this.cooldownTimer = null;
2175
+ if (this.pendingWhileCoolingDown) {
2176
+ this.pendingWhileCoolingDown = false;
2177
+ this.fire();
2178
+ }
2179
+ }, COOLDOWN_MS);
2180
+ }
2181
+ };
2182
+ function startNotificationDispatcher(opts = {}) {
2183
+ return new NotificationDispatcher(opts).start();
2184
+ }
2185
+
1980
2186
  // src/lib/denials-reader.ts
1981
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1982
- import { join as join3 } from "path";
2187
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
2188
+ import { join as join4 } from "path";
1983
2189
  function loadDenials(dataDir, decrypt) {
1984
- const encPath = join3(dataDir, "denials.enc.json");
1985
- const plainPath = join3(dataDir, "denials.json");
2190
+ const encPath = join4(dataDir, "denials.enc.json");
2191
+ const plainPath = join4(dataDir, "denials.json");
1986
2192
  if (existsSync4(encPath)) {
1987
- const { blob } = JSON.parse(readFileSync3(encPath, "utf-8"));
2193
+ const { blob } = JSON.parse(readFileSync4(encPath, "utf-8"));
1988
2194
  return JSON.parse(decrypt(blob)).records ?? [];
1989
2195
  }
1990
2196
  if (existsSync4(plainPath)) {
1991
- return JSON.parse(readFileSync3(plainPath, "utf-8")).records ?? [];
2197
+ return JSON.parse(readFileSync4(plainPath, "utf-8")).records ?? [];
1992
2198
  }
1993
2199
  return [];
1994
2200
  }
@@ -2003,8 +2209,8 @@ function selectDenials(records, opts = {}) {
2003
2209
  // src/index.ts
2004
2210
  (function migrateDataDir() {
2005
2211
  if (process.env.SUVEREN_DATA_DIR) return;
2006
- const oldDir = join4(homedir3(), ".hap");
2007
- const newDir = join4(homedir3(), ".suveren");
2212
+ const oldDir = join5(homedir4(), ".hap");
2213
+ const newDir = join5(homedir4(), ".suveren");
2008
2214
  if (!existsSync5(newDir) && existsSync5(oldDir)) {
2009
2215
  try {
2010
2216
  renameSync(oldDir, newDir);
@@ -2018,7 +2224,7 @@ function selectDenials(records, opts = {}) {
2018
2224
  var SP_URL2 = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
2019
2225
  var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
2020
2226
  var HAP_MODE = process.env.HAP_MODE ?? "personal";
2021
- var UI_DIST = process.env.HAP_UI_DIST ?? join4(import.meta.dirname ?? __dirname, "../../ui/dist");
2227
+ var UI_DIST = process.env.HAP_UI_DIST ?? join5(import.meta.dirname ?? __dirname, "../../ui/dist");
2022
2228
  var vault = new Vault();
2023
2229
  var internalSecret2 = process.env.SUVEREN_INTERNAL_SECRET ?? randomBytes2(32).toString("hex");
2024
2230
  setInternalSecret(internalSecret2);
@@ -2247,12 +2453,14 @@ app.get("/auth/oauth/:integrationId/health", authGuard, async (req, res) => {
2247
2453
  res.json({ status: "failed", account, error: err instanceof Error ? err.message : "probe failed" });
2248
2454
  }
2249
2455
  });
2250
- app.get("/events", requireAuthQueryOrHeader(vault), createEventsHandler());
2456
+ app.get("/events", requireAllowedHost, requireAuthQueryOrHeader(vault), createEventsHandler());
2457
+ app.use("/internal", jsonParser, createInternalEventsRouter(() => internalSecret2));
2251
2458
  app.use("/vault", jsonParser, authGuard, createVaultRouter(vault));
2252
2459
  app.use("/ai", jsonParser, authGuard, createAIRouter(vault));
2253
2460
  app.use("/ai-prompts", jsonParser, authGuard, createAIPromptsRouter());
2254
2461
  app.use("/mcp", jsonParser, authGuard, createMCPRouter());
2255
2462
  app.use("/autostart", jsonParser, authGuard, createAutostartRouter());
2463
+ app.use("/api/gateway-settings", jsonParser, authGuard, createGatewaySettingsRouter());
2256
2464
  app.use("/api/encrypt-intent", jsonParser, authGuard, createEncryptIntentRouter());
2257
2465
  app.use("/api/decrypt-intent", jsonParser, authGuard, createDecryptIntentRouter(vault));
2258
2466
  app.use("/api/approved-intents", jsonParser, authGuard, createApprovedIntentsRouter(vault));
@@ -2384,7 +2592,7 @@ app.get("/active-authorizations", authGuard, async (_req, res) => {
2384
2592
  });
2385
2593
  app.get("/denials", authGuard, (req, res) => {
2386
2594
  try {
2387
- const dataDir = process.env.SUVEREN_DATA_DIR ?? join4(homedir3(), ".suveren");
2595
+ const dataDir = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
2388
2596
  const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
2389
2597
  const since = Number(req.query.since);
2390
2598
  const limit = Number(req.query.limit);
@@ -2417,16 +2625,16 @@ app.post("/resync-gates", authGuard, async (_req, res) => {
2417
2625
  }
2418
2626
  });
2419
2627
  var AGENT_CONTEXT_MAX_BYTES = 16 * 1024;
2420
- var SUVEREN_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join4(homedir3(), ".suveren");
2628
+ var SUVEREN_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
2421
2629
  app.get("/agent-brief/context", authGuard, async (_req, res) => {
2422
2630
  try {
2423
- const { readFileSync: readFileSync5, existsSync: fileExists } = await import("fs");
2424
- const filePath = join4(SUVEREN_DATA_DIR2, "context.md");
2631
+ const { readFileSync: readFileSync6, existsSync: fileExists } = await import("fs");
2632
+ const filePath = join5(SUVEREN_DATA_DIR2, "context.md");
2425
2633
  if (!fileExists(filePath)) {
2426
2634
  res.json({ content: "" });
2427
2635
  return;
2428
2636
  }
2429
- const content = readFileSync5(filePath, "utf-8");
2637
+ const content = readFileSync6(filePath, "utf-8");
2430
2638
  res.json({ content });
2431
2639
  } catch (err) {
2432
2640
  console.error("[Control Plane] agent-brief/context GET failed:", err);
@@ -2444,11 +2652,11 @@ app.put("/agent-brief/context", jsonParser, authGuard, async (req, res) => {
2444
2652
  res.status(413).json({ error: `Context exceeds ${AGENT_CONTEXT_MAX_BYTES}-byte cap` });
2445
2653
  return;
2446
2654
  }
2447
- const { mkdirSync: mkdirSync3, writeFileSync: writeFileSync3, renameSync: renameSync2 } = await import("fs");
2448
- mkdirSync3(SUVEREN_DATA_DIR2, { recursive: true });
2449
- const filePath = join4(SUVEREN_DATA_DIR2, "context.md");
2655
+ const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync4, renameSync: renameSync2 } = await import("fs");
2656
+ mkdirSync4(SUVEREN_DATA_DIR2, { recursive: true });
2657
+ const filePath = join5(SUVEREN_DATA_DIR2, "context.md");
2450
2658
  const tmpPath = `${filePath}.tmp`;
2451
- writeFileSync3(tmpPath, content, "utf-8");
2659
+ writeFileSync4(tmpPath, content, "utf-8");
2452
2660
  renameSync2(tmpPath, filePath);
2453
2661
  res.json({ ok: true });
2454
2662
  } catch (err) {
@@ -2503,8 +2711,8 @@ function detectInstallMethod() {
2503
2711
  if (dir.includes("/node_modules/@suveren/gateway/")) return "npm";
2504
2712
  let cursor = dir;
2505
2713
  for (let i = 0; i < 8; i++) {
2506
- if (existsSync5(join4(cursor, ".git"))) return "dev";
2507
- const parent = dirname3(cursor);
2714
+ if (existsSync5(join5(cursor, ".git"))) return "dev";
2715
+ const parent = dirname4(cursor);
2508
2716
  if (parent === cursor) break;
2509
2717
  cursor = parent;
2510
2718
  }
@@ -2516,10 +2724,10 @@ function detectRunningVersion() {
2516
2724
  return process.env.HAP_BUILD_SHA ?? "dev";
2517
2725
  }
2518
2726
  const dir = import.meta.dirname ?? __dirname;
2519
- const bundlePkg = join4(dir, "..", "..", "package.json");
2727
+ const bundlePkg = join5(dir, "..", "..", "package.json");
2520
2728
  if (existsSync5(bundlePkg)) {
2521
2729
  try {
2522
- const pkg = JSON.parse(readFileSync4(bundlePkg, "utf8"));
2730
+ const pkg = JSON.parse(readFileSync5(bundlePkg, "utf8"));
2523
2731
  if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
2524
2732
  return pkg.version;
2525
2733
  }
@@ -2557,7 +2765,7 @@ if (existsSync5(UI_DIST)) {
2557
2765
  }));
2558
2766
  app.get("*", (_req, res) => {
2559
2767
  res.setHeader("Cache-Control", "no-store, must-revalidate");
2560
- res.sendFile(join4(UI_DIST, "index.html"));
2768
+ res.sendFile(join5(UI_DIST, "index.html"));
2561
2769
  });
2562
2770
  } else {
2563
2771
  app.get("/", (_req, res) => {
@@ -2573,6 +2781,7 @@ app.listen(port, "0.0.0.0", () => {
2573
2781
  const { title, message, url } = lockedNotification(port);
2574
2782
  notify(title, message, process.platform, url);
2575
2783
  }
2784
+ startNotificationDispatcher({ url: `http://localhost:${port}` });
2576
2785
  console.error(`[Control Plane] SP proxy: ${SP_URL2}`);
2577
2786
  console.error(`[Control Plane] UI dist: ${UI_DIST}`);
2578
2787
  console.error(`[Control Plane] Internal secret: configured`);
@@ -6,6 +6,33 @@ import express from "express";
6
6
  import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
7
7
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8
8
 
9
+ // src/lib/cp-notify.ts
10
+ var CP_PORT = process.env.SUVEREN_CP_PORT ?? "3402";
11
+ var CP_BASE = process.env.SUVEREN_CP_INTERNAL_URL ?? `http://127.0.0.1:${CP_PORT}`;
12
+ var TIMEOUT_MS = 2e3;
13
+ async function notifyControlPlane(type) {
14
+ const secret = process.env.SUVEREN_INTERNAL_SECRET ?? "";
15
+ if (!secret) return;
16
+ try {
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
19
+ try {
20
+ await fetch(`${CP_BASE}/internal/event`, {
21
+ method: "POST",
22
+ headers: {
23
+ "Content-Type": "application/json",
24
+ "X-Internal-Secret": secret
25
+ },
26
+ body: JSON.stringify({ type }),
27
+ signal: controller.signal
28
+ });
29
+ } finally {
30
+ clearTimeout(timer);
31
+ }
32
+ } catch {
33
+ }
34
+ }
35
+
9
36
  // src/lib/sp-client.ts
10
37
  var SPReceiptError = class extends Error {
11
38
  constructor(message, statusCode, body) {
@@ -167,6 +194,7 @@ var SPClient = class {
167
194
  const body = await res.json().catch(() => ({}));
168
195
  throw new Error(body.error ?? `SP proposal submission failed: ${res.status}`);
169
196
  }
197
+ void notifyControlPlane("proposal-added");
170
198
  return res.json();
171
199
  }
172
200
  /**
@@ -0,0 +1 @@
1
+ @font-face{font-family:Inter;font-style:normal;font-weight:400;font-display:swap;src:url(/fonts/Inter-Regular.woff2) format("woff2")}@font-face{font-family:Inter;font-style:normal;font-weight:500;font-display:swap;src:url(/fonts/Inter-Medium.woff2) format("woff2")}@font-face{font-family:Inter;font-style:normal;font-weight:600;font-display:swap;src:url(/fonts/Inter-SemiBold.woff2) format("woff2")}@font-face{font-family:Inter;font-style:normal;font-weight:700;font-display:swap;src:url(/fonts/Inter-Bold.woff2) format("woff2")}*,*:before,*:after{margin:0;padding:0;box-sizing:border-box}:root{--text-primary: #111111;--text-secondary: #111111;--text-tertiary: #111111;--text-muted: #888888;--bg-main: #fafafa;--bg-elevated: #ffffff;--bg-input: #ffffff;--accent: #888888;--accent-subtle: #f0f0f0;--accent-hover: #555555;--border: #e5e5e5;--edit-border: #c7c7c7;--danger: #dc2626;--danger-subtle: #fef2f2;--success: #16a34a;--success-subtle: #f0fdf4;--warning: #d97706;--btn-primary-bg: #111111;--btn-primary-fg: #ffffff;--btn-primary-bg-hover: #000000;--btn-primary-bg-active: #2a2a2a;--icon-filter: brightness(0) invert(1);--primary-glow: rgba(0, 0, 0, .2);--primary-focus-ring: rgba(0, 0, 0, .15);--bg-field: #111111;--text-field: #ffffff;--border-field: #111111;--placeholder-field: rgba(255, 255, 255, .55);--field-focus-ring: rgba(0, 0, 0, .18);--nav-height: 3.5rem;--sidebar-width: 15rem;--content-width: 75rem;--content-padding: 2rem}:root[data-theme=dark]{--text-primary: #ffffff;--text-secondary: #ffffff;--text-tertiary: #ffffff;--text-muted: #888888;--bg-main: #0a0a0a;--bg-elevated: #1a1a1a;--bg-input: #141414;--accent: #888888;--accent-subtle: #1f1f1f;--accent-hover: #bbbbbb;--border: #2a2a2a;--edit-border: #4a4a4a;--danger-subtle: #2a0a0a;--success-subtle: #0a2a0a;--btn-primary-bg: #ffffff;--btn-primary-fg: #000000;--btn-primary-bg-hover: #e5e5e5;--btn-primary-bg-active: #c4c4c4;--icon-filter: brightness(0);--primary-glow: rgba(255, 255, 255, .25);--primary-focus-ring: rgba(255, 255, 255, .2);--bg-field: #ffffff;--text-field: #111111;--border-field: #ffffff;--placeholder-field: rgba(0, 0, 0, .5);--field-focus-ring: rgba(255, 255, 255, .25)}html{scroll-behavior:smooth}body{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-feature-settings:"cv11","ss01","ss03";font-size:16px;line-height:1.6;color:var(--text-primary);background:var(--bg-main);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (min-width: 1900px){body{font-size:17.5px}}@media (min-width: 2800px){body{font-size:19px}}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}h1,h2,h3,h4{font-weight:700;letter-spacing:-.02em;line-height:1.2;color:var(--text-primary)}code{background:var(--bg-elevated);border:1px solid var(--border);padding:.15rem .4rem;border-radius:.25rem;font-size:.875em;font-family:SF Mono,Monaco,Cascadia Code,Courier New,monospace}.top-nav{position:fixed;top:0;width:100%;height:var(--nav-height);background:#fafafaeb;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border-bottom:1px solid var(--border);z-index:1000}[data-theme=dark] .top-nav{background:#0a0a0aeb}.top-nav-inner{max-width:100%;margin:0 auto;padding:0 1.5rem;height:100%;display:flex;align-items:center;gap:1rem}.logo-group{display:flex;align-items:center;gap:.5rem;flex-shrink:0}.logo-group svg{flex-shrink:0}.logo{font-weight:700;font-size:.95rem;color:var(--text-primary);text-decoration:none}.logo:hover{text-decoration:none}.version-badge{font-size:.65rem;font-weight:600;padding:.15rem .4rem;background:var(--btn-primary-bg);color:var(--btn-primary-fg);border-radius:.25rem}.gw-version{font-size:.7rem;padding:.15rem .4rem;border-radius:.25rem;color:var(--text-muted);background:var(--accent-subtle);font-variant-numeric:tabular-nums;white-space:nowrap}.gw-version[data-stale=true]{color:var(--warning);background:transparent;border:1px solid var(--warning)}.nav-spacer{flex:1}.nav-actions{display:flex;align-items:center;gap:.75rem}.user-chip{display:flex;align-items:center;gap:.5rem;font-size:.8rem;color:var(--text-secondary)}.user-chip strong{color:var(--text-primary)}.user-chip .dot{width:3px;height:3px;border-radius:50%;background:var(--text-tertiary)}.theme-toggle{width:2rem;height:2rem;background:none;border:1px solid var(--border);border-radius:.375rem;cursor:pointer;color:var(--text-secondary);font-size:1rem;display:flex;align-items:center;justify-content:center}.theme-toggle:hover{color:var(--accent);border-color:var(--accent)}.nav-logout{font-size:.8rem;color:var(--text-tertiary);cursor:pointer;background:none;border:none;font-family:inherit}.nav-logout:hover{color:var(--text-primary)}.domain-switcher{position:relative}.domain-switcher-btn{background:none;border:none;font-family:inherit;font-size:.8rem;color:var(--text-secondary);cursor:pointer;display:flex;align-items:center;gap:.25rem;padding:.125rem .25rem;border-radius:.25rem}.domain-switcher-btn:hover{color:var(--accent)}.domain-switcher-arrow{font-size:.6rem;color:var(--text-tertiary)}.domain-switcher-menu{position:absolute;top:calc(100% + .375rem);right:0;min-width:10rem;background:var(--bg-elevated);border:1px solid var(--border);border-radius:.5rem;box-shadow:0 4px 12px #0000001a;z-index:200;padding:.25rem}.domain-switcher-item{display:block;width:100%;text-align:left;background:none;border:none;font-family:inherit;font-size:.8rem;color:var(--text-secondary);padding:.5rem .75rem;border-radius:.375rem;cursor:pointer;white-space:nowrap}.domain-switcher-item:hover{background:var(--accent-subtle);color:var(--text-primary)}.domain-switcher-item.active{color:var(--accent);font-weight:600}.sidebar{position:fixed;top:var(--nav-height);left:0;width:var(--sidebar-width);height:calc(100vh - var(--nav-height));background:var(--bg-elevated);border-right:1px solid var(--border);padding:1rem 0;z-index:900;display:flex;flex-direction:column}.sidebar-nav{list-style:none;padding:0 .5rem;flex:1}.sidebar-item{display:flex;align-items:center;gap:.625rem;padding:.5rem .75rem;margin-bottom:.125rem;border-radius:.5rem;font-size:.85rem;font-weight:500;color:var(--text-secondary);cursor:pointer;transition:background .15s,color .15s;text-decoration:none}.sidebar-item:hover{background:var(--accent-subtle);color:var(--text-primary);text-decoration:none}.sidebar-item.active{background:var(--accent-subtle);color:var(--accent);font-weight:600}.sidebar-item .icon{width:1.125rem;text-align:center;font-size:.9rem;flex-shrink:0}.sidebar-section-label{font-size:.65rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-tertiary);padding:1rem .75rem .375rem}.sidebar-context{padding:.75rem 1rem;border-top:1px solid var(--border);font-size:.75rem;color:var(--text-tertiary)}.sidebar-context .ctx-label{font-weight:600;color:var(--text-secondary);margin-bottom:.125rem}.sidebar-context .ctx-value{color:var(--text-primary);font-weight:500}.main-content{margin-left:var(--sidebar-width);margin-top:var(--nav-height);padding:2rem;min-height:calc(100vh - var(--nav-height))}.main-content.no-sidebar{margin-left:0}.page-inner{max-width:56rem;margin:0 auto}.page-header{margin-bottom:1.5rem}.page-title{font-size:1.5rem;margin-bottom:.25rem}.page-subtitle{font-size:.9rem;color:var(--text-secondary)}.btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;padding:.625rem 1.25rem;font-size:.9rem;font-weight:500;font-family:inherit;border-radius:.5rem;border:none;cursor:pointer;transition:all .2s;text-decoration:none;line-height:1.4}.btn:disabled{opacity:.5;cursor:not-allowed}.btn-primary{background:var(--btn-primary-bg);color:var(--btn-primary-fg);border:2px solid var(--btn-primary-bg)}.btn-primary:hover:not(:disabled){background:var(--btn-primary-bg-hover);border-color:var(--btn-primary-bg-hover);color:var(--btn-primary-fg);transform:translateY(-1px);box-shadow:0 4px 12px var(--primary-glow)}.btn-primary:active:not(:disabled){background:var(--btn-primary-bg-active);border-color:var(--btn-primary-bg-active)}.btn-secondary{background:transparent;color:var(--text-primary);border:1px solid var(--border)}.btn-secondary:hover:not(:disabled){border-color:var(--btn-primary-bg);color:var(--text-primary);background:var(--accent-subtle)}.btn-ghost{background:transparent;color:var(--text-secondary);border:none;padding:.5rem .75rem}.btn-ghost:hover:not(:disabled){color:var(--text-primary);background:var(--accent-subtle)}.btn-full{width:100%}.btn-sm{padding:.375rem .75rem;font-size:.8rem}.btn-lg{padding:.875rem 2rem;font-size:1rem;font-weight:600}.form-group{margin-bottom:1.25rem}.form-label{display:block;font-size:.875rem;font-weight:600;color:var(--text-primary);margin-bottom:.375rem}.form-input,.form-select,.form-textarea{width:100%;padding:.625rem .75rem;font-size:.9rem;font-family:inherit;color:var(--text-field);background:var(--bg-field);border:1px solid var(--border-field);border-radius:.5rem;outline:none;transition:box-shadow .2s,border-color .2s}.form-input::placeholder,.form-textarea::placeholder{color:var(--placeholder-field);opacity:1}.form-input:focus,.form-textarea:focus,.form-select:focus{box-shadow:0 0 0 3px var(--field-focus-ring)}.form-input:disabled,.form-textarea:disabled,.form-select:disabled{opacity:.55;cursor:not-allowed}.form-input-invalid,.form-input-invalid:focus{border-color:var(--danger);box-shadow:0 0 0 3px #dc26262e}.form-textarea{resize:vertical;min-height:120px;line-height:1.5}.form-hint{font-size:.8rem;color:var(--text-tertiary);margin-top:.375rem}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ffffff' d='M6 8L1 3h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right .75rem center;padding-right:2.25rem}[data-theme=dark] .form-select{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23111111' d='M6 8L1 3h10z'/%3E%3C/svg%3E")}.card{background:var(--bg-elevated);border:1px solid var(--border);border-radius:.75rem;padding:1.5rem}.card+.card{margin-top:1rem}.card-highlight{animation:hap-card-highlight 2.4s ease-out 1}@keyframes hap-card-highlight{0%{box-shadow:0 0 0 2px var(--accent),0 0 24px var(--accent-subtle);border-color:var(--accent)}60%{box-shadow:0 0 0 2px var(--accent),0 0 24px var(--accent-subtle);border-color:var(--accent)}to{box-shadow:0 0 0 0 transparent,0 0 0 transparent;border-color:var(--border)}}@media (prefers-reduced-motion: reduce){.card-highlight{animation:none;border-color:var(--accent)}}.card-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.card-title{font-size:1rem;font-weight:600;margin:0}.auth-card{background:var(--bg-elevated);border:1px solid var(--border);border-radius:.75rem;padding:1.25rem;transition:opacity .2s,border-color .2s;cursor:pointer}.auth-card:hover{border-color:var(--accent)}.auth-card+.auth-card{margin-top:.75rem}.auth-card.expired{opacity:.5}.auth-card-header{display:flex;align-items:center;gap:.75rem;margin-bottom:.5rem;flex-wrap:wrap}.auth-card-path{font-weight:600;font-size:.95rem}.auth-card-time{font-size:.8rem;color:var(--text-tertiary);margin-left:auto}.success-card{background:var(--accent-subtle);border:1px solid var(--accent);border-radius:.75rem;padding:1.5rem;text-align:center}.success-card-title{font-size:1.1rem;font-weight:700;color:var(--accent);margin-bottom:.5rem}.success-card-hash{font-family:SF Mono,Monaco,monospace;font-size:.8rem;color:var(--text-secondary);word-break:break-all;margin-bottom:.5rem}.status-badge{display:inline-block;padding:.2rem .6rem;border-radius:9999px;font-size:.7rem;font-weight:600;letter-spacing:.03em;text-transform:uppercase}.status-active{background:var(--accent-subtle);color:var(--accent)}.status-pending{background:var(--danger-subtle);color:var(--danger)}.status-expired{background:var(--bg-elevated);color:var(--text-tertiary)}.status-revoked{background:var(--danger-subtle);color:var(--danger)}.int-chip{display:inline-flex;align-items:center;gap:.4rem;padding:.2rem .6rem;border-radius:9999px;font-size:.7rem;font-weight:600;letter-spacing:.03em;text-transform:uppercase}.int-chip .int-dot{width:6px;height:6px;border-radius:50%;flex:none}.int-chip-ok{background:var(--success-subtle);color:var(--success)}.int-chip-ok .int-dot{background:var(--success)}.int-chip-bad{background:var(--danger-subtle);color:var(--danger)}.int-chip-bad .int-dot{background:var(--danger)}.int-chip-idle{background:var(--accent-subtle);color:var(--accent)}.int-chip-idle .int-dot{background:#bbb}.int-details{border-top:1px solid var(--border);padding-top:.7rem;margin-top:.25rem}.int-details>summary{cursor:pointer;font-size:.78rem;color:var(--text-muted);list-style:none;display:flex;align-items:center;gap:.35rem;-webkit-user-select:none;user-select:none}.int-details>summary::-webkit-details-marker{display:none}.int-details>summary:before{content:"▸";font-size:.7rem;transition:transform .15s}.int-details[open]>summary:before{transform:rotate(90deg)}.int-meta{display:grid;grid-template-columns:auto 1fr;gap:.4rem 1rem;font-size:.8rem;margin-top:.7rem}.int-meta .int-k{color:var(--text-muted)}.int-account{font-size:.85rem;display:flex;align-items:center;gap:.5rem}.int-account .int-none{color:var(--text-muted);font-style:italic}.domain-badge{display:inline-block;padding:.2rem .5rem;font-size:.8rem;font-family:SF Mono,Monaco,monospace;background:var(--bg-main);border:1px solid var(--border);border-radius:.25rem;color:var(--text-secondary)}.domain-attested{color:var(--accent);border-color:var(--accent)}.domain-missing{color:var(--text-tertiary)}.profile-badge{display:inline-block;padding:.15rem .5rem;font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.03em;background:var(--accent-subtle);color:var(--accent);border-radius:.25rem}.step-indicator{display:flex;align-items:center;justify-content:center;margin-bottom:2rem;gap:0}.step{display:flex;align-items:center;position:relative}.step-circle{width:2rem;height:2rem;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:600;border:2px solid var(--border);color:var(--text-tertiary);background:var(--bg-main);flex-shrink:0;transition:all .2s}.step-circle.completed{background:var(--accent);border-color:var(--accent);color:#fff;cursor:pointer}.step-circle.current{border-color:var(--accent);color:var(--accent);box-shadow:0 0 0 3px var(--primary-focus-ring)}.step-line{width:2rem;height:2px;background:var(--border);flex-shrink:1;min-width:.5rem}.step-line.completed{background:var(--accent)}.step-label{font-size:.65rem;color:var(--text-tertiary);position:absolute;top:2.25rem;white-space:nowrap;text-align:center}.alert{padding:.75rem 1rem;border-radius:.5rem;font-size:.875rem;margin-bottom:1rem;color:#fff}.alert-error{background:var(--danger)}.alert-info{background:var(--accent)}.alert-success{background:#1a7f37}.nav-tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:1.5rem}.nav-tab{padding:.75rem 1.25rem;font-size:.9rem;font-weight:500;color:var(--text-tertiary);background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:color .2s;margin-bottom:-1px;font-family:inherit}.nav-tab:hover{color:var(--text-primary)}.nav-tab.active{color:var(--accent);border-bottom-color:var(--accent);font-weight:600}.review-grid{display:grid;grid-template-columns:auto 1fr;gap:.5rem 1rem;font-size:.9rem;margin-bottom:1.5rem}.review-grid dt{font-weight:600;color:var(--text-secondary);padding:.25rem 0}.review-grid dd{color:var(--text-primary);padding:.25rem 0}.gate-content-block{background:var(--bg-main);border:1px solid var(--border);border-radius:.5rem;padding:1rem;font-size:.875rem;margin-bottom:1.5rem}.gate-content-item{margin-bottom:.5rem}.gate-content-item:last-child{margin-bottom:0}.gate-content-label{font-weight:600;color:var(--text-secondary);font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;margin-bottom:.125rem}.gate-content-text{color:var(--text-primary);line-height:1.5}.empty-state{text-align:center;padding:3rem 1.5rem;color:var(--text-tertiary)}.empty-state-icon{font-size:2.5rem;margin-bottom:.75rem;opacity:.5}.empty-state-title{font-size:1rem;font-weight:600;color:var(--text-secondary);margin-bottom:.25rem}.empty-state-text{font-size:.875rem;color:var(--text-tertiary);margin-bottom:1rem;max-width:28rem;margin-left:auto;margin-right:auto}.login-split{margin-top:var(--nav-height);display:flex;min-height:calc(100vh - var(--nav-height))}.login-split-left{flex:1;background:var(--bg-main);display:flex;align-items:center;justify-content:center;padding:4rem 3rem}.login-split-right{flex:1;background:var(--bg-elevated);border-left:1px solid var(--border);display:flex;align-items:center;justify-content:center;padding:4rem 3rem}.login-page{display:flex;align-items:center;justify-content:center;min-height:calc(100vh - var(--nav-height));padding:2rem}.login-card{width:100%;max-width:420px}.login-header{text-align:center;margin-bottom:2rem}.login-title{font-size:1.5rem;font-weight:700;margin-bottom:.5rem}.login-subtitle{font-size:.9rem;color:var(--text-secondary);line-height:1.5}.login-divider{display:flex;align-items:center;gap:.75rem;margin:1.5rem 0;font-size:.8rem;color:var(--text-tertiary)}.login-divider:before,.login-divider:after{content:"";flex:1;height:1px;background:var(--border)}.onboarding-card{background:var(--bg-elevated);border:1px solid var(--border);border-radius:.75rem;padding:2rem;max-width:32rem;margin:0 auto}.onboarding-step{display:flex;gap:1rem;margin-bottom:1.25rem}.onboarding-step:last-child{margin-bottom:0}.onboarding-num{width:1.75rem;height:1.75rem;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:700;flex-shrink:0;margin-top:.1rem}.onboarding-num.done{background:var(--accent)}.onboarding-text h4{font-size:.9rem;margin-bottom:.125rem}.onboarding-text p{font-size:.825rem;color:var(--text-secondary);line-height:1.5}.selection-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(14rem,1fr));gap:.75rem;margin-bottom:1.5rem}.selection-card{background:var(--bg-elevated);border:2px solid var(--border);border-radius:.75rem;padding:1.25rem;cursor:pointer;transition:all .15s}.selection-card:hover{border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 12px #0000000f}.selection-card.selected{border-color:var(--accent);background:var(--accent-subtle)}.selection-card-title{font-size:1rem;font-weight:600;margin-bottom:.25rem}.selection-card-subtitle{font-size:.8rem;color:var(--text-secondary);margin-bottom:.5rem}.selection-card-id{font-size:.7rem;color:var(--text-tertiary);margin-bottom:.375rem;word-break:break-all}.selection-card-meta{display:flex;gap:.375rem;flex-wrap:wrap}.dashboard-section{margin-bottom:2rem}.section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem}.section-title{font-size:1rem;font-weight:600}.section-count{font-size:.8rem;color:var(--text-tertiary)}.ttl-badge{display:inline-flex;align-items:center;gap:.25rem;font-size:.75rem;font-weight:600;padding:.2rem .5rem;border-radius:.25rem;font-family:SF Mono,Monaco,monospace}.ttl-ok{background:var(--accent-subtle);color:var(--accent)}.ttl-warn{background:var(--danger-subtle);color:var(--danger)}.quick-actions{display:grid;grid-template-columns:repeat(auto-fill,minmax(16rem,1fr));gap:.75rem;margin-bottom:2rem}.quick-action-card{background:var(--bg-elevated);border:1px solid var(--border);border-radius:.75rem;padding:1.25rem;cursor:pointer;transition:all .15s;display:flex;align-items:flex-start;gap:.875rem;text-decoration:none;color:inherit}.quick-action-card:hover{border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 12px #0000000f;text-decoration:none}.quick-action-icon{width:2.5rem;height:2.5rem;border-radius:.5rem;background:var(--accent-subtle);color:var(--accent);display:flex;align-items:center;justify-content:center;font-size:1.25rem;flex-shrink:0}.quick-action-title{font-size:.9rem;font-weight:600;margin-bottom:.125rem}.quick-action-desc{font-size:.8rem;color:var(--text-secondary)}.pr-card{background:var(--bg-elevated);border:1px solid var(--border);border-radius:.75rem;padding:1.25rem;cursor:pointer;transition:border-color .15s}.pr-card:hover{border-color:var(--accent)}.pr-card+.pr-card{margin-top:.75rem}.pr-card-header{display:flex;align-items:center;gap:.5rem;margin-bottom:.375rem}.pr-card-repo{font-size:.75rem;color:var(--text-tertiary);font-family:SF Mono,Monaco,monospace}.pr-card-title{font-weight:600;font-size:.95rem}.pr-card-footer{display:flex;align-items:center;gap:.75rem;margin-top:.5rem;flex-wrap:wrap}.member-row{display:flex;align-items:center;gap:.75rem;padding:.75rem 0;border-bottom:1px solid var(--border)}.member-row:last-child{border-bottom:none}.member-avatar{width:2rem;height:2rem;border-radius:50%;background:var(--accent-subtle);color:var(--accent);display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:700;flex-shrink:0}.member-info{flex:1}.member-name{font-size:.9rem;font-weight:600}.member-email{font-size:.75rem;color:var(--text-tertiary)}.member-domains{display:flex;gap:.25rem;flex-wrap:wrap}.char-counter{text-align:right;font-size:.75rem;color:var(--text-tertiary);margin-top:.25rem}.invite-input-row{display:flex;gap:.5rem}.invite-input-row .form-input{flex:1}.service-card{background:var(--bg-elevated);border:1px solid var(--border);border-radius:.75rem;padding:1.25rem;display:flex;align-items:center;gap:1rem;transition:border-color .15s}.service-card:hover{border-color:var(--accent)}.service-card+.service-card{margin-top:.75rem}.service-icon{width:2.75rem;height:2.75rem;border-radius:.625rem;display:flex;align-items:center;justify-content:center;font-size:1.25rem;flex-shrink:0}.service-icon-configured{background:var(--success-subtle);color:var(--success)}.service-icon-missing{background:var(--bg-main);color:var(--text-tertiary);border:1px dashed var(--border)}.service-icon-error{background:var(--danger-subtle);color:var(--danger)}.service-info{flex:1}.service-name{font-size:.95rem;font-weight:600;margin-bottom:.125rem}.service-desc{font-size:.8rem;color:var(--text-secondary);margin-bottom:.375rem}.service-status{display:inline-flex;align-items:center;gap:.375rem;font-size:.75rem;font-weight:600}.service-status-dot{width:6px;height:6px;border-radius:50%}.service-status-connected .service-status-dot{background:var(--success)}.service-status-connected{color:var(--success)}.service-status-missing .service-status-dot{background:var(--text-tertiary)}.service-status-missing{color:var(--text-tertiary)}.service-status-error .service-status-dot{background:var(--danger)}.service-status-error{color:var(--danger)}.service-tools{display:flex;gap:.25rem;flex-wrap:wrap;margin-top:.25rem}.service-tool-badge{display:inline-block;padding:.1rem .4rem;font-size:.65rem;font-family:SF Mono,Monaco,monospace;background:var(--bg-main);border:1px solid var(--border);border-radius:.25rem;color:var(--text-tertiary)}.template-card{display:flex;flex-direction:column;align-items:flex-start;text-align:left;padding:.75rem;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-main);cursor:pointer;transition:border-color .15s,background .15s;font-family:inherit;font-size:inherit;color:inherit}.template-card:hover{border-color:var(--btn-primary-bg);background:var(--bg-elevated)}.template-mode{display:inline-block;padding:.1rem .4rem;font-size:.6rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;border-radius:3px}.template-mode-auto{background:#16a34a1f;color:#16a34a}.template-mode-review{background:#d977061f;color:#d97706}.template-risk{font-size:.6rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--text-tertiary)}.template-tag{display:inline-block;padding:.1rem .35rem;font-size:.6rem;background:var(--bg-elevated);border:1px solid var(--border);border-radius:3px;color:var(--text-secondary)}@media (max-width: 560px){.template-card{grid-column:span 1!important}}.modal-backdrop{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;z-index:1500;display:flex;align-items:center;justify-content:center}.modal{background:var(--bg-elevated);border-radius:.75rem;width:100%;max-width:32rem;max-height:calc(100vh - 4rem);overflow-y:auto;box-shadow:0 16px 48px #00000026}.modal-header{padding:1.5rem 1.5rem 0;display:flex;justify-content:space-between;align-items:flex-start}.modal-title{font-size:1.125rem;font-weight:700}.modal-close{width:2rem;height:2rem;background:none;border:none;cursor:pointer;color:var(--text-tertiary);font-size:1.25rem;display:flex;align-items:center;justify-content:center;border-radius:.375rem}.modal-close:hover{background:var(--bg-main);color:var(--text-primary)}.modal-body{padding:1.25rem 1.5rem}.modal-footer{padding:0 1.5rem 1.5rem;display:flex;gap:.5rem;justify-content:flex-end}.status-banner{padding:.625rem 1rem;font-size:.8rem;display:flex;align-items:center;gap:.5rem;border-radius:.5rem;margin-bottom:1rem}.status-banner-warn{background:var(--accent-subtle);color:var(--accent-hover);border:1px solid var(--accent)}.status-banner-error{background:var(--danger-subtle);color:var(--danger);border:1px solid var(--danger)}.status-banner-success{background:var(--success-subtle);color:var(--success);border:1px solid var(--success)}.status-banner-icon{font-size:1rem;flex-shrink:0}.status-banner-text{flex:1}.status-banner a{font-weight:600}.vault-progress{display:flex;flex-direction:column;align-items:center;gap:.75rem;padding:1.5rem 0}.vault-spinner{width:2rem;height:2rem;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.vault-progress-text{font-size:.875rem;color:var(--text-secondary)}.service-health-row{display:flex;align-items:center;gap:.75rem;padding:.625rem 0;border-bottom:1px solid var(--border);font-size:.85rem}.service-health-row:last-child{border-bottom:none}.service-health-indicator{width:8px;height:8px;border-radius:50%;flex-shrink:0}.service-health-indicator.connected{background:var(--success)}.service-health-indicator.missing{background:var(--text-tertiary)}.service-health-indicator.error{background:var(--danger)}.service-health-name{font-weight:600;flex:1}.service-health-detail{font-size:.75rem;color:var(--text-tertiary)}.cred-field{position:relative}.cred-field .form-input{padding-right:3rem}.cred-toggle{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);background:none;border:none;font-size:.75rem;color:var(--text-tertiary);cursor:pointer;padding:.25rem}.cred-toggle:hover{color:var(--accent)}.context-strip{background:var(--bg-elevated);border:1px solid var(--border);border-radius:.5rem;padding:.625rem 1rem;margin-bottom:1.5rem;display:flex;align-items:center;gap:.75rem;font-size:.8rem}.context-strip-label{font-weight:600;color:var(--text-secondary)}.timeline{position:relative;padding-left:2rem}.timeline:before{content:"";position:absolute;left:.5rem;top:0;bottom:0;width:2px;background:var(--border)}.timeline-event{position:relative;margin-bottom:1.5rem}.timeline-event:before{content:"";position:absolute;left:-1.625rem;top:.5rem;width:10px;height:10px;border-radius:50%;background:var(--accent);border:2px solid var(--bg-main)}.timeline-event.expired:before{background:var(--text-tertiary)}.error-message{padding:.75rem 1rem;background:#dc262614;border:1px solid var(--danger);border-radius:.5rem;color:var(--danger);font-size:.875rem;margin-bottom:1rem}.mobile-menu-btn{display:none;width:2.25rem;height:2.25rem;background:none;border:1px solid var(--border);border-radius:.375rem;cursor:pointer;color:var(--text-secondary);font-size:1.1rem;align-items:center;justify-content:center;font-family:inherit}.mobile-menu-btn:hover{color:var(--accent);border-color:var(--accent)}.mobile-menu-backdrop{display:none;position:fixed;top:0;right:0;bottom:0;left:0;background:#0006;z-index:2000}.mobile-menu-panel{display:none;position:fixed;top:0;right:0;bottom:0;width:16rem;background:var(--bg-elevated);border-left:1px solid var(--border);z-index:2001;flex-direction:column;overflow-y:auto}.mobile-menu-header{padding:1.25rem 1rem;border-bottom:1px solid var(--border);display:flex;flex-direction:column;gap:.25rem}.mobile-menu-nav{flex:1;padding:.5rem;display:flex;flex-direction:column}.mobile-menu-item{display:flex;align-items:center;gap:.625rem;padding:.5rem .75rem;margin-bottom:.125rem;border-radius:.375rem;font-size:.85rem;color:var(--text-secondary);text-decoration:none;transition:background .15s,color .15s}.mobile-menu-item:hover{background:var(--accent-subtle);color:var(--text-primary)}.mobile-menu-item.active{background:var(--accent-subtle);color:var(--accent);font-weight:600}.mobile-menu-item .icon{width:1.125rem;text-align:center;font-size:.9rem;flex-shrink:0}.mobile-menu-footer{padding:.75rem 1rem;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:.25rem}.mobile-menu-action{display:flex;align-items:center;gap:.625rem;padding:.5rem .75rem;border-radius:.375rem;font-size:.85rem;color:var(--text-secondary);background:none;border:none;cursor:pointer;font-family:inherit;text-align:left}.mobile-menu-action:hover{background:var(--accent-subtle);color:var(--text-primary)}.mobile-menu-action .icon{width:1.125rem;text-align:center;font-size:.9rem;flex-shrink:0}@media (max-width: 768px){.sidebar{display:none}.main-content{margin-left:0}.quick-actions,.selection-grid{grid-template-columns:1fr}.nav-actions-desktop,.step-label-text{display:none}.mobile-menu-btn{display:flex}.mobile-menu-backdrop{display:block}.mobile-menu-panel{display:flex}.login-split{flex-direction:column;min-height:auto}.login-split-left{padding:3rem 1.5rem;align-items:flex-start}.login-split-right{max-width:none;border-left:none;border-top:1px solid var(--border);padding:2.5rem 1.5rem}}.update-banner{position:fixed;top:var(--nav-height);left:0;right:0;z-index:900;background:#c0392b;color:#fff;border-top:1px solid rgba(0,0,0,.2);border-bottom:1px solid rgba(0,0,0,.2);padding:.6rem 1.25rem;display:flex;flex-direction:column;gap:.6rem}body.has-update-banner .sidebar{top:calc(var(--nav-height) + var(--update-banner-h, 0px));height:calc(100vh - var(--nav-height) - var(--update-banner-h, 0px))}body.has-update-banner .main-content{margin-top:calc(var(--nav-height) + var(--update-banner-h, 0px))}.update-banner-row{display:flex;align-items:center;justify-content:space-between;gap:1rem;min-width:0}.update-banner-cmd-row{flex-wrap:wrap}.update-banner-text{font-size:.9rem;font-weight:600;color:#fff}.update-banner-cmd{display:block;flex:1;min-width:0;font-size:.78rem;padding:.5rem .75rem;word-break:break-all;white-space:pre-wrap;background:#0003;border:1px solid rgba(255,255,255,.65);border-radius:.375rem;font-family:SF Mono,Fira Code,Fira Mono,Menlo,Consolas,monospace;color:#fff;-webkit-user-select:all;user-select:all}.update-banner-hint{margin:0;font-size:.8rem;color:#ffffffe6}.update-banner .btn-secondary{color:#fff;border-color:#ffffffa6;background:transparent}.update-banner .btn-secondary:hover:not(:disabled){background:#ffffff26;color:#fff;border-color:#fff}.update-banner-x{background:transparent;border:none;color:#ffffffd9;cursor:pointer;font-size:1.15rem;line-height:1;padding:0 .25rem}.update-banner-x:hover{color:#fff}.update-banner.is-live{background:var(--success)}body.is-updating .top-nav,body.is-updating .sidebar,body.is-updating .main-content,body.is-updating .update-banner{filter:blur(5px);pointer-events:none;-webkit-user-select:none;user-select:none}.update-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:2000;display:flex;align-items:center;justify-content:center;background:#11111159;padding:1rem}.update-overlay-card{background:var(--bg-elevated);border:1px solid var(--border);border-radius:1rem;padding:2rem 2.25rem;max-width:460px;width:100%;text-align:center;box-shadow:0 20px 60px #00000040}.update-overlay-card h2{margin:0 0 .6rem;font-size:1.15rem}.update-overlay-card p{margin:0 0 .5rem;font-size:.88rem;color:var(--text-muted);line-height:1.55}.update-spinner{width:2.25rem;height:2.25rem;border:3px solid var(--border);border-top-color:var(--text-primary);border-radius:50%;margin:0 auto 1.1rem;animation:update-spin 1s linear infinite}@keyframes update-spin{to{transform:rotate(360deg)}}.update-cmd-row{display:flex;gap:.5rem;align-items:flex-start;margin:.9rem 0;text-align:left}.update-cmd{flex:1;min-width:0;font-size:.78rem;padding:.6rem .75rem;background:#111;color:#fff;border-radius:.45rem;font-family:SF Mono,Fira Code,Fira Mono,Menlo,Consolas,monospace;word-break:break-all;white-space:pre-wrap}.update-status{font-size:.8rem;color:var(--text-muted);margin:.4rem 0 1.1rem;display:flex;align-items:center;justify-content:center;gap:.4rem}.update-pulse{width:.55rem;height:.55rem;border-radius:50%;background:var(--success);animation:update-pulse 1.2s ease infinite}@keyframes update-pulse{0%,to{opacity:.3}50%{opacity:1}}.update-overlay-actions{display:flex;gap:.6rem;justify-content:center}.search-filter-bar{display:flex;gap:.5rem;margin-bottom:.75rem}.search-input-wrap{flex:1;position:relative}.search-icon{position:absolute;left:.75rem;top:50%;transform:translateY(-50%);color:var(--text-tertiary);font-size:1rem;pointer-events:none}.search-input{padding-left:2.25rem!important;padding-right:2rem!important}.search-clear{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);background:none;border:none;color:var(--text-tertiary);font-size:1.1rem;cursor:pointer;padding:.25rem;line-height:1}.search-clear:hover{color:var(--text-primary)}.filter-panel{background:var(--bg-elevated);border:1px solid var(--border);border-radius:.5rem;padding:1rem;margin-bottom:.75rem;display:flex;flex-direction:column;gap:.75rem}.filter-section{display:flex;align-items:baseline;gap:.75rem}.filter-label{font-size:.75rem;font-weight:600;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:.04em;min-width:5rem;flex-shrink:0}.filter-chips{display:flex;flex-wrap:wrap;gap:.375rem}.filter-chip{font-family:inherit;font-size:.8rem;padding:.3rem .625rem;border-radius:1rem;border:1px solid var(--border);background:var(--bg-main);color:var(--text-secondary);cursor:pointer;transition:all .15s}.filter-chip:hover{border-color:var(--accent);color:var(--text-primary)}.filter-chip.selected{background:var(--accent-subtle);border-color:var(--accent);color:var(--accent);font-weight:600}.active-filters{display:flex;flex-wrap:wrap;align-items:center;gap:.375rem;margin-bottom:.75rem}.active-chip{display:inline-flex;align-items:center;gap:.25rem;font-size:.75rem;padding:.2rem .5rem;border-radius:1rem;background:var(--accent-subtle);color:var(--accent);cursor:pointer;transition:background .15s}.active-chip:hover{background:var(--accent);color:var(--bg-elevated)}.chip-remove{font-size:.85rem;line-height:1}.clear-filters{font-family:inherit;font-size:.75rem;color:var(--text-tertiary);background:none;border:none;cursor:pointer;padding:.2rem 0}.clear-filters:hover{color:var(--text-primary)}.receipt-action{display:inline-block;font-size:.75rem;font-weight:600;padding:.15rem .5rem;border-radius:1rem;background:var(--bg-main);border:1px solid var(--border);color:var(--text-secondary);font-family:SF Mono,Monaco,monospace}.bounds-section,.context-section{background:var(--bg-elevated);border:2px solid var(--edit-border);border-radius:.75rem;padding:1.25rem;margin-bottom:1.25rem;transition:border-color .15s}.bounds-section:hover,.bounds-section:focus-within,.context-section:hover,.context-section:focus-within{border-color:var(--accent-hover)}.bounds-section-header{display:flex;align-items:flex-start;gap:.625rem;margin-bottom:1rem}.bounds-section-icon{font-size:1.1rem;flex-shrink:0;margin-top:.1rem}.bounds-section-title{font-size:.9rem;font-weight:700;color:var(--text-primary)}.bounds-section-subtitle{font-size:.75rem;color:var(--text-tertiary)}.bounds-fields-grid{display:flex;flex-direction:column;gap:.75rem}.bounds-field-row{display:flex;flex-direction:row;align-items:center;justify-content:space-between;gap:1rem;width:100%}.bounds-field-label{flex:1;min-width:0}.bounds-field-label .form-label{display:inline;margin-bottom:0}.bounds-field-input{flex-shrink:0;display:flex;flex-direction:column;align-items:flex-end}.bounds-field-input .bounded-input{align-items:flex-end}.bounds-field-input .bounded-input-presets{justify-content:flex-end}.bounds-field-label-inline{display:flex;align-items:center;gap:.375rem;margin-bottom:.375rem}.bounds-field-label-inline .form-label{margin-bottom:0}.field-info-toggle{display:inline-flex;align-items:center;justify-content:center;width:1.125rem;height:1.125rem;border-radius:50%;border:1px solid var(--border);background:var(--bg-main);color:var(--text-tertiary);font-size:.65rem;font-weight:700;cursor:pointer;flex-shrink:0;transition:color .15s,border-color .15s;font-family:inherit;line-height:1;padding:0}.field-info-toggle:hover{color:var(--accent);border-color:var(--accent)}.field-description{margin-top:.25rem;margin-bottom:.25rem}@media (max-width: 768px){.bounds-field-row{flex-direction:column;gap:.375rem}}.bounded-input{display:flex;flex-direction:column;align-items:flex-start;gap:.375rem}.bounded-input-field{display:inline-flex;align-items:stretch;border:1px solid var(--border-field);border-radius:.5rem;overflow:hidden;background:var(--bg-field);color:var(--text-field)}.stepper-btn{display:flex;align-items:center;justify-content:center;width:2.25rem;height:2.25rem;background:transparent;border:none;cursor:pointer;font-size:1.1rem;font-weight:600;color:var(--text-field);font-family:inherit;flex-shrink:0;transition:background .15s}.stepper-btn:hover:not(:disabled){background:var(--placeholder-field)}.stepper-btn:disabled{opacity:.35;cursor:not-allowed}.stepper-input{width:4.5rem;text-align:center;border:none;border-left:1px solid var(--placeholder-field);border-right:1px solid var(--placeholder-field);padding:.5rem .25rem;font-size:.9rem;font-family:inherit;color:var(--text-field);background:var(--bg-field);outline:none;-moz-appearance:textfield}.stepper-input::-webkit-outer-spin-button,.stepper-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.stepper-input:focus{box-shadow:inset 0 0 0 2px var(--field-focus-ring)}.bounded-input-unit{display:inline-flex;align-items:center;padding:0 .75rem;font-size:.8rem;color:var(--text-field);opacity:.7;white-space:nowrap;-webkit-user-select:none;user-select:none;background:transparent}.bounded-input-presets{display:flex;flex-wrap:wrap;gap:.25rem}.preset-chip{font-family:inherit;font-size:.75rem;padding:.2rem .625rem;border:1px solid var(--border);border-radius:999px;background:transparent;color:var(--text-secondary);cursor:pointer;transition:background .15s,color .15s,border-color .15s}.preset-chip:hover{background:var(--accent-subtle);color:var(--accent);border-color:var(--accent)}.preset-chip-selected{background:var(--accent);color:var(--bg-main);border-color:var(--accent)}.bounded-input-echo{font-size:.75rem;color:var(--text-secondary);font-style:italic}.field-label-with-tip{display:inline-flex;align-items:center;gap:.375rem;position:relative}.field-tip-trigger{display:inline-flex;align-items:center;justify-content:center;width:1.125rem;height:1.125rem;border-radius:999px;border:1px solid var(--border);background:transparent;color:var(--text-secondary);font-size:.7rem;font-style:italic;font-family:serif;font-weight:600;line-height:1;cursor:help;padding:0}.field-tip-trigger:hover,.field-tip-trigger[aria-expanded=true]{border-color:var(--accent);color:var(--accent)}.field-tip-bubble{position:absolute;top:calc(100% + .375rem);left:0;z-index:10;width:max-content;max-width:22rem;padding:.5rem .75rem;background:var(--bg-elevated);border:1px solid var(--border);border-radius:.375rem;font-size:.8rem;color:var(--text-primary);line-height:1.45;box-shadow:0 4px 12px #00000040;pointer-events:none}.tag-input{display:flex;flex-wrap:wrap;align-items:center;gap:.375rem;padding:.375rem .5rem;background:var(--bg-field);border:1px solid var(--border-field);border-radius:.5rem;cursor:text;min-height:2.5rem;transition:box-shadow .2s,border-color .2s;color:var(--text-field)}.tag-input:focus-within{box-shadow:0 0 0 3px var(--field-focus-ring)}.tag-input-invalid{border-color:var(--danger)}.tag-input-invalid:focus-within{box-shadow:0 0 0 3px #dc26262e}.tag-pill{display:inline-flex;align-items:center;gap:.25rem;font-size:.8rem;padding:.2rem .5rem;border-radius:1rem;background:var(--placeholder-field);color:var(--text-field);font-weight:500;white-space:nowrap}.tag-remove{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;font-size:.9rem;line-height:1;color:var(--text-field);cursor:pointer;padding:0;margin-left:.125rem;opacity:.7;transition:opacity .15s}.tag-remove:hover{opacity:1}.tag-input-field{flex:1;min-width:8rem;border:none;outline:none;background:transparent;font-size:.875rem;font-family:inherit;color:var(--text-field);padding:.2rem 0}.tag-input-field::placeholder{color:var(--placeholder-field);opacity:1}.checkbox-group{display:flex;flex-wrap:wrap;gap:.5rem}.checkbox-pill{display:inline-flex;align-items:center;gap:.35rem;font-size:.85rem;padding:.35rem .75rem;border-radius:1rem;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-secondary);cursor:pointer;transition:all .15s;-webkit-user-select:none;user-select:none}.checkbox-pill input[type=checkbox]{display:none}.checkbox-pill:hover{border-color:var(--accent);color:var(--text-primary)}.checkbox-pill-selected{background:var(--btn-primary-bg);border-color:var(--btn-primary-bg);color:var(--btn-primary-fg);font-weight:500}.checkbox-pill-selected:hover{background:var(--btn-primary-bg-hover);border-color:var(--btn-primary-bg-hover);color:var(--btn-primary-fg)}@keyframes hap-skeleton-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.skeleton{display:block;position:relative;border-radius:.375rem;background-color:var(--bg-main);background-image:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.06) 20%,rgba(255,255,255,.16) 50%,rgba(255,255,255,.06) 80%,transparent 100%);background-size:200% 100%;background-repeat:no-repeat;animation:hap-skeleton-shimmer 1.6s ease-in-out infinite;will-change:background-position}[data-theme=light] .skeleton,:root:not([data-theme=dark]) .skeleton{background-image:linear-gradient(90deg,transparent 0%,rgba(0,0,0,.03) 20%,rgba(0,0,0,.08) 50%,rgba(0,0,0,.03) 80%,transparent 100%)}.skeleton-title{height:1.5rem;width:3rem;margin:0 auto}.skeleton-line{height:.75rem;width:100%}.skeleton-line-sm{height:.65rem;width:60%}.skeleton-card{background:var(--bg-elevated);border:1px solid var(--border);border-radius:.5rem;padding:.75rem 1rem;box-shadow:0 1px 2px #0000000a;display:flex;align-items:center;gap:.75rem}.skeleton-dot{width:.5rem;height:.5rem;border-radius:50%;flex-shrink:0}@media (prefers-reduced-motion: reduce){.skeleton{animation:none;background-image:none}}.intent-layout{display:grid;grid-template-columns:minmax(320px,1fr) minmax(420px,1.4fr);gap:1.25rem;align-items:start}.intent-pane.chat{display:flex;flex-direction:column;gap:.75rem;position:sticky;top:1rem;align-self:start;max-height:calc(100vh - 4rem)}.intent-pane.document{display:flex;flex-direction:column;gap:.875rem}.intent-pane .card-title{font-size:1.25rem;margin:0 0 1rem}.intent-textarea{width:100%;background:var(--bg-field);border:1px solid var(--border-field);border-radius:.5rem;padding:.625rem .75rem;color:var(--text-field);font-size:.9rem;line-height:1.5;resize:vertical;min-height:22rem;outline:none;font-family:inherit;transition:box-shadow .15s}.intent-textarea:focus{box-shadow:0 0 0 3px var(--field-focus-ring)}.intent-textarea::placeholder{color:var(--placeholder-field);opacity:1}.intent-footer{display:flex;align-items:center;gap:.625rem;margin-top:.5rem}.intent-continue-btn{flex:1}.intent-chat{display:flex;flex-direction:column;gap:.75rem;flex:1;min-height:0}.intent-chat .chat-header{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:var(--text-muted)}.intent-chat .chat-messages{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:1rem;padding:.25rem 0;min-height:14rem;max-height:32rem}.intent-chat .chat-msg{display:flex;max-width:100%}.intent-chat .chat-msg.assistant{justify-content:flex-start}.intent-chat .chat-msg.assistant .chat-msg-body{max-width:100%;color:var(--text-primary);font-size:.9rem;line-height:1.65;white-space:pre-wrap;word-break:break-word}.intent-chat .chat-msg.user{justify-content:flex-end}.intent-chat .chat-msg.user .chat-msg-body{max-width:85%;background:var(--bg-main);border:1px solid var(--border);border-radius:1.125rem;padding:.55rem .875rem;color:var(--text-primary);font-size:.9rem;line-height:1.55;white-space:pre-wrap;word-break:break-word}.intent-chat .chat-msg-prose{white-space:pre-wrap;word-break:break-word}.intent-chat .chat-msg-thinking{color:var(--text-muted);font-style:italic}.intent-chat .chat-fenced-block{display:block;background:var(--bg-elevated);border:1px solid var(--border);border-radius:.5rem;padding:.625rem .75rem;margin:.5rem 0;font-family:SF Mono,Monaco,Cascadia Code,monospace;font-size:.8rem;white-space:pre-wrap;color:var(--text-primary);line-height:1.55}.intent-chat .chat-apply-btn{margin-top:.5rem}.intent-chat .chat-composer{display:flex;flex-direction:column;gap:.375rem}.intent-chat .chat-composer-pill{display:flex;align-items:center;gap:.5rem;background:var(--bg-field);border:1px solid var(--border-field);border-radius:1.5rem;padding:.25rem .25rem .25rem 1rem;transition:box-shadow .15s;color:var(--text-field)}.intent-chat .chat-composer-pill:focus-within{box-shadow:0 0 0 3px var(--field-focus-ring)}.intent-chat .chat-input{flex:1;resize:none;background:transparent;border:none;padding:.5rem 0;color:var(--text-field);font-size:.9rem;line-height:1.4;outline:none;font-family:inherit;max-height:8rem;align-self:stretch}.intent-chat .chat-input::placeholder{color:var(--placeholder-field);opacity:1}.intent-chat .chat-send-btn{flex-shrink:0;width:2rem;height:2rem;border-radius:999px;background:var(--text-field);color:var(--bg-field);border:none;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:1rem;font-weight:700;line-height:1;align-self:center;transition:opacity .15s,transform .1s}.intent-chat .chat-send-btn:hover:not(:disabled){opacity:.85}.intent-chat .chat-send-btn:active:not(:disabled){transform:scale(.95)}.intent-chat .chat-send-btn:disabled{opacity:.35;cursor:not-allowed}.intent-chat .chat-composer-hint{font-size:.72rem;color:var(--text-muted);padding-left:.5rem}.hint-box{display:flex;gap:.625rem;padding:.75rem .875rem;background:var(--bg-main);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:.5rem;font-size:.85rem;color:var(--text-primary);line-height:1.55;margin-top:.25rem}.hint-icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:1.125rem;height:1.125rem;border-radius:999px;border:1px solid var(--accent);color:var(--accent);font-size:.7rem;font-style:italic;font-family:serif;font-weight:700;line-height:1;margin-top:.125rem}.hint-body{flex:1;min-width:0}.hint-head{font-weight:600;margin-bottom:.25rem}@media (max-width: 1024px){.intent-layout{grid-template-columns:1fr}.intent-pane.chat{position:static;max-height:none}}@media (max-width: 768px){.intent-layout>.intent-pane.chat{display:none}.floating-help{display:inline-flex}}.floating-help{display:none;align-items:center;gap:.5rem;position:fixed;bottom:1rem;right:1rem;z-index:5;background:var(--btn-primary-bg);color:var(--btn-primary-fg);border:2px solid var(--btn-primary-bg);border-radius:999px;padding:.625rem 1.125rem;font-weight:700;font-size:.85rem;cursor:pointer;font-family:inherit;box-shadow:0 6px 18px var(--primary-glow)}.floating-help:hover{background:var(--btn-primary-bg-hover);border-color:var(--btn-primary-bg-hover)}.bottom-sheet{position:fixed;left:0;right:0;bottom:0;height:88vh;background:var(--bg-elevated);border-top:1px solid var(--border);border-radius:.75rem .75rem 0 0;z-index:11;padding:1rem 1rem 1.25rem;overflow-y:auto;display:flex;flex-direction:column;gap:.75rem;transform:translateY(100%);transition:transform .25s ease-out;box-shadow:0 -8px 32px #0009;outline:none}.bottom-sheet.is-open{transform:translateY(0)}.bottom-sheet-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000008c;z-index:10;opacity:0;pointer-events:none;transition:opacity .2s}.bottom-sheet-backdrop.is-open{opacity:1;pointer-events:auto}.bottom-sheet-handle{width:2.5rem;height:.25rem;background:var(--text-muted);border-radius:999px;margin:0 auto .5rem;flex-shrink:0}.bottom-sheet-close{position:absolute;top:.5rem;right:.875rem;background:transparent;border:none;color:var(--text-primary);cursor:pointer;font-size:1.5rem;line-height:1;font-family:inherit}@media (prefers-reduced-motion: reduce){.bottom-sheet,.bottom-sheet-backdrop{transition:none}}.field-editable{background:var(--bg-elevated);border:2px solid var(--edit-border);border-radius:.5rem;padding:.875rem 1rem;margin-bottom:1rem;transition:border-color .15s}.field-editable:hover,.field-editable:focus-within{border-color:var(--accent-hover)}.field-label{font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-tertiary);margin-bottom:.5rem;display:flex;align-items:center;gap:.35rem}.field-label .pen{opacity:.7;font-size:.75rem}.info-block{background:var(--bg-main);border-left:3px solid var(--border);border-radius:0 .375rem .375rem 0;padding:.75rem 1rem;margin-bottom:.75rem;font-size:.85rem;color:var(--text-secondary)}.info-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:.4rem}.info-title{font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-tertiary)}.info-edit{font-size:.72rem;color:var(--text-tertiary);text-decoration:underline;cursor:pointer;background:none;border:none;font-family:inherit;padding:0}.info-edit:hover{color:var(--accent-hover)}.info-kv{display:grid;grid-template-columns:max-content 1fr;gap:.15rem 1rem;margin:0}.info-kv dt{color:var(--text-tertiary)}.info-kv dd{margin:0;color:var(--text-secondary);word-break:break-word}.info-section-label{margin:1.25rem 0 .5rem;font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-tertiary)}.intent-pane.chat.tool{background:var(--bg-main);border:1px dashed var(--border)}.recent-blocks .card-header{margin-bottom:.35rem}.recent-blocks .rb-sub{color:var(--text-muted);font-size:.8rem;margin:0 0 1.1rem;line-height:1.45;max-width:36ch}.recent-blocks .rb-count{flex:none;min-width:1.6rem;height:1.6rem;padding:0 .45rem;display:inline-flex;align-items:center;justify-content:center;font-size:.8rem;font-weight:600;font-variant-numeric:tabular-nums;background:color-mix(in srgb,var(--warning) 14%,transparent);color:var(--warning);border:1px solid color-mix(in srgb,var(--warning) 35%,transparent);border-radius:999px}.recent-blocks .rb-rows{display:flex;flex-direction:column}.recent-blocks .rb-row{display:grid;grid-template-columns:auto 1fr auto;gap:.75rem .85rem;align-items:start;padding:.85rem 0;border-top:1px solid var(--border)}.recent-blocks .rb-row:first-child{border-top:none}.recent-blocks .rb-dot{width:.5rem;height:.5rem;border-radius:999px;margin-top:.35rem;background:var(--warning)}.recent-blocks .rb-dot.act{background:var(--danger)}.recent-blocks .rb-dot.info{background:var(--text-muted)}.recent-blocks .rb-body{min-width:0}.recent-blocks .rb-line{font-size:.86rem;line-height:1.4;color:var(--text-primary)}.recent-blocks .rb-line b{font-weight:600}.recent-blocks .rb-meta{display:flex;align-items:center;gap:.5rem;margin-top:.3rem;flex-wrap:wrap}.recent-blocks .rb-chip{font-size:.64rem;letter-spacing:.06em;text-transform:uppercase;font-weight:600;padding:.12rem .44rem;border-radius:.35rem;background:var(--accent-subtle);color:var(--text-muted)}.recent-blocks .rb-chip.warn{background:color-mix(in srgb,var(--warning) 14%,transparent);color:var(--warning)}.recent-blocks .rb-chip.act{background:var(--danger-subtle);color:var(--danger)}.recent-blocks .rb-via{font-size:.72rem;color:var(--text-muted)}.recent-blocks .rb-when{font-size:.74rem;color:var(--text-muted);font-variant-numeric:tabular-nums;white-space:nowrap;text-align:right}.recent-blocks .rb-fix{display:inline-block;margin-top:.4rem;font-size:.76rem;color:var(--text-primary);text-underline-offset:2px}.recent-blocks .rb-foot{margin-top:1rem;padding-top:.9rem;border-top:1px solid var(--border)}.recent-blocks .rb-viewall{font:inherit;font-size:.8rem;color:var(--text-primary);background:none;border:none;padding:0;cursor:pointer;text-decoration:underline;text-underline-offset:3px}.recent-blocks .rb-empty{text-align:center;padding:1.25rem 1rem .25rem}.recent-blocks .rb-mark{width:2.25rem;height:2.25rem;border-radius:999px;background:var(--success-subtle);color:var(--success);display:inline-flex;align-items:center;justify-content:center;margin-bottom:.75rem}.recent-blocks .rb-empty h3{font-size:.9rem;margin:0 0 .3rem}.recent-blocks .rb-empty p{font-size:.8rem;color:var(--text-muted);margin:0 auto;max-width:30ch;line-height:1.45}.receipt-head{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.receipt-what{font-size:.95rem;font-weight:600;letter-spacing:-.01em;color:var(--text-primary)}.receipt-tag{font-size:.68rem;padding:.12rem .4rem;border-radius:4px;border:1px solid var(--border);color:var(--text-muted);white-space:nowrap}.receipt-tag-bound{color:var(--success);border-color:var(--success)}.receipt-scope{font-size:.82rem;color:var(--text-muted);margin-top:.2rem}.receipt-foot{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;margin-top:.55rem;padding-top:.55rem;border-top:1px solid var(--border)}.receipt-link{font-size:.78rem;color:var(--text-secondary);text-decoration:none;border-bottom:1px solid var(--border)}.receipt-link:hover{color:var(--accent-hover);border-color:var(--accent-hover)}.receipt-id{font-family:SF Mono,Monaco,monospace;font-size:.7rem;color:var(--text-muted)}.receipt-more{margin-top:.5rem}.receipt-more summary{cursor:pointer;font-size:.75rem;color:var(--text-muted);list-style:none}.receipt-more summary::-webkit-details-marker{display:none}.receipt-more summary:before{content:"▸"}.receipt-more[open] summary:before{content:"▾"}.receipt-kv{font-size:.72rem;color:var(--text-muted);margin-top:.4rem;line-height:1.7;font-family:SF Mono,Monaco,monospace;word-break:break-all}