@bitcobblers/wod-wiki-core 0.35.2116 → 0.35.2117

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.cjs CHANGED
@@ -485,6 +485,7 @@ var bySpelling = (() => {
485
485
  return map;
486
486
  })();
487
487
  function lookupUnit(spelling) {
488
+ if (typeof spelling !== "string") return void 0;
488
489
  return bySpelling.get(spelling.trim().toLowerCase());
489
490
  }
490
491
  function dimensionKeyOf(spelling) {
@@ -978,393 +979,246 @@ var toNotebookTag = (notebookId) => `${NOTEBOOK_TAG_PREFIX}${notebookId}`;
978
979
  var fromNotebookTag = (tag) => tag.startsWith(NOTEBOOK_TAG_PREFIX) ? tag.slice(NOTEBOOK_TAG_PREFIX.length) : null;
979
980
  var isNotebookTag = (tag) => tag.startsWith(NOTEBOOK_TAG_PREFIX);
980
981
 
981
- // src/sections/blockDetection.ts
982
+ // src/sections/sectionParser.ts
983
+ var KNOWN_SECTION_WIDGETS = ["playground-run-tip", "attention", "code-example", "syntax-group"];
982
984
  function matchDialectFence(trimmedLine) {
983
- if (!trimmedLine.startsWith("```")) return null;
984
- const tag = trimmedLine.slice(3).split(/[\s\t]/)[0].toLowerCase();
985
- const [base, sport] = tag.split(":", 2);
986
- if (VALID_FENCE_DIALECTS.includes(base)) {
987
- return { dialect: base, sport: sport || void 0 };
988
- }
989
- return null;
985
+ const match = trimmedLine.match(/^`{3,}(time|log)(?::([\w-]+))?\s*$/i);
986
+ if (!match) return null;
987
+ return { dialect: match[1].toLowerCase() === "time" ? "time" : "log", ...match[2] ? { sport: match[2] } : {} };
990
988
  }
991
- function detectScriptBlocks(content) {
992
- const lines = content.split("\n");
993
- const blocks = [];
994
- let inBlock = false;
995
- let currentBlock = {};
996
- let blockContent = [];
997
- lines.forEach((line, index) => {
998
- const trimmedLine = line.trim();
999
- if (!inBlock) {
1000
- const fence = matchDialectFence(trimmedLine);
1001
- if (fence) {
1002
- inBlock = true;
1003
- const now = Date.now();
1004
- currentBlock = {
1005
- id: `${fence.dialect}-block-${now}-${Math.random().toString(36).slice(2, 9)}`,
1006
- dialect: fence.dialect,
1007
- sport: fence.sport,
1008
- startLine: index,
989
+ function frontmatterSubtype(properties) {
990
+ const type = properties.type?.toLowerCase();
991
+ if (type === "youtube" || type === "strava" || type === "amazon" || type === "file" || type === "effort") return type;
992
+ const url = properties.url || properties.link || "";
993
+ const host = url.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").split(/[/?#:]/, 1)[0].toLowerCase();
994
+ if (host === "youtu.be" || host === "youtube.com" || host.endsWith(".youtube.com")) return "youtube";
995
+ if (host === "strava.com" || host.endsWith(".strava.com")) return "strava";
996
+ if (host === "amzn.to" || host === "amazon.com" || host.endsWith(".amazon.com")) return "amazon";
997
+ if (["baseAttributes", "discipline", "effortType", "targetRPE", "equipment", "aliases", "derivation"].some((key) => key in properties)) return "effort";
998
+ return "default";
999
+ }
1000
+ function markdownSubtype(text) {
1001
+ if (/^#{1,6}\s/.test(text)) return "heading";
1002
+ if (/^[-*+]\s|^\d+\.\s/.test(text)) return "list";
1003
+ if (/^>\s?/.test(text)) return "blockquote";
1004
+ if (text.startsWith("|") && /^\|[\s:]*-+/m.test(text)) return "table";
1005
+ return "paragraph";
1006
+ }
1007
+ function parseDocumentSections(content, options = {}) {
1008
+ if (!content) return [];
1009
+ const lines = content.split(/\r\n|\n|\r/);
1010
+ const offsets = [];
1011
+ const separators = [];
1012
+ let offset = 0;
1013
+ for (const text of lines) {
1014
+ offsets.push(offset);
1015
+ offset += text.length;
1016
+ const separator = content.startsWith("\r\n", offset) ? "\r\n" : /[\r\n]/.test(content[offset] ?? "") ? content[offset] : "";
1017
+ separators.push(separator);
1018
+ offset += separator.length;
1019
+ }
1020
+ const sections = [];
1021
+ const createdAt = Date.now();
1022
+ let firstText = true;
1023
+ let line = 0;
1024
+ function make(type, start, end) {
1025
+ const from = offsets[start];
1026
+ const to = offsets[end] + lines[end].length;
1027
+ return {
1028
+ id: crypto.randomUUID(),
1029
+ type,
1030
+ from,
1031
+ to,
1032
+ rawContent: content.slice(from, to),
1033
+ displayContent: content.slice(from, to),
1034
+ startLine: start,
1035
+ endLine: end,
1036
+ lineCount: end - start + 1,
1037
+ version: 1,
1038
+ createdAt,
1039
+ separatorAfter: separators[end]
1040
+ };
1041
+ }
1042
+ while (line < lines.length) {
1043
+ const text = lines[line].trim();
1044
+ const fence = text.match(/^(`{3,}|~{3,})(.*)$/);
1045
+ if (fence) {
1046
+ const marker = fence[1];
1047
+ let close = line + 1;
1048
+ const closing = new RegExp(`^${marker[0]}{${marker.length},}\\s*$`);
1049
+ while (close < lines.length && !closing.test(lines[close].trim())) close++;
1050
+ const closed = close < lines.length;
1051
+ const end2 = closed ? close : lines.length - 1;
1052
+ const tag = fence[2].trim();
1053
+ const dialect = marker[0] === "`" ? matchDialectFence(`\`\`\`${tag}`) : null;
1054
+ const widget = marker[0] === "`" ? tag.match(/^widget:([\w-]+)$/) : null;
1055
+ const query = marker[0] === "`" ? tag.match(/^query(?:[:\s](.*))?$/i) : null;
1056
+ const names = options.registeredWidgets ?? KNOWN_SECTION_WIDGETS;
1057
+ const knownWidget = widget && ("has" in names ? names.has(widget[1]) : names.includes(widget[1]));
1058
+ const type = closed && dialect ? dialect.dialect : closed && knownWidget ? "widget" : closed && query ? "query" : "code";
1059
+ const section2 = make(type, line, end2);
1060
+ section2.contentFrom = offsets[line] + lines[line].length + separators[line].length;
1061
+ section2.contentTo = closed ? offsets[close] : section2.to;
1062
+ section2.displayContent = content.slice(section2.contentFrom, section2.contentTo);
1063
+ if (dialect && closed) {
1064
+ section2.sport = dialect.sport;
1065
+ section2.contentId = blockContentId(section2.displayContent);
1066
+ section2.scriptBlock = {
1067
+ id: section2.id,
1068
+ contentId: section2.contentId,
1069
+ dialect: dialect.dialect,
1070
+ sport: dialect.sport,
1071
+ startLine: line,
1072
+ endLine: end2,
1073
+ content: section2.displayContent,
1009
1074
  state: "idle",
1010
1075
  version: 1,
1011
- createdAt: now
1076
+ createdAt
1012
1077
  };
1013
- blockContent = [];
1014
- return;
1015
1078
  }
1079
+ if (type === "widget" && widget) section2.widgetName = widget[1];
1080
+ if (type === "query" && query) section2.querySuffix = (query[1] ?? "").trim();
1081
+ sections.push(section2);
1082
+ line = end2 + 1;
1083
+ continue;
1016
1084
  }
1017
- if (inBlock && trimmedLine.startsWith("```")) {
1018
- inBlock = false;
1019
- currentBlock.endLine = index;
1020
- currentBlock.content = blockContent.join("\n");
1021
- blocks.push(currentBlock);
1022
- currentBlock = {};
1023
- blockContent = [];
1024
- } else if (inBlock) {
1025
- blockContent.push(line);
1085
+ if (text === "---" && lines[line + 1]?.trim()) {
1086
+ let close = line + 1;
1087
+ while (close < lines.length && lines[close].trim() !== "---" && !/^(`{3,}|~{3,})/.test(lines[close].trim())) close++;
1088
+ const inner = lines.slice(line + 1, close);
1089
+ if (lines[close]?.trim() === "---" && inner.some((value) => /^[A-Za-z_][\w-]*\s*:/.test(value.trim()))) {
1090
+ const section2 = make("frontmatter", line, close);
1091
+ section2.contentFrom = offsets[line + 1];
1092
+ section2.contentTo = offsets[close];
1093
+ section2.displayContent = content.slice(section2.contentFrom, section2.contentTo);
1094
+ section2.properties = {};
1095
+ for (const value of inner) {
1096
+ const pair = value.match(/^([^:]+):\s*(.*)$/);
1097
+ if (pair) section2.properties[pair[1].trim()] = pair[2].trim();
1098
+ }
1099
+ section2.frontmatterType = frontmatterSubtype(section2.properties);
1100
+ sections.push(section2);
1101
+ line = close + 1;
1102
+ continue;
1103
+ }
1026
1104
  }
1027
- });
1028
- if (inBlock && currentBlock.startLine !== void 0) {
1029
- currentBlock.endLine = lines.length - 1;
1030
- currentBlock.content = blockContent.join("\n");
1031
- blocks.push(currentBlock);
1032
- }
1033
- return blocks;
1034
- }
1035
- function findBlockAtLine(blocks, lineNumber) {
1036
- return blocks.find(
1037
- (block) => lineNumber >= block.startLine && lineNumber <= block.endLine
1038
- ) ?? null;
1039
- }
1040
- function extractBlockContent(content, startLine, endLine) {
1041
- const lines = content.split("\n");
1042
- return lines.slice(startLine + 1, endLine).join("\n");
1043
- }
1044
-
1045
- // src/sections/sectionParser.ts
1046
- var METADATA_REGEX = /<!--\s*section-metadata\s+id:(\S+)\s+version:(\d+)\s+created:(\d+)\s*-->/i;
1047
- function extractMetadata(text) {
1048
- const match = text.match(METADATA_REGEX);
1049
- if (!match) return { metadata: null, cleanText: text };
1050
- return {
1051
- metadata: {
1052
- id: match[1],
1053
- version: parseInt(match[2], 10),
1054
- createdAt: parseInt(match[3], 10)
1055
- },
1056
- cleanText: text.replace(METADATA_REGEX, "").trim()
1057
- };
1058
- }
1059
- function generateSectionId(type, startLine, content) {
1060
- let hash = 0;
1061
- for (let i = 0; i < content.length && i < 64; i++) {
1062
- hash = (hash << 5) - hash + content.charCodeAt(i) | 0;
1105
+ const embed = text.match(/^(!)?\[([^\]]*)\]\((https?:\/\/[^)\s]+|\/[^)\s]+)\)$/);
1106
+ if (embed) {
1107
+ const section2 = make("embed", line, line);
1108
+ const isImage = !!embed[1];
1109
+ section2.embed = { type: isImage ? "image" : /(?:youtube\.com|youtu\.be)/i.test(embed[3]) ? "youtube" : "link", label: embed[2], url: embed[3], isImage };
1110
+ sections.push(section2);
1111
+ line++;
1112
+ continue;
1113
+ }
1114
+ let end = line;
1115
+ if (text) {
1116
+ while (end + 1 < lines.length && lines[end + 1].trim() && !/^(?:`{3,}|~{3,}|---\s*$|!?\[[^\]]*\]\()/.test(lines[end + 1].trim())) end++;
1117
+ }
1118
+ const section = make(text && firstText ? "title" : "markdown", line, end);
1119
+ section.subtype = markdownSubtype(section.displayContent.trim());
1120
+ if (text) firstText = false;
1121
+ sections.push(section);
1122
+ line = end + 1;
1063
1123
  }
1064
- const hashHex = (hash >>> 0).toString(16).padStart(8, "0");
1065
- return `${type}-${startLine}-${hashHex}`;
1124
+ return sections;
1066
1125
  }
1067
- function detectUrlSubtype(url) {
1068
- if (!url) return null;
1069
- const withoutScheme = url.trim().replace(/^[a-z][a-z0-9+.-]*:\/\//i, "");
1070
- const host = withoutScheme.split(/[/?#:]/, 1)[0].toLowerCase();
1071
- if (host === "youtube.com" || host.endsWith(".youtube.com") || host === "youtu.be") return "youtube";
1072
- if (host === "strava.com" || host.endsWith(".strava.com")) return "strava";
1073
- if (host === "amazon.com" || host.endsWith(".amazon.com") || host === "amzn.to") return "amazon";
1126
+ function actionableSectionKind(section) {
1127
+ if (section.type === "time" || section.type === "log") return "wod";
1128
+ if (section.type === "query" || section.type === "widget" || section.type === "embed") return section.type;
1074
1129
  return null;
1075
1130
  }
1076
- function resolveFrontMatterSubtype(props) {
1077
- const typeValue = (props["type"] || "").toLowerCase();
1078
- if (typeValue === "youtube") return "youtube";
1079
- if (typeValue === "strava") return "strava";
1080
- if (typeValue === "amazon") return "amazon";
1081
- if (typeValue === "file") return "file";
1082
- if (typeValue === "effort") return "effort";
1083
- const url = props["url"] || props["link"] || "";
1084
- if (url) {
1085
- const detected = detectUrlSubtype(url);
1086
- if (detected) return detected;
1087
- }
1088
- if (props["baseAttributes"] !== void 0 || props["discipline"] !== void 0 || props["effortType"] !== void 0 || props["targetRPE"] !== void 0 || props["equipment"] !== void 0 || props["aliases"] !== void 0 || props["derivation"] !== void 0) {
1089
- return "effort";
1131
+ function reconcileNoteBlocks(rawContent, options) {
1132
+ if (!Number.isSafeInteger(options.sourceRevision) || options.sourceRevision < 1) throw new Error("Invalid source revision");
1133
+ const result = /* @__PURE__ */ Object.create(null);
1134
+ const lineage = Object.values(options.blocks ?? {}).filter((block) => block && typeof block === "object");
1135
+ const createId = options.createId ?? (() => crypto.randomUUID());
1136
+ for (const section of parseDocumentSections(rawContent, options)) {
1137
+ const kind = actionableSectionKind(section);
1138
+ if (!kind) continue;
1139
+ const matches = lineage.filter((block) => block.from === section.from && block.to === section.to && block.kind === kind);
1140
+ const prior = matches.length === 1 ? matches[0] : void 0;
1141
+ const id = prior?.id ?? createId();
1142
+ if (!id || Object.hasOwn(result, id)) throw new Error("Duplicate block occurrence ID");
1143
+ result[id] = {
1144
+ id,
1145
+ kind,
1146
+ from: section.from,
1147
+ to: section.to,
1148
+ sourceRevision: options.sourceRevision,
1149
+ version: prior?.version ?? 1,
1150
+ ...kind === "wod" && (section.type === "time" || section.type === "log") ? { dialect: section.type, ...section.sport ? { sport: section.sport } : {} } : {}
1151
+ };
1090
1152
  }
1091
- return "default";
1153
+ return result;
1092
1154
  }
1093
- function parseFrontMatterProperties(innerLines) {
1094
- const props = {};
1095
- for (const line of innerLines) {
1096
- const match = line.match(/^([^:]+):\s*(.*)$/);
1097
- if (match) {
1098
- props[match[1].trim()] = match[2].trim();
1099
- }
1155
+ function validateNoteBlocks(rawContent, blocks, sourceRevision, options = {}) {
1156
+ if (typeof rawContent !== "string" || !blocks || typeof blocks !== "object" || Array.isArray(blocks)) throw new Error("Invalid Note source or blocks");
1157
+ for (const [id, block] of Object.entries(blocks)) {
1158
+ if (!block || block.id !== id || !id || !Number.isSafeInteger(block.version) || block.version < 1 || block.sourceRevision !== sourceRevision || !Number.isSafeInteger(block.from) || !Number.isSafeInteger(block.to)) throw new Error("Invalid block identity, version or source revision");
1100
1159
  }
1101
- return props;
1102
- }
1103
- function matchMarkdownEmbed(trimmed) {
1104
- const match = trimmed.match(/^(!)?\[([^\]]*)\]\(([^)]+)\)$/);
1105
- if (!match) return null;
1106
- const isImage = !!match[1];
1107
- const label = match[2];
1108
- const url = match[3];
1109
- let type = isImage ? "image" : "link";
1110
- if (/youtube\.com|youtu\.be/i.test(url)) {
1111
- type = "youtube";
1112
- }
1113
- return { type, label, url, isImage };
1114
- }
1115
- function detectFrontMatterBlocks(lines, scriptBlocks) {
1116
- const ranges = [];
1117
- let i = 0;
1118
- while (i < lines.length) {
1119
- const inScript = scriptBlocks.some((b) => i >= b.startLine && i <= b.endLine);
1120
- if (inScript) {
1121
- i++;
1122
- continue;
1123
- }
1124
- if (lines[i].trim() === "---") {
1125
- const openLine = i;
1126
- let j = i + 1;
1127
- while (j < lines.length) {
1128
- const inScriptInner = scriptBlocks.some((b) => j >= b.startLine && j <= b.endLine);
1129
- if (inScriptInner) {
1130
- j++;
1131
- continue;
1132
- }
1133
- if (lines[j].trim() === "---") {
1134
- ranges.push({ startLine: openLine, endLine: j });
1135
- i = j + 1;
1136
- break;
1137
- }
1138
- j++;
1139
- }
1140
- if (j >= lines.length) {
1141
- i++;
1142
- }
1143
- } else {
1144
- i++;
1145
- }
1160
+ const expected = reconcileNoteBlocks(rawContent, { ...options, sourceRevision, blocks });
1161
+ if (Object.keys(expected).length !== Object.keys(blocks).length) throw new Error("Incomplete actionable block map");
1162
+ for (const [id, block] of Object.entries(blocks)) {
1163
+ const recognized = expected[id];
1164
+ if (!recognized || recognized.from !== block.from || recognized.to !== block.to || recognized.kind !== block.kind || recognized.dialect !== block.dialect || recognized.sport !== block.sport) throw new Error("Block does not match authored source");
1146
1165
  }
1147
- return ranges;
1148
1166
  }
1149
- function parseDocumentSections(content, scriptBlocks) {
1150
- if (!content) return [];
1151
- const blocks = scriptBlocks ?? detectScriptBlocks(content);
1152
- const lines = content.split("\n");
1153
- const sections = [];
1154
- const fmRanges = detectFrontMatterBlocks(lines, blocks);
1155
- const now = Date.now();
1156
- let currentLine = 0;
1157
- let isFirstTextBlock = true;
1158
- function flushMarkdownLines(mdLines, startLine) {
1159
- if (mdLines.length === 0) return;
1160
- let currentGroup = [];
1161
- let groupStartLine = startLine;
1162
- const flushGroup = () => {
1163
- if (currentGroup.length > 0) {
1164
- const raw = currentGroup.join("\n");
1165
- const { metadata, cleanText } = extractMetadata(raw);
1166
- const trimmed = cleanText.trim();
1167
- const embed = currentGroup.length === 1 ? matchMarkdownEmbed(trimmed) : null;
1168
- if (embed) {
1169
- sections.push({
1170
- id: metadata?.id || generateSectionId("embed", groupStartLine, trimmed),
1171
- type: "embed",
1172
- rawContent: cleanText,
1173
- displayContent: cleanText,
1174
- startLine: groupStartLine,
1175
- endLine: groupStartLine,
1176
- lineCount: 1,
1177
- embed,
1178
- version: metadata?.version || 1,
1179
- createdAt: metadata?.createdAt || now
1180
- });
1181
- } else if (isFirstTextBlock) {
1182
- isFirstTextBlock = false;
1183
- sections.push({
1184
- id: metadata?.id || generateSectionId("title", groupStartLine, cleanText),
1185
- type: "title",
1186
- rawContent: cleanText,
1187
- displayContent: cleanText,
1188
- startLine: groupStartLine,
1189
- endLine: groupStartLine + currentGroup.length - 1,
1190
- lineCount: currentGroup.length,
1191
- version: metadata?.version || 1,
1192
- createdAt: metadata?.createdAt || now
1193
- });
1194
- } else {
1195
- sections.push({
1196
- id: metadata?.id || generateSectionId("markdown", groupStartLine, cleanText),
1197
- type: "markdown",
1198
- rawContent: cleanText,
1199
- displayContent: cleanText,
1200
- startLine: groupStartLine,
1201
- endLine: groupStartLine + currentGroup.length - 1,
1202
- lineCount: currentGroup.length,
1203
- version: metadata?.version || 1,
1204
- createdAt: metadata?.createdAt || now
1205
- });
1206
- }
1207
- }
1208
- currentGroup = [];
1209
- };
1210
- for (let i = 0; i < mdLines.length; i++) {
1211
- const line = mdLines[i];
1212
- if (line.trim().length === 0) {
1213
- flushGroup();
1214
- sections.push({
1215
- id: generateSectionId("markdown", startLine + i, ""),
1216
- type: "markdown",
1217
- rawContent: "",
1218
- displayContent: "",
1219
- startLine: startLine + i,
1220
- endLine: startLine + i,
1221
- lineCount: 1,
1222
- version: 1,
1223
- createdAt: now
1224
- });
1225
- groupStartLine = startLine + i + 1;
1226
- } else {
1227
- currentGroup.push(line);
1167
+ function mapNoteBlockChanges(blocks, changes) {
1168
+ let previousEnd = 0;
1169
+ for (const change of changes) {
1170
+ if (!Number.isSafeInteger(change.from) || !Number.isSafeInteger(change.to) || change.from < previousEnd || change.to < change.from || typeof change.insert !== "string") throw new Error("Invalid source changes");
1171
+ previousEnd = change.to;
1172
+ }
1173
+ const result = {};
1174
+ for (const block of Object.values(blocks)) {
1175
+ let from = block.from;
1176
+ let to = block.to;
1177
+ let removed = false;
1178
+ for (const change of changes) {
1179
+ const delta = change.insert.length - (change.to - change.from);
1180
+ if (change.from <= block.from && change.to >= block.to || change.from < block.from && change.to > block.from || change.from < block.to && change.to > block.to) {
1181
+ removed = true;
1182
+ break;
1228
1183
  }
1184
+ if (change.to <= block.from) {
1185
+ from += delta;
1186
+ to += delta;
1187
+ } else if (change.from < block.to) to += delta;
1229
1188
  }
1230
- flushGroup();
1231
- }
1232
- let mdBuffer = [];
1233
- let mdBufferStart = 0;
1234
- while (currentLine < lines.length) {
1235
- const scriptBlock = blocks.find((b) => b.startLine === currentLine);
1236
- if (scriptBlock) {
1237
- flushMarkdownLines(mdBuffer, mdBufferStart);
1238
- mdBuffer = [];
1239
- const dialect = scriptBlock.dialect ?? "time";
1240
- const { metadata, cleanText } = extractMetadata(scriptBlock.content);
1241
- const fenceTag = scriptBlock.sport ? `${dialect}:${scriptBlock.sport}` : dialect;
1242
- const cleanRawContent = `\`\`\`${fenceTag}
1243
- ${cleanText}
1244
- \`\`\``;
1245
- const cleanLineCount = cleanRawContent.split("\n").length;
1246
- const sectionId = metadata?.id || generateSectionId(dialect, scriptBlock.startLine, cleanText);
1247
- const contentId = blockContentId(cleanText);
1248
- sections.push({
1249
- id: sectionId,
1250
- contentId,
1251
- type: dialect,
1252
- sport: scriptBlock.sport,
1253
- rawContent: cleanRawContent,
1254
- displayContent: cleanText,
1255
- startLine: scriptBlock.startLine,
1256
- endLine: scriptBlock.startLine + cleanLineCount - 1,
1257
- lineCount: cleanLineCount,
1258
- scriptBlock: {
1259
- ...scriptBlock,
1260
- id: sectionId,
1261
- contentId,
1262
- content: cleanText,
1263
- dialect,
1264
- version: metadata?.version || 1,
1265
- createdAt: metadata?.createdAt || now
1266
- },
1267
- version: metadata?.version || 1,
1268
- createdAt: metadata?.createdAt || now
1269
- });
1270
- currentLine = scriptBlock.endLine + 1;
1271
- mdBufferStart = currentLine;
1272
- continue;
1273
- }
1274
- const fmRange = fmRanges.find((r) => r.startLine === currentLine);
1275
- if (fmRange) {
1276
- flushMarkdownLines(mdBuffer, mdBufferStart);
1277
- mdBuffer = [];
1278
- const rawLines = lines.slice(fmRange.startLine, fmRange.endLine + 1);
1279
- const raw = rawLines.join("\n");
1280
- const innerLines = lines.slice(fmRange.startLine + 1, fmRange.endLine);
1281
- const { metadata, cleanText } = extractMetadata(innerLines.join("\n"));
1282
- const properties = parseFrontMatterProperties(cleanText.split("\n"));
1283
- const subtype = resolveFrontMatterSubtype(properties);
1284
- const lineCount = fmRange.endLine - fmRange.startLine + 1;
1285
- sections.push({
1286
- id: metadata?.id || generateSectionId("frontmatter", fmRange.startLine, raw),
1287
- type: "frontmatter",
1288
- rawContent: raw,
1289
- displayContent: cleanText,
1290
- startLine: fmRange.startLine,
1291
- endLine: fmRange.endLine,
1292
- lineCount,
1293
- properties,
1294
- frontmatterType: subtype,
1295
- version: metadata?.version || 1,
1296
- createdAt: metadata?.createdAt || now
1297
- });
1298
- currentLine = fmRange.endLine + 1;
1299
- mdBufferStart = currentLine;
1300
- continue;
1301
- }
1302
- if (mdBuffer.length === 0) {
1303
- mdBufferStart = currentLine;
1304
- }
1305
- mdBuffer.push(lines[currentLine]);
1306
- currentLine++;
1189
+ if (!removed) result[block.id] = { ...block, from, to };
1307
1190
  }
1308
- flushMarkdownLines(mdBuffer, mdBufferStart);
1309
- return sections;
1191
+ return result;
1310
1192
  }
1311
1193
  function buildRawContent(sections) {
1312
- return sections.filter((s) => !s.deleted).map((s) => s.rawContent).join("\n");
1194
+ return sections.filter((section) => !section.deleted).map((section) => section.rawContent + (section.separatorAfter ?? "")).join("");
1313
1195
  }
1314
1196
  function calculateTotalLines(sections) {
1315
- const visible = sections.filter((s) => !s.deleted);
1316
- if (visible.length === 0) return 0;
1317
- const last = visible[visible.length - 1];
1318
- return last.endLine + 1;
1197
+ const visible = sections.filter((section) => !section.deleted);
1198
+ return visible.length ? visible[visible.length - 1].endLine + 1 : 0;
1319
1199
  }
1320
- function matchSectionIds(oldSections, newSections) {
1321
- return newSections.map((newSec) => {
1322
- const { metadata } = extractMetadata(newSec.rawContent);
1323
- if (metadata) {
1324
- const updated = { ...newSec, id: metadata.id, version: metadata.version, createdAt: metadata.createdAt };
1325
- if (updated.scriptBlock) {
1326
- updated.scriptBlock = { ...updated.scriptBlock, id: metadata.id };
1327
- }
1328
- return updated;
1329
- }
1330
- const exactMatch = oldSections.find(
1331
- (old) => old.type === newSec.type && old.startLine === newSec.startLine && old.displayContent === newSec.displayContent
1332
- );
1333
- if (exactMatch) {
1334
- const updated = { ...newSec, id: exactMatch.id, version: exactMatch.version, createdAt: exactMatch.createdAt };
1335
- if (updated.scriptBlock) {
1336
- updated.scriptBlock = { ...updated.scriptBlock, id: exactMatch.id };
1337
- }
1338
- return updated;
1339
- }
1340
- const positionMatch = oldSections.find(
1341
- (old) => old.type === newSec.type && old.startLine === newSec.startLine
1342
- );
1343
- if (positionMatch) {
1344
- const newVersion = (positionMatch.version || 1) + 1;
1345
- const updated = {
1346
- ...newSec,
1347
- id: positionMatch.id,
1348
- version: newVersion,
1349
- createdAt: positionMatch.createdAt
1350
- };
1351
- if (updated.scriptBlock) {
1352
- updated.scriptBlock = { ...updated.scriptBlock, id: positionMatch.id, version: newVersion };
1353
- }
1354
- return updated;
1355
- }
1356
- return newSec;
1357
- });
1200
+
1201
+ // src/sections/blockDetection.ts
1202
+ function detectScriptBlocks(content) {
1203
+ return parseDocumentSections(content).flatMap((section) => section.scriptBlock ? [section.scriptBlock] : []);
1204
+ }
1205
+ function findBlockAtLine(blocks, lineNumber) {
1206
+ return blocks.find(
1207
+ (block) => lineNumber >= block.startLine && lineNumber <= block.endLine
1208
+ ) ?? null;
1209
+ }
1210
+ function extractBlockContent(content, startLine, endLine) {
1211
+ return parseDocumentSections(content).find((section) => section.startLine === startLine && section.endLine === endLine && section.scriptBlock)?.scriptBlock?.content ?? "";
1358
1212
  }
1359
1213
 
1360
1214
  // src/version.ts
1361
- var VERSION = "0.35.2116";
1362
- var GIT_SHA = "a4d411f";
1363
- var BUILD_TIME = "2026-10-12T00:14:24.795Z";
1215
+ var VERSION = "0.35.2117";
1216
+ var GIT_SHA = "03d1dfc";
1217
+ var BUILD_TIME = "2026-10-12T20:08:12.374Z";
1364
1218
  var SEMVER = {
1365
- version: "0.35.2116",
1366
- gitSha: "a4d411f",
1367
- buildTime: "2026-10-12T00:14:24.795Z"
1219
+ version: "0.35.2117",
1220
+ gitSha: "03d1dfc",
1221
+ buildTime: "2026-10-12T20:08:12.374Z"
1368
1222
  };
1369
1223
 
1370
1224
  exports.BUILD_TIME = BUILD_TIME;
@@ -1373,6 +1227,7 @@ exports.CodeMetadata = CodeMetadata;
1373
1227
  exports.CodeStatement = CodeStatement;
1374
1228
  exports.GIT_SHA = GIT_SHA;
1375
1229
  exports.KNOWN_OUTPUT_TYPES = KNOWN_OUTPUT_TYPES;
1230
+ exports.KNOWN_SECTION_WIDGETS = KNOWN_SECTION_WIDGETS;
1376
1231
  exports.LEGACY_ORIGIN_TO_OWNERSHIP_LAYER = LEGACY_ORIGIN_TO_OWNERSHIP_LAYER;
1377
1232
  exports.METRIC_OWNERSHIP_LAYER_CHAIN = METRIC_OWNERSHIP_LAYER_CHAIN;
1378
1233
  exports.Metric = Metric;
@@ -1388,6 +1243,7 @@ exports.UNIT_CATALOG = UNIT_CATALOG;
1388
1243
  exports.UnitConversionError = UnitConversionError;
1389
1244
  exports.VALID_FENCE_DIALECTS = VALID_FENCE_DIALECTS;
1390
1245
  exports.VERSION = VERSION;
1246
+ exports.actionableSectionKind = actionableSectionKind;
1391
1247
  exports.blockContentId = blockContentId;
1392
1248
  exports.buildRawContent = buildRawContent;
1393
1249
  exports.calculateTotalLines = calculateTotalLines;
@@ -1401,21 +1257,22 @@ exports.fieldRefKey = fieldRefKey;
1401
1257
  exports.findBlockAtLine = findBlockAtLine;
1402
1258
  exports.flattenFieldLeaves = flattenFieldLeaves;
1403
1259
  exports.fromNotebookTag = fromNotebookTag;
1404
- exports.generateSectionId = generateSectionId;
1405
1260
  exports.getMetricOwnershipLayer = getMetricOwnershipLayer;
1406
1261
  exports.isNotebookTag = isNotebookTag;
1407
1262
  exports.isWorkoutSectionType = isWorkoutSectionType;
1408
1263
  exports.lookupUnit = lookupUnit;
1264
+ exports.mapNoteBlockChanges = mapNoteBlockChanges;
1409
1265
  exports.matchDialectFence = matchDialectFence;
1410
- exports.matchSectionIds = matchSectionIds;
1411
1266
  exports.normalizeFieldComponent = normalizeFieldComponent;
1412
1267
  exports.normalizeFieldPath = normalizeFieldPath;
1413
1268
  exports.ownershipRank = ownershipRank;
1414
1269
  exports.parseDocumentSections = parseDocumentSections;
1270
+ exports.reconcileNoteBlocks = reconcileNoteBlocks;
1415
1271
  exports.resolveLayer = resolveLayer;
1416
1272
  exports.runAffordance = runAffordance;
1417
1273
  exports.systemDefaultUnit = systemDefaultUnit;
1418
1274
  exports.toNotebookTag = toNotebookTag;
1275
+ exports.validateNoteBlocks = validateNoteBlocks;
1419
1276
  exports.valueKindOf = valueKindOf;
1420
1277
  //# sourceMappingURL=index.cjs.map
1421
1278
  //# sourceMappingURL=index.cjs.map