@bitcobblers/wod-wiki-engine 0.37.2137 → 0.37.2139

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/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { KNOWN_SECTION_WIDGETS, MetricType, validateNoteBlocks, parseDocumentSections, blockContentId, reconcileNoteBlocks, mapNoteBlockChanges } from '@bitcobblers/wod-wiki-core';
2
2
  export * from '@bitcobblers/wod-wiki-core';
3
- import { dialectRegistry, realtimeProcessorRegistry, summaryProcessorRegistry, getHints, isEffortDiscipline } from '@bitcobblers/wod-wiki-lang';
3
+ import { dialectRegistry, realtimeProcessorRegistry, summaryProcessorRegistry, getHints, parseEffortFile, parseEquipmentFile } from '@bitcobblers/wod-wiki-lang';
4
4
  export { ABSENT, AGGREGATE_BUILTINS, AUTHORITATIVE_CASTS, AmrapLogicStrategy, AnalyticsEngine, AnalyticsTransformer, BUILTIN_CALCS, BUNDLED_EFFORT_COUNT, BlockBuilder, ButtonBehavior, CONSUMED_HINTS, CONSUMED_HINT_KEYS, CONTEXT_ATOMS, CalcEngine, CalculationRegistry, CardioDialect, ChildSelectionBehavior, ChildrenStrategy, ChoiceGroupMetric, ClimbDialect, ClimbMetricType, CountdownTimerBehavior, CountupTimerBehavior, CrossFitDialect, DEFAULT_RESOLVER_OPTIONS, DEFAULT_RUNTIME_OPTIONS, DEFAULT_UNRESOLVED_EFFORT_MET, DIM_LENGTH, DIM_MASS, DIM_TIME, DIM_ZERO, DialectStack, EFFORT_DATA_METRIC_TYPE, EFFORT_REGISTRY_ORIGINS, EffortFallbackStrategy, EffortResolver, ErrorAction, EventBus, ExecutionContext, ExitBehavior, FixtureSyntaxError, GenericGroupStrategy, GenericLoopStrategy, GenericTimerStrategy, HabitsDialect, INTENSITY_TIERS, InMemoryEffortRegistry, IntervalLogicStrategy, JitCompiler, LabelingBehavior, LocalRuntimeSubscription, LookupRegistry, MdTimerRuntime, MemoryLocation, MemoryTypeEnum, NextEvent, OutputEmitter, PRODUCTION_STRATEGIES, PopBlockAction, PushBlockAction, RegisterEventHandlerAction, Registry, ReportOutputBehavior, ReportOutputStrategy, RoundsMetric, RuntimeAdapter, RuntimeClock, RuntimeFactory, RuntimeLogger, RuntimeMemory, RuntimeObservers, RuntimeStack, STORE_CALCS, STREAM_ATOMS, ScriptRuntime, SessionRootStrategy, SnapshotClock, SoundCueBehavior, SoundStrategy, StandardAnalyticsProfile, StartSessionAction, StartWorkoutAction, SubscriptionManager, TimeSpan, TypedMemoryReference, UNITS, UnitsDialect, UnregisterEventHandlerAction, VISIBILITY_ICONS, VISIBILITY_LABELS, WhiteboardScript, WodDialect, YogaDialect, buildTooltip, bundledEfforts, calculateElapsed, canonicalizeType, collapseUnresolvedChoices, compareStatement, compileLineForm, compoundName, computeColumnLabel, computeLabel, createAnalyticsEngineForBlock, createCalcEngine, createCompiler, createDialectStack, createMetricPresentationPolicy, createMockClock, createParser, dialectRegistry, dialectStack, diffStatement, evaluate, evaluateCalculateDefinitions, extractMetrics, extractStatements, findBestFuzzyMatch, findUnresolvedChoices, frozenNow, getAnalyticsFromLogs, getHints, getMetricVisibility, getRuntimeNowMs, hasHint, hintMetric, hintsToContainer, isCastSubscription, isChoiceResolved, isTimeLikeMetric, isWithinThreshold, levenshteinDistance, metricPresentation, normalizeForFuzzy, outputNodeId, outputNodeIds, parseCalcLine, parseCalculateBlock, parseExpression, parseMetricLine, parseScript, realtimeProcessorRegistry, renderMetric, strategyRegistry, summaryProcessorRegistry, toStoredOutputStatement, truthy, wallClockNow, whiteboardParser, whiteboardScript, whiteboardScriptLanguage, writeChoiceSelection } from '@bitcobblers/wod-wiki-lang';
5
5
  export { BlockTimerDisplay, ScriptRuntimeProvider, useActiveControls, useBlockMemory, useLiveAnalytics, useMemorySubscription, useNextPreview, useOutputStatements, usePrimaryTimer, useRoundDisplay, useRuntimeExecution, useScriptRuntime, useSecondaryTimers, useSnapshotBlocks, useStackDisplayRows, useStackFragmentSources, useStackSnapshot, useStackTimers, useTimerDisplay, useTimerElapsed } from '@bitcobblers/wod-wiki-lang/react';
6
6
  import { WQL_TYPED_TAG_KEYS } from '@bitcobblers/wod-wiki-wql';
@@ -246,43 +246,6 @@ function extractTypedTags(markdown, typedKeys = WQL_TYPED_TAG_KEYS) {
246
246
  }
247
247
  return results.filter((t) => t.label.length > 0);
248
248
  }
249
- function markdownToEffort(raw) {
250
- const match = FRONTMATTER_BLOCK_RE.exec(raw);
251
- if (!match) return null;
252
- const meta = readFrontmatterMeta(raw);
253
- const body = raw.slice(match[0].length).trim();
254
- const id = scalarOf(meta["id"]);
255
- const slug = scalarOf(meta["slug"]);
256
- const label = scalarOf(meta["label"]);
257
- if (!id || !slug || !label) return null;
258
- const baseAttributes = {};
259
- const blockRe = /^\s{2,}([^:]+):\s*(.+)$/;
260
- let inAttrs = false;
261
- for (const line of match[1].split(/\r?\n/)) {
262
- if (/^baseAttributes:/.test(line)) {
263
- inAttrs = true;
264
- continue;
265
- }
266
- if (inAttrs) {
267
- const attr = blockRe.exec(line);
268
- if (attr && /^ {2,}/.test(line)) {
269
- const v = attr[2].trim();
270
- baseAttributes[attr[1].trim()] = /^\d+(\.\d+)?$/.test(v) ? Number(v) : v;
271
- } else if (/^\S/.test(line)) {
272
- inAttrs = false;
273
- }
274
- }
275
- }
276
- if (typeof baseAttributes["met"] !== "number") return null;
277
- return {
278
- id,
279
- slug,
280
- label,
281
- aliases: asList(meta["aliases"]),
282
- baseAttributes,
283
- ...body ? { body } : {}
284
- };
285
- }
286
249
  function effortToMarkdown(effort) {
287
250
  const lines = ["---", `id: ${effort.id}`, `slug: ${effort.slug}`, `label: ${effort.label}`];
288
251
  if (effort.aliases && effort.aliases.length > 0) {
@@ -298,8 +261,15 @@ function effortToMarkdown(effort) {
298
261
  return `${lines.join("\n")}
299
262
  ${(effort.body ?? "").trim()}`.trimEnd() + "\n";
300
263
  }
301
- function scalarOf(value) {
302
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
264
+ function equipmentToMarkdown(equipment) {
265
+ const lines = ["---", "type: equipment", `id: ${equipment.id}`, `slug: ${equipment.slug}`, `label: ${equipment.label}`];
266
+ if (equipment.aliases && equipment.aliases.length > 0) {
267
+ lines.push("aliases:");
268
+ for (const alias of equipment.aliases) lines.push(` - ${alias}`);
269
+ }
270
+ lines.push("---", "");
271
+ return `${lines.join("\n")}
272
+ ${(equipment.body ?? "").trim()}`.trimEnd() + "\n";
303
273
  }
304
274
  function equipmentToCsv(labels) {
305
275
  return ["label", ...labels].map((l) => escapeCsvField(l)).join("\n") + "\n";
@@ -648,18 +618,19 @@ function csvToSessionRows(csv) {
648
618
  }
649
619
 
650
620
  // src/version.ts
651
- var VERSION = "0.37.2137";
652
- var GIT_SHA = "b82a721";
653
- var BUILD_TIME = "2026-10-16T20:56:02.597Z";
621
+ var VERSION = "0.37.2139";
622
+ var GIT_SHA = "491ec71";
623
+ var BUILD_TIME = "2026-10-17T15:36:01.834Z";
654
624
  var SEMVER = {
655
- version: "0.37.2137",
656
- gitSha: "b82a721",
657
- buildTime: "2026-10-16T20:56:02.597Z"
625
+ version: "0.37.2139",
626
+ gitSha: "491ec71",
627
+ buildTime: "2026-10-17T15:36:01.834Z"
658
628
  };
659
- var SEED_SCHEMA = 8;
629
+ var SEED_SCHEMA = 9;
660
630
  var SEED_REGISTERED_WIDGETS = KNOWN_SECTION_WIDGETS;
661
631
  var SEED_BROADCAST_CHANNEL = "wodwiki.seed";
662
632
  var EFFORTS_CHUNK_ID = "efforts";
633
+ var EQUIPMENT_CHUNK_ID = "equipment";
663
634
  var CANVAS_CHUNK_ID = "canvas";
664
635
  var DASHBOARDS_CHUNK_ID = "dashboards";
665
636
  var TEMPLATE_CHUNK_ID = "template";
@@ -685,6 +656,7 @@ function seedNoteSourceId(path, content, chunkId) {
685
656
  if (chunkId === CANVAS_CHUNK_ID) return `guides:${canvasRouteSlug(content) ?? under}`;
686
657
  if (chunkId === DASHBOARDS_CHUNK_ID) return "dashboards";
687
658
  if (chunkId === EFFORTS_CHUNK_ID) return "efforts";
659
+ if (chunkId === EQUIPMENT_CHUNK_ID) return "equipment";
688
660
  if (chunkId === TEMPLATE_CHUNK_ID) return "playground";
689
661
  if (chunkId.startsWith("collection.")) {
690
662
  const catalog = catalogForChunkId(chunkId);
@@ -693,141 +665,6 @@ function seedNoteSourceId(path, content, chunkId) {
693
665
  if (chunkId.startsWith("feed.")) return `feed:feeds/${under}`;
694
666
  return void 0;
695
667
  }
696
-
697
- // src/seed/frontmatter.ts
698
- var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
699
- function unquote(value) {
700
- return value.replace(
701
- /^(['"])(.*)\1$/,
702
- (_match, quote, inner) => quote === '"' ? inner.replace(/\\(["\\])/g, "$1") : inner
703
- );
704
- }
705
- function parseFrontmatter(raw) {
706
- const match = raw.match(FRONTMATTER_RE);
707
- if (!match) return { meta: {}, body: raw };
708
- return { meta: parseMetaLines(match[1].split(/\r?\n/)), body: raw.slice(match[0].length) };
709
- }
710
- function parseMetaLines(lines) {
711
- const meta = {};
712
- for (let i = 0; i < lines.length; i++) {
713
- const keyMatch = lines[i].match(/^([A-Za-z][\w.-]*)\s*:(.*)$/);
714
- if (!keyMatch) continue;
715
- const key = keyMatch[1];
716
- const rawVal = keyMatch[2].trim();
717
- if (rawVal === "") {
718
- const items = [];
719
- while (i + 1 < lines.length) {
720
- const itemMatch = lines[i + 1].match(/^\s+-\s+(.+)$/);
721
- if (!itemMatch) break;
722
- items.push(itemMatch[1].trim());
723
- i++;
724
- }
725
- meta[key] = items.length > 0 ? items : "";
726
- } else {
727
- const wasQuoted = /^(['"])(.*)\1$/.test(rawVal);
728
- const value = unquote(rawVal);
729
- const num2 = Number(value);
730
- meta[key] = !wasQuoted && value !== "" && !isNaN(num2) ? num2 : value;
731
- }
732
- }
733
- return meta;
734
- }
735
- function getScalar(meta, key) {
736
- const value = meta[key];
737
- return typeof value === "string" || typeof value === "number" ? value : void 0;
738
- }
739
- function getList(meta, key) {
740
- const value = meta[key];
741
- return Array.isArray(value) ? value : [];
742
- }
743
-
744
- // src/seed/effort-file.ts
745
- function parseDiscipline(val) {
746
- if (!val) return void 0;
747
- const normalized = unquoteYaml(val.trim()).toLowerCase();
748
- if (isEffortDiscipline(normalized)) return normalized;
749
- console.warn(`[effort-file] unknown discipline '${val}' \u2014 treating as unset`);
750
- return void 0;
751
- }
752
- function unquoteYaml(val) {
753
- if (val.startsWith('"') && val.endsWith('"')) {
754
- return val.slice(1, -1).replace(/\\(["\\])/g, "$1");
755
- }
756
- if (val.startsWith("'") && val.endsWith("'")) {
757
- return val.slice(1, -1).replace(/\\'/g, "'");
758
- }
759
- return val;
760
- }
761
- function coerceNestedScalar(raw) {
762
- if (raw === "true") return true;
763
- if (raw === "false") return false;
764
- if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
765
- return raw;
766
- }
767
- function readNestedSection(lines, key) {
768
- const start = lines.findIndex((l) => new RegExp(`^${key}:\\s*$`).test(l));
769
- if (start === -1) return void 0;
770
- const result = {};
771
- let subKey = null;
772
- for (let i = start + 1; i < lines.length; i++) {
773
- const line = lines[i];
774
- if (/^\S/.test(line)) break;
775
- const trimmed = line.trim();
776
- if (!trimmed || trimmed.startsWith("#")) continue;
777
- const indent = line.length - line.trimStart().length;
778
- const m = trimmed.match(/^([\w.-]+):\s*(.*)$/);
779
- if (!m) continue;
780
- const [, k, rawVal] = m;
781
- if (indent === 2) {
782
- if (rawVal === "") {
783
- subKey = k;
784
- result[k] = {};
785
- } else {
786
- subKey = null;
787
- result[k] = coerceNestedScalar(rawVal);
788
- }
789
- } else if (indent >= 4 && subKey !== null) {
790
- result[subKey][k] = coerceNestedScalar(rawVal);
791
- }
792
- }
793
- return Object.keys(result).length > 0 ? result : void 0;
794
- }
795
- function parseEffortFile(raw) {
796
- const blockMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
797
- if (!blockMatch) return null;
798
- const { meta, body } = parseFrontmatter(raw);
799
- const innerLines = blockMatch[1].split(/\r?\n/);
800
- const id = getScalar(meta, "id");
801
- const slug = getScalar(meta, "slug");
802
- const label = getScalar(meta, "label");
803
- const aliases = getList(meta, "aliases");
804
- const baseAttrs = readNestedSection(innerLines, "baseAttributes");
805
- const hints = readNestedSection(innerLines, "hints");
806
- const derivation = readNestedSection(innerLines, "derivation");
807
- const metRaw = baseAttrs?.met;
808
- const met = typeof metRaw === "number" ? metRaw : Number(metRaw);
809
- if (!id || !slug || !label || metRaw === void 0 || Number.isNaN(met)) return null;
810
- const discipline = parseDiscipline(
811
- typeof baseAttrs?.discipline === "string" ? baseAttrs.discipline : void 0
812
- );
813
- const intensityTier = baseAttrs?.intensityTier;
814
- const trimmedBody = body.trim();
815
- return {
816
- id: String(id),
817
- slug: String(slug),
818
- label: String(label),
819
- aliases,
820
- baseAttributes: {
821
- met,
822
- ...discipline ? { discipline } : {},
823
- ...intensityTier ? { intensityTier } : {}
824
- },
825
- registrySource: "bundled",
826
- ...trimmedBody ? { body: trimmedBody } : {},
827
- ...hints ? { hints } : {},
828
- ...derivation ? { derivation } : {}
829
- };
830
- }
831
668
  function object(value) {
832
669
  return typeof value === "object" && value !== null && !Array.isArray(value);
833
670
  }
@@ -855,7 +692,7 @@ function assertSeedRows(value) {
855
692
  if (!Array.isArray(value)) throw new Error("seed chunk is not an array");
856
693
  const paths = /* @__PURE__ */ new Set();
857
694
  for (const row of value) {
858
- if (!object(row) || typeof row.path !== "string" || !row.path || row.path.startsWith("/") || row.path.split("/").includes("..") || paths.has(row.path) || typeof row.content !== "string" || !object(row.blocks) || !Array.isArray(row.blockIndex) || !Array.isArray(row.blockEfforts)) throw new Error("seed chunk row failed shape validation");
695
+ if (!object(row) || typeof row.path !== "string" || !row.path || row.path.startsWith("/") || row.path.split("/").includes("..") || paths.has(row.path) || typeof row.content !== "string" || !object(row.blocks) || !Array.isArray(row.blockIndex) || !Array.isArray(row.blockEfforts) || !Array.isArray(row.blockEquipment)) throw new Error("seed chunk row failed shape validation");
859
696
  paths.add(row.path);
860
697
  for (const [id, block] of Object.entries(row.blocks)) {
861
698
  if (!object(block) || block.id !== id || !id || block.version !== 1 || block.sourceRevision !== 1 || !nonnegative(block.from) || !positive(block.to) || block.from >= block.to || block.to > row.content.length || !["wod", "query", "widget", "embed"].includes(String(block.kind)) || (block.kind === "wod" ? !["time", "log"].includes(String(block.dialect)) : block.dialect !== void 0) || block.sport !== void 0 && typeof block.sport !== "string") throw new Error("invalid seed block range or revision");
@@ -886,6 +723,15 @@ function assertSeedRows(value) {
886
723
  if (!object(projection) || link.noteId !== projection.noteId || link.blockContentId !== projection.blockContentId || !Number.isFinite(link.createdAt)) throw new Error("invalid seed effort metadata");
887
724
  links.add(String(link.id));
888
725
  }
726
+ const equipmentLinks = /* @__PURE__ */ new Set();
727
+ for (const link of row.blockEquipment) {
728
+ if (!object(link) || typeof link.blockId !== "string" || typeof link.noteId !== "string" || typeof link.equipmentSlug !== "string" || !link.equipmentSlug) throw new Error("invalid seed equipment identity");
729
+ const block = row.blocks[link.blockId];
730
+ if (link.id !== `${link.noteId}:${link.blockId}:${link.equipmentSlug}` || equipmentLinks.has(String(link.id)) || !object(block) || block.kind !== "wod" || !indexed.has(link.blockId) || link.sourcePath !== row.path || link.isStatic !== true || typeof link.createdAt !== "number") throw new Error("orphan or incompatible seed equipment link");
731
+ const projection = row.blockIndex.find((p) => object(p) && p.blockId === link.blockId);
732
+ if (!object(projection) || link.noteId !== projection.noteId || link.blockContentId !== projection.blockContentId || !Number.isFinite(link.createdAt)) throw new Error("invalid seed equipment metadata");
733
+ equipmentLinks.add(String(link.id));
734
+ }
889
735
  }
890
736
  return value;
891
737
  }
@@ -923,6 +769,40 @@ function refreshedBlocks(existing, rawContent, sourceRevision) {
923
769
  let ordinal = 0;
924
770
  return reconcileNoteBlocks(rawContent, { sourceRevision, blocks: mapped, registeredWidgets: SEED_REGISTERED_WIDGETS, createId: () => `seed:${existing.id}:r${sourceRevision}:${ordinal++}` });
925
771
  }
772
+ async function applyDefinitionChunk(defStore, parse, family, rows, ids, owned, existingById, allIds) {
773
+ const projections = [];
774
+ for (const [i, row] of rows.entries()) {
775
+ if (!owned.has(ids[i])) continue;
776
+ const parsed = parse(row.content);
777
+ if (!parsed) continue;
778
+ projections.push({ ...parsed, registrySource: "bundled", ownerNoteId: ids[i], sourceId: family, sourcePath: row.path });
779
+ }
780
+ const prior = await defStore.getMany(projections.map((p) => p.slug));
781
+ for (const [i, p] of projections.entries()) {
782
+ const before = prior[i];
783
+ if (before?.registrySource === "bundled" && before.ownerNoteId && before.ownerNoteId !== p.ownerNoteId) {
784
+ throw new Error(`Seed definition collision: '${p.slug}' is already owned by another seed source (note ${before.ownerNoteId})`);
785
+ }
786
+ }
787
+ const changed = projections.filter((p, i) => (!prior[i] || prior[i]?.registrySource === "bundled") && !equal(p, prior[i]));
788
+ if (changed.length) await defStore.putMany(changed);
789
+ const wantedSlugs = new Set(projections.map((p) => p.slug));
790
+ const removedBySlug = /* @__PURE__ */ new Map();
791
+ for (const id of allIds) {
792
+ if (!owned.has(id)) continue;
793
+ const note = existingById.get(id);
794
+ const slug = note ? parse(note.rawContent)?.slug : void 0;
795
+ if (slug && !wantedSlugs.has(slug)) removedBySlug.set(slug, id);
796
+ }
797
+ const removedSlugs = [...removedBySlug.keys()];
798
+ const old = await defStore.getMany(removedSlugs);
799
+ for (const [i, row] of old.entries()) {
800
+ if (row?.registrySource === "bundled" && removedBySlug.get(removedSlugs[i]) === row.ownerNoteId) {
801
+ await defStore.delete(removedSlugs[i]);
802
+ }
803
+ }
804
+ return changed.length;
805
+ }
926
806
  async function putChanged(store, values, keys) {
927
807
  if (!values.length) return;
928
808
  const prior = await store.getMany(keys);
@@ -932,7 +812,7 @@ async function putChanged(store, values, keys) {
932
812
  async function applySeedChunk(storage, write) {
933
813
  assertSeedManifest(write.manifest);
934
814
  assertSeedRows(write.rows);
935
- const result = { totalNotes: 0, totalEfforts: 0, totalBlocks: 0, skippedUserOwned: 0, deleted: 0, addedNotes: 0, updatedNotes: 0 };
815
+ const result = { totalNotes: 0, totalEfforts: 0, totalEquipment: 0, totalBlocks: 0, skippedUserOwned: 0, deleted: 0, addedNotes: 0, updatedNotes: 0 };
936
816
  const ids = await Promise.all(write.rows.map((row) => seedNoteId(row.path)));
937
817
  const pagesBySlug = /* @__PURE__ */ new Map();
938
818
  const linkIds = /* @__PURE__ */ new Map();
@@ -947,7 +827,7 @@ async function applySeedChunk(storage, write) {
947
827
  if (!tagIds.has(label)) tagIds.set(label, await seedNoteId(`tag:${label}`));
948
828
  }
949
829
  }
950
- return storage.transaction(["notes", "block_revisions", "block_index", "block_efforts", "page", "page_notes", "tags", "note_tags", "efforts", "meta"], "readwrite", async (tx) => {
830
+ return storage.transaction(["notes", "block_revisions", "block_index", "block_efforts", "block_equipment", "page", "page_notes", "tags", "note_tags", "efforts", "equipment", "meta"], "readwrite", async (tx) => {
951
831
  const metaStore = tx.readwrite("meta");
952
832
  const actualMeta = await metaStore.get(write.meta.key);
953
833
  if (!equal(actualMeta, write.expectedMeta)) throw new Error("Seed checkpoint changed during import");
@@ -964,14 +844,34 @@ async function applySeedChunk(storage, write) {
964
844
  return !note || note.seedOrigin === "seed" && (declared.has(id) || note.seedChunkId === write.chunkId);
965
845
  });
966
846
  const owned = new Set(ownedIds);
847
+ {
848
+ let checkpoints;
849
+ for (const [i, id] of ids.entries()) {
850
+ const existing = existingById.get(id);
851
+ if (!existing || existing.seedOrigin !== "seed") continue;
852
+ if (checkpoints === void 0) {
853
+ checkpoints = [];
854
+ for (const row of await metaStore.getAll()) {
855
+ const meta = row;
856
+ if (!meta || typeof meta.key !== "string" || !meta.key.startsWith("seed:") || meta.key === write.meta.key) continue;
857
+ const noteIds = /* @__PURE__ */ new Set();
858
+ for (const chunk of Object.values(meta.chunks ?? {})) for (const noteId of chunk.noteIds ?? []) noteIds.add(noteId);
859
+ checkpoints.push({ key: meta.key, noteIds });
860
+ }
861
+ }
862
+ const owner = checkpoints.find((checkpoint) => checkpoint.noteIds.has(id));
863
+ if (owner) throw new Error(`Seed note collision: '${write.rows[i].path}' is already owned by ${owner.key}`);
864
+ }
865
+ }
967
866
  const revisionStore = tx.readwrite("block_revisions");
968
867
  const historyIds = ids.filter((id, i) => owned.has(id) && existingById.get(id)?.rawContent !== write.rows[i].content);
969
868
  const histories = await revisionStore.getAllFromIndexMany("by-note", historyIds);
970
869
  const history = new Map(historyIds.map((id, i) => [id, histories[i]]));
971
- const indexStore = tx.readwrite("block_index"), effortLinks = tx.readwrite("block_efforts");
870
+ const indexStore = tx.readwrite("block_index"), effortLinks = tx.readwrite("block_efforts"), equipmentLinks = tx.readwrite("block_equipment");
972
871
  const tagLinks = tx.readwrite("note_tags"), pageLinks = tx.readwrite("page_notes");
973
872
  const currentIndexes = await indexStore.getAllFromIndexMany("by-note", ownedIds);
974
873
  const currentEfforts = await effortLinks.getAllFromIndexMany("by-note", ownedIds);
874
+ const currentEquipment = await equipmentLinks.getAllFromIndexMany("by-note", ownedIds);
975
875
  const currentTags = await tagLinks.getAllFromIndexMany("by-note", ownedIds);
976
876
  const currentPages = await pageLinks.getAllFromIndexMany("by-note", ownedIds);
977
877
  const slot = new Map(ownedIds.map((id, i) => [id, i]));
@@ -980,7 +880,7 @@ async function applySeedChunk(storage, write) {
980
880
  const matches = await tagsStore.getAllFromIndexMany("by-label", labels);
981
881
  const tagsByLabel = new Map(labels.map((label, i) => [label, matches[i][0]]));
982
882
  const changedTags = [], changedNotes = [], revisions = [], pages = [], pageNotes = [], noteTags = [];
983
- const blocks = [], blockEfforts = [];
883
+ const blocks = [], blockEfforts = [], blockEquipment = [];
984
884
  for (const [i, row] of write.rows.entries()) {
985
885
  const id = ids[i], existing = existingById.get(id);
986
886
  if (!owned.has(id)) {
@@ -1036,6 +936,13 @@ async function applySeedChunk(storage, write) {
1036
936
  const prior = currentEfforts[slot.get(id)].find((b) => b.id === link.id);
1037
937
  if (!prior || prior.isStatic === true) blockEfforts.push(prior ? { ...link, createdAt: prior.createdAt } : link);
1038
938
  }
939
+ for (const compiled of row.blockEquipment) {
940
+ const blockId = remap.get(compiled.blockId);
941
+ if (!blockId || compiledById.get(compiled.blockId)?.dataType !== "wod") throw new Error("Orphan seed equipment link");
942
+ const link = { ...compiled, id: `${id}:${blockId}:${compiled.equipmentSlug}`, noteId: id, blockId, sourcePath: row.path };
943
+ const prior = currentEquipment[slot.get(id)].find((b) => b.id === link.id);
944
+ if (!prior || prior.isStatic === true) blockEquipment.push(prior ? { ...link, createdAt: prior.createdAt } : link);
945
+ }
1039
946
  const n = slot.get(id);
1040
947
  const wantedTags = /* @__PURE__ */ new Set();
1041
948
  for (const item of extractTypedTags(row.content)) {
@@ -1064,9 +971,10 @@ async function applySeedChunk(storage, write) {
1064
971
  result.totalNotes++;
1065
972
  result.totalBlocks += Object.keys(map).length;
1066
973
  }
1067
- const wantedBlocks = new Set(blocks.map((b) => b.id)), wantedEfforts = new Set(blockEfforts.map((b) => b.id));
974
+ const wantedBlocks = new Set(blocks.map((b) => b.id)), wantedEfforts = new Set(blockEfforts.map((b) => b.id)), wantedEquipment = new Set(blockEquipment.map((b) => b.id));
1068
975
  for (const rows of currentIndexes) for (const row of rows) if (row.isStatic === true && !wantedBlocks.has(row.id)) await indexStore.delete(row.id);
1069
976
  for (const rows of currentEfforts) for (const row of rows) if (row.isStatic === true && !wantedEfforts.has(row.id)) await effortLinks.delete(row.id);
977
+ for (const rows of currentEquipment) for (const row of rows) if (row.isStatic === true && !wantedEquipment.has(row.id)) await equipmentLinks.delete(row.id);
1070
978
  for (const id of goneIds) {
1071
979
  if (!owned.has(id) || !existingById.get(id)) continue;
1072
980
  const n = slot.get(id);
@@ -1075,25 +983,17 @@ async function applySeedChunk(storage, write) {
1075
983
  for (const link of currentPages[n]) await pageLinks.delete(link.id);
1076
984
  result.deleted++;
1077
985
  }
1078
- const effortStore = tx.readwrite("efforts");
1079
986
  if (write.chunkId === EFFORTS_CHUNK_ID) {
1080
- const efforts = write.rows.filter((_row, i) => owned.has(ids[i])).map((r) => parseEffortFile(r.content)).filter((e) => e !== null).map((e) => ({ ...e, registrySource: "bundled" }));
1081
- const prior = await effortStore.getMany(efforts.map((e) => e.slug));
1082
- const changed = efforts.filter((e, i) => (!prior[i] || prior[i]?.registrySource === "bundled") && !equal(e, prior[i]));
1083
- if (changed.length) await effortStore.putMany(changed);
1084
- result.totalEfforts = changed.length;
1085
- const wantedSlugs = new Set(efforts.map((effort) => effort.slug));
1086
- const removed = allIds.map((id) => {
1087
- const note = existingById.get(id);
1088
- return owned.has(id) && note ? parseEffortFile(note.rawContent)?.slug : void 0;
1089
- }).filter((slug) => !!slug && !wantedSlugs.has(slug));
1090
- const old = await effortStore.getMany(removed);
1091
- for (const [i, effort] of old.entries()) if (effort?.registrySource === "bundled") await effortStore.delete(removed[i]);
987
+ result.totalEfforts = await applyDefinitionChunk(tx.readwrite("efforts"), parseEffortFile, "efforts", write.rows, ids, owned, existingById, allIds);
988
+ }
989
+ if (write.chunkId === EQUIPMENT_CHUNK_ID) {
990
+ result.totalEquipment = await applyDefinitionChunk(tx.readwrite("equipment"), parseEquipmentFile, "equipment", write.rows, ids, owned, existingById, allIds);
1092
991
  }
1093
992
  if (changedNotes.length) await noteStore.putMany(changedNotes);
1094
993
  if (revisions.length) await revisionStore.putMany(revisions);
1095
994
  await putChanged(indexStore, blocks, blocks.map((b) => b.id));
1096
995
  await putChanged(effortLinks, blockEfforts, blockEfforts.map((b) => b.id));
996
+ await putChanged(equipmentLinks, blockEquipment, blockEquipment.map((b) => b.id));
1097
997
  if (changedTags.length) await tagsStore.putMany([...new Map(changedTags.map((t) => [t.id, t])).values()]);
1098
998
  if (noteTags.length) await tagLinks.putMany(noteTags);
1099
999
  await putChanged(tx.readwrite("page"), pages, pages.map((p) => p.id));
@@ -1103,6 +1003,6 @@ async function applySeedChunk(storage, write) {
1103
1003
  });
1104
1004
  }
1105
1005
 
1106
- export { BUILD_TIME, CANVAS_CHUNK_ID, DASHBOARDS_CHUNK_ID, EFFORTS_CHUNK_ID, GIT_SHA, NOTE_ID_FRONTMATTER_KEY, SEED_BROADCAST_CHANNEL, SEED_REGISTERED_WIDGETS, SEED_SCHEMA, SEMVER, SESSION_CSV_HEADER, TEMPLATE_CHUNK_ID, VERSION, applySeedChunk, assertSeedManifest, assertSeedRows, buildStatementTree, canvasRouteSlug, catalogForChunkId, composeNoteMarkdown, createIRFile, csvToEquipmentLabels, csvToSessionRows, defineLanguagePack, effortToMarkdown, emptySeedMeta, equipmentToCsv, extractNoteIdentity, extractTypedTags, getRegisteredLanguagePacks, inMemoryEventStore, injectIdentityFrontmatter, isIRFile, listLanguagePacks, markdownToEffort, parseCsvLine, readFrontmatterMeta, registerLanguagePack, seedMetaKey, seedNoteId, seedNoteSourceId, sessionToCsv, splitCsvRecords, statementToNode, unregisterLanguagePack };
1006
+ export { BUILD_TIME, CANVAS_CHUNK_ID, DASHBOARDS_CHUNK_ID, EFFORTS_CHUNK_ID, EQUIPMENT_CHUNK_ID, GIT_SHA, NOTE_ID_FRONTMATTER_KEY, SEED_BROADCAST_CHANNEL, SEED_REGISTERED_WIDGETS, SEED_SCHEMA, SEMVER, SESSION_CSV_HEADER, TEMPLATE_CHUNK_ID, VERSION, applySeedChunk, assertSeedManifest, assertSeedRows, buildStatementTree, canvasRouteSlug, catalogForChunkId, composeNoteMarkdown, createIRFile, csvToEquipmentLabels, csvToSessionRows, defineLanguagePack, effortToMarkdown, emptySeedMeta, equipmentToCsv, equipmentToMarkdown, extractNoteIdentity, extractTypedTags, getRegisteredLanguagePacks, inMemoryEventStore, injectIdentityFrontmatter, isIRFile, listLanguagePacks, parseCsvLine, readFrontmatterMeta, registerLanguagePack, seedMetaKey, seedNoteId, seedNoteSourceId, sessionToCsv, splitCsvRecords, statementToNode, unregisterLanguagePack };
1107
1007
  //# sourceMappingURL=index.mjs.map
1108
1008
  //# sourceMappingURL=index.mjs.map