@monster-orange/den 0.4.3 → 0.4.5

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 (85) hide show
  1. package/CHANGELOG.md +348 -0
  2. package/README.md +12 -11
  3. package/docs/distributions.md +438 -91
  4. package/docs/reference.md +8 -3
  5. package/package.json +1 -1
  6. package/src/shared/agents/catalog-runtime.ts +178 -0
  7. package/src/shared/agents/catalog.ts +343 -0
  8. package/src/shared/agents/den-claude.ts +14 -18
  9. package/src/shared/agents/runtime-search.ts +142 -15
  10. package/src/shared/agents/runtime.ts +18 -1
  11. package/src/shared/agents/runtimes.ts +23 -1
  12. package/src/shared/auth/sudo-vault.ts +131 -11
  13. package/src/shared/cli-command.ts +186 -0
  14. package/src/shared/confirm-code.ts +34 -0
  15. package/src/shared/den-config.ts +64 -3
  16. package/src/shared/dev-metrics/emit.ts +31 -1
  17. package/src/shared/edge/config.ts +39 -0
  18. package/src/shared/edge/render.ts +92 -18
  19. package/src/shared/edge/runtime.ts +57 -1
  20. package/src/shared/edge/status.ts +134 -0
  21. package/src/shared/edge/types.ts +11 -1
  22. package/src/shared/environment-manager/health.ts +82 -8
  23. package/src/shared/environment-manager/workspaces.ts +54 -0
  24. package/src/shared/github-app.ts +94 -0
  25. package/src/shared/github-device.ts +286 -0
  26. package/src/shared/health/not-checked.ts +42 -0
  27. package/src/shared/health/rollup.ts +4 -0
  28. package/src/shared/process/spawn-streaming.ts +8 -3
  29. package/src/shared/secrets/onepassword.ts +27 -4
  30. package/src/shared/sessions/client.ts +5 -1
  31. package/src/shared/sessions/host.ts +29 -1
  32. package/src/shared/sessions/protocol.ts +6 -1
  33. package/src/shared/sessions/types.ts +6 -0
  34. package/src/shared/stacks/status.ts +24 -0
  35. package/src/shared/strategies/registry.ts +5 -0
  36. package/src/shared/types/index.ts +6 -0
  37. package/src/shared/web-owner.ts +91 -0
  38. package/src/shared/web-session.ts +135 -1
  39. package/src/ui/cli/agent-ops.ts +210 -19
  40. package/src/ui/cli/agents-command.ts +50 -21
  41. package/src/ui/cli/edge-command.ts +16 -3
  42. package/src/ui/cli/flag-parser.ts +1 -1
  43. package/src/ui/cli/health-display.ts +4 -1
  44. package/src/ui/cli/web-command.ts +512 -33
  45. package/src/ui/mcp/scope.ts +23 -3
  46. package/src/ui/mcp/server.ts +31 -7
  47. package/src/ui/mcp/tools.ts +327 -20
  48. package/src/ui/mcp/views.ts +450 -0
  49. package/src/ui/server/agent-routes.ts +118 -28
  50. package/src/ui/server/device-flows.ts +274 -0
  51. package/src/ui/server/edge-exposure.ts +16 -4
  52. package/src/ui/server/github-routes.ts +321 -0
  53. package/src/ui/server/http-errors.ts +10 -0
  54. package/src/ui/server/index.ts +540 -33
  55. package/src/ui/server/login-waits.ts +203 -0
  56. package/src/ui/server/pair-requests.ts +237 -0
  57. package/src/ui/server/pair-routes.ts +196 -0
  58. package/src/ui/server/public/agent-start.js +46 -7
  59. package/src/ui/server/public/agents-page.js +232 -97
  60. package/src/ui/server/public/agents-section.js +3 -5
  61. package/src/ui/server/public/agents.html +12 -28
  62. package/src/ui/server/public/error-page.js +452 -40
  63. package/src/ui/server/public/error.html +59 -0
  64. package/src/ui/server/public/graph-model.js +2 -0
  65. package/src/ui/server/public/header.js +2 -3
  66. package/src/ui/server/public/health-rollup.js +20 -8
  67. package/src/ui/server/public/i18n/es.js +89 -21
  68. package/src/ui/server/public/i18n/ja.js +88 -21
  69. package/src/ui/server/public/index.html +17 -4
  70. package/src/ui/server/public/local-dev.html +44 -21
  71. package/src/ui/server/public/qr.js +317 -0
  72. package/src/ui/server/public/services.html +21 -5
  73. package/src/ui/server/public/settings-page.js +10 -2
  74. package/src/ui/server/public/settings-registry.js +617 -1
  75. package/src/ui/server/public/settings.html +90 -1
  76. package/src/ui/server/public/shell.js +151 -33
  77. package/src/ui/server/public/topbar.js +170 -206
  78. package/src/ui/server/public/workspace-picker.js +1 -1
  79. package/src/ui/server/readonly-routes.ts +29 -5
  80. package/src/ui/server/request-guard.ts +103 -36
  81. package/src/ui/server/route-classes.ts +116 -4
  82. package/src/ui/server/session-routes.ts +9 -1
  83. package/src/ui/server/sudo-store.ts +170 -6
  84. package/src/ui/server/ui-assets.ts +2 -0
  85. package/src/ui/server/workspace-routes.ts +10 -3
@@ -95,7 +95,9 @@
95
95
  top: 0;
96
96
  z-index: 4;
97
97
  background: var(--bg);
98
- box-shadow: 0 -20px 0 0 var(--bg), 0 12px 0 0 var(--bg);
98
+ box-shadow:
99
+ 0 -20px 0 0 var(--bg),
100
+ 0 12px 0 0 var(--bg);
99
101
  }
100
102
  .ph .act {
101
103
  margin-left: auto;
@@ -352,6 +354,93 @@
352
354
  border-radius: 999px;
353
355
  padding: 0 6px;
354
356
  }
357
+ /* Settings › Agents › Runtimes (AG-0.5): one row per runtime. */
358
+ .set-custom {
359
+ flex: 1 1 100%;
360
+ min-width: 0;
361
+ }
362
+ .rt-scope {
363
+ display: flex;
364
+ align-items: center;
365
+ gap: 8px;
366
+ flex-wrap: wrap;
367
+ font-size: 13px;
368
+ color: var(--muted);
369
+ margin-bottom: 6px;
370
+ }
371
+ .rt-list {
372
+ display: grid;
373
+ border: 1px solid var(--border);
374
+ border-radius: var(--radius-button);
375
+ }
376
+ .rt {
377
+ display: grid;
378
+ grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr) minmax(
379
+ 0,
380
+ 2fr
381
+ ) auto;
382
+ align-items: center;
383
+ gap: 4px 12px;
384
+ padding: 8px 10px;
385
+ font-size: 13px;
386
+ }
387
+ .rt + .rt {
388
+ border-top: 1px solid var(--border);
389
+ }
390
+ .rt-v {
391
+ color: var(--muted);
392
+ }
393
+ .rt-n .set-tag {
394
+ white-space: nowrap;
395
+ }
396
+ .rt-ok {
397
+ color: var(--ok);
398
+ }
399
+ .rt-bad {
400
+ color: var(--muted);
401
+ overflow-wrap: anywhere;
402
+ }
403
+ .rt-w {
404
+ min-width: 0;
405
+ overflow-wrap: anywhere;
406
+ }
407
+ .rt-p {
408
+ font-size: 12px;
409
+ }
410
+ .rt-f {
411
+ grid-column: 1 / -1;
412
+ display: flex;
413
+ flex-wrap: wrap;
414
+ align-items: center;
415
+ gap: 6px;
416
+ }
417
+ .rt-f[hidden],
418
+ .rt-err[hidden] {
419
+ display: none;
420
+ }
421
+ .rt-fl {
422
+ flex-basis: 100%;
423
+ color: var(--muted);
424
+ font-size: 12.5px;
425
+ }
426
+ .rt-f input {
427
+ flex: 1 1 260px;
428
+ min-width: 0;
429
+ }
430
+ .rt-err {
431
+ flex-basis: 100%;
432
+ color: var(--bad);
433
+ font-size: 12.5px;
434
+ overflow-wrap: anywhere;
435
+ }
436
+ @media (max-width: 640px) {
437
+ .rt {
438
+ grid-template-columns: minmax(0, 1fr) auto;
439
+ }
440
+ .rt-w {
441
+ grid-column: 1 / -1;
442
+ }
443
+ }
355
444
  .set-langs {
356
445
  display: grid;
357
446
  gap: 4px;
@@ -22,10 +22,10 @@
22
22
  * each part an .ib button with a .pop menu (an ARIA menu: ↑/↓,
23
23
  * Home, End, Enter; Escape and a click outside close it) ·
24
24
  * the ⌘K search field (R5) · the status chip (R6) · the Runs
25
- * chip (R7) · the lock chip (R8) · What's new · ◐ ▾ (R10).
25
+ * chip (R7) · the sudo chip (R8, only while asked) · What's new · ◐ ▾ (R10).
26
26
  * shell.js draws them; topbar.js (through header.js) drives the
27
27
  * chips, the command palette (R17), the status drawer (R15) and
28
- * the lock popover; appearance.js the user menu (dev.20).
28
+ * the admin-password prompt; appearance.js the user menu (dev.20).
29
29
  * rail Pinned, then the NAV registry by GROUPS (Develop, Verify,
30
30
  * Ship, a distribution's own), then Settings and the collapse
31
31
  * toggle. One tab stop (the current page); ↑/↓, Home and End
@@ -132,7 +132,9 @@
132
132
  "org.title": "Org: {title}",
133
133
  "org.titleId": "Org: {title} ({id})",
134
134
  "ws.all": "All active",
135
- "ws.focus": "focus",
135
+ "ws.focus": "CLI default",
136
+ "ws.focusTip":
137
+ "CLI commands run here when you don't name a workspace. Picking a workspace in the top bar only changes what pages show.",
136
138
  "ws.none": "No active workspaces",
137
139
  "ws.manage": "Manage workspaces…",
138
140
  "ws.allN": "All active ({n})",
@@ -142,12 +144,12 @@
142
144
  "ws.viewTitle":
143
145
  "View: what every page shows ({ws}). A view filter only: it starts, stops and routes nothing.",
144
146
  "ws.viewTitleFocus":
145
- "View: what every page shows ({ws}). A view filter only: it starts, stops and routes nothing, and focus ({focus}) stays the target for commands.",
146
- "ws.setFocus": "Set {ws} as focus",
147
+ "View: what every page shows ({ws}). A view filter only: it starts, stops and routes nothing, and the CLI default ({focus}) stays the target for commands.",
148
+ "ws.setFocus": "Make {ws} the CLI default",
147
149
  "ws.setFocusTip":
148
- "Make it the default target for commands (writes workspaces.yml)",
150
+ "CLI commands run in it when you don't name a workspace (writes workspaces.yml)",
149
151
  "ws.viewFailed": "Could not change the view: {msg}",
150
- "ws.focusFailed": "Could not set the focus: {msg}",
152
+ "ws.focusFailed": "Could not change the CLI default: {msg}",
151
153
  // A count's singular is its .one (0.3.0-dev.40: en had none, so the org
152
154
  // menu said "1 workspaces").
153
155
  "org.n": "{n} workspaces",
@@ -251,24 +253,20 @@
251
253
  "dock.err": "failed",
252
254
  "dock.last": "Last run: {s} ↗",
253
255
  "dock.resize": "Drag to resize",
254
- // The lock chip and its popover (R8).
255
- "lock.locked": "Locked",
256
- "lock.unlocked": "Unlocked",
257
- "lock.title": "Unlock to start, stop and restart services and agents",
258
- "lock.nopw": "Passwordless sudo (NOPASSWD): no admin password needed",
259
- "lock.session": "Unlocked for this session",
260
- "lock.sessionDesc":
261
- "Unlocked for this session: start, stop and restart work on every page.",
256
+ // The admin password, asked only when a command needs sudo (R8, LK-0).
257
+ "lock.waiting": "Admin password needed",
258
+ "lock.needs": "{cmd} needs your admin password",
259
+ "lock.cmd": "A command",
262
260
  "lock.cfg":
263
261
  "Saving the password is off: set secrets.provider and secrets.refs.sudo in {file}.",
264
262
  "lock.update": "Update stored password",
265
263
  "lock.saveTo": "Save to {s}",
266
264
  "lock.pw": "Admin password",
267
- "lock.go": "Unlock",
265
+ "lock.go": "Continue",
268
266
  "lock.checking": "Checking…",
269
- "lock.note": "Den asks once per session. Agents can't unlock.",
270
- "lock.again": "Enter the password again…",
271
- "lock.saveFailed": "Unlocked, but saving to {s} failed: {err}",
267
+ "lock.note": "Den uses it only for sudo. Agents can't enter it.",
268
+ "lock.saved": "Admin password kept for this session",
269
+ "lock.saveFailed": "Kept for this session, but saving to {s} failed: {err}",
272
270
  "lock.wrong": "Incorrect password — try again",
273
271
  "lock.reqFailed": "Request failed — is the server running?",
274
272
  // What's new (R9) and the stale-server pill.
@@ -358,19 +356,71 @@
358
356
  "This server answers only on its own addresses, so a page on another name can't reach it.",
359
357
  "err.421.open": "Open on localhost",
360
358
  "err.refused.title": "Request refused",
361
- "err.signin.title": "Sign in to use {name}",
362
359
  "err.signin.terminal": "Sign in to use the terminal",
363
360
  "err.signin.agents": "Sign in to start agents",
364
361
  "err.signin.sub": "This browser isn't signed in",
365
362
  "err.signin.lead": "Run {cmd} and open the link.",
366
363
  "err.signin.expiredTitle": "Sign-in link expired",
367
- "err.signin.expired":
368
- "That sign-in link expired or was used. Run {cmd} for a new one.",
369
364
  "err.signin.local":
370
365
  "A sign-in belongs to one address. This page is on {host}: open the same page on localhost, where this browser is signed in.",
371
- "err.signin.why":
372
- "There is no password: the link comes from a file only your user account can read.",
373
366
  "err.signin.copy": "Copy command",
367
+ "err.signin.title": "Sign in to {name}",
368
+ "err.signin.where":
369
+ "You're opening {name} from another computer. {name} runs on {host}, so it needs to know this is you.",
370
+ "err.signin.githubNote":
371
+ "Uses your GitHub account. The owner of this {name} has to have linked theirs.",
372
+ "err.signin.githubNoOwner":
373
+ "Sign in with GitHub needs an owner, and this {name} has none yet. On {host} (not this computer), run:",
374
+ "err.signin.githubRefused":
375
+ "@{login} isn't the owner of this {name}. Ask the owner to add you, or use 'Approve this computer'.",
376
+ "err.signin.approveOr": "Or approve this computer",
377
+ "err.signin.approve": "Approve this computer",
378
+ "err.signin.step1": "On {host} (not this computer), run:",
379
+ "err.signin.step1Or":
380
+ "Or open {name} on {host} itself and go to Settings › Access › Waiting devices.",
381
+ "err.signin.step2":
382
+ "This page signs you in by itself once approved. The code expires in 10 minutes.",
383
+ "err.signin.waiting": "Waiting for approval…",
384
+ "err.signin.noTerminal":
385
+ "Don't have a terminal on {host}? From this computer, run:",
386
+ "err.signin.openTerminal": "Open Terminal",
387
+ "err.signin.sshNote":
388
+ "Open Terminal opens ssh://{target} in your terminal app. It needs SSH access to {target}.",
389
+ "err.signin.whyTitle": "Why?",
390
+ "err.signin.whyText":
391
+ "{name} can start terminals and agents on {host}, so it only lets in people who prove they're the owner. You only do this once per computer: you stay signed in.",
392
+ "err.signin.other": "Other ways (the one-time link with {cmd})",
393
+ "err.signin.otherStep":
394
+ "On {host} (not this computer), run this, then open the link it prints in this browser:",
395
+ "err.signin.codeExpired": "This code expired.",
396
+ "err.signin.codeDenied": "This computer was denied on {host}.",
397
+ "err.signin.codeFailed": "{name} didn't give this page a code. Try again.",
398
+ "err.signin.newCode": "Get a new code",
399
+ "err.signin.youreIn": "You're in.",
400
+ "err.signin.expiredLead":
401
+ "That sign-in link expired or was used. Sign in again below.",
402
+ "err.signin.confirmLead":
403
+ "Enter the code that web login printed on {host}:",
404
+ "err.signin.confirmGo": "Sign in",
405
+ "err.signin.confirmWrong":
406
+ "That code isn't right. Check what web login printed and try again.",
407
+ "err.signin.confirmBusy": "Too many tries. Wait a minute and try again.",
408
+ "err.signin.confirmGone":
409
+ "This link no longer works. On {host}, run web login again for a new one.",
410
+ "err.signin.confirmWhy":
411
+ "The link alone isn't enough: it needs the code shown on {host} too, so a link someone else sees can't sign them in.",
412
+ "err.signin.github": "Sign in with GitHub",
413
+ "err.signin.githubEnter": "Enter this code at {url}:",
414
+ "err.signin.githubOpen": "Open GitHub",
415
+ "err.signin.githubCopy": "Copy code",
416
+ "err.signin.githubWaiting": "Waiting for you to approve it on GitHub…",
417
+ "err.signin.githubDone": "Signed in. Loading…",
418
+ "err.signin.githubExpired": "The code expired before it was approved.",
419
+ "err.signin.githubDenied": "The request was cancelled on GitHub.",
420
+ "err.signin.githubBusy":
421
+ "Too many sign-ins are in progress. Try again in a few minutes.",
422
+ "err.signin.githubFailed": "GitHub couldn't be reached. Try again.",
423
+ "err.signin.githubAgain": "Start again",
374
424
  "err.422.title": "Config is invalid",
375
425
  "err.422.sub": "The distribution config fails validation",
376
426
  "err.422.lead":
@@ -402,11 +452,21 @@
402
452
  "set.graph.density": "Cards",
403
453
  "set.graph.panelWidth": "Details panel",
404
454
  "set.workspace.editor": "IDE for this workspace",
455
+ "set.agents.runtimes": "Runtimes",
405
456
  "set.cli.local_dev": "Local environment",
406
457
  "set.cli.cli_tabs": "Terminal tabs",
407
458
  "set.cli.log_window_lines": "Log pane lines",
408
459
  "set.cli.web_port": "Your web port",
409
460
  "set.cli.signin": "Browser sign-in",
461
+ "set.cli.sudo": "Admin password",
462
+ "set.access.owner": "Owner",
463
+ "set.access.devices": "Linked devices",
464
+ "ident.local": "Local",
465
+ "ident.device": "Signed in · this device",
466
+ "ident.toast": "This device is signed in to {name} on {host}",
467
+ "set.access.waiting": "Waiting devices",
468
+ "set.access.machine_name": "Machine name",
469
+ "set.access.pair": "Pair a device",
410
470
  "set.distribution.brand": "Product name",
411
471
  "set.distribution.github_org": "GitHub org",
412
472
  "set.distribution.orgs": "Org labels",
@@ -495,6 +555,11 @@
495
555
  "ag.rt.fix":
496
556
  "Install it, or set {key} to its path in {file}, or run {cmd}.",
497
557
  "ag.rt.unknown": "No runtime {id} here.",
558
+ "ag.rt.denTools": "Den tools: coming",
559
+ "ag.rt.ownCli":
560
+ "It runs as its own CLI in the workspace: Den's tools and permission modes are coming for it.",
561
+ "ag.rt.notInstalled": "not installed",
562
+ "ag.rt.needsArgv": "the persona has no argv template",
498
563
  };
499
564
  // The languages the UI has a catalog for, each named in itself (a name is
500
565
  // never translated). The server's list is ui-language.ts's UI_LANGUAGES.
@@ -581,6 +646,9 @@
581
646
  agents:
582
647
  '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="M7 10l3 2-3 2M12 15h5"/>',
583
648
  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"/>',
649
+ // SEC-2a: who this browser is (the rail footer's identity chip).
650
+ person:
651
+ '<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"/>',
584
652
  };
585
653
  function icon(name) {
586
654
  var d = ICONS[name] || (/^</.test(name || "") ? name : ICONS.page);
@@ -871,6 +939,9 @@
871
939
  // Whether the edge is configured (the Edge page's when): null until
872
940
  // known, the last answer cached so the rail doesn't jump.
873
941
  edge: null,
942
+ // SEC-2a: who this browser is (GET /api/session `identity`): local,
943
+ // a signed-in device, or a GitHub login. Null until known.
944
+ identity: null,
874
945
  };
875
946
  try {
876
947
  var cached = JSON.parse(localStorage.getItem(LS) || "{}");
@@ -1347,9 +1418,9 @@
1347
1418
 
1348
1419
  // ── the frame ──────────────────────────────────────────────────────────
1349
1420
  // The top bar after the scope, in the prototype's order (dev.20): the ⌘K
1350
- // search field (R5), then .chips: status (R6), Runs (R7), the lock (R8),
1421
+ // search field (R5), then .chips: status (R6), Runs (R7), the sudo ask (R8),
1351
1422
  // What's new (R9) and ◐ ▾ (R10, appearance.js fills #den-user). The
1352
- // chips start neutral ("Checking…", "Runs", "Locked") and topbar.js
1423
+ // chips start neutral ("Checking…", "Runs"; the sudo chip hidden) and topbar.js
1353
1424
  // paints them: a cold load never shows a partial red.
1354
1425
  var chips =
1355
1426
  '<button type="button" class="den-search" id="den-search" aria-haspopup="dialog" aria-keyshortcuts="Meta+K Control+K" aria-label="' +
@@ -1366,10 +1437,10 @@
1366
1437
  '<span class="den-dot off" aria-hidden="true"></span><span class="den-n den-lbl-l">' +
1367
1438
  esc(t("runs.title")) +
1368
1439
  "</span></button>" +
1369
- '<button type="button" class="den-chip" id="den-lock" aria-haspopup="dialog" aria-expanded="false" title="' +
1370
- esc(t("lock.title")) +
1371
- '"><span class="den-lock-ic" aria-hidden="true">🔒</span><span class="den-lbl-l">' +
1372
- esc(t("lock.locked")) +
1440
+ // LK-0: shown only while a run waits for the admin password (topbar.js).
1441
+ '<button type="button" class="den-chip" id="den-lock" aria-haspopup="dialog" aria-expanded="false" hidden>' +
1442
+ '<span class="den-lock-ic" aria-hidden="true">🔑</span><span class="den-lbl-l">' +
1443
+ esc(t("lock.waiting")) +
1373
1444
  "</span></button>" +
1374
1445
  '<button class="den-ib v-pill" id="version-pill" onclick="openChangelog()" title="' +
1375
1446
  esc(t("v.title")) +
@@ -1777,6 +1848,7 @@
1777
1848
  },
1778
1849
  page === SETTINGS.id,
1779
1850
  );
1851
+ html += identityChip();
1780
1852
  html +=
1781
1853
  '<button class="den-ni den-collapse-btn" type="button" data-act="collapse" aria-expanded="' +
1782
1854
  (state.collapsed ? "false" : "true") +
@@ -1790,6 +1862,49 @@
1790
1862
  return html;
1791
1863
  }
1792
1864
 
1865
+ /**
1866
+ * SEC-2a: the rail footer's identity chip: "Local" (this machine, no
1867
+ * sign-in), "Signed in · this device" (a one-time link or a code) or
1868
+ * "@login" (GitHub). A rail item like Settings (the same .den-ni
1869
+ * metrics), opening Settings › Access.
1870
+ */
1871
+ function identityLabel(id) {
1872
+ if (!id) return "";
1873
+ if (id.kind === "local") return t("ident.local");
1874
+ if (id.kind === "github" && id.login) return "@" + id.login;
1875
+ if (id.kind === "github" || id.kind === "device") return t("ident.device");
1876
+ return "";
1877
+ }
1878
+ function identityChip() {
1879
+ var label = identityLabel(state.identity);
1880
+ if (!label) return "";
1881
+ return item(
1882
+ {
1883
+ id: "identity",
1884
+ href: "/settings#access",
1885
+ title: label,
1886
+ icon: "person",
1887
+ },
1888
+ false,
1889
+ "",
1890
+ ' id="den-ident" data-ident="' + esc(state.identity.kind) + '"',
1891
+ );
1892
+ }
1893
+ /** What GET /api/session said about this browser: the chip, and a toast once after a sign-in. */
1894
+ function applyIdentity(b) {
1895
+ if (!b || !b.identity) return;
1896
+ var was = state.identity;
1897
+ if (!was || was.kind !== b.identity.kind || was.login !== b.identity.login)
1898
+ setState({ identity: b.identity });
1899
+ if (b.justSignedIn)
1900
+ toast(
1901
+ t("ident.toast", {
1902
+ name: b.product || "__DEN_TITLE__",
1903
+ host: b.machine || "",
1904
+ }),
1905
+ );
1906
+ }
1907
+
1793
1908
  // One tab stop for the rail: the current page, else the first item.
1794
1909
  function rove() {
1795
1910
  var links = Array.prototype.slice.call(nav.querySelectorAll(".den-ni"));
@@ -3522,6 +3637,7 @@
3522
3637
  function (b) {
3523
3638
  csrf.pending = null;
3524
3639
  csrf.token = b && typeof b.csrf === "string" ? b.csrf : null;
3640
+ applyIdentity(b);
3525
3641
  return csrf.token;
3526
3642
  },
3527
3643
  function () {
@@ -3853,7 +3969,7 @@
3853
3969
  " max-height: calc(100vh - 72px); overflow-y: auto; box-sizing: border-box;",
3854
3970
  " font: 14px/1.45 var(--sans); color: var(--text); }",
3855
3971
  ".den-pop:focus { outline: none; }",
3856
- // LK-1: the lock popover is a <dialog> in the top layer (a popover, or
3972
+ // LK-1: the admin-password prompt is a <dialog> in the top layer (a popover, or
3857
3973
  // a modal over another modal): placed by its top/left, no UA box.
3858
3974
  "dialog.den-pop { margin: 0; inset: auto; max-width: none; }",
3859
3975
  "dialog.den-pop::backdrop { background: transparent; }",
@@ -3909,6 +4025,8 @@
3909
4025
  ".den-chip .den-n { font-variant-numeric: tabular-nums; }",
3910
4026
  ".den-chip .den-dot, .den-drawer .den-dot, .den-pal .den-dot { margin: 0; }",
3911
4027
  ".den-chip[aria-expanded='true'] { border-color: var(--border-strong); }",
4028
+ ".den-chip[hidden] { display: none; }",
4029
+ "#den-lock { border-color: color-mix(in srgb, var(--warn) 55%, var(--border)); }",
3912
4030
  ".den-dot.run { background: var(--accent); animation: den-pulse 1.2s infinite; }",
3913
4031
  ".den-dot.pending { background: var(--faint); animation: den-pulse 1.2s infinite; }",
3914
4032
  "@keyframes den-pulse { 50% { opacity: .35; } }",
@@ -3973,7 +4091,7 @@
3973
4091
  ".den-pal li.den-pal-empty { cursor: default; color: var(--muted); }",
3974
4092
  ".den-pal .den-foot { display: flex; gap: 14px; padding: 8px 14px; border-top: 1px solid var(--border);",
3975
4093
  " color: var(--faint); font-size: 12px; flex-wrap: wrap; }",
3976
- // The lock popover (.pop form, .pop input, .note: R8)
4094
+ // The admin-password prompt (.pop form, .pop input, .note: R8, LK-0)
3977
4095
  ".den-pop form { display: grid; gap: 8px; padding: 6px 8px 8px; width: 260px; box-sizing: content-box; margin: 0; }",
3978
4096
  ".den-pop input:not([type='checkbox']) { border: 1px solid var(--border); background: var(--bg); color: var(--text);",
3979
4097
  " border-radius: var(--radius-button); padding: 6px 8px; font: inherit; font-size: 13.3333px; line-height: normal; }",
@@ -3982,7 +4100,7 @@
3982
4100
  ".den-pop .den-lock-save { display: flex; gap: 6px; align-items: center; color: var(--muted); font-size: 12.5px; }",
3983
4101
  ".den-pop .den-lock-save[hidden], .den-pop .den-lock-err[hidden], .den-mslot[hidden] { display: none; }",
3984
4102
  ".den-pop .den-lock-err { color: var(--bad); font-size: 12.5px; }",
3985
- ".den-pop .den-lock-desc { padding: 2px 8px 6px; color: var(--muted); font-size: 12.5px; max-width: 260px; }",
4103
+ ".den-pop #den-lock-t { overflow-wrap: anywhere; }",
3986
4104
  // What's new (.ib.whatsnew + .badge-dot)
3987
4105
  "#den-shell .v-pill { font: inherit; padding: 4px 8px; border: 1px solid transparent;",
3988
4106
  " border-radius: var(--radius-button); color: var(--muted); flex-shrink: 0; }",