@crvouga/mockingbird-service-paddle 0.3.0 → 0.3.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-paddle
2
2
 
3
- ## 0.3.0 (2026-09-30)
3
+ ## 0.3.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
+ ## 0.3.0 (2026-09-29)
4
12
 
5
13
  ### Features
6
14
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createRuntime
3
- } from "./chunk-2LDOLWZ5.js";
3
+ } from "./chunk-LW2XNBX2.js";
4
4
 
5
5
  // ../../adapters/node/dist/cli.js
6
6
  import { readFile } from "node:fs/promises";
@@ -370,4 +370,4 @@ export {
370
370
  createServer2 as createServer,
371
371
  serveTarget
372
372
  };
373
- //# sourceMappingURL=chunk-YFGZYQKB.js.map
373
+ //# sourceMappingURL=chunk-KVA6Z6FB.js.map
@@ -2283,6 +2283,50 @@ function bootAdmin(config) {
2283
2283
  if (render)
2284
2284
  await render();
2285
2285
  }
2286
+ const delay = (ms) => new Promise((resolve) => {
2287
+ window.setTimeout(resolve, ms);
2288
+ });
2289
+ const confirmAction = async (button, pending, done, work) => {
2290
+ if (button.dataset.pending === "1")
2291
+ return;
2292
+ const label = button.textContent ?? "";
2293
+ const width = button.offsetWidth;
2294
+ button.dataset.pending = "1";
2295
+ button.classList.add("pressed");
2296
+ button.setAttribute("aria-busy", "true");
2297
+ button.style.minWidth = `${width}px`;
2298
+ button.textContent = pending;
2299
+ try {
2300
+ await work();
2301
+ button.textContent = done;
2302
+ await delay(1e3);
2303
+ } catch (error) {
2304
+ showError(error);
2305
+ } finally {
2306
+ button.textContent = label;
2307
+ button.style.minWidth = "";
2308
+ button.classList.remove("pressed");
2309
+ button.removeAttribute("aria-busy");
2310
+ delete button.dataset.pending;
2311
+ }
2312
+ };
2313
+ document.addEventListener("click", (event) => {
2314
+ const target = event.target;
2315
+ if (!(target instanceof Element))
2316
+ return;
2317
+ const button = target.closest("button");
2318
+ if (!(button instanceof HTMLButtonElement) || button.dataset.pending === "1")
2319
+ return;
2320
+ button.classList.add("pressed");
2321
+ const token = String(Date.now());
2322
+ button.dataset.press = token;
2323
+ window.setTimeout(() => {
2324
+ if (button.dataset.press !== token || button.dataset.pending === "1")
2325
+ return;
2326
+ button.classList.remove("pressed");
2327
+ delete button.dataset.press;
2328
+ }, 450);
2329
+ });
2286
2330
  el("namespace", HTMLSelectElement).addEventListener("change", () => {
2287
2331
  state.namespace = el("namespace", HTMLSelectElement).value;
2288
2332
  refresh().catch(showError);
@@ -2293,7 +2337,7 @@ function bootAdmin(config) {
2293
2337
  refresh().catch(showError);
2294
2338
  });
2295
2339
  el("refresh", HTMLButtonElement).addEventListener("click", () => {
2296
- refresh().catch(showError);
2340
+ confirmAction(el("refresh", HTMLButtonElement), "Refreshing...", "Updated", () => refresh()).catch(showError);
2297
2341
  });
2298
2342
  el("collections", HTMLElement).addEventListener("click", (event) => {
2299
2343
  const target = event.target;
@@ -2416,10 +2460,10 @@ function bootAdmin(config) {
2416
2460
  }).then(() => renderFaults()).catch(showError);
2417
2461
  });
2418
2462
  el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2419
- api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2463
+ confirmAction(el("fault-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/faults").then(() => renderFaults())).catch(showError);
2420
2464
  });
2421
2465
  el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2422
- api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2466
+ confirmAction(el("journal-clear", HTMLButtonElement), "Clearing...", "Cleared", () => api("DELETE", "/requests").then(() => renderJournal())).catch(showError);
2423
2467
  });
2424
2468
  el("route-list", HTMLElement).addEventListener("click", (event) => {
2425
2469
  const target = event.target;
@@ -2534,9 +2578,13 @@ function bootAdmin(config) {
2534
2578
  var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2535
2579
  var adminClientSource = (config) => {
2536
2580
  const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2581
+ const entry = bootAdmin.name;
2582
+ if (!/^[A-Za-z_$][\w$]*$/.test(entry)) {
2583
+ throw new Error("admin client cannot call its boot function");
2584
+ }
2537
2585
  return `<script>
2538
2586
  ${clientPrelude}
2539
- bootAdmin(${payload});
2587
+ ${entry}(${payload});
2540
2588
  </script>`;
2541
2589
  };
2542
2590
 
@@ -2750,9 +2798,24 @@ var renderAdminDocument = (service) => {
2750
2798
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
2751
2799
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
2752
2800
  label.field input, label.field select, .controls input[type="password"] {
2753
- background: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2754
- padding: 7px 8px; min-height: 36px;
2755
- }
2801
+ background-color: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2802
+ padding: 7px 8px; min-height: 36px; line-height: 1.2;
2803
+ }
2804
+ /* A native menu ignores min-height, so Preset rendered about half as tall as
2805
+ the text fields in the same form. Paint it like those fields. */
2806
+ label.field select {
2807
+ -webkit-appearance: none; appearance: none;
2808
+ padding-right: 28px;
2809
+ 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");
2810
+ background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
2811
+ }
2812
+ .stack > .field > input,
2813
+ .stack > .field > select,
2814
+ .stack > .field > textarea,
2815
+ dialog .field > input,
2816
+ dialog .field > select,
2817
+ dialog .field > textarea { width: 100%; }
2818
+ .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger, nav.side button { cursor: pointer; }
2756
2819
  .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger {
2757
2820
  border-radius: 9px; min-height: 36px; padding: 0 12px; border: 1px solid var(--line);
2758
2821
  background: var(--bg-elev);
@@ -2760,6 +2823,14 @@ var renderAdminDocument = (service) => {
2760
2823
  .btn-primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
2761
2824
  .btn-quiet { background: transparent; }
2762
2825
  .btn-danger { color: var(--err); }
2826
+ /* Held after mouseup. Clearing an empty journal changes nothing on screen,
2827
+ so the press itself has to stay visible. */
2828
+ .icon-btn:active, .btn:active, .btn-quiet:active, .btn-danger:active, nav.side button:active,
2829
+ .icon-btn.pressed, .btn.pressed, .btn-quiet.pressed, .btn-danger.pressed, nav.side button.pressed {
2830
+ background: var(--bg-inset); border-color: var(--line-strong); color: var(--ink);
2831
+ }
2832
+ .btn-danger:active, .btn-danger.pressed { background: var(--err-soft); color: var(--err); }
2833
+ .btn-primary:active, .btn-primary.pressed { filter: brightness(0.88); color: var(--accent-ink); }
2763
2834
  /* minmax(0, 1fr): a 1fr column will not shrink below its content, so a long
2764
2835
  record or the full nav row would widen the page past the viewport. */
2765
2836
  .layout {
@@ -2848,8 +2919,16 @@ var renderAdminDocument = (service) => {
2848
2919
  input, select, textarea { font-size: 16px; }
2849
2920
  .brand, .vendor, .controls { width: 100%; }
2850
2921
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
2851
- .layout { grid-template-columns: minmax(0, 1fr); }
2922
+ /* The layout fills the screen. A single-column grid stretches every auto
2923
+ row, so the tab bar grew with the leftover height and the buttons floated
2924
+ in the middle of it. The bar stays one row; the page below takes the rest. */
2925
+ .layout {
2926
+ grid-template-columns: minmax(0, 1fr);
2927
+ grid-template-rows: auto minmax(0, 1fr);
2928
+ align-content: start;
2929
+ }
2852
2930
  nav.side {
2931
+ align-self: start; height: auto;
2853
2932
  flex-direction: row; align-items: center; overflow-x: auto; min-width: 0;
2854
2933
  border-right: 0; border-bottom: 1px solid var(--line);
2855
2934
  padding: 8px;
@@ -3717,7 +3796,7 @@ var stateAdminRoutes = (routes) => {
3717
3796
  };
3718
3797
 
3719
3798
  // ../core/dist/version.js
3720
- var PACKAGE_VERSION = true ? "0.3.0" : UNRELEASED_VERSION;
3799
+ var PACKAGE_VERSION = true ? "0.3.1" : UNRELEASED_VERSION;
3721
3800
 
3722
3801
  // ../core/dist/signing.js
3723
3802
  var encoder = new TextEncoder();
@@ -7436,4 +7515,4 @@ export {
7436
7515
  AUTH_TOKEN_TTL_MS,
7437
7516
  PaddleAPI
7438
7517
  };
7439
- //# sourceMappingURL=chunk-2LDOLWZ5.js.map
7518
+ //# sourceMappingURL=chunk-LW2XNBX2.js.map