@monster-orange/den 0.4.17 → 0.4.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/README.md +14 -13
  3. package/docs/distributions.md +138 -25
  4. package/docs/reference.md +1 -1
  5. package/package.json +2 -1
  6. package/skills/install-den/SKILL.md +35 -75
  7. package/src/shared/agents/den-claude.ts +40 -6
  8. package/src/shared/agents/guardrails/stop-hook.ts +30 -4
  9. package/src/shared/agents/guardrails.ts +40 -4
  10. package/src/shared/agents/handback.ts +22 -4
  11. package/src/shared/agents/personas.ts +32 -5
  12. package/src/shared/agents/runtime.ts +8 -0
  13. package/src/shared/agents/server-agent.ts +57 -0
  14. package/src/shared/agents/server-pack/server.md +45 -0
  15. package/src/shared/environment-manager/workspaces.ts +4 -0
  16. package/src/ui/cli/agent-ops.ts +84 -6
  17. package/src/ui/cli/create.ts +2 -2
  18. package/src/ui/cli/index.ts +1 -1
  19. package/src/ui/cli/repo-command.ts +8 -1
  20. package/src/ui/cli/repo-detect.ts +58 -0
  21. package/src/ui/cli/server-agent-ops.ts +411 -0
  22. package/src/ui/mcp/server.ts +29 -12
  23. package/src/ui/mcp/tools.ts +4 -2
  24. package/src/ui/server/agent-routes.ts +10 -1
  25. package/src/ui/server/held-actions.ts +35 -7
  26. package/src/ui/server/index.ts +20 -5
  27. package/src/ui/server/public/agents-page.js +33 -1
  28. package/src/ui/server/public/agents.html +13 -0
  29. package/src/ui/server/public/error-page.js +39 -1
  30. package/src/ui/server/public/graph.css +43 -0
  31. package/src/ui/server/public/graph.js +68 -1
  32. package/src/ui/server/public/header.js +36 -7
  33. package/src/ui/server/public/i18n/es.js +72 -0
  34. package/src/ui/server/public/i18n/ja.js +74 -0
  35. package/src/ui/server/public/local-dev.html +33 -3
  36. package/src/ui/server/public/log-source.js +26 -0
  37. package/src/ui/server/public/logs.html +7 -20
  38. package/src/ui/server/public/persona-ui.css +6 -0
  39. package/src/ui/server/public/server-agent-page.js +415 -0
  40. package/src/ui/server/public/server-agent.html +222 -0
  41. package/src/ui/server/public/services.html +18 -3
  42. package/src/ui/server/public/shell.js +274 -31
  43. package/src/ui/server/public/svc-panel.js +685 -0
  44. package/src/ui/server/public/topbar.js +13 -1
  45. package/src/ui/server/route-classes.ts +21 -0
  46. package/src/ui/server/server-agent-routes.ts +176 -0
  47. package/src/ui/server/session-routes.ts +12 -1
  48. package/src/ui/server/ui-assets.ts +6 -0
  49. package/src/ui/server/workspace-routes.ts +3 -0
@@ -169,6 +169,43 @@
169
169
  "act.unpinX": "Unpin {s}",
170
170
  "toast.pinned": "Pinned {s}",
171
171
  "toast.unpinned": "Unpinned {s}",
172
+ // 0.4.18: the service panel (svc-panel.js) and Start's Folder.
173
+ "svp.label": "{s}: quick panel",
174
+ "svp.more": "Quick panel for {s}",
175
+ "svp.notRunning": "Not running",
176
+ "svp.up": "up {d}",
177
+ "svp.upNow": "up",
178
+ "svp.down": "down: {c}",
179
+ "svp.downNone": "down",
180
+ "svp.starting": "starting…",
181
+ "svp.stopped": "stopped",
182
+ "svp.noUrl": "No URL",
183
+ "svp.start": "Start",
184
+ "svp.stop": "Stop",
185
+ "svp.restart": "Restart",
186
+ "svp.logs": "Logs",
187
+ "svp.stopping": "Stopping {s}",
188
+ "svp.tests": "Last test run",
189
+ "svp.testsOk": "✓ {t}",
190
+ "svp.testsBad": "✗ failed {t}",
191
+ "svp.testsRun": "running…",
192
+ "svp.testsNone": "—",
193
+ "svp.runTests": "Run tests",
194
+ "svp.testsDone": "Tests passed: {s}",
195
+ "svp.testsFailed": "Tests failed: {s}",
196
+ "svp.agents": "Agents",
197
+ "svp.noAgents": "No agents in this repo.",
198
+ "svp.open": "Open",
199
+ "svp.openAg": "Open {s}",
200
+ "svp.startHere": "Start agent here",
201
+ "svp.openSvc": "Open in Services",
202
+ "svp.openGraph": "Open in Graph",
203
+ "graph.outside": "{s} is in {ws}, outside this View.",
204
+ "graph.showWs": "Show {ws}",
205
+ "svp.close": "Close",
206
+ "ag.new.folder": "Folder (optional)",
207
+ "ag.new.folderHelp":
208
+ "Inside the workspace's folder. Empty: the workspace's own.",
172
209
  collapse: "Collapse",
173
210
  expand: "Expand",
174
211
  menu: "Menu",
@@ -393,6 +430,10 @@
393
430
  "err.signin.step2":
394
431
  "This page signs you in by itself once approved. The code expires in 10 minutes.",
395
432
  "err.signin.waiting": "Waiting for approval…",
433
+ "err.signin.windowsSteps":
434
+ "Open Windows Terminal or PowerShell, paste this, and press Enter.",
435
+ "err.signin.nothingOpened":
436
+ "Nothing opened? Copy the command below and paste it into your terminal app.",
396
437
  "err.signin.noTerminal":
397
438
  "Don't have a terminal on {host}? From this computer, run:",
398
439
  "err.signin.openTerminal": "Open Terminal",
@@ -656,6 +697,8 @@
656
697
  "ap.do.repo_add.new":
657
698
  'Add the repo at {path} to a new workspace "{workspace}"',
658
699
  "ap.do.service_try": "Start {service}",
700
+ "ap.do.service_try.activates":
701
+ "Start {service} (this activates {workspace})",
659
702
  "ap.says": "Agent says:",
660
703
  "ap.review": "Review",
661
704
  "ap.hide": "Hide details",
@@ -798,6 +841,34 @@
798
841
  "ag.ws": "Workspace",
799
842
  "ag.pal": "Agents › {ws} / {name}",
800
843
  "ag.palStart": "Start agent…",
844
+ // ON-6: the server agent (server-agent-page.js, the rail, ⌘K).
845
+ "n.serverAgent": "__DEN_TITLE__ agent",
846
+ "ag.server": "Server",
847
+ "pal.askServer": "Ask __DEN_TITLE__",
848
+ "sa.sub.fresh":
849
+ "Let's set up __DEN_TITLE__. It reads only until you allow a write.",
850
+ "sa.sub":
851
+ "For anything about this server: add a repo, fix a failing service, change a setting, or explain what's running.",
852
+ "sa.login":
853
+ "If Claude asks you to log in, log in here. Then {name} starts.",
854
+ "sa.trust": "Claude asks whether to trust this folder. Choose Yes.",
855
+ "sa.model": "Model",
856
+ "sa.readsOnly":
857
+ "It acts only through __DEN_TITLE__'s own tools. Each change asks you first, in this browser.",
858
+ "sa.starting": "Starting the agent…",
859
+ "sa.idle": "{name} isn't running.",
860
+ "sa.start": "Start",
861
+ "sa.failed": "It didn't start: {why}. {cli} agents doctor says more.",
862
+ "sa.unread": "Couldn't read the agent's status. Is the web server running?",
863
+ "sa.none.h": "No runtime can run the agent yet",
864
+ "sa.none":
865
+ "The agent runs on an agent CLI from the runtime catalog. Install one, log in to it once, then reload this page.",
866
+ "sa.noTools": "can't use __DEN_TITLE__'s tools yet",
867
+ "sa.manual.h": "Or set it up yourself",
868
+ "sa.manual": "Every step the agent takes is also a command. In a terminal:",
869
+ "sa.runsOn": "Runs on",
870
+ "sa.openAgents": "Open in Agents",
871
+ "sa.saved": "Saved. The agent uses it from its next start.",
801
872
  "ag.cap": "Up to 4 panes: {id}'s closed to make room (it keeps running)",
802
873
  // AG-4b: the Agents tab's layouts and named tabs.
803
874
  "ag.layout": "Layout",
@@ -918,6 +989,9 @@
918
989
  agents:
919
990
  '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="M7 10l3 2-3 2M12 15h5"/>',
920
991
  edge: '<path d="M12 3l8 4v5c0 4.5-3.4 8.3-8 9-4.6-.7-8-4.5-8-9V7z"/><path d="M8 12h8M12 8v8"/>',
992
+ // ON-6: the server agent, a speech bubble with a spark.
993
+ assistant:
994
+ '<path d="M5 5h14a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1h-8l-4 3v-3H5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z"/><path d="M12 8v4M10 10h4"/>',
921
995
  // SEC-2a: who this browser is (the rail footer's identity chip).
922
996
  person:
923
997
  '<circle cx="12" cy="8" r="3.5"/><path d="M5 20c.8-3.6 3.6-5.5 7-5.5s6.2 1.9 7 5.5"/>',
@@ -1214,6 +1288,12 @@
1214
1288
  // SEC-2a: who this browser is (GET /api/session `identity`): local,
1215
1289
  // a signed-in device, or a GitHub login. Null until known.
1216
1290
  identity: null,
1291
+ // 0.4.18: each pin's URL (svc-panel.js, from the health feed) for its
1292
+ // Visit ↗, whether that Visit is off (down or starting: the panel's own
1293
+ // live state), and the pin whose service panel is open.
1294
+ pinUrls: {},
1295
+ pinOff: {},
1296
+ svpOpen: null,
1217
1297
  };
1218
1298
  try {
1219
1299
  var cached = JSON.parse(localStorage.getItem(LS) || "{}");
@@ -1441,6 +1521,31 @@
1441
1521
  var r = anchor.getBoundingClientRect();
1442
1522
  var gap = opts.gap != null ? opts.gap : 4;
1443
1523
  el.style.maxHeight = "";
1524
+ // 0.4.18: a bottom sheet is placed by its CSS (bottom: 0), not here.
1525
+ if (opts.sheet) {
1526
+ el.style.removeProperty("top");
1527
+ el.style.removeProperty("left");
1528
+ return;
1529
+ }
1530
+ // 0.4.18: beside the anchor (the rail), level with a row in it.
1531
+ if (opts.side === "right") {
1532
+ var row = typeof opts.row === "function" ? opts.row() : null;
1533
+ var rr = row ? row.getBoundingClientRect() : r;
1534
+ var bw = el.offsetWidth;
1535
+ var bh = el.offsetHeight;
1536
+ var bl = Math.min(r.right + gap, window.innerWidth - bw - TOP_MARGIN);
1537
+ var bt = Math.min(
1538
+ rr.top + (opts.dy || 0),
1539
+ window.innerHeight - bh - TOP_MARGIN,
1540
+ );
1541
+ el.style.setProperty("top", Math.max(TOP_MARGIN, bt) + "px", "important");
1542
+ el.style.setProperty(
1543
+ "left",
1544
+ Math.max(TOP_MARGIN, bl) + "px",
1545
+ "important",
1546
+ );
1547
+ return;
1548
+ }
1444
1549
  var w = el.offsetWidth;
1445
1550
  var h = el.offsetHeight;
1446
1551
  var vw = window.innerWidth;
@@ -1747,6 +1852,16 @@
1747
1852
  href: "/agents",
1748
1853
  keywords: ["sessions", "terminal"],
1749
1854
  },
1855
+ // ON-6: the server agent, this Den's own: one click away, after Agents.
1856
+ {
1857
+ id: "server-agent",
1858
+ label: "n.serverAgent",
1859
+ icon: "assistant",
1860
+ group: "develop",
1861
+ order: 18,
1862
+ href: "/server-agent",
1863
+ keywords: ["server agent", "ask", "setup", "onboarding"],
1864
+ },
1750
1865
  {
1751
1866
  id: "services",
1752
1867
  label: "n.services",
@@ -1900,11 +2015,16 @@
1900
2015
  "</div>" +
1901
2016
  chips +
1902
2017
  "</header>" +
2018
+ // The rail's box: no box of its own (display: contents). As ☰'s
2019
+ // drawer it is the modal dialog the rail sits in, so the rail stays a
2020
+ // nav (a <nav> may not be a dialog).
2021
+ '<div class="den-rail-box" id="den-rail-box">' +
1903
2022
  '<nav class="den-rail" id="den-rail" aria-label="' +
1904
2023
  esc(t("pages")) +
1905
- '"></nav>';
2024
+ '"></nav></div>';
1906
2025
 
1907
2026
  var nav = host.querySelector(".den-rail");
2027
+ var railBox = host.querySelector(".den-rail-box");
1908
2028
 
1909
2029
  // ── the scope parts (R4) ───────────────────────────────────────────────
1910
2030
  // Each part's menu comes from its model, set by header.js: setOrgs (the
@@ -2123,6 +2243,47 @@
2123
2243
  : " off";
2124
2244
  return '<span class="den-dot' + cls + '" aria-hidden="true"></span>';
2125
2245
  }
2246
+ /**
2247
+ * 0.4.18: a pin's Visit ↗: none without a URL, greyed out while it is
2248
+ * down. Down is the service panel's live state (pinOff), the same as its
2249
+ * dot, so it holds for a pin outside the View too; the rail's rollup
2250
+ * only without it.
2251
+ */
2252
+ function pinVisit(id) {
2253
+ var url = state.pinUrls && state.pinUrls[id];
2254
+ if (!url) return "";
2255
+ var off = state.pinOff || {};
2256
+ var down = Object.prototype.hasOwnProperty.call(off, id)
2257
+ ? !!off[id]
2258
+ : state.health[id] === "down";
2259
+ return (
2260
+ '<button type="button" class="den-pvisit" tabindex="-1" data-visit="' +
2261
+ esc(id) +
2262
+ '" data-url="' +
2263
+ esc(url) +
2264
+ '" aria-label="' +
2265
+ esc(t("act.visitX", { s: id })) +
2266
+ '" title="' +
2267
+ esc(down ? t("svp.notRunning") : url) +
2268
+ '"' +
2269
+ (down ? ' aria-disabled="true"' : "") +
2270
+ '><span aria-hidden="true">↗</span></button>'
2271
+ );
2272
+ }
2273
+ function visitPin(b) {
2274
+ if (b.getAttribute("aria-disabled") === "true") {
2275
+ toast(t("svp.notRunning"));
2276
+ return;
2277
+ }
2278
+ var url = b.getAttribute("data-url");
2279
+ if (url && /^https?:\/\//i.test(url))
2280
+ window.open(url, "_blank", "noopener");
2281
+ }
2282
+ // 0.4.18: the service panel (svc-panel.js registers it).
2283
+ var servicePanel = null;
2284
+ function setServicePanel(p) {
2285
+ servicePanel = p && typeof p.open === "function" ? p : null;
2286
+ }
2126
2287
  function pinHref(id) {
2127
2288
  var repo = id.split("/")[0];
2128
2289
  return "/services#repo-" + encodeURIComponent(repo);
@@ -2191,9 +2352,12 @@
2191
2352
  null,
2192
2353
  state.pins
2193
2354
  .map(function (p) {
2194
- // The row is the link (named by the id alone, Delete unpins
2195
- // it); the × beside it is its own control, out of the tab
2196
- // order, named "Unpin <id>", its glyph hidden from readers.
2355
+ // 0.4.18: the row (named by the id alone) opens the service's
2356
+ // panel in place (svc-panel.js); it is still the link to
2357
+ // Services, so ⌘-click and middle-click open that in a new
2358
+ // tab. Delete unpins it. Visit ↗ beside it opens the service's
2359
+ // URL, out of the tab order: the panel's first button is the
2360
+ // keyboard's Visit. Unpin is in the panel's footer.
2197
2361
  return (
2198
2362
  '<div class="den-pin-row">' +
2199
2363
  item(
@@ -2205,17 +2369,17 @@
2205
2369
  mono: true,
2206
2370
  },
2207
2371
  false,
2208
- // Holds the ×'s place in the row, as in the prototype.
2209
- '<span class="den-pin-sp" aria-hidden="true">×</span>',
2210
- ' data-pin="' + esc(p) + '" aria-keyshortcuts="Delete"',
2372
+ "",
2373
+ ' data-pin="' +
2374
+ esc(p) +
2375
+ '" data-svpanel="' +
2376
+ esc(p) +
2377
+ '" aria-haspopup="dialog" aria-expanded="' +
2378
+ (state.svpOpen === p ? "true" : "false") +
2379
+ '" aria-keyshortcuts="Delete"',
2211
2380
  ) +
2212
- '<button type="button" class="den-pin-x" tabindex="-1" data-unpin="' +
2213
- esc(p) +
2214
- '" aria-label="' +
2215
- esc(t("act.unpinX", { s: p })) +
2216
- '" title="' +
2217
- esc(t("act.unpin")) +
2218
- '"><span aria-hidden="true">×</span></button></div>'
2381
+ pinVisit(p) +
2382
+ "</div>"
2219
2383
  );
2220
2384
  })
2221
2385
  .join(""),
@@ -2423,10 +2587,28 @@
2423
2587
  });
2424
2588
  nav.addEventListener("click", function (e) {
2425
2589
  var tgt = e.target;
2426
- var x = tgt && tgt.closest ? tgt.closest("[data-unpin]") : null;
2427
- if (x) {
2590
+ var v = tgt && tgt.closest ? tgt.closest("[data-visit]") : null;
2591
+ if (v) {
2428
2592
  e.preventDefault();
2429
- unpinFromRail(x.getAttribute("data-unpin"));
2593
+ visitPin(v);
2594
+ return;
2595
+ }
2596
+ // 0.4.18: a plain click (or Enter) on a pinned row opens its panel;
2597
+ // ⌘/Ctrl-, Shift- and middle-click keep the link (Services).
2598
+ var sp = tgt && tgt.closest ? tgt.closest("[data-svpanel]") : null;
2599
+ if (
2600
+ sp &&
2601
+ servicePanel &&
2602
+ e.button === 0 &&
2603
+ !e.metaKey &&
2604
+ !e.ctrlKey &&
2605
+ !e.shiftKey &&
2606
+ !e.altKey
2607
+ ) {
2608
+ e.preventDefault();
2609
+ servicePanel.open(sp.getAttribute("data-svpanel"), sp, {
2610
+ fromRail: true,
2611
+ });
2430
2612
  return;
2431
2613
  }
2432
2614
  var c = tgt && tgt.closest ? tgt.closest('[data-act="collapse"]') : null;
@@ -2465,8 +2647,9 @@
2465
2647
  function openDrawer() {
2466
2648
  document.body.classList.add("den-rail-open");
2467
2649
  if (menu) menu.setAttribute("aria-expanded", "true");
2468
- nav.setAttribute("role", "dialog");
2469
- nav.setAttribute("aria-modal", "true");
2650
+ railBox.setAttribute("role", "dialog");
2651
+ railBox.setAttribute("aria-modal", "true");
2652
+ railBox.setAttribute("aria-label", t("pages"));
2470
2653
  if (!scrim) {
2471
2654
  scrim = document.createElement("div");
2472
2655
  scrim.className = "den-scrim";
@@ -2486,8 +2669,9 @@
2486
2669
  if (!drawerOpen()) return;
2487
2670
  document.body.classList.remove("den-rail-open");
2488
2671
  if (menu) menu.setAttribute("aria-expanded", "false");
2489
- nav.removeAttribute("role");
2490
- nav.removeAttribute("aria-modal");
2672
+ railBox.removeAttribute("role");
2673
+ railBox.removeAttribute("aria-modal");
2674
+ railBox.removeAttribute("aria-label");
2491
2675
  if (scrim && scrim.remove) scrim.remove();
2492
2676
  setInert(false);
2493
2677
  if (refocus && menu) menu.focus();
@@ -4206,7 +4390,10 @@
4206
4390
  }
4207
4391
  function apWhat(a) {
4208
4392
  var p = a.params || {};
4209
- var key = "ap.do." + a.action + (p.isNew ? ".new" : "");
4393
+ var key =
4394
+ "ap.do." +
4395
+ a.action +
4396
+ (p.isNew ? ".new" : p.activates ? ".activates" : "");
4210
4397
  var s = t(key, p);
4211
4398
  if (s !== key) return s;
4212
4399
  s = String(a.summary || a.action);
@@ -4523,6 +4710,14 @@
4523
4710
  * anchor; hide(el). The page hiding it (display: none) closes it too.
4524
4711
  */
4525
4712
  topLayer: { show: topShow, hide: topHide },
4713
+ /**
4714
+ * 0.4.18: the service panel. svc-panel.js registers { open, close,
4715
+ * visit }; openServicePanel(id, opener) opens it beside opener.
4716
+ */
4717
+ setServicePanel: setServicePanel,
4718
+ openServicePanel: function (id, opener) {
4719
+ if (servicePanel) servicePanel.open(id, opener);
4720
+ },
4526
4721
  closeMenu: function () {
4527
4722
  closeMenu(false);
4528
4723
  },
@@ -4830,6 +5025,7 @@
4830
5025
  // ◐ ▾ (the prototype's user menu, an .ib: appearance.js)
4831
5026
  ".den-top .den-ib[aria-expanded='true'] { background: var(--sunken); color: var(--text); }",
4832
5027
  // Rail (.rail)
5028
+ ".den-rail-box { display: contents; }",
4833
5029
  ".den-rail { position: fixed; left: 0; top: var(--den-top-h); bottom: 0; width: var(--den-rail-cw);",
4834
5030
  " box-sizing: content-box; display: flex; flex-direction: column; padding: 10px 8px;",
4835
5031
  " background: var(--surface); border-right: 1px solid var(--border); overflow-y: auto; z-index: 15;",
@@ -4865,18 +5061,65 @@
4865
5061
  " display: inline-block; margin: 0 5px; }",
4866
5062
  ".den-dot.bad { background: var(--bad); } .den-dot.warn { background: var(--warn); }",
4867
5063
  ".den-dot.off { background: var(--faint); }",
4868
- ".den-pin-row { position: relative; }",
4869
- ".den-pin-sp { visibility: hidden; font-size: 12px; }",
4870
- ".den-pin-x { position: absolute; top: 0; bottom: 0; right: 10px; display: flex; align-items: center;",
4871
- " padding: 0; border: 0; background: none; cursor: pointer; font: 12px var(--sans);",
4872
- " opacity: 0; color: var(--faint); }",
4873
- ".den-pin-x:hover { color: var(--text); }",
4874
- ".den-pin-row:hover .den-pin-x, .den-pin-row:focus-within .den-pin-x { opacity: 1; }",
5064
+ // 0.4.18: a pinned row is its name (opens the service panel) and Visit ↗.
5065
+ ".den-pin-row { display: flex; align-items: center; gap: 2px; min-width: 0; }",
5066
+ ".den-pin-row .den-ni { flex: 1; min-width: 0; }",
5067
+ ".den-pin-row .den-ni[aria-expanded='true'] { background: var(--sunken); color: var(--text); }",
5068
+ ".den-pvisit { flex: none; width: 26px; height: 26px; padding: 0; display: grid; place-items: center;",
5069
+ " background: none; border: 1px solid transparent; border-radius: var(--radius-button);",
5070
+ " color: var(--muted); cursor: pointer; font: 13px/1 var(--sans); }",
5071
+ ".den-pvisit:hover { background: var(--sunken); color: var(--accent-text); border-color: var(--border); }",
5072
+ ".den-pvisit[aria-disabled='true'], .den-svp .den-btn[aria-disabled='true'] { opacity: .45; cursor: not-allowed; }",
5073
+ ".den-pvisit[aria-disabled='true']:hover { background: none; color: var(--muted); border-color: transparent; }",
5074
+ "body.den-rail-collapsed .den-rail .den-pvisit { display: none; }",
5075
+ "@media (max-width: 720px) { .den-pvisit { width: 32px; height: 32px; }",
5076
+ " body.den-rail-collapsed .den-rail .den-pvisit { display: grid; } }",
5077
+ // The service panel (svc-panel.js): beside its row, a bottom sheet at phone width.
5078
+ ".den-pop.den-svp { width: 340px; max-width: calc(100vw - 16px); box-sizing: border-box; padding: 0;",
5079
+ " max-height: calc(100dvh - 16px); overflow-y: auto; min-width: 0; }",
5080
+ ".den-svp-h { display: grid; gap: 4px; padding: 12px 12px 10px; border-bottom: 1px solid var(--border); }",
5081
+ ".den-svp-t { display: flex; align-items: center; gap: 8px; min-width: 0; }",
5082
+ ".den-svp-t .den-dot, .den-svp-ag .den-dot, .den-svp-line .den-dot { margin: 0; }",
5083
+ // Line heights pinned to the mock's (its mono font's normal), whatever the theme's font.
5084
+ ".den-svp-t b { flex: 1; min-width: 0; font: 600 14px/21px var(--mono); overflow: hidden;",
5085
+ " text-overflow: ellipsis; white-space: nowrap; }",
5086
+ ".den-svp-x { padding: 2px 6px; margin: -4px -6px -4px 0; }",
5087
+ ".den-svp-meta { display: flex; align-items: center; gap: 4px 8px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); }",
5088
+ ".den-wsp { font: 11px/16px var(--mono); border-radius: 999px; padding: 0 7px; white-space: nowrap;",
5089
+ " border: 1px solid var(--border); color: var(--muted); }",
5090
+ ".den-svp-st { color: var(--ok); } .den-svp-st.bad { color: var(--bad); }",
5091
+ ".den-svp-st.off { color: var(--faint); } .den-svp-st.run { color: var(--accent-text); }",
5092
+ ".den-svp-ms { font: 11.5px var(--mono); color: var(--faint); }",
5093
+ ".den-svp-url { font: 12px/17px var(--mono); color: var(--faint); overflow-wrap: anywhere; }",
5094
+ ".den-svp-acts { display: flex; gap: 6px; flex-wrap: wrap; padding: 10px 12px; }",
5095
+ ".den-svp-sec { display: grid; gap: 6px; padding: 8px 12px 10px; border-top: 1px solid var(--border); }",
5096
+ ".den-svp-k { font: 11px/16px var(--mono); color: var(--faint); text-transform: uppercase; letter-spacing: .07em; }",
5097
+ ".den-svp-line { display: flex; align-items: center; gap: 8px; font-size: 13px; }",
5098
+ ".den-svp-line .den-svp-sp { margin-left: auto; }",
5099
+ ".den-svp-ok { color: var(--ok); } .den-svp-bad { color: var(--bad); } .den-svp-none { color: var(--faint); }",
5100
+ ".den-svp-ag { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; gap: 0 8px;",
5101
+ " align-items: center; font-size: 13px; }",
5102
+ ".den-svp-nm { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
5103
+ ".den-svp-as { font: 11px var(--mono); color: var(--faint); white-space: nowrap; }",
5104
+ ".den-wbdg { font: 10.5px var(--mono); color: var(--warn); border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);",
5105
+ " border-radius: 999px; padding: 0 6px; }",
5106
+ ".den-svp-help { font-size: 12px; color: var(--faint); line-height: 1.4; }",
5107
+ ".den-svp-foot { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 8px 12px 10px;",
5108
+ " border-top: 1px solid var(--border); }",
5109
+ ".den-svp-grow { margin-left: auto; }",
5110
+ ".den-lnk { color: var(--accent-text); cursor: pointer; font: inherit; font-size: 12.5px; text-decoration: underline;",
5111
+ " background: none; border: 0; padding: 0; }",
5112
+ ".den-scrim.den-svp-scrim { z-index: 43; }",
5113
+ ".den-pop.den-svp.den-sheet.den-toplayer { top: auto !important; left: 0 !important; right: 0 !important;",
5114
+ " bottom: 0 !important; width: auto; max-width: none; border-bottom: 0; border-radius: 14px 14px 0 0;",
5115
+ " max-height: 85dvh; padding-bottom: env(safe-area-inset-bottom, 0px); }",
5116
+ ".den-pop.den-svp.den-sheet::before { content: ''; display: block; width: 36px; height: 4px; border-radius: 2px;",
5117
+ " background: var(--border-strong); margin: 8px auto 0; }",
5118
+ ".den-pop.den-svp.den-sheet .den-svp-acts .den-btn { flex: 1 1 auto; padding: 9px 12px; }",
4875
5119
  // Collapsed: icons only. The prototype hides headings and labels; they
4876
5120
  // stay here for screen readers (visually hidden, no space taken).
4877
5121
  "body.den-rail-collapsed .den-rail .den-lbl, body.den-rail-collapsed .den-rail .den-gh,",
4878
- "body.den-rail-collapsed .den-rail .den-bdg, body.den-rail-collapsed .den-rail .den-pin-x,",
4879
- "body.den-rail-collapsed .den-rail .den-pin-sp {",
5122
+ "body.den-rail-collapsed .den-rail .den-bdg {",
4880
5123
  " position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden;",
4881
5124
  " clip: rect(0 0 0 0); white-space: nowrap; border: 0; }",
4882
5125
  // Toast (.toast)