@crvouga/mockingbird-service-fullscript 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-fullscript
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-OBQHBBFC.js";
3
+ } from "./chunk-UZIN4H2O.js";
4
4
 
5
5
  // ../../adapters/node/dist/cli.js
6
6
  import { readFile } from "node:fs/promises";
@@ -381,4 +381,4 @@ export {
381
381
  createServer2 as createServer,
382
382
  serveTarget
383
383
  };
384
- //# sourceMappingURL=chunk-ZDHQHF3E.js.map
384
+ //# sourceMappingURL=chunk-AJ6RYUHV.js.map
@@ -2327,6 +2327,50 @@ function bootAdmin(config) {
2327
2327
  if (render)
2328
2328
  await render();
2329
2329
  }
2330
+ const delay = (ms) => new Promise((resolve) => {
2331
+ window.setTimeout(resolve, ms);
2332
+ });
2333
+ const confirmAction = async (button, pending, done, work) => {
2334
+ if (button.dataset.pending === "1")
2335
+ return;
2336
+ const label = button.textContent ?? "";
2337
+ const width = button.offsetWidth;
2338
+ button.dataset.pending = "1";
2339
+ button.classList.add("pressed");
2340
+ button.setAttribute("aria-busy", "true");
2341
+ button.style.minWidth = `${width}px`;
2342
+ button.textContent = pending;
2343
+ try {
2344
+ await work();
2345
+ button.textContent = done;
2346
+ await delay(1e3);
2347
+ } catch (error) {
2348
+ showError(error);
2349
+ } finally {
2350
+ button.textContent = label;
2351
+ button.style.minWidth = "";
2352
+ button.classList.remove("pressed");
2353
+ button.removeAttribute("aria-busy");
2354
+ delete button.dataset.pending;
2355
+ }
2356
+ };
2357
+ document.addEventListener("click", (event) => {
2358
+ const target = event.target;
2359
+ if (!(target instanceof Element))
2360
+ return;
2361
+ const button = target.closest("button");
2362
+ if (!(button instanceof HTMLButtonElement) || button.dataset.pending === "1")
2363
+ return;
2364
+ button.classList.add("pressed");
2365
+ const token = String(Date.now());
2366
+ button.dataset.press = token;
2367
+ window.setTimeout(() => {
2368
+ if (button.dataset.press !== token || button.dataset.pending === "1")
2369
+ return;
2370
+ button.classList.remove("pressed");
2371
+ delete button.dataset.press;
2372
+ }, 450);
2373
+ });
2330
2374
  el("namespace", HTMLSelectElement).addEventListener("change", () => {
2331
2375
  state.namespace = el("namespace", HTMLSelectElement).value;
2332
2376
  refresh().catch(showError);
@@ -2337,7 +2381,7 @@ function bootAdmin(config) {
2337
2381
  refresh().catch(showError);
2338
2382
  });
2339
2383
  el("refresh", HTMLButtonElement).addEventListener("click", () => {
2340
- refresh().catch(showError);
2384
+ confirmAction(el("refresh", HTMLButtonElement), "Refreshing...", "Updated", () => refresh()).catch(showError);
2341
2385
  });
2342
2386
  el("collections", HTMLElement).addEventListener("click", (event) => {
2343
2387
  const target = event.target;
@@ -2460,10 +2504,10 @@ function bootAdmin(config) {
2460
2504
  }).then(() => renderFaults()).catch(showError);
2461
2505
  });
2462
2506
  el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2463
- api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2507
+ confirmAction(el("fault-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/faults").then(() => renderFaults())).catch(showError);
2464
2508
  });
2465
2509
  el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2466
- api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2510
+ confirmAction(el("journal-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/requests").then(() => renderJournal())).catch(showError);
2467
2511
  });
2468
2512
  el("route-list", HTMLElement).addEventListener("click", (event) => {
2469
2513
  const target = event.target;
@@ -2578,9 +2622,13 @@ function bootAdmin(config) {
2578
2622
  var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2579
2623
  var adminClientSource = (config) => {
2580
2624
  const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2625
+ const entry = bootAdmin.name;
2626
+ if (!/^[A-Za-z_$][\w$]*$/.test(entry)) {
2627
+ throw new Error("admin client cannot call its boot function");
2628
+ }
2581
2629
  return `<script>
2582
2630
  ${clientPrelude}
2583
- bootAdmin(${payload});
2631
+ ${entry}(${payload});
2584
2632
  </script>`;
2585
2633
  };
2586
2634
 
@@ -2794,9 +2842,24 @@ var renderAdminDocument = (service) => {
2794
2842
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
2795
2843
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
2796
2844
  label.field input, label.field select, .controls input[type="password"] {
2797
- background: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2798
- padding: 7px 8px; min-height: 36px;
2799
- }
2845
+ background-color: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2846
+ padding: 7px 8px; min-height: 36px; line-height: 1.2;
2847
+ }
2848
+ /* A native menu ignores min-height, so Preset rendered about half as tall as
2849
+ the text fields in the same form. Paint it like those fields. */
2850
+ label.field select {
2851
+ -webkit-appearance: none; appearance: none;
2852
+ padding-right: 28px;
2853
+ 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");
2854
+ background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
2855
+ }
2856
+ .stack > .field > input,
2857
+ .stack > .field > select,
2858
+ .stack > .field > textarea,
2859
+ dialog .field > input,
2860
+ dialog .field > select,
2861
+ dialog .field > textarea { width: 100%; }
2862
+ .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger, nav.side button { cursor: pointer; }
2800
2863
  .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger {
2801
2864
  border-radius: 9px; min-height: 36px; padding: 0 12px; border: 1px solid var(--line);
2802
2865
  background: var(--bg-elev);
@@ -2804,6 +2867,14 @@ var renderAdminDocument = (service) => {
2804
2867
  .btn-primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
2805
2868
  .btn-quiet { background: transparent; }
2806
2869
  .btn-danger { color: var(--err); }
2870
+ /* Held after mouseup. Clearing an empty journal changes nothing on screen,
2871
+ so the press itself has to stay visible. */
2872
+ .icon-btn:active, .btn:active, .btn-quiet:active, .btn-danger:active, nav.side button:active,
2873
+ .icon-btn.pressed, .btn.pressed, .btn-quiet.pressed, .btn-danger.pressed, nav.side button.pressed {
2874
+ background: var(--bg-inset); border-color: var(--line-strong); color: var(--ink);
2875
+ }
2876
+ .btn-danger:active, .btn-danger.pressed { background: var(--err-soft); color: var(--err); }
2877
+ .btn-primary:active, .btn-primary.pressed { filter: brightness(0.88); color: var(--accent-ink); }
2807
2878
  /* minmax(0, 1fr): a 1fr column will not shrink below its content, so a long
2808
2879
  record or the full nav row would widen the page past the viewport. */
2809
2880
  .layout {
@@ -2892,8 +2963,16 @@ var renderAdminDocument = (service) => {
2892
2963
  input, select, textarea { font-size: 16px; }
2893
2964
  .brand, .vendor, .controls { width: 100%; }
2894
2965
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
2895
- .layout { grid-template-columns: minmax(0, 1fr); }
2966
+ /* The layout fills the screen. A single-column grid stretches every auto
2967
+ row, so the tab bar grew with the leftover height and the buttons floated
2968
+ in the middle of it. The bar stays one row; the page below takes the rest. */
2969
+ .layout {
2970
+ grid-template-columns: minmax(0, 1fr);
2971
+ grid-template-rows: auto minmax(0, 1fr);
2972
+ align-content: start;
2973
+ }
2896
2974
  nav.side {
2975
+ align-self: start; height: auto;
2897
2976
  flex-direction: row; align-items: center; overflow-x: auto; min-width: 0;
2898
2977
  border-right: 0; border-bottom: 1px solid var(--line);
2899
2978
  padding: 8px;
@@ -3761,7 +3840,7 @@ var stateAdminRoutes = (routes) => {
3761
3840
  };
3762
3841
 
3763
3842
  // ../core/dist/version.js
3764
- var PACKAGE_VERSION = true ? "1.6.0" : UNRELEASED_VERSION;
3843
+ var PACKAGE_VERSION = true ? "1.6.1" : UNRELEASED_VERSION;
3765
3844
 
3766
3845
  // ../core/dist/signing.js
3767
3846
  var encoder = new TextEncoder();
@@ -5687,4 +5766,4 @@ export {
5687
5766
  envelope,
5688
5767
  FullscriptAPI
5689
5768
  };
5690
- //# sourceMappingURL=chunk-OBQHBBFC.js.map
5769
+ //# sourceMappingURL=chunk-UZIN4H2O.js.map