@monster-orange/den 0.4.17 → 0.4.18

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.
@@ -211,6 +211,20 @@
211
211
  font-weight: 600;
212
212
  color: var(--text);
213
213
  }
214
+ /* 0.4.18: the name opens the service panel (svc-panel.js). */
215
+ .svc-name-btn {
216
+ background: none;
217
+ border: 0;
218
+ padding: 0;
219
+ font: inherit;
220
+ color: inherit;
221
+ cursor: pointer;
222
+ text-align: left;
223
+ }
224
+ .svc-name-btn:hover,
225
+ .svc-name-btn[aria-expanded="true"] {
226
+ text-decoration: underline;
227
+ }
214
228
  .service-repo .owners {
215
229
  color: var(--accent-text);
216
230
  }
@@ -335,6 +349,7 @@
335
349
  envReady,
336
350
  sudoRun,
337
351
  } from "/header.js";
352
+ import { logStreamUrl } from "/log-source.js";
338
353
  import { ownersLabel } from "/workspace-picker.js";
339
354
 
340
355
  let es = null;
@@ -656,7 +671,7 @@
656
671
  <div class="service-card-header">
657
672
  <span class="dot" id="dot-${s.repo}-${s.service}"></span>
658
673
  <div class="service-card-title">
659
- <div class="service-name">${addr}</div>
674
+ <div class="service-name"><button type="button" class="svc-name-btn" data-svpanel-open="${esc(isSub ? addr : s.repo)}" aria-haspopup="dialog" aria-expanded="false" title="${esc(window.denShell?.t?.("svp.more", { s: isSub ? addr : s.repo }) ?? addr)}">${esc(addr)}</button></div>
660
675
  <div class="service-repo">${s.repo}${
661
676
  ownersLabel(s.inWorkspaces)
662
677
  ? `<span class="owners" title="Shared: listed once for every workspace in view that defines it">${esc(ownersLabel(s.inWorkspaces))}</span>`
@@ -821,8 +836,8 @@
821
836
  );
822
837
  appendLine(`\n▶ ${repo}/${service} logs · ${env}\n`, "cmd");
823
838
 
824
- const params = new URLSearchParams({ repo, service, env });
825
- es = new EventSource(`/api/service/logs?${params}`);
839
+ // The Logs page's source (log-source.js): taillog for local.
840
+ es = new EventSource(logStreamUrl(repo, service, env));
826
841
  handleSSE(es, repo, service, { stream: true });
827
842
  }
828
843
 
@@ -169,6 +169,41 @@
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
+ "svp.close": "Close",
204
+ "ag.new.folder": "Folder (optional)",
205
+ "ag.new.folderHelp":
206
+ "Inside the workspace's folder. Empty: the workspace's own.",
172
207
  collapse: "Collapse",
173
208
  expand: "Expand",
174
209
  menu: "Menu",
@@ -1214,6 +1249,12 @@
1214
1249
  // SEC-2a: who this browser is (GET /api/session `identity`): local,
1215
1250
  // a signed-in device, or a GitHub login. Null until known.
1216
1251
  identity: null,
1252
+ // 0.4.18: each pin's URL (svc-panel.js, from the health feed) for its
1253
+ // Visit ↗, whether that Visit is off (down or starting: the panel's own
1254
+ // live state), and the pin whose service panel is open.
1255
+ pinUrls: {},
1256
+ pinOff: {},
1257
+ svpOpen: null,
1217
1258
  };
1218
1259
  try {
1219
1260
  var cached = JSON.parse(localStorage.getItem(LS) || "{}");
@@ -1441,6 +1482,31 @@
1441
1482
  var r = anchor.getBoundingClientRect();
1442
1483
  var gap = opts.gap != null ? opts.gap : 4;
1443
1484
  el.style.maxHeight = "";
1485
+ // 0.4.18: a bottom sheet is placed by its CSS (bottom: 0), not here.
1486
+ if (opts.sheet) {
1487
+ el.style.removeProperty("top");
1488
+ el.style.removeProperty("left");
1489
+ return;
1490
+ }
1491
+ // 0.4.18: beside the anchor (the rail), level with a row in it.
1492
+ if (opts.side === "right") {
1493
+ var row = typeof opts.row === "function" ? opts.row() : null;
1494
+ var rr = row ? row.getBoundingClientRect() : r;
1495
+ var bw = el.offsetWidth;
1496
+ var bh = el.offsetHeight;
1497
+ var bl = Math.min(r.right + gap, window.innerWidth - bw - TOP_MARGIN);
1498
+ var bt = Math.min(
1499
+ rr.top + (opts.dy || 0),
1500
+ window.innerHeight - bh - TOP_MARGIN,
1501
+ );
1502
+ el.style.setProperty("top", Math.max(TOP_MARGIN, bt) + "px", "important");
1503
+ el.style.setProperty(
1504
+ "left",
1505
+ Math.max(TOP_MARGIN, bl) + "px",
1506
+ "important",
1507
+ );
1508
+ return;
1509
+ }
1444
1510
  var w = el.offsetWidth;
1445
1511
  var h = el.offsetHeight;
1446
1512
  var vw = window.innerWidth;
@@ -2123,6 +2189,47 @@
2123
2189
  : " off";
2124
2190
  return '<span class="den-dot' + cls + '" aria-hidden="true"></span>';
2125
2191
  }
2192
+ /**
2193
+ * 0.4.18: a pin's Visit ↗: none without a URL, greyed out while it is
2194
+ * down. Down is the service panel's live state (pinOff), the same as its
2195
+ * dot, so it holds for a pin outside the View too; the rail's rollup
2196
+ * only without it.
2197
+ */
2198
+ function pinVisit(id) {
2199
+ var url = state.pinUrls && state.pinUrls[id];
2200
+ if (!url) return "";
2201
+ var off = state.pinOff || {};
2202
+ var down = Object.prototype.hasOwnProperty.call(off, id)
2203
+ ? !!off[id]
2204
+ : state.health[id] === "down";
2205
+ return (
2206
+ '<button type="button" class="den-pvisit" tabindex="-1" data-visit="' +
2207
+ esc(id) +
2208
+ '" data-url="' +
2209
+ esc(url) +
2210
+ '" aria-label="' +
2211
+ esc(t("act.visitX", { s: id })) +
2212
+ '" title="' +
2213
+ esc(down ? t("svp.notRunning") : url) +
2214
+ '"' +
2215
+ (down ? ' aria-disabled="true"' : "") +
2216
+ '><span aria-hidden="true">↗</span></button>'
2217
+ );
2218
+ }
2219
+ function visitPin(b) {
2220
+ if (b.getAttribute("aria-disabled") === "true") {
2221
+ toast(t("svp.notRunning"));
2222
+ return;
2223
+ }
2224
+ var url = b.getAttribute("data-url");
2225
+ if (url && /^https?:\/\//i.test(url))
2226
+ window.open(url, "_blank", "noopener");
2227
+ }
2228
+ // 0.4.18: the service panel (svc-panel.js registers it).
2229
+ var servicePanel = null;
2230
+ function setServicePanel(p) {
2231
+ servicePanel = p && typeof p.open === "function" ? p : null;
2232
+ }
2126
2233
  function pinHref(id) {
2127
2234
  var repo = id.split("/")[0];
2128
2235
  return "/services#repo-" + encodeURIComponent(repo);
@@ -2191,9 +2298,12 @@
2191
2298
  null,
2192
2299
  state.pins
2193
2300
  .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.
2301
+ // 0.4.18: the row (named by the id alone) opens the service's
2302
+ // panel in place (svc-panel.js); it is still the link to
2303
+ // Services, so ⌘-click and middle-click open that in a new
2304
+ // tab. Delete unpins it. Visit ↗ beside it opens the service's
2305
+ // URL, out of the tab order: the panel's first button is the
2306
+ // keyboard's Visit. Unpin is in the panel's footer.
2197
2307
  return (
2198
2308
  '<div class="den-pin-row">' +
2199
2309
  item(
@@ -2205,17 +2315,17 @@
2205
2315
  mono: true,
2206
2316
  },
2207
2317
  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"',
2318
+ "",
2319
+ ' data-pin="' +
2320
+ esc(p) +
2321
+ '" data-svpanel="' +
2322
+ esc(p) +
2323
+ '" aria-haspopup="dialog" aria-expanded="' +
2324
+ (state.svpOpen === p ? "true" : "false") +
2325
+ '" aria-keyshortcuts="Delete"',
2211
2326
  ) +
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>'
2327
+ pinVisit(p) +
2328
+ "</div>"
2219
2329
  );
2220
2330
  })
2221
2331
  .join(""),
@@ -2423,10 +2533,28 @@
2423
2533
  });
2424
2534
  nav.addEventListener("click", function (e) {
2425
2535
  var tgt = e.target;
2426
- var x = tgt && tgt.closest ? tgt.closest("[data-unpin]") : null;
2427
- if (x) {
2536
+ var v = tgt && tgt.closest ? tgt.closest("[data-visit]") : null;
2537
+ if (v) {
2428
2538
  e.preventDefault();
2429
- unpinFromRail(x.getAttribute("data-unpin"));
2539
+ visitPin(v);
2540
+ return;
2541
+ }
2542
+ // 0.4.18: a plain click (or Enter) on a pinned row opens its panel;
2543
+ // ⌘/Ctrl-, Shift- and middle-click keep the link (Services).
2544
+ var sp = tgt && tgt.closest ? tgt.closest("[data-svpanel]") : null;
2545
+ if (
2546
+ sp &&
2547
+ servicePanel &&
2548
+ e.button === 0 &&
2549
+ !e.metaKey &&
2550
+ !e.ctrlKey &&
2551
+ !e.shiftKey &&
2552
+ !e.altKey
2553
+ ) {
2554
+ e.preventDefault();
2555
+ servicePanel.open(sp.getAttribute("data-svpanel"), sp, {
2556
+ fromRail: true,
2557
+ });
2430
2558
  return;
2431
2559
  }
2432
2560
  var c = tgt && tgt.closest ? tgt.closest('[data-act="collapse"]') : null;
@@ -4523,6 +4651,14 @@
4523
4651
  * anchor; hide(el). The page hiding it (display: none) closes it too.
4524
4652
  */
4525
4653
  topLayer: { show: topShow, hide: topHide },
4654
+ /**
4655
+ * 0.4.18: the service panel. svc-panel.js registers { open, close,
4656
+ * visit }; openServicePanel(id, opener) opens it beside opener.
4657
+ */
4658
+ setServicePanel: setServicePanel,
4659
+ openServicePanel: function (id, opener) {
4660
+ if (servicePanel) servicePanel.open(id, opener);
4661
+ },
4526
4662
  closeMenu: function () {
4527
4663
  closeMenu(false);
4528
4664
  },
@@ -4865,18 +5001,65 @@
4865
5001
  " display: inline-block; margin: 0 5px; }",
4866
5002
  ".den-dot.bad { background: var(--bad); } .den-dot.warn { background: var(--warn); }",
4867
5003
  ".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; }",
5004
+ // 0.4.18: a pinned row is its name (opens the service panel) and Visit ↗.
5005
+ ".den-pin-row { display: flex; align-items: center; gap: 2px; min-width: 0; }",
5006
+ ".den-pin-row .den-ni { flex: 1; min-width: 0; }",
5007
+ ".den-pin-row .den-ni[aria-expanded='true'] { background: var(--sunken); color: var(--text); }",
5008
+ ".den-pvisit { flex: none; width: 26px; height: 26px; padding: 0; display: grid; place-items: center;",
5009
+ " background: none; border: 1px solid transparent; border-radius: var(--radius-button);",
5010
+ " color: var(--muted); cursor: pointer; font: 13px/1 var(--sans); }",
5011
+ ".den-pvisit:hover { background: var(--sunken); color: var(--accent-text); border-color: var(--border); }",
5012
+ ".den-pvisit[aria-disabled='true'], .den-svp .den-btn[aria-disabled='true'] { opacity: .45; cursor: not-allowed; }",
5013
+ ".den-pvisit[aria-disabled='true']:hover { background: none; color: var(--muted); border-color: transparent; }",
5014
+ "body.den-rail-collapsed .den-rail .den-pvisit { display: none; }",
5015
+ "@media (max-width: 720px) { .den-pvisit { width: 32px; height: 32px; }",
5016
+ " body.den-rail-collapsed .den-rail .den-pvisit { display: grid; } }",
5017
+ // The service panel (svc-panel.js): beside its row, a bottom sheet at phone width.
5018
+ ".den-pop.den-svp { width: 340px; max-width: calc(100vw - 16px); box-sizing: border-box; padding: 0;",
5019
+ " max-height: calc(100dvh - 16px); overflow-y: auto; min-width: 0; }",
5020
+ ".den-svp-h { display: grid; gap: 4px; padding: 12px 12px 10px; border-bottom: 1px solid var(--border); }",
5021
+ ".den-svp-t { display: flex; align-items: center; gap: 8px; min-width: 0; }",
5022
+ ".den-svp-t .den-dot, .den-svp-ag .den-dot, .den-svp-line .den-dot { margin: 0; }",
5023
+ // Line heights pinned to the mock's (its mono font's normal), whatever the theme's font.
5024
+ ".den-svp-t b { flex: 1; min-width: 0; font: 600 14px/21px var(--mono); overflow: hidden;",
5025
+ " text-overflow: ellipsis; white-space: nowrap; }",
5026
+ ".den-svp-x { padding: 2px 6px; margin: -4px -6px -4px 0; }",
5027
+ ".den-svp-meta { display: flex; align-items: center; gap: 4px 8px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); }",
5028
+ ".den-wsp { font: 11px/16px var(--mono); border-radius: 999px; padding: 0 7px; white-space: nowrap;",
5029
+ " border: 1px solid var(--border); color: var(--muted); }",
5030
+ ".den-svp-st { color: var(--ok); } .den-svp-st.bad { color: var(--bad); }",
5031
+ ".den-svp-st.off { color: var(--faint); } .den-svp-st.run { color: var(--accent-text); }",
5032
+ ".den-svp-ms { font: 11.5px var(--mono); color: var(--faint); }",
5033
+ ".den-svp-url { font: 12px/17px var(--mono); color: var(--faint); overflow-wrap: anywhere; }",
5034
+ ".den-svp-acts { display: flex; gap: 6px; flex-wrap: wrap; padding: 10px 12px; }",
5035
+ ".den-svp-sec { display: grid; gap: 6px; padding: 8px 12px 10px; border-top: 1px solid var(--border); }",
5036
+ ".den-svp-k { font: 11px/16px var(--mono); color: var(--faint); text-transform: uppercase; letter-spacing: .07em; }",
5037
+ ".den-svp-line { display: flex; align-items: center; gap: 8px; font-size: 13px; }",
5038
+ ".den-svp-line .den-svp-sp { margin-left: auto; }",
5039
+ ".den-svp-ok { color: var(--ok); } .den-svp-bad { color: var(--bad); } .den-svp-none { color: var(--faint); }",
5040
+ ".den-svp-ag { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; gap: 0 8px;",
5041
+ " align-items: center; font-size: 13px; }",
5042
+ ".den-svp-nm { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
5043
+ ".den-svp-as { font: 11px var(--mono); color: var(--faint); white-space: nowrap; }",
5044
+ ".den-wbdg { font: 10.5px var(--mono); color: var(--warn); border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);",
5045
+ " border-radius: 999px; padding: 0 6px; }",
5046
+ ".den-svp-help { font-size: 12px; color: var(--faint); line-height: 1.4; }",
5047
+ ".den-svp-foot { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 8px 12px 10px;",
5048
+ " border-top: 1px solid var(--border); }",
5049
+ ".den-svp-grow { margin-left: auto; }",
5050
+ ".den-lnk { color: var(--accent-text); cursor: pointer; font: inherit; font-size: 12.5px; text-decoration: underline;",
5051
+ " background: none; border: 0; padding: 0; }",
5052
+ ".den-scrim.den-svp-scrim { z-index: 43; }",
5053
+ ".den-pop.den-svp.den-sheet.den-toplayer { top: auto !important; left: 0 !important; right: 0 !important;",
5054
+ " bottom: 0 !important; width: auto; max-width: none; border-bottom: 0; border-radius: 14px 14px 0 0;",
5055
+ " max-height: 85dvh; padding-bottom: env(safe-area-inset-bottom, 0px); }",
5056
+ ".den-pop.den-svp.den-sheet::before { content: ''; display: block; width: 36px; height: 4px; border-radius: 2px;",
5057
+ " background: var(--border-strong); margin: 8px auto 0; }",
5058
+ ".den-pop.den-svp.den-sheet .den-svp-acts .den-btn { flex: 1 1 auto; padding: 9px 12px; }",
4875
5059
  // Collapsed: icons only. The prototype hides headings and labels; they
4876
5060
  // stay here for screen readers (visually hidden, no space taken).
4877
5061
  "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 {",
5062
+ "body.den-rail-collapsed .den-rail .den-bdg {",
4880
5063
  " position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden;",
4881
5064
  " clip: rect(0 0 0 0); white-space: nowrap; border: 0; }",
4882
5065
  // Toast (.toast)