@justai/cuts 0.104.0 → 0.105.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justai/cuts",
3
- "version": "0.104.0",
3
+ "version": "0.105.0",
4
4
  "description": "A persona's named parts \u2014 the page shell that holds them, and the cuts themselves.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -49,7 +49,15 @@ interface Props {
49
49
  /** Layer one. Empty is valid and correct for an apex with nothing seeded yet. */
50
50
  seed?: CatalogueEntry[];
51
51
  /** The curated sequence, keyed by host — read by the runtime sort. */
52
- order?: Record<string, number>;
52
+ /** queries: order prop · curated order · data-order · the grid is not sorted · order type
53
+ *
54
+ * THE CURATED SEQUENCE, AS A LIST OF HOSTS — which is what the runtime sort reads, and what this
55
+ * type said until 2026-08-05. It was declared `Record<string, number>` while the script parsed it
56
+ * as `string[]`; app.zone happened to pass an array so nothing ever disagreed out loud, and
57
+ * pin.games passed `{}` for "no order" and took the whole runtime append down with it. A type that
58
+ * describes something other than what the consumer reads is worse than no type: it invites the one
59
+ * value that will break. */
60
+ order?: readonly string[];
53
61
  /** A shelf label per host: a card has one line to earn a tap, and a bio clamps. Keyed by host. */
54
62
  taglines?: Record<string, string>;
55
63
  /** Localized category names. The English key stays the stable filter value. */
@@ -78,7 +86,7 @@ interface Props {
78
86
  }
79
87
  const {
80
88
  lang, kind, apexHost,
81
- seed = [], order = {}, taglines = {}, catLabels = {}, entries = {},
89
+ seed = [], order = [], taglines = {}, catLabels = {}, entries = {},
82
90
  locales, strings,
83
91
  } = Astro.props;
84
92
 
@@ -539,8 +547,15 @@ const isNew = (added: string): boolean => { const t0 = Date.parse(added || "");
539
547
  // the registry — and an identifier that only matches one route fails silently on the other. A card
540
548
  // that fails to match reads as unlisted and jumps to the top, which is the worst possible failure
541
549
  // for an ordering bug: it looks deliberate. The host is what both routes agree on.
550
+ // Anything that is not a list means "no curated order" — never an exception. This parsed a
551
+ // `{}` once and `{}.map is not a function` killed everything after it, including the event
552
+ // that tells the filter new cards exist: the shelf showed a card AND "the first games are on
553
+ // their way" underneath it.
542
554
  const orderList: string[] = (() => {
543
- try { return JSON.parse(grid.dataset.order || "[]"); } catch { return []; }
555
+ try {
556
+ const parsed = JSON.parse(grid.dataset.order || "[]");
557
+ return Array.isArray(parsed) ? parsed : [];
558
+ } catch { return []; }
544
559
  })();
545
560
  const rank = new Map(orderList.map((h, i) => [h, i]));
546
561
  const UNLISTED = -1;
@@ -559,9 +574,28 @@ const isNew = (added: string): boolean => { const t0 = Date.parse(added || "");
559
574
  return x.added < y.added ? 1 : -1;
560
575
  })
561
576
  .forEach(({ el }) => grid.appendChild(el));
562
- document.dispatchEvent(new Event("zone:cards")); // newcomers in — let the URL filter catch them
577
+ // queries: the empty message shows under a card · zone:cards never fired · the filter did not
578
+ // see the new cards · silent catch
579
+ //
580
+ // IN A `finally`, BECAUSE EVERYTHING ABOVE IT IS ORNAMENT AND THIS IS NOT. Once cards are in
581
+ // the DOM, the filter has to be told — it owns which of them are visible and whether the
582
+ // "nothing here" line is showing. Sorting them is a nicety; leaving the page claiming to be
583
+ // empty while a card sits above the claim is a defect a visitor sees.
584
+ //
585
+ // It sat at the end of a promise chain ending in `.catch(() => {})`, so one TypeError in the
586
+ // sort silenced it with nothing logged anywhere. That combination — a throw between the work
587
+ // and its announcement, and a catch that says nothing — is how a page ends up confidently
588
+ // wrong. The `finally` fixes the ordering; the catch below now says what it swallowed.
589
+ })
590
+ .catch((err) => {
591
+ // Never fatal — a storefront must render without the registry. But it must not be SILENT:
592
+ // the seed and the build-time layer are already on the page, and a swallowed error here is
593
+ // the difference between "no new personas today" and "the append is broken".
594
+ console.warn("[catalogue] runtime append failed", err);
563
595
  })
564
- .catch(() => {});
596
+ .finally(() => {
597
+ document.dispatchEvent(new Event("zone:cards")); // cards are in — let the filter catch them
598
+ });
565
599
  })();
566
600
  </script>
567
601