jaml-ui 0.24.15 → 0.24.18

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.
Files changed (54) hide show
  1. package/dist/components/DeckSprite.js +2 -0
  2. package/dist/components/GameCard.js +1 -0
  3. package/dist/components/JamlAestheticSelector.js +3 -2
  4. package/dist/components/JamlCurator.js +3 -2
  5. package/dist/components/JamlIde.d.ts +4 -3
  6. package/dist/components/JamlIde.js +3 -31
  7. package/dist/components/JamlIdeToolbar.d.ts +1 -1
  8. package/dist/components/JamlIdeToolbar.js +5 -5
  9. package/dist/components/JamlMapPreview.js +3 -37
  10. package/dist/components/Jimbolate.d.ts +7 -0
  11. package/dist/components/Jimbolate.js +17 -0
  12. package/dist/components/PaginatedFilterBrowser.d.ts +23 -0
  13. package/dist/components/PaginatedFilterBrowser.js +54 -0
  14. package/dist/components/RunConfigModal.js +11 -150
  15. package/dist/components/jamlMap/CategoryPicker.d.ts +1 -2
  16. package/dist/components/jamlMap/CategoryPicker.js +2 -1
  17. package/dist/components/jamlMap/JamlMapEditor.js +8 -10
  18. package/dist/components/jamlMap/JokerPicker.d.ts +1 -2
  19. package/dist/components/jamlMap/JokerPicker.js +3 -7
  20. package/dist/components/jamlMap/MysterySlot.js +0 -15
  21. package/dist/hooks/searchWorker.d.ts +1 -29
  22. package/dist/hooks/searchWorker.js +8 -6
  23. package/dist/hooks/useIntersectionObserver.js +5 -3
  24. package/dist/hooks/useSearch.js +5 -1
  25. package/dist/hooks/useShopStream.js +5 -2
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +1 -0
  28. package/dist/lib/cardParser.d.ts +1 -1
  29. package/dist/lib/cardParser.js +19 -17
  30. package/dist/lib/classes/BuyMetaData.d.ts +2 -2
  31. package/dist/lib/const.d.ts +22 -13
  32. package/dist/lib/data/constants.js +10 -9
  33. package/dist/lib/hooks/useJamlFilter.js +5 -5
  34. package/dist/lib/hooks/useSeedAnalyzer.js +7 -1
  35. package/dist/lib/jaml/jamlSchema.d.ts +18 -3
  36. package/dist/lib/jaml/jamlSchema.js +2 -2
  37. package/dist/lib/parseDailyRitual.d.ts +1 -1
  38. package/dist/lib/parseDailyRitual.js +2 -1
  39. package/dist/r3f/Card3D.js +2 -0
  40. package/dist/r3f/JimboBillboard.d.ts +1 -1
  41. package/dist/r3f/JimboBillboard.js +5 -2
  42. package/dist/ui/JimboInputModal.js +8 -2
  43. package/dist/ui/PanelSplitter.js +4 -2
  44. package/dist/ui/hooks.js +14 -5
  45. package/dist/ui/ide/JamlEditor.js +53 -63
  46. package/dist/ui/jimbo.css +70 -16
  47. package/dist/ui/jimboTooltip.js +12 -6
  48. package/dist/ui/panel.d.ts +1 -2
  49. package/dist/ui/panel.js +2 -2
  50. package/dist/ui/radial/RadialButton.js +1 -1
  51. package/dist/ui/showcase.js +1 -1
  52. package/dist/utils/jamlMapPreview.js +2 -1
  53. package/dist/utils/jamlVisualFilter.js +3 -2
  54. package/package.json +12 -6
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useCallback, useMemo, useRef } from "react";
3
+ import { useState, useCallback, useRef, useEffect } from "react";
4
4
  import { MysterySlot } from "./MysterySlot.js";
5
5
  import { JokerPicker } from "./JokerPicker.js";
6
6
  import { CategoryPicker, VOUCHER_PICKER_CONFIG, TAG_PICKER_CONFIG, BOSS_PICKER_CONFIG, TAROT_PICKER_CONFIG, PLANET_PICKER_CONFIG, SPECTRAL_PICKER_CONFIG, PACK_PICKER_CONFIG, } from "./CategoryPicker.js";
@@ -31,7 +31,6 @@ const ZONE_LABEL = {
31
31
  mustnot: "Avoid",
32
32
  };
33
33
  const CATEGORY_CONFIG_MAP = {
34
- joker: VOUCHER_PICKER_CONFIG,
35
34
  voucher: VOUCHER_PICKER_CONFIG,
36
35
  tag: TAG_PICKER_CONFIG,
37
36
  boss: BOSS_PICKER_CONFIG,
@@ -43,15 +42,15 @@ const CATEGORY_CONFIG_MAP = {
43
42
  // ─── Component ───────────────────────────────────────────────────────────────
44
43
  export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
45
44
  const onChangeRef = useRef(onChange);
46
- onChangeRef.current = onChange;
45
+ useEffect(() => {
46
+ onChangeRef.current = onChange;
47
+ });
47
48
  const [currentZone, setCurrentZone] = useState(initialZone);
48
- const [ante, setAnte] = useState(1);
49
49
  const [antesState, setAntesState] = useState({});
50
50
  const [activeSlot, setActiveSlot] = useState(null);
51
51
  const [activePackDetail, setActivePackDetail] = useState(null);
52
52
  const [pickerFlow, setPickerFlow] = useState("category");
53
53
  const [activePackSelection, setActivePackSelection] = useState(null);
54
- const currentAnteSelections = antesState[ante] || {};
55
54
  const handleSlotTap = useCallback((anteIndex, id, forceCategory) => {
56
55
  const existing = (antesState[anteIndex] || {})[id];
57
56
  if (forceCategory === "pack" && existing?.packName) {
@@ -131,7 +130,6 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
131
130
  setActiveSlot(null);
132
131
  setActivePackDetail(null);
133
132
  }, []);
134
- const jamlText = useMemo(() => buildJamlText(antesState), [antesState]);
135
133
  const activePackDetailSelection = activePackDetail
136
134
  ? (antesState[activePackDetail.ante] || {})[activePackDetail.id]
137
135
  : undefined;
@@ -154,7 +152,7 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
154
152
  scrollBehavior: "smooth",
155
153
  WebkitOverflowScrolling: "touch",
156
154
  overscrollBehaviorY: "contain",
157
- }, children: Array.from({ length: 40 }, (_, i) => i).map((a) => (_jsxs("div", { style: {
155
+ }, children: Array.from({ length: 8 }, (_, i) => i + 1).map((a) => (_jsxs("div", { style: {
158
156
  scrollSnapAlign: "start",
159
157
  scrollSnapStop: "always",
160
158
  padding: "24px 8px 64px 8px",
@@ -164,10 +162,10 @@ export function JamlMapEditor({ zone: initialZone = "must", onChange, }) {
164
162
  flexDirection: "column",
165
163
  gap: 24,
166
164
  borderBottom: `2px solid ${C.DARK_GREY}`
167
- }, children: [_jsxs(JimboText, { size: "md", tone: "white", style: { textAlign: "center", marginBottom: 8 }, children: ["Ante ", a] }), _jsxs("div", { className: "j-flex j-justify-between j-items-end", children: [_jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Voucher" }), renderSlot(a, `ante_${a}_voucher`, 42, "Vouchers", "voucher")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Small" }), renderSlot(a, `ante_${a}_tag_small`, 42, "tags", "tag")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Big" }), renderSlot(a, `ante_${a}_tag_big`, 42, "tags", "tag")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Boss" }), renderSlot(a, `ante_${a}_boss`, 42, "BlindChips", "boss")] })] }), _jsxs("div", { className: "j-flex-col j-gap-xs", children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { letterSpacing: 1 }, children: "Shop Items" }), _jsx("div", { className: "j-flex hide-scrollbar j-gap-sm", style: { overflowX: "auto", paddingBottom: 8 }, children: [1, 2, 3, 4, 5, 6, 7, 8].map(i => renderSlot(a, `ante_${a}_shop_${i}`, 52, "Jokers")) })] }), _jsxs("div", { className: "j-flex-col j-gap-xs", children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { letterSpacing: 1 }, children: "Packs" }), _jsx("div", { className: "j-flex-col j-gap-sm", children: getPackRows(a).map((row, rowIndex) => (_jsx("div", { className: "j-flex j-gap-sm", style: { flexWrap: "nowrap" }, children: row.map(i => renderSlot(a, `ante_${a}_pack_${i}`, 64, "Boosters", "pack")) }, rowIndex))) })] })] }, a))) }), _jsx(JimboModal, { open: activeSlot !== null, onClose: handlePickerCancel, title: pickerFlow === "category" ? "Select Category" : undefined, className: "j-picker-modal", children: activeSlot !== null && (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [_jsxs("div", { className: "j-inner-panel", style: { padding: "8px 10px", marginBottom: 2 }, children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { display: "block", marginBottom: 6 }, children: "This pick will be added as" }), _jsx("div", { className: "j-flex j-gap-sm", style: { flexWrap: "wrap" }, children: ["must", "should", "mustnot"].map((z) => (_jsx(JimboButton, { tone: currentZone === z ? ZONE_TONE[z] : "grey", size: "xs", onClick: () => setCurrentZone(z), children: ZONE_LABEL[z] }, z))) })] }), pickerFlow === "category" ? (_jsx(CategoryMenu, { onSelect: handleCategorySelect })) : pickerFlow === "joker" ? (_jsx(JokerPicker, { onSelect: handleItemSelect, onCancel: handlePickerCancel })) : pickerFlow === "packUnsupported" ? (_jsxs("div", { className: "j-flex-col j-gap-sm", style: { padding: 10, maxWidth: 360 }, children: [_jsxs("div", { className: "j-inner-panel", style: { padding: "10px 12px" }, children: [_jsx(JimboText, { size: "sm", tone: "orange", children: "Standard Packs need a dedicated playing-card picker." }), _jsx(JimboText, { size: "xs", tone: "grey", style: { display: "block", marginTop: 6 }, children: "Arcana, Celestial, Spectral, and Buffoon pack flows are wired. Standard Pack support can come next without faking the JAML shape." })] }), _jsx(JimboButton, { tone: "orange", size: "sm", fullWidth: true, onClick: () => {
165
+ }, children: [_jsxs(JimboText, { size: "md", tone: "white", style: { textAlign: "center", marginBottom: 8 }, children: ["Ante ", a] }), _jsxs("div", { className: "j-flex j-justify-between j-items-end", children: [_jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Voucher" }), renderSlot(a, `ante_${a}_voucher`, 42, "Vouchers", "voucher")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Small" }), renderSlot(a, `ante_${a}_tag_small`, 42, "tags", "tag")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Big" }), renderSlot(a, `ante_${a}_tag_big`, 42, "tags", "tag")] }), _jsxs("div", { className: "j-flex-col j-items-center j-gap-xs", children: [_jsx(JimboText, { size: "micro", tone: "grey", children: "Boss" }), renderSlot(a, `ante_${a}_boss`, 42, "BlindChips", "boss")] })] }), _jsxs("div", { className: "j-flex-col j-gap-xs", children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { letterSpacing: 1 }, children: "Shop Items" }), _jsx("div", { className: "j-flex hide-scrollbar j-gap-sm", style: { overflowX: "auto", paddingBottom: 8 }, children: [1, 2, 3, 4, 5, 6, 7, 8].map(i => renderSlot(a, `ante_${a}_shop_${i}`, 52, "Jokers")) })] }), _jsxs("div", { className: "j-flex-col j-gap-xs", children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { letterSpacing: 1 }, children: "Packs" }), _jsx("div", { className: "j-flex-col j-gap-sm", children: getPackRows(a).map((row, rowIndex) => (_jsx("div", { className: "j-flex j-gap-sm", style: { flexWrap: "nowrap" }, children: row.map(i => renderSlot(a, `ante_${a}_pack_${i}`, 64, "Boosters", "pack")) }, rowIndex))) })] })] }, a))) }), _jsx(JimboModal, { open: activeSlot !== null, onClose: handlePickerCancel, title: pickerFlow === "category" ? "Select Category" : undefined, className: "j-picker-modal", children: activeSlot !== null && (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [_jsxs("div", { className: "j-inner-panel", style: { padding: "8px 10px", marginBottom: 2 }, children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { display: "block", marginBottom: 6 }, children: "This pick will be added as" }), _jsx("div", { className: "j-flex j-gap-sm", style: { flexWrap: "wrap" }, children: ["must", "should", "mustnot"].map((z) => (_jsx(JimboButton, { tone: currentZone === z ? ZONE_TONE[z] : "grey", size: "xs", onClick: () => setCurrentZone(z), children: ZONE_LABEL[z] }, z))) })] }), pickerFlow === "category" ? (_jsx(CategoryMenu, { onSelect: handleCategorySelect })) : pickerFlow === "joker" ? (_jsx(JokerPicker, { onSelect: handleItemSelect })) : pickerFlow === "packUnsupported" ? (_jsxs("div", { className: "j-flex-col j-gap-sm", style: { padding: 10, maxWidth: 360 }, children: [_jsxs("div", { className: "j-inner-panel", style: { padding: "10px 12px" }, children: [_jsx(JimboText, { size: "sm", tone: "orange", children: "Standard Packs need a dedicated playing-card picker." }), _jsx(JimboText, { size: "xs", tone: "grey", style: { display: "block", marginTop: 6 }, children: "Arcana, Celestial, Spectral, and Buffoon pack flows are wired. Standard Pack support can come next without faking the JAML shape." })] }), _jsx(JimboButton, { tone: "orange", size: "sm", fullWidth: true, onClick: () => {
168
166
  setActivePackSelection(null);
169
167
  setPickerFlow("pack");
170
- }, children: "Back to Packs" })] })) : (_jsx(CategoryPicker, { config: CATEGORY_CONFIG_MAP[pickerFlow], onSelect: handleItemSelect, onCancel: handlePickerCancel }))] })) }), _jsx(JimboModal, { open: activePackDetail !== null && !!activePackDetailSelection?.packName, onClose: handleOverlayClose, title: activePackDetailSelection?.packName ?? "Pack", className: "j-picker-modal", children: activePackDetail !== null && activePackDetailSelection?.packName && (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 10, maxWidth: 360 }, children: [_jsx("div", { className: "j-inner-panel", style: { padding: "10px 12px", background: withAlpha(C.DARKEST, 0.84) }, children: _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [_jsx(JimboSprite, { name: activePackDetailSelection.packName, sheet: "Boosters", width: 56 }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [_jsxs(JimboText, { size: "sm", tone: "white", children: ["Ante ", activePackDetail.ante, " pack ", ((activePackDetailSelection.boosterPacks?.[0] ?? 0) + 1)] }), _jsx(JimboText, { size: "xs", tone: "grey", children: getPackHelperText(activePackDetailSelection.packName) })] })] }) }), _jsxs("div", { className: "j-inner-panel", style: { padding: "10px 12px" }, children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { display: "block", marginBottom: 8 }, children: "Peek" }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 10 }, children: [_jsx(JimboSprite, { name: activePackDetailSelection.packName, sheet: "Boosters", width: 44 }), _jsx(JimboText, { size: "sm", tone: "grey", children: "\u2192" }), _jsx(JimboSprite, { name: activePackDetailSelection.value, sheet: categoryToPreviewSheet(activePackDetailSelection.category), width: 48 }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 3 }, children: [_jsx(JimboText, { size: "sm", tone: "white", children: activePackDetailSelection.value }), _jsxs(JimboText, { size: "xs", tone: "grey", children: [getSelectionCategoryLabel(activePackDetailSelection.category), " from ", activePackDetailSelection.packName] })] })] }), _jsxs("div", { className: "j-flex j-gap-sm", style: { flexWrap: "wrap" }, children: [_jsxs("div", { className: "j-inner-panel", style: { padding: "6px 8px", minWidth: 96 }, children: [_jsx(JimboText, { size: "micro", tone: "grey", style: { display: "block", marginBottom: 2 }, children: "Zone" }), _jsx(JimboText, { size: "xs", tone: getZoneTextTone(activePackDetailSelection.zone), children: ZONE_LABEL[activePackDetailSelection.zone] })] }), _jsxs("div", { className: "j-inner-panel", style: { padding: "6px 8px", minWidth: 120 }, children: [_jsx(JimboText, { size: "micro", tone: "grey", style: { display: "block", marginBottom: 2 }, children: "Clause" }), _jsx(JimboText, { size: "xs", tone: "white", children: activePackDetailSelection.clauseKey })] }), _jsxs("div", { className: "j-inner-panel", style: { padding: "6px 8px", minWidth: 96 }, children: [_jsx(JimboText, { size: "micro", tone: "grey", style: { display: "block", marginBottom: 2 }, children: "Source" }), _jsxs(JimboText, { size: "xs", tone: "white", children: ["boosterPacks: [", activePackDetailSelection.boosterPacks?.join(", ") ?? "", "]"] })] })] })] }), _jsxs("div", { className: "j-flex j-gap-sm", style: { flexWrap: "wrap" }, children: [_jsx(JimboButton, { tone: "blue", size: "sm", fullWidth: true, onClick: handlePackChange, children: "Re-pick Contents" }), _jsx(JimboButton, { tone: "red", size: "sm", fullWidth: true, onClick: () => handleSlotClear(activePackDetail.ante, activePackDetail.id), children: "Clear This Pack" })] })] })) })] }));
168
+ }, children: "Back to Packs" })] })) : (_jsx(CategoryPicker, { config: CATEGORY_CONFIG_MAP[pickerFlow], onSelect: handleItemSelect }))] })) }), _jsx(JimboModal, { open: activePackDetail !== null && !!activePackDetailSelection?.packName, onClose: handleOverlayClose, title: activePackDetailSelection?.packName ?? "Pack", className: "j-picker-modal", children: activePackDetail !== null && activePackDetailSelection?.packName && (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 10, maxWidth: 360 }, children: [_jsx("div", { className: "j-inner-panel", style: { padding: "10px 12px", background: withAlpha(C.DARKEST, 0.84) }, children: _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [_jsx(JimboSprite, { name: activePackDetailSelection.packName, sheet: "Boosters", width: 56 }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [_jsxs(JimboText, { size: "sm", tone: "white", children: ["Ante ", activePackDetail.ante, " pack ", ((activePackDetailSelection.boosterPacks?.[0] ?? 0) + 1)] }), _jsx(JimboText, { size: "xs", tone: "grey", children: getPackHelperText(activePackDetailSelection.packName) })] })] }) }), _jsxs("div", { className: "j-inner-panel", style: { padding: "10px 12px" }, children: [_jsx(JimboText, { size: "xs", tone: "grey", style: { display: "block", marginBottom: 8 }, children: "Peek" }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 10 }, children: [_jsx(JimboSprite, { name: activePackDetailSelection.packName, sheet: "Boosters", width: 44 }), _jsx(JimboText, { size: "sm", tone: "grey", children: "\u2192" }), _jsx(JimboSprite, { name: activePackDetailSelection.value, sheet: categoryToPreviewSheet(activePackDetailSelection.category), width: 48 }), _jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 3 }, children: [_jsx(JimboText, { size: "sm", tone: "white", children: activePackDetailSelection.value }), _jsxs(JimboText, { size: "xs", tone: "grey", children: [getSelectionCategoryLabel(activePackDetailSelection.category), " from ", activePackDetailSelection.packName] })] })] }), _jsxs("div", { className: "j-flex j-gap-sm", style: { flexWrap: "wrap" }, children: [_jsxs("div", { className: "j-inner-panel", style: { padding: "6px 8px", minWidth: 96 }, children: [_jsx(JimboText, { size: "micro", tone: "grey", style: { display: "block", marginBottom: 2 }, children: "Zone" }), _jsx(JimboText, { size: "xs", tone: getZoneTextTone(activePackDetailSelection.zone), children: ZONE_LABEL[activePackDetailSelection.zone] })] }), _jsxs("div", { className: "j-inner-panel", style: { padding: "6px 8px", minWidth: 120 }, children: [_jsx(JimboText, { size: "micro", tone: "grey", style: { display: "block", marginBottom: 2 }, children: "Clause" }), _jsx(JimboText, { size: "xs", tone: "white", children: activePackDetailSelection.clauseKey })] }), _jsxs("div", { className: "j-inner-panel", style: { padding: "6px 8px", minWidth: 96 }, children: [_jsx(JimboText, { size: "micro", tone: "grey", style: { display: "block", marginBottom: 2 }, children: "Source" }), _jsxs(JimboText, { size: "xs", tone: "white", children: ["boosterPacks: [", activePackDetailSelection.boosterPacks?.join(", ") ?? "", "]"] })] })] })] }), _jsxs("div", { className: "j-flex j-gap-sm", style: { flexWrap: "wrap" }, children: [_jsx(JimboButton, { tone: "blue", size: "sm", fullWidth: true, onClick: handlePackChange, children: "Re-pick Contents" }), _jsx(JimboButton, { tone: "red", size: "sm", fullWidth: true, onClick: () => handleSlotClear(activePackDetail.ante, activePackDetail.id), children: "Clear This Pack" })] })] })) })] }));
171
169
  }
172
170
  // ─── Category Selection Menu ─────────────────────────────────────────────────
173
171
  export function CategoryMenu({ onSelect, }) {
@@ -277,7 +275,7 @@ function buildJamlText(antes) {
277
275
  }
278
276
  }
279
277
  }
280
- let lines = [];
278
+ const lines = [];
281
279
  lines.push("name: My Custom Seed Map");
282
280
  lines.push("author: JamlBuilder");
283
281
  lines.push("description: Auto-generated from the visual editor.");
@@ -2,6 +2,5 @@ import type { SlotSelection } from "./MysterySlot.js";
2
2
  export type JokerRarity = "common" | "uncommon" | "rare" | "legendary";
3
3
  export interface JokerPickerProps {
4
4
  onSelect: (selection: SlotSelection) => void;
5
- onCancel: () => void;
6
5
  }
7
- export declare function JokerPicker({ onSelect, onCancel }: JokerPickerProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function JokerPicker({ onSelect }: JokerPickerProps): import("react/jsx-runtime").JSX.Element;
@@ -62,7 +62,7 @@ const RARITY_META = {
62
62
  rare: { label: "Rare", tone: "red", hint: "Found in shops and Buffoon Packs" },
63
63
  legendary: { label: "Legendary", tone: "tarot", hint: "Spawns from The Soul only!" },
64
64
  };
65
- export function JokerPicker({ onSelect, onCancel }) {
65
+ export function JokerPicker({ onSelect }) {
66
66
  const [step, setStep] = useState("rarity");
67
67
  const [selectedRarity, setSelectedRarity] = useState(null);
68
68
  const [search, setSearch] = useState("");
@@ -96,11 +96,7 @@ export function JokerPicker({ onSelect, onCancel }) {
96
96
  return (_jsxs("div", { style: { padding: 0, display: "flex", flexDirection: "column" }, children: [step === "rarity" && (_jsx("div", { className: "j-flex-col j-gap-sm", style: { padding: 10 }, children: ["common", "uncommon", "rare", "legendary"].map((rarity) => {
97
97
  const meta = RARITY_META[rarity];
98
98
  return (_jsx(JimboButton, { tone: meta.tone, size: "md", fullWidth: true, onClick: () => handleRaritySelect(rarity), children: _jsxs("span", { style: { display: "flex", flexDirection: "column", gap: 2, textAlign: "left", width: "100%" }, children: [_jsx("span", { children: meta.label }), _jsx("span", { style: { fontSize: 9, opacity: 0.7 }, children: meta.hint })] }) }, rarity));
99
- }) })), step === "specific" && selectedRarity && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "j-flex j-items-center", style: {
100
- justifyContent: "space-between",
101
- padding: "8px 10px",
102
- borderBottom: `2px solid ${C.PANEL_EDGE}`,
103
- }, children: [_jsx(JimboButton, { tone: "orange", size: "xs", onClick: () => setStep("rarity"), children: "\u2190 Back" }), _jsxs(JimboText, { size: "md", children: [RARITY_META[selectedRarity].label, " Jokers"] }), _jsx("div", { style: { width: 44 } })] }), _jsx("div", { className: "j-flex j-gap-sm", style: { padding: "8px 10px 4px" }, children: _jsx("input", { className: "j-seed-input__field", type: "text", placeholder: "Search jokers...", value: search, onChange: (e) => setSearch(e.target.value), style: { fontSize: 13, padding: "6px 10px", textTransform: "none", letterSpacing: "0.04em" } }) }), selectedRarity === "legendary" && (_jsx("div", { className: "j-inner-panel", style: { margin: "4px 10px 6px", padding: "6px 10px" }, children: _jsx(JimboText, { size: "xs", tone: "purple", children: "Legendary jokers spawn from The Soul. Find it in Arcana Pack, Spectral Pack, Charm Tag, or Ethereal Tag only!" }) })), _jsxs("div", { style: {
99
+ }) })), step === "specific" && selectedRarity && (_jsxs(_Fragment, { children: [_jsx("div", { className: "j-flex j-items-center j-justify-center", style: { padding: "8px 10px 4px" }, children: _jsxs(JimboText, { size: "md", children: [RARITY_META[selectedRarity].label, " Jokers"] }) }), _jsx("div", { className: "j-flex j-gap-sm", style: { padding: "4px 10px 4px" }, children: _jsx("input", { className: "j-seed-input__field", type: "text", placeholder: "Search jokers...", value: search, onChange: (e) => setSearch(e.target.value), style: { fontSize: 13, padding: "6px 10px", textTransform: "none", letterSpacing: "0.04em" } }) }), selectedRarity === "legendary" && (_jsx("div", { className: "j-inner-panel", style: { margin: "4px 10px 6px", padding: "6px 10px" }, children: _jsx(JimboText, { size: "xs", tone: "purple", children: "Legendary jokers spawn from The Soul. Find it in Arcana Pack, Spectral Pack, Charm Tag, or Ethereal Tag only!" }) })), _jsxs("div", { style: {
104
100
  display: "grid",
105
101
  gridTemplateColumns: "repeat(auto-fill, minmax(64px, 1fr))",
106
102
  gap: 6,
@@ -113,5 +109,5 @@ export function JokerPicker({ onSelect, onCancel }) {
113
109
  padding: 4,
114
110
  borderRadius: 4,
115
111
  cursor: "pointer",
116
- }, children: [_jsx(JimboSprite, { name: joker.name, sheet: "Jokers", width: 48 }), _jsx(JimboText, { size: "micro", tone: "grey", style: { maxWidth: 60, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: joker.name })] }, joker.name))), filteredJokers.length === 0 && (_jsx("div", { style: { gridColumn: "1 / -1", padding: 20, textAlign: "center" }, children: _jsxs(JimboText, { size: "sm", tone: "grey", children: ["No jokers match \"", search, "\""] }) }))] })] }))] }));
112
+ }, children: [_jsx(JimboSprite, { name: joker.name, sheet: "Jokers", width: 48 }), _jsx(JimboText, { size: "micro", tone: "grey", style: { maxWidth: 60, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: joker.name })] }, joker.name))), filteredJokers.length === 0 && (_jsx("div", { style: { gridColumn: "1 / -1", padding: 20, textAlign: "center" }, children: _jsxs(JimboText, { size: "sm", tone: "grey", children: ["No jokers match \"", search, "\""] }) }))] }), _jsx("div", { style: { padding: "4px 10px 10px" }, children: _jsx(JimboButton, { tone: "orange", size: "md", fullWidth: true, onClick: () => setStep("rarity"), children: "Back" }) })] }))] }));
117
113
  }
@@ -11,21 +11,6 @@ const ZONE_BORDER = {
11
11
  mustnot: C.ORANGE,
12
12
  };
13
13
  // ─── Sheet → "Any" wildcard mapping ─────────────────────────────────────────
14
- function getWildcardName(category) {
15
- if (!category)
16
- return null;
17
- switch (category) {
18
- case "joker": return null; // uses generic mystery
19
- case "voucher": return null;
20
- case "tag": return null;
21
- case "boss": return null;
22
- case "tarot": return null;
23
- case "spectral": return null;
24
- case "planet": return null;
25
- case "pack": return null;
26
- default: return null;
27
- }
28
- }
29
14
  // ─── Component ───────────────────────────────────────────────────────────────
30
15
  export function MysterySlot({ zone, sheetType, selection, width = 56, onTap, onClear, style, }) {
31
16
  const [hover, setHover] = useState(false);
@@ -1,29 +1 @@
1
- declare let MotelyWasm: any;
2
- declare let MotelyWasmEvents: any;
3
- declare let activeSearch: {
4
- cancel(): void;
5
- } | null;
6
- declare let activeSearchRunId: number;
7
- type WorkerMessage = {
8
- type: "init";
9
- url: string;
10
- } | {
11
- type: "start";
12
- jaml: string;
13
- mode?: string;
14
- count?: number;
15
- aesthetic?: number;
16
- seeds?: string[];
17
- keywords?: string;
18
- padding?: string;
19
- batchCharCount?: number;
20
- startBatch?: string;
21
- endBatch?: string;
22
- } | {
23
- type: "stop";
24
- } | {
25
- type: "get_tally_labels";
26
- jaml: string;
27
- };
28
- declare function post(message: Record<string, unknown>): void;
29
- declare function resetEventHandlers(): void;
1
+ export {};
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  let MotelyWasm = null;
3
2
  let MotelyWasmEvents = null;
4
3
  let activeSearch = null;
@@ -46,6 +45,8 @@ self.addEventListener("message", async (e) => {
46
45
  activeSearch = null;
47
46
  }
48
47
  }
48
+ if (!MotelyWasmEvents)
49
+ return;
49
50
  MotelyWasmEvents.notifyResult = (seed, score, tallyColumns) => {
50
51
  if (runId !== activeSearchRunId)
51
52
  return;
@@ -65,19 +66,19 @@ self.addEventListener("message", async (e) => {
65
66
  try {
66
67
  const mode = msg.mode || "random";
67
68
  if (mode === "random") {
68
- activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count);
69
+ activeSearch = MotelyWasm.startRandomSearch(msg.jaml, msg.count || 1);
69
70
  }
70
71
  else if (mode === "aesthetic") {
71
- activeSearch = MotelyWasm.startAestheticSearch(msg.jaml, msg.aesthetic);
72
+ activeSearch = MotelyWasm.startAestheticSearch(msg.jaml, msg.aesthetic || 0);
72
73
  }
73
74
  else if (mode === "seedList") {
74
- activeSearch = MotelyWasm.startSeedListSearch(msg.jaml, msg.seeds);
75
+ activeSearch = MotelyWasm.startSeedListSearch(msg.jaml, msg.seeds || []);
75
76
  }
76
77
  else if (mode === "keyword") {
77
- activeSearch = MotelyWasm.startKeywordSearch(msg.jaml, msg.keywords, msg.padding || "");
78
+ activeSearch = MotelyWasm.startKeywordSearch(msg.jaml, msg.keywords || "", msg.padding || "");
78
79
  }
79
80
  else if (mode === "sequential") {
80
- activeSearch = MotelyWasm.startSequentialSearch(msg.jaml, msg.batchCharCount, BigInt(msg.startBatch || "0"), BigInt(msg.endBatch || "0"));
81
+ activeSearch = MotelyWasm.startSequentialSearch(msg.jaml, msg.batchCharCount || 1, BigInt(msg.startBatch || "0"), BigInt(msg.endBatch || "0"));
81
82
  }
82
83
  else {
83
84
  post({ type: "error", message: `Unknown search mode: ${mode}` });
@@ -115,3 +116,4 @@ self.addEventListener("message", async (e) => {
115
116
  }
116
117
  }
117
118
  });
119
+ export {};
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef, useState, useCallback } from "react";
1
+ import { useEffect, useLayoutEffect, useRef, useState, useCallback } from "react";
2
2
  /**
3
3
  * Encapsulates IntersectionObserver logic.
4
4
  */
@@ -26,7 +26,7 @@ export function useIntersectionObserver(options = {}) {
26
26
  if (observer.current)
27
27
  observer.current.disconnect();
28
28
  };
29
- }, [node, options.root, options.rootMargin, options.threshold, options.freezeOnceVisible]);
29
+ }, [node, options, options.root, options.rootMargin, options.threshold, options.freezeOnceVisible]);
30
30
  return { ref, entry };
31
31
  }
32
32
  /**
@@ -34,7 +34,9 @@ export function useIntersectionObserver(options = {}) {
34
34
  */
35
35
  export function useInfiniteScroll(onVisible, options = {}, active = true) {
36
36
  const onVisibleRef = useRef(onVisible);
37
- onVisibleRef.current = onVisible;
37
+ useLayoutEffect(() => {
38
+ onVisibleRef.current = onVisible;
39
+ });
38
40
  const { ref, entry } = useIntersectionObserver({
39
41
  ...options,
40
42
  threshold: options.threshold ?? 0.1,
@@ -17,8 +17,12 @@ export function useSearch(motelyWasmUrl) {
17
17
  const workerRef = useRef(null);
18
18
  const readyRef = useRef(false); // Worker is NOT implicitly ready, must wait for 'ready' message
19
19
  const speedRef = useRef({ lastSearched: 0n, lastTime: 0, ema: 0 });
20
- useEffect(() => {
20
+ const [prevUrl, setPrevUrl] = useState(motelyWasmUrl);
21
+ if (motelyWasmUrl !== prevUrl) {
22
+ setPrevUrl(motelyWasmUrl);
21
23
  setState((s) => ({ ...s, status: "idle" }));
24
+ }
25
+ useEffect(() => {
22
26
  const worker = createWorker();
23
27
  workerRef.current = worker;
24
28
  if (motelyWasmUrl) {
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useState, useEffect, useRef, useCallback, useLayoutEffect } from "react";
3
3
  const DEFAULT_PULL = 12;
4
4
  /**
5
5
  * Generic stream hook for iterating any motely-wasm analyzer stream.
@@ -16,12 +16,15 @@ export function useMotelyStream(initStream, nextItem, deps, initialItems = []) {
16
16
  const [loading, setLoading] = useState(true);
17
17
  const [loadingMore, setLoadingMore] = useState(false);
18
18
  const nextRef = useRef(nextItem);
19
- nextRef.current = nextItem;
19
+ useLayoutEffect(() => {
20
+ nextRef.current = nextItem;
21
+ });
20
22
  const busyRef = useRef(false);
21
23
  const genRef = useRef(0);
22
24
  useEffect(() => {
23
25
  const gen = ++genRef.current;
24
26
  const base = initialItems.map(i => ({ ...i }));
27
+ // eslint-disable-next-line react-hooks/set-state-in-effect
25
28
  setItems(base);
26
29
  setError(null);
27
30
  setReady(false);
package/dist/index.d.ts CHANGED
@@ -25,6 +25,7 @@ export { useSearch, type SearchResult, type SearchStatus, type UseSearchState, }
25
25
  export { useAnalyzer, type AnalyzerStatus, type AnalyzerLive, type MotelyJsRunState, } from "./hooks/useAnalyzer.js";
26
26
  export { setMotelyEnums as setMotelyDisplayEnums } from "./motelyDisplay.js";
27
27
  export { setMotelyEnums as setMotelyDecoderEnums } from "./decode/motelyItemDecoder.js";
28
+ export { PaginatedFilterBrowser, type PaginatedFilterBrowserProps, type FilterItem, } from "./components/PaginatedFilterBrowser.js";
28
29
  export { JamlAestheticSelector, type JamlAestheticSelectorProps, type JamlAestheticOption, } from "./components/JamlAestheticSelector.js";
29
30
  export { JamlSeedInput, type JamlSeedInputProps, } from "./components/JamlSeedInput.js";
30
31
  export { JamlMapEditor, JokerPicker, MysterySlot, CategoryPicker, type JamlMapEditorProps, type JokerPickerProps, type JokerRarity, type MysterySlotProps, type SlotCategory, type SlotSelection, type CategoryPickerConfig, type CategoryPickerProps, VOUCHER_PICKER_CONFIG, TAG_PICKER_CONFIG, BOSS_PICKER_CONFIG, TAROT_PICKER_CONFIG, PLANET_PICKER_CONFIG, SPECTRAL_PICKER_CONFIG, PACK_PICKER_CONFIG, } from "./components/jamlMap/index.js";
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ export { useAnalyzer, } from "./hooks/useAnalyzer.js";
29
29
  // can resolve enum keys without statically importing motely-wasm.
30
30
  export { setMotelyEnums as setMotelyDisplayEnums } from "./motelyDisplay.js";
31
31
  export { setMotelyEnums as setMotelyDecoderEnums } from "./decode/motelyItemDecoder.js";
32
+ export { PaginatedFilterBrowser, } from "./components/PaginatedFilterBrowser.js";
32
33
  export { JamlAestheticSelector, } from "./components/JamlAestheticSelector.js";
33
34
  export { JamlSeedInput, } from "./components/JamlSeedInput.js";
34
35
  export { JamlMapEditor, JokerPicker, MysterySlot, CategoryPicker, VOUCHER_PICKER_CONFIG, TAG_PICKER_CONFIG, BOSS_PICKER_CONFIG, TAROT_PICKER_CONFIG, PLANET_PICKER_CONFIG, SPECTRAL_PICKER_CONFIG, PACK_PICKER_CONFIG, } from "./components/jamlMap/index.js";
@@ -5,4 +5,4 @@ export interface ParsedCard {
5
5
  seal: string | null;
6
6
  edition: string | null;
7
7
  }
8
- export declare function parseCardToken(item: any): ParsedCard | null;
8
+ export declare function parseCardToken(item: unknown): ParsedCard | null;
@@ -1,8 +1,6 @@
1
1
  const ENHANCEMENTS = ["Bonus", "Mult", "Wild", "Lucky", "Glass", "Steel", "Stone", "Gold"];
2
2
  const SEALS = ["Gold", "Purple", "Red", "Blue"];
3
3
  const EDITIONS = ["Foil", "Holographic", "Polychrome", "Negative"];
4
- const RANKS = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"];
5
- const SUITS = ["Hearts", "Clubs", "Diamonds", "Spades"];
6
4
  // Internal Balatro short codes
7
5
  const RANK_MAP = {
8
6
  "2": "2", "3": "3", "4": "4", "5": "5", "6": "6", "7": "7", "8": "8", "9": "9", "10": "10",
@@ -15,21 +13,25 @@ export function parseCardToken(item) {
15
13
  if (!item)
16
14
  return null;
17
15
  // 1. If it's already an object with rank/suit
18
- if (typeof item === 'object' && (item.rank || item.base)) {
19
- let rank = item.rank || item.base?.[2] || item.base?.[0]; // Handle different analyzer versions
20
- let suit = item.suit || item.base?.[0];
21
- // Normalize
22
- rank = RANK_MAP[rank] || rank;
23
- suit = SUIT_MAP[suit] || suit;
24
- if (suit && !suit.endsWith('s'))
25
- suit += 's';
26
- return {
27
- rank: rank || "Ace",
28
- suit: suit || "Spades",
29
- enhancement: item.enhancement || item.modifier || null,
30
- seal: item.seal || null,
31
- edition: item.edition || null
32
- };
16
+ if (typeof item === 'object') {
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ const obj = item;
19
+ if (obj.rank || obj.base) {
20
+ let rank = obj.rank || obj.base?.[2] || obj.base?.[0]; // Handle different analyzer versions
21
+ let suit = obj.suit || obj.base?.[0];
22
+ // Normalize
23
+ rank = RANK_MAP[rank] || rank;
24
+ suit = SUIT_MAP[suit] || suit;
25
+ if (suit && !suit.endsWith('s'))
26
+ suit += 's';
27
+ return {
28
+ rank: rank || "Ace",
29
+ suit: suit || "Spades",
30
+ enhancement: obj.enhancement || obj.modifier || null,
31
+ seal: obj.seal || null,
32
+ edition: obj.edition || null
33
+ };
34
+ }
33
35
  }
34
36
  // 2. If it's a string token
35
37
  if (typeof item === 'string') {
@@ -5,7 +5,7 @@ export interface BuyMetaData {
5
5
  card?: {
6
6
  type?: string;
7
7
  base?: string | string[];
8
- [key: string]: any;
8
+ [key: string]: unknown;
9
9
  };
10
- [key: string]: any;
10
+ [key: string]: unknown;
11
11
  }
@@ -1,17 +1,26 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { BuyMetaData } from "./classes/BuyMetaData.ts";
3
- export declare const jokers: any;
4
- export declare const jokerFaces: any;
5
- export declare const consumablesFaces: any;
6
- export declare const boosterPacks: any;
7
- export declare const tarotsAndPlanets: any;
8
- export declare const tags: any;
9
- export declare const vouchers: any;
10
- export declare const bosses: any;
11
- export declare const editionMap: any;
12
- export declare const stickerMap: any;
13
- export declare const options: any;
14
- export declare const blinds: any;
3
+ export interface Pos {
4
+ x: number;
5
+ y: number;
6
+ }
7
+ export interface Entity {
8
+ name: string;
9
+ pos: Pos;
10
+ animated?: boolean;
11
+ }
12
+ export declare const jokers: Entity[];
13
+ export declare const jokerFaces: Entity[];
14
+ export declare const consumablesFaces: Entity[];
15
+ export declare const boosterPacks: Entity[];
16
+ export declare const tarotsAndPlanets: Entity[];
17
+ export declare const tags: Entity[];
18
+ export declare const vouchers: Entity[];
19
+ export declare const bosses: Entity[];
20
+ export declare const editionMap: Record<string, number>;
21
+ export declare const stickerMap: Record<string, Pos>;
22
+ export declare const options: string[];
23
+ export declare const blinds: string[];
15
24
  export declare const blindsCamelCase: string[];
16
25
  export declare const SeedsWithLegendary: Array<string>;
17
26
  export declare const popularSeeds: Array<string>;
@@ -24,7 +33,7 @@ export interface AnalyzeOptions {
24
33
  [key: string]: BuyMetaData;
25
34
  };
26
35
  updates: Array<{
27
- [key: string]: any;
36
+ [key: string]: unknown;
28
37
  }>;
29
38
  unlocks: Array<string>;
30
39
  events: Array<Event>;
@@ -1,13 +1,14 @@
1
- import { CLAUSE_TYPE_KEYS, DECK_VALUES, EDITION_VALUES, ENHANCEMENT_VALUES, RANK_VALUES, SEAL_VALUES, SOURCE_KEYS, STAKE_VALUES, SUIT_VALUES, } from '../jaml/jamlSchema.js';
2
- // UI options derived from the shipped JAML schema wherever possible.
3
- export const DECK_OPTIONS = [...DECK_VALUES];
4
- export const STAKE_OPTIONS = [...STAKE_VALUES];
1
+ import { Motely } from 'motely-wasm';
2
+ import { CLAUSE_TYPE_KEYS, SOURCE_KEYS, } from '../jaml/jamlSchema.js';
3
+ // UI options derived from motely-wasm directly
4
+ export const DECK_OPTIONS = Object.keys(Motely.MotelyDeck).filter(k => isNaN(Number(k)));
5
+ export const STAKE_OPTIONS = Object.keys(Motely.MotelyStake).filter(k => isNaN(Number(k)));
5
6
  export const ANTE_OPTIONS = [1, 2, 3, 4, 5, 6, 7, 8];
6
7
  export const SLOT_OPTIONS = [1, 2, 3, 4, 5];
7
- export const RANK_OPTIONS = [...RANK_VALUES];
8
- export const SUIT_OPTIONS = [...SUIT_VALUES];
9
- export const ENHANCEMENT_OPTIONS = [...ENHANCEMENT_VALUES];
10
- export const EDITION_OPTIONS = [...EDITION_VALUES];
11
- export const SEAL_OPTIONS = [...SEAL_VALUES];
8
+ export const RANK_OPTIONS = Object.keys(Motely.MotelyStandardcardRank).filter(k => isNaN(Number(k)));
9
+ export const SUIT_OPTIONS = Object.keys(Motely.MotelyStandardcardSuit).filter(k => isNaN(Number(k)));
10
+ export const ENHANCEMENT_OPTIONS = Object.keys(Motely.MotelyItemEnhancement).filter(k => isNaN(Number(k)) && k !== "None");
11
+ export const EDITION_OPTIONS = Object.keys(Motely.MotelyItemEdition).filter(k => isNaN(Number(k)) && k !== "None");
12
+ export const SEAL_OPTIONS = Object.keys(Motely.MotelyItemSeal).filter(k => isNaN(Number(k)) && k !== "None");
12
13
  export const CLAUSE_TYPES = [...CLAUSE_TYPE_KEYS];
13
14
  export const SOURCE_OPTIONS = [...SOURCE_KEYS];
@@ -84,7 +84,7 @@ function parseJamlToFilter(text) {
84
84
  // 2. Detect Root Properties (indent 0)
85
85
  const rootMatch = line.match(/^(\w+):\s*(.+)$/);
86
86
  if (rootMatch && !line.startsWith(' ') && !line.startsWith('-')) {
87
- const [_, key, val] = rootMatch;
87
+ const [, key, val] = rootMatch;
88
88
  if (['name', 'deck', 'stake', 'description', 'author'].includes(key)) {
89
89
  filter[key] = val.trim();
90
90
  }
@@ -95,7 +95,7 @@ function parseJamlToFilter(text) {
95
95
  if (currentSection === 'defaults') {
96
96
  const propMatch = trimmed.match(/^(\w+):\s*(.+)$/);
97
97
  if (propMatch) {
98
- const [_, key, val] = propMatch;
98
+ const [, key, val] = propMatch;
99
99
  if (key === 'antes')
100
100
  filter.defaults.antes = parseNumArray(val);
101
101
  if (key === 'packSlots')
@@ -118,7 +118,7 @@ function parseJamlToFilter(text) {
118
118
  if (currentClause) {
119
119
  const propMatch = trimmed.match(/^(\w+):\s*(.+)$/);
120
120
  if (propMatch) {
121
- const [_, key, val] = propMatch;
121
+ const [, key, val] = propMatch;
122
122
  if (key === 'antes')
123
123
  currentClause.antes = parseNumArray(val);
124
124
  else if (key === 'sources')
@@ -140,10 +140,10 @@ function parseJamlToFilter(text) {
140
140
  }
141
141
  }
142
142
  function parseNumArray(str) {
143
- return str.replace(/[\[\]]/g, '').split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
143
+ return str.replace(/[[\]]/g, '').split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
144
144
  }
145
145
  function parseStringArray(str) {
146
- return str.replace(/[\[\]]/g, '').split(',').map(s => s.trim()).filter(s => !!s);
146
+ return str.replace(/[[\]]/g, '').split(',').map(s => s.trim()).filter(s => !!s);
147
147
  }
148
148
  function filterToJaml(filter) {
149
149
  const lines = [];
@@ -4,9 +4,15 @@ export function useSeedAnalyzer(motely, seed) {
4
4
  const [data, setData] = useState(null);
5
5
  const [loading, setLoading] = useState(false);
6
6
  const [error, setError] = useState(null);
7
- useEffect(() => {
7
+ const [prevInputs, setPrevInputs] = useState({ seed, motely });
8
+ if (seed !== prevInputs.seed || motely !== prevInputs.motely) {
9
+ setPrevInputs({ seed, motely });
8
10
  if (!seed || seed === "LOCKED" || !motely) {
9
11
  setData(null);
12
+ }
13
+ }
14
+ useEffect(() => {
15
+ if (!seed || seed === "LOCKED" || !motely) {
10
16
  return;
11
17
  }
12
18
  const abortController = new AbortController();
@@ -4,9 +4,23 @@
4
4
  * Derives all JAML schema constants from motely-wasm/jaml.schema.json.
5
5
  * All JAML-aware files import from here — single swap point if the API changes.
6
6
  */
7
+ type SchemaNode = {
8
+ enum?: string[];
9
+ items?: {
10
+ enum?: string[];
11
+ };
12
+ properties?: Record<string, SchemaNode>;
13
+ };
14
+ interface JamlSchema {
15
+ version?: string;
16
+ definitions?: {
17
+ clause?: SchemaNode;
18
+ };
19
+ properties?: Record<string, SchemaNode>;
20
+ }
7
21
  export declare const JAML_SCHEMA_VERSION: string;
8
22
  /** The raw JSON schema object. */
9
- export declare const jamlSchema: any;
23
+ export declare const jamlSchema: JamlSchema;
10
24
  /** Root-level metadata keys (name, author, deck, etc.) */
11
25
  export declare const METADATA_KEYS: readonly string[];
12
26
  /** Section keys that contain clause arrays. */
@@ -26,12 +40,12 @@ export declare function getValidValuesForKey(key: string): readonly string[] | n
26
40
  * Get the available properties for a given clause type.
27
41
  * Returns all clause property keys (the type system doesn't restrict per-type in JSON schema v7).
28
42
  */
29
- export declare function getAvailablePropsForType(_clauseType: string): readonly string[];
43
+ export declare function getAvailablePropsForType(): readonly string[];
30
44
  /**
31
45
  * Check if a property is invalid for a clause type.
32
46
  * In the v7 schema, all properties are available on all clause types.
33
47
  */
34
- export declare function isInvalidPropForType(_prop: string, _clauseType: string): boolean;
48
+ export declare function isInvalidPropForType(): boolean;
35
49
  /**
36
50
  * Check if a value is invalid for a property.
37
51
  */
@@ -52,3 +66,4 @@ export declare const STICKER_VALUES: readonly string[];
52
66
  * All JAML keywords — sections, metadata keys, clause types, and property keys.
53
67
  */
54
68
  export declare const ALL_JAML_KEYWORDS: string[];
69
+ export {};
@@ -51,14 +51,14 @@ export function getValidValuesForKey(key) {
51
51
  * Get the available properties for a given clause type.
52
52
  * Returns all clause property keys (the type system doesn't restrict per-type in JSON schema v7).
53
53
  */
54
- export function getAvailablePropsForType(_clauseType) {
54
+ export function getAvailablePropsForType( /* _clauseType */) {
55
55
  return PROPERTY_KEYS;
56
56
  }
57
57
  /**
58
58
  * Check if a property is invalid for a clause type.
59
59
  * In the v7 schema, all properties are available on all clause types.
60
60
  */
61
- export function isInvalidPropForType(_prop, _clauseType) {
61
+ export function isInvalidPropForType( /* _prop, _clauseType */) {
62
62
  return false;
63
63
  }
64
64
  /**
@@ -34,7 +34,7 @@ export interface ParsedSeed {
34
34
  * ]
35
35
  * }
36
36
  */
37
- export declare function parseDailyRitualSeed(raw: any): ParsedSeed;
37
+ export declare function parseDailyRitualSeed(raw: Record<string, unknown>): ParsedSeed;
38
38
  /**
39
39
  * Group items by type for rendering
40
40
  */
@@ -36,12 +36,13 @@ export function parseDailyRitualSeed(raw) {
36
36
  const items = [];
37
37
  // Parse each field looking for pattern: letters + number (e.g., "wj2", "hc1")
38
38
  Object.keys(raw).forEach(key => {
39
+ if (key === 'id' || key === 't' || key === 's' || key === 'w')
40
+ return;
39
41
  const match = key.match(/^([a-z]+)(\d+)$/);
40
42
  if (match) {
41
43
  const [, code, ante] = match;
42
44
  const itemData = ITEM_MAP[code];
43
45
  if (itemData) {
44
- const count = raw[key]; // Could be used for duplicates
45
46
  items.push({
46
47
  ...itemData,
47
48
  ante: Number(ante)