@crvouga/mockingbird-service-junction 1.7.0 → 1.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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Changelog — @crvouga/mockingbird-service-junction
2
2
 
3
- ## 1.7.0 (2026-09-30)
3
+ ## 1.7.1 (2026-09-30)
4
+
5
+ ### Fixes and improvements
6
+
7
+ - keep admin buttons pressed until the action finishes ([0aabfcb](https://github.com/crvouga/mockingbird/commit/0aabfcb5f392aa51e193f9c3f817d5877d92bc6a))
8
+ - size admin select fields like the text inputs beside them ([0fb3ef2](https://github.com/crvouga/mockingbird/commit/0fb3ef2ddbf43376c93a2ae6d87c6c9bf890dafc))
9
+ - keep admin section tabs wired when boot is renamed ([4eda25e](https://github.com/crvouga/mockingbird/commit/4eda25eacd59da048d196f7cc8369d7a02b47bfc))
10
+
11
+ ## 1.7.0 (2026-09-29)
4
12
 
5
13
  ### Features
6
14
 
@@ -2111,6 +2111,50 @@ function bootAdmin(config) {
2111
2111
  if (render2)
2112
2112
  await render2();
2113
2113
  }
2114
+ const delay = (ms) => new Promise((resolve) => {
2115
+ window.setTimeout(resolve, ms);
2116
+ });
2117
+ const confirmAction = async (button, pending, done, work) => {
2118
+ if (button.dataset.pending === "1")
2119
+ return;
2120
+ const label = button.textContent ?? "";
2121
+ const width = button.offsetWidth;
2122
+ button.dataset.pending = "1";
2123
+ button.classList.add("pressed");
2124
+ button.setAttribute("aria-busy", "true");
2125
+ button.style.minWidth = `${width}px`;
2126
+ button.textContent = pending;
2127
+ try {
2128
+ await work();
2129
+ button.textContent = done;
2130
+ await delay(1e3);
2131
+ } catch (error) {
2132
+ showError(error);
2133
+ } finally {
2134
+ button.textContent = label;
2135
+ button.style.minWidth = "";
2136
+ button.classList.remove("pressed");
2137
+ button.removeAttribute("aria-busy");
2138
+ delete button.dataset.pending;
2139
+ }
2140
+ };
2141
+ document.addEventListener("click", (event) => {
2142
+ const target = event.target;
2143
+ if (!(target instanceof Element))
2144
+ return;
2145
+ const button = target.closest("button");
2146
+ if (!(button instanceof HTMLButtonElement) || button.dataset.pending === "1")
2147
+ return;
2148
+ button.classList.add("pressed");
2149
+ const token = String(Date.now());
2150
+ button.dataset.press = token;
2151
+ window.setTimeout(() => {
2152
+ if (button.dataset.press !== token || button.dataset.pending === "1")
2153
+ return;
2154
+ button.classList.remove("pressed");
2155
+ delete button.dataset.press;
2156
+ }, 450);
2157
+ });
2114
2158
  el("namespace", HTMLSelectElement).addEventListener("change", () => {
2115
2159
  state.namespace = el("namespace", HTMLSelectElement).value;
2116
2160
  refresh().catch(showError);
@@ -2121,7 +2165,7 @@ function bootAdmin(config) {
2121
2165
  refresh().catch(showError);
2122
2166
  });
2123
2167
  el("refresh", HTMLButtonElement).addEventListener("click", () => {
2124
- refresh().catch(showError);
2168
+ confirmAction(el("refresh", HTMLButtonElement), "Refreshing...", "Updated", () => refresh()).catch(showError);
2125
2169
  });
2126
2170
  el("collections", HTMLElement).addEventListener("click", (event) => {
2127
2171
  const target = event.target;
@@ -2244,10 +2288,10 @@ function bootAdmin(config) {
2244
2288
  }).then(() => renderFaults()).catch(showError);
2245
2289
  });
2246
2290
  el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2247
- api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2291
+ confirmAction(el("fault-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/faults").then(() => renderFaults())).catch(showError);
2248
2292
  });
2249
2293
  el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2250
- api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2294
+ confirmAction(el("journal-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/requests").then(() => renderJournal())).catch(showError);
2251
2295
  });
2252
2296
  el("route-list", HTMLElement).addEventListener("click", (event) => {
2253
2297
  const target = event.target;
@@ -2362,9 +2406,13 @@ function bootAdmin(config) {
2362
2406
  var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2363
2407
  var adminClientSource = (config) => {
2364
2408
  const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2409
+ const entry = bootAdmin.name;
2410
+ if (!/^[A-Za-z_$][\w$]*$/.test(entry)) {
2411
+ throw new Error("admin client cannot call its boot function");
2412
+ }
2365
2413
  return `<script>
2366
2414
  ${clientPrelude}
2367
- bootAdmin(${payload});
2415
+ ${entry}(${payload});
2368
2416
  </script>`;
2369
2417
  };
2370
2418
 
@@ -2578,9 +2626,24 @@ var renderAdminDocument = (service) => {
2578
2626
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
2579
2627
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
2580
2628
  label.field input, label.field select, .controls input[type="password"] {
2581
- background: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2582
- padding: 7px 8px; min-height: 36px;
2583
- }
2629
+ background-color: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2630
+ padding: 7px 8px; min-height: 36px; line-height: 1.2;
2631
+ }
2632
+ /* A native menu ignores min-height, so Preset rendered about half as tall as
2633
+ the text fields in the same form. Paint it like those fields. */
2634
+ label.field select {
2635
+ -webkit-appearance: none; appearance: none;
2636
+ padding-right: 28px;
2637
+ 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='none' stroke='%2371717a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E");
2638
+ background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
2639
+ }
2640
+ .stack > .field > input,
2641
+ .stack > .field > select,
2642
+ .stack > .field > textarea,
2643
+ dialog .field > input,
2644
+ dialog .field > select,
2645
+ dialog .field > textarea { width: 100%; }
2646
+ .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger, nav.side button { cursor: pointer; }
2584
2647
  .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger {
2585
2648
  border-radius: 9px; min-height: 36px; padding: 0 12px; border: 1px solid var(--line);
2586
2649
  background: var(--bg-elev);
@@ -2588,6 +2651,14 @@ var renderAdminDocument = (service) => {
2588
2651
  .btn-primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
2589
2652
  .btn-quiet { background: transparent; }
2590
2653
  .btn-danger { color: var(--err); }
2654
+ /* Held after mouseup. Clearing an empty journal changes nothing on screen,
2655
+ so the press itself has to stay visible. */
2656
+ .icon-btn:active, .btn:active, .btn-quiet:active, .btn-danger:active, nav.side button:active,
2657
+ .icon-btn.pressed, .btn.pressed, .btn-quiet.pressed, .btn-danger.pressed, nav.side button.pressed {
2658
+ background: var(--bg-inset); border-color: var(--line-strong); color: var(--ink);
2659
+ }
2660
+ .btn-danger:active, .btn-danger.pressed { background: var(--err-soft); color: var(--err); }
2661
+ .btn-primary:active, .btn-primary.pressed { filter: brightness(0.88); color: var(--accent-ink); }
2591
2662
  /* minmax(0, 1fr): a 1fr column will not shrink below its content, so a long
2592
2663
  record or the full nav row would widen the page past the viewport. */
2593
2664
  .layout {
@@ -2676,8 +2747,16 @@ var renderAdminDocument = (service) => {
2676
2747
  input, select, textarea { font-size: 16px; }
2677
2748
  .brand, .vendor, .controls { width: 100%; }
2678
2749
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
2679
- .layout { grid-template-columns: minmax(0, 1fr); }
2750
+ /* The layout fills the screen. A single-column grid stretches every auto
2751
+ row, so the tab bar grew with the leftover height and the buttons floated
2752
+ in the middle of it. The bar stays one row; the page below takes the rest. */
2753
+ .layout {
2754
+ grid-template-columns: minmax(0, 1fr);
2755
+ grid-template-rows: auto minmax(0, 1fr);
2756
+ align-content: start;
2757
+ }
2680
2758
  nav.side {
2759
+ align-self: start; height: auto;
2681
2760
  flex-direction: row; align-items: center; overflow-x: auto; min-width: 0;
2682
2761
  border-right: 0; border-bottom: 1px solid var(--line);
2683
2762
  padding: 8px;
@@ -3471,7 +3550,7 @@ var stateAdminRoutes = (routes) => {
3471
3550
  };
3472
3551
 
3473
3552
  // ../core/dist/version.js
3474
- var PACKAGE_VERSION = true ? "1.7.0" : UNRELEASED_VERSION;
3553
+ var PACKAGE_VERSION = true ? "1.7.1" : UNRELEASED_VERSION;
3475
3554
 
3476
3555
  // ../core/dist/webhooks.js
3477
3556
  var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
@@ -11990,4 +12069,4 @@ export {
11990
12069
  JunctionAPI,
11991
12070
  LabAccountConflict
11992
12071
  };
11993
- //# sourceMappingURL=chunk-LUHSOIDO.js.map
12072
+ //# sourceMappingURL=chunk-KWBRKBUI.js.map