@crvouga/mockingbird-service-odx 1.6.0 → 1.6.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-odx
2
2
 
3
- ## 1.6.0 (2026-09-30)
3
+ ## 1.6.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.6.0 (2026-09-29)
4
12
 
5
13
  ### Features
6
14
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createRuntime
3
- } from "./chunk-JQSFQSCJ.js";
3
+ } from "./chunk-QY3DD74Z.js";
4
4
 
5
5
  // ../../adapters/node/dist/cli.js
6
6
  import { readFile } from "node:fs/promises";
@@ -360,4 +360,4 @@ export {
360
360
  createServer2 as createServer,
361
361
  serveTarget
362
362
  };
363
- //# sourceMappingURL=chunk-POYXJBSV.js.map
363
+ //# sourceMappingURL=chunk-BHLJXOSH.js.map
@@ -2066,6 +2066,50 @@ function bootAdmin(config) {
2066
2066
  if (render)
2067
2067
  await render();
2068
2068
  }
2069
+ const delay = (ms) => new Promise((resolve) => {
2070
+ window.setTimeout(resolve, ms);
2071
+ });
2072
+ const confirmAction = async (button, pending, done, work) => {
2073
+ if (button.dataset.pending === "1")
2074
+ return;
2075
+ const label = button.textContent ?? "";
2076
+ const width = button.offsetWidth;
2077
+ button.dataset.pending = "1";
2078
+ button.classList.add("pressed");
2079
+ button.setAttribute("aria-busy", "true");
2080
+ button.style.minWidth = `${width}px`;
2081
+ button.textContent = pending;
2082
+ try {
2083
+ await work();
2084
+ button.textContent = done;
2085
+ await delay(1e3);
2086
+ } catch (error) {
2087
+ showError(error);
2088
+ } finally {
2089
+ button.textContent = label;
2090
+ button.style.minWidth = "";
2091
+ button.classList.remove("pressed");
2092
+ button.removeAttribute("aria-busy");
2093
+ delete button.dataset.pending;
2094
+ }
2095
+ };
2096
+ document.addEventListener("click", (event) => {
2097
+ const target = event.target;
2098
+ if (!(target instanceof Element))
2099
+ return;
2100
+ const button = target.closest("button");
2101
+ if (!(button instanceof HTMLButtonElement) || button.dataset.pending === "1")
2102
+ return;
2103
+ button.classList.add("pressed");
2104
+ const token = String(Date.now());
2105
+ button.dataset.press = token;
2106
+ window.setTimeout(() => {
2107
+ if (button.dataset.press !== token || button.dataset.pending === "1")
2108
+ return;
2109
+ button.classList.remove("pressed");
2110
+ delete button.dataset.press;
2111
+ }, 450);
2112
+ });
2069
2113
  el("namespace", HTMLSelectElement).addEventListener("change", () => {
2070
2114
  state.namespace = el("namespace", HTMLSelectElement).value;
2071
2115
  refresh().catch(showError);
@@ -2076,7 +2120,7 @@ function bootAdmin(config) {
2076
2120
  refresh().catch(showError);
2077
2121
  });
2078
2122
  el("refresh", HTMLButtonElement).addEventListener("click", () => {
2079
- refresh().catch(showError);
2123
+ confirmAction(el("refresh", HTMLButtonElement), "Refreshing...", "Updated", () => refresh()).catch(showError);
2080
2124
  });
2081
2125
  el("collections", HTMLElement).addEventListener("click", (event) => {
2082
2126
  const target = event.target;
@@ -2199,10 +2243,10 @@ function bootAdmin(config) {
2199
2243
  }).then(() => renderFaults()).catch(showError);
2200
2244
  });
2201
2245
  el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2202
- api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2246
+ confirmAction(el("fault-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/faults").then(() => renderFaults())).catch(showError);
2203
2247
  });
2204
2248
  el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2205
- api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2249
+ confirmAction(el("journal-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/requests").then(() => renderJournal())).catch(showError);
2206
2250
  });
2207
2251
  el("route-list", HTMLElement).addEventListener("click", (event) => {
2208
2252
  const target = event.target;
@@ -2317,9 +2361,13 @@ function bootAdmin(config) {
2317
2361
  var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2318
2362
  var adminClientSource = (config) => {
2319
2363
  const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2364
+ const entry = bootAdmin.name;
2365
+ if (!/^[A-Za-z_$][\w$]*$/.test(entry)) {
2366
+ throw new Error("admin client cannot call its boot function");
2367
+ }
2320
2368
  return `<script>
2321
2369
  ${clientPrelude}
2322
- bootAdmin(${payload});
2370
+ ${entry}(${payload});
2323
2371
  </script>`;
2324
2372
  };
2325
2373
 
@@ -2533,9 +2581,24 @@ var renderAdminDocument = (service) => {
2533
2581
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
2534
2582
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
2535
2583
  label.field input, label.field select, .controls input[type="password"] {
2536
- background: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2537
- padding: 7px 8px; min-height: 36px;
2538
- }
2584
+ background-color: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2585
+ padding: 7px 8px; min-height: 36px; line-height: 1.2;
2586
+ }
2587
+ /* A native menu ignores min-height, so Preset rendered about half as tall as
2588
+ the text fields in the same form. Paint it like those fields. */
2589
+ label.field select {
2590
+ -webkit-appearance: none; appearance: none;
2591
+ padding-right: 28px;
2592
+ 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");
2593
+ background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
2594
+ }
2595
+ .stack > .field > input,
2596
+ .stack > .field > select,
2597
+ .stack > .field > textarea,
2598
+ dialog .field > input,
2599
+ dialog .field > select,
2600
+ dialog .field > textarea { width: 100%; }
2601
+ .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger, nav.side button { cursor: pointer; }
2539
2602
  .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger {
2540
2603
  border-radius: 9px; min-height: 36px; padding: 0 12px; border: 1px solid var(--line);
2541
2604
  background: var(--bg-elev);
@@ -2543,6 +2606,14 @@ var renderAdminDocument = (service) => {
2543
2606
  .btn-primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
2544
2607
  .btn-quiet { background: transparent; }
2545
2608
  .btn-danger { color: var(--err); }
2609
+ /* Held after mouseup. Clearing an empty journal changes nothing on screen,
2610
+ so the press itself has to stay visible. */
2611
+ .icon-btn:active, .btn:active, .btn-quiet:active, .btn-danger:active, nav.side button:active,
2612
+ .icon-btn.pressed, .btn.pressed, .btn-quiet.pressed, .btn-danger.pressed, nav.side button.pressed {
2613
+ background: var(--bg-inset); border-color: var(--line-strong); color: var(--ink);
2614
+ }
2615
+ .btn-danger:active, .btn-danger.pressed { background: var(--err-soft); color: var(--err); }
2616
+ .btn-primary:active, .btn-primary.pressed { filter: brightness(0.88); color: var(--accent-ink); }
2546
2617
  /* minmax(0, 1fr): a 1fr column will not shrink below its content, so a long
2547
2618
  record or the full nav row would widen the page past the viewport. */
2548
2619
  .layout {
@@ -2631,8 +2702,16 @@ var renderAdminDocument = (service) => {
2631
2702
  input, select, textarea { font-size: 16px; }
2632
2703
  .brand, .vendor, .controls { width: 100%; }
2633
2704
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
2634
- .layout { grid-template-columns: minmax(0, 1fr); }
2705
+ /* The layout fills the screen. A single-column grid stretches every auto
2706
+ row, so the tab bar grew with the leftover height and the buttons floated
2707
+ in the middle of it. The bar stays one row; the page below takes the rest. */
2708
+ .layout {
2709
+ grid-template-columns: minmax(0, 1fr);
2710
+ grid-template-rows: auto minmax(0, 1fr);
2711
+ align-content: start;
2712
+ }
2635
2713
  nav.side {
2714
+ align-self: start; height: auto;
2636
2715
  flex-direction: row; align-items: center; overflow-x: auto; min-width: 0;
2637
2716
  border-right: 0; border-bottom: 1px solid var(--line);
2638
2717
  padding: 8px;
@@ -3426,7 +3505,7 @@ var stateAdminRoutes = (routes) => {
3426
3505
  };
3427
3506
 
3428
3507
  // ../core/dist/version.js
3429
- var PACKAGE_VERSION = true ? "1.6.0" : UNRELEASED_VERSION;
3508
+ var PACKAGE_VERSION = true ? "1.6.1" : UNRELEASED_VERSION;
3430
3509
 
3431
3510
  // ../core/dist/signing.js
3432
3511
  var encoder = new TextEncoder();
@@ -5462,4 +5541,4 @@ export {
5462
5541
  apiKeyCredential,
5463
5542
  OdxAPI
5464
5543
  };
5465
- //# sourceMappingURL=chunk-JQSFQSCJ.js.map
5544
+ //# sourceMappingURL=chunk-QY3DD74Z.js.map