@bitcobblers/wod-wiki-ui 0.45.2167 → 0.46.2168

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.
@@ -1319,9 +1319,51 @@ var referenceIndexReload = state.StateEffect.define();
1319
1319
  var frontmatterVisibility = state.Facet.define({
1320
1320
  combine: (values2) => values2[0] ?? true
1321
1321
  });
1322
+ var setFrontmatterDetailed = state.StateEffect.define();
1323
+ var frontmatterDetailed = state.StateField.define({
1324
+ create: () => false,
1325
+ update(value, tr) {
1326
+ for (const effect of tr.effects) {
1327
+ if (effect.is(setFrontmatterDetailed)) return effect.value;
1328
+ }
1329
+ return value;
1330
+ }
1331
+ });
1332
+ var setFrontmatterSource = state.StateEffect.define();
1333
+ var frontmatterSource = state.StateField.define({
1334
+ create: () => false,
1335
+ update(value, tr) {
1336
+ let source = value;
1337
+ for (const effect of tr.effects) {
1338
+ if (effect.is(setFrontmatterSource)) source = effect.value;
1339
+ }
1340
+ if (source && tr.selection) {
1341
+ const section = tr.state.field(sectionField).sections.find((item) => item.type === "frontmatter");
1342
+ if (!section || !tr.state.selection.ranges.some((range) => range.from <= section.to && range.to >= section.from)) {
1343
+ source = false;
1344
+ }
1345
+ }
1346
+ return source;
1347
+ }
1348
+ });
1322
1349
  var pendingFocus = /* @__PURE__ */ new WeakMap();
1323
1350
  var widgetCleanup = /* @__PURE__ */ new WeakMap();
1324
1351
  var helperId = 0;
1352
+ var TAG_CATEGORY_SUMMARY_KEYS = {
1353
+ tag: true,
1354
+ tags: true,
1355
+ category: true,
1356
+ domain: true,
1357
+ format: true,
1358
+ equipment: true,
1359
+ effort: true,
1360
+ quality: true,
1361
+ intent: true,
1362
+ discipline: true,
1363
+ intensity: true,
1364
+ intensitytier: true,
1365
+ sound: true
1366
+ };
1325
1367
  function isTagProperty(key) {
1326
1368
  return ["tag", "tags", "category"].includes(key.trim().toLowerCase());
1327
1369
  }
@@ -1438,12 +1480,18 @@ function parseTagList(val) {
1438
1480
  }
1439
1481
  return [];
1440
1482
  }
1441
- function patchPropertyInView(view, _sectionId, path, value) {
1483
+ function patchPropertyInView(view, _sectionId, path, value, expandDetailed = false) {
1442
1484
  if (view.state.readOnly) return;
1443
1485
  const wholeDoc = view.state.doc.toString();
1444
1486
  const next = wodWikiCore.patchFrontmatterProperty(wholeDoc, path, value);
1445
- if (next === wholeDoc) return;
1446
- view.dispatch({ changes: { from: 0, to: wholeDoc.length, insert: next } });
1487
+ if (next === wholeDoc) {
1488
+ if (expandDetailed) view.dispatch({ effects: setFrontmatterDetailed.of(true) });
1489
+ return;
1490
+ }
1491
+ view.dispatch({
1492
+ changes: { from: 0, to: wholeDoc.length, insert: next },
1493
+ ...expandDetailed ? { effects: setFrontmatterDetailed.of(true) } : {}
1494
+ });
1447
1495
  }
1448
1496
  function renamePropertyInView(view, _sectionId, fromKey, toKey, value) {
1449
1497
  if (view.state.readOnly) return;
@@ -1463,12 +1511,22 @@ function detectDefinitionKind(meta) {
1463
1511
  if (meta.registrySource !== void 0 || meta.baseAttributes !== void 0) return "effort";
1464
1512
  return null;
1465
1513
  }
1466
- function readBaseAttributes(body) {
1514
+ function readBaseAttributes(body, meta = {}) {
1467
1515
  const out = {};
1516
+ if (meta["metrics.met"] !== void 0 && !Array.isArray(meta["metrics.met"])) {
1517
+ out.met = String(meta["metrics.met"]);
1518
+ }
1519
+ if (meta["discipline"] !== void 0 && !Array.isArray(meta["discipline"])) {
1520
+ out.discipline = String(meta["discipline"]);
1521
+ }
1522
+ if (meta["intensityTier"] !== void 0 && !Array.isArray(meta["intensityTier"])) {
1523
+ out.intensityTier = String(meta["intensityTier"]);
1524
+ }
1468
1525
  let inBase = false;
1469
1526
  for (const line of body.split(/\r?\n/)) {
1470
1527
  if (/^baseAttributes:\s*(#.*)?$/.test(line)) {
1471
1528
  inBase = true;
1529
+ out.hasBaseBlock = true;
1472
1530
  continue;
1473
1531
  }
1474
1532
  if (!inBase) continue;
@@ -1598,7 +1656,8 @@ function renderEffortValueDiagnostics(provider, token, onPick) {
1598
1656
  var METRIC_DRAFT_KINDS = {
1599
1657
  effort: { label: "Effort", path: "metrics.effort", kind: "effort" },
1600
1658
  equipment: { label: "Equipment", path: "metrics.equipment", kind: "equipment" },
1601
- rpe: { label: "RPE", path: "metrics.rpe", kind: "number" }
1659
+ rpe: { label: "RPE", path: "metrics.rpe", kind: "number" },
1660
+ custom: { label: "Custom / Equation", path: "metrics.", kind: "custom" }
1602
1661
  };
1603
1662
  function createMetricDraftRow(view, root, referenceProvider, commit) {
1604
1663
  const row = document.createElement("div");
@@ -1617,6 +1676,7 @@ function createMetricDraftRow(view, root, referenceProvider, commit) {
1617
1676
  row.appendChild(select);
1618
1677
  const valCol = document.createElement("div");
1619
1678
  valCol.className = "flex items-center flex-wrap gap-1.5 px-3 py-1.5 flex-1 min-w-0";
1679
+ let customKeyInput = null;
1620
1680
  const input = document.createElement("input");
1621
1681
  input.className = "flex-none w-44 max-w-full rounded-md border border-field-border bg-background/60 px-2 py-1 text-xs text-foreground outline-none placeholder:text-field-placeholder focus:border-ring focus:ring-1 focus:ring-ring";
1622
1682
  input.placeholder = "Value\u2026";
@@ -1639,6 +1699,10 @@ function createMetricDraftRow(view, root, referenceProvider, commit) {
1639
1699
  else if (spec.kind === "number") {
1640
1700
  const num = Number(raw);
1641
1701
  if (!Number.isNaN(num)) commit([spec.path], num);
1702
+ } else if (spec.kind === "custom") {
1703
+ const keyName = (customKeyInput?.value.trim() || "custom").replace(/^metrics\./i, "");
1704
+ const num = Number(raw);
1705
+ commit([`metrics.${keyName}`], !Number.isNaN(num) && raw !== "" ? num : raw);
1642
1706
  } else commit([spec.path], raw);
1643
1707
  }
1644
1708
  row.remove();
@@ -1646,6 +1710,31 @@ function createMetricDraftRow(view, root, referenceProvider, commit) {
1646
1710
  };
1647
1711
  const attachFor = () => {
1648
1712
  const spec = METRIC_DRAFT_KINDS[select.value];
1713
+ if (spec?.kind === "custom") {
1714
+ if (!customKeyInput) {
1715
+ customKeyInput = document.createElement("input");
1716
+ customKeyInput.className = "flex-none w-32 max-w-full rounded-md border border-field-border bg-background/60 px-2 py-1 text-xs text-foreground outline-none placeholder:text-field-placeholder focus:border-ring focus:ring-1 focus:ring-ring";
1717
+ customKeyInput.placeholder = "Metric key\u2026";
1718
+ customKeyInput.setAttribute("aria-label", "Metric key");
1719
+ customKeyInput.dataset.frontmatterField = "key";
1720
+ customKeyInput.addEventListener("keydown", (e) => {
1721
+ if (e.key === "Escape") {
1722
+ e.preventDefault();
1723
+ settle(false);
1724
+ } else if (e.key === "Enter") {
1725
+ e.preventDefault();
1726
+ if (input.value.trim()) settle(true);
1727
+ else input.focus();
1728
+ }
1729
+ });
1730
+ valCol.insertBefore(customKeyInput, input);
1731
+ }
1732
+ input.placeholder = "Value or equation\u2026";
1733
+ } else if (customKeyInput) {
1734
+ customKeyInput.remove();
1735
+ customKeyInput = null;
1736
+ input.placeholder = "Value\u2026";
1737
+ }
1649
1738
  if (spec && (spec.kind === "effort" || spec.kind === "equipment") && referenceProvider) {
1650
1739
  attachSuggestions(input, view, root, "value", spec.path, [], () => settle(true), spec.kind);
1651
1740
  }
@@ -1666,6 +1755,7 @@ function createMetricDraftRow(view, root, referenceProvider, commit) {
1666
1755
  });
1667
1756
  input.addEventListener("blur", () => queueMicrotask(() => {
1668
1757
  if (!input.isConnected) return;
1758
+ if (row.contains(document.activeElement)) return;
1669
1759
  settle(input.value.trim().length > 0);
1670
1760
  }));
1671
1761
  select.addEventListener("keydown", (e) => {
@@ -1676,6 +1766,82 @@ function createMetricDraftRow(view, root, referenceProvider, commit) {
1676
1766
  });
1677
1767
  return row;
1678
1768
  }
1769
+ function createSummaryDraftRow(view, commit) {
1770
+ const row = document.createElement("div");
1771
+ row.className = "flex items-center min-h-[34px] border-b border-dashed border-border last:border-b-0 group hover:bg-muted/10 transition-colors max-[479px]:flex-col max-[479px]:items-stretch";
1772
+ row.setAttribute("data-frontmatter-summary-draft", "true");
1773
+ row.dataset.frontmatterRow = "summary";
1774
+ const keyCol = document.createElement("div");
1775
+ keyCol.className = "flex items-center gap-2 px-3 py-1.5 w-36 shrink-0 border-r border-dashed border-border text-muted-foreground max-[479px]:w-auto max-[479px]:border-r-0 max-[479px]:border-b";
1776
+ keyCol.appendChild(createListIcon());
1777
+ const keyInput = document.createElement("input");
1778
+ keyInput.className = "w-full rounded-md border border-field-border bg-background/60 px-2 py-1 text-xs text-foreground outline-none placeholder:text-field-placeholder focus:border-ring focus:ring-1 focus:ring-ring";
1779
+ keyInput.placeholder = "Summary key\u2026";
1780
+ keyInput.setAttribute("aria-label", "Summary key");
1781
+ keyInput.dataset.frontmatterField = "key";
1782
+ keyInput.setAttribute(
1783
+ "aria-describedby",
1784
+ appendFieldHelper(keyCol, "Summary metric name. Press Enter to move to the equation.").id
1785
+ );
1786
+ keyCol.appendChild(keyInput);
1787
+ row.appendChild(keyCol);
1788
+ const valCol = document.createElement("div");
1789
+ valCol.className = "flex items-center flex-wrap gap-1.5 px-3 py-1.5 flex-1 min-w-0";
1790
+ const valInput = document.createElement("input");
1791
+ valInput.className = "flex-1 min-w-[160px] rounded-md border border-field-border bg-background/60 px-2 py-1 text-xs text-foreground outline-none placeholder:text-field-placeholder focus:border-ring focus:ring-1 focus:ring-ring";
1792
+ valInput.placeholder = "Equation (e.g. sum(work))\u2026";
1793
+ valInput.setAttribute("aria-label", "Summary equation");
1794
+ valInput.dataset.frontmatterField = "value";
1795
+ valInput.setAttribute(
1796
+ "aria-describedby",
1797
+ appendFieldHelper(valCol, "Summary equation. Press Enter to apply.").id
1798
+ );
1799
+ valCol.appendChild(valInput);
1800
+ row.appendChild(valCol);
1801
+ let settled = false;
1802
+ const settle = (doCommit) => {
1803
+ if (settled) return;
1804
+ settled = true;
1805
+ const rawKey = (keyInput.value.trim() || "summary").replace(/^summaries\./i, "");
1806
+ const rawExpr = valInput.value.trim();
1807
+ if (doCommit && rawExpr) {
1808
+ commit([`summaries.${rawKey}`], rawExpr);
1809
+ }
1810
+ row.remove();
1811
+ view.requestMeasure();
1812
+ };
1813
+ keyInput.addEventListener("keydown", (e) => {
1814
+ if (e.key === "Escape") {
1815
+ e.preventDefault();
1816
+ settle(false);
1817
+ return;
1818
+ }
1819
+ if (e.key === "Enter") {
1820
+ e.preventDefault();
1821
+ if (valInput.value.trim()) settle(true);
1822
+ else valInput.focus();
1823
+ }
1824
+ });
1825
+ valInput.addEventListener("keydown", (e) => {
1826
+ if (e.key === "Escape") {
1827
+ e.preventDefault();
1828
+ settle(false);
1829
+ return;
1830
+ }
1831
+ if (e.key === "Enter") {
1832
+ e.preventDefault();
1833
+ settle(true);
1834
+ }
1835
+ });
1836
+ const onBlur = () => queueMicrotask(() => {
1837
+ if (!row.isConnected) return;
1838
+ if (row.contains(document.activeElement)) return;
1839
+ settle(valInput.value.trim().length > 0);
1840
+ });
1841
+ keyInput.addEventListener("blur", onBlur);
1842
+ valInput.addEventListener("blur", onBlur);
1843
+ return row;
1844
+ }
1679
1845
  function createTagIcon() {
1680
1846
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
1681
1847
  svg.setAttribute("viewBox", "0 0 24 24");
@@ -1784,18 +1950,88 @@ function createCloseIcon(size = "md") {
1784
1950
  svg.appendChild(l2);
1785
1951
  return svg;
1786
1952
  }
1953
+ function isAliasProperty(key) {
1954
+ return ["alias", "aliases"].includes(key.trim().toLowerCase());
1955
+ }
1956
+ function createSummaryPill(value, hash2) {
1957
+ const pill = document.createElement("span");
1958
+ pill.className = "inline-flex max-w-full items-center rounded-md border border-border/60 bg-muted/40 px-2 py-0.5 text-xs text-foreground";
1959
+ const label = document.createElement("span");
1960
+ label.className = "truncate";
1961
+ label.textContent = hash2 ? `#${value}` : value;
1962
+ pill.appendChild(label);
1963
+ return pill;
1964
+ }
1965
+ function createFrontmatterSummary(entries, onShowDetails) {
1966
+ const summary = document.createElement("div");
1967
+ summary.dataset.frontmatterSummary = "true";
1968
+ summary.className = "divide-y divide-border/60";
1969
+ const groups = [
1970
+ { label: "Tags", entries: entries.filter(([key]) => TAG_CATEGORY_SUMMARY_KEYS[key.trim().toLowerCase()] === true), hash: true },
1971
+ { label: "Aliases", entries: entries.filter(([key]) => isAliasProperty(key)), hash: false }
1972
+ ];
1973
+ for (const group of groups) {
1974
+ const values2 = group.entries.flatMap(([, value]) => parseTagList(value));
1975
+ if (values2.length === 0) continue;
1976
+ const row = document.createElement("div");
1977
+ row.className = "flex flex-wrap items-center gap-1.5 px-3 py-2";
1978
+ const label = document.createElement("span");
1979
+ label.className = "mr-1 w-14 shrink-0 text-[11px] font-medium text-muted-foreground";
1980
+ label.textContent = group.label;
1981
+ row.appendChild(label);
1982
+ for (const value of values2) row.appendChild(createSummaryPill(value, group.hash));
1983
+ summary.appendChild(row);
1984
+ }
1985
+ const hidden = entries.filter(([key]) => TAG_CATEGORY_SUMMARY_KEYS[key.trim().toLowerCase()] !== true && !isAliasProperty(key));
1986
+ const metricCount = hidden.filter(([key]) => {
1987
+ const k = key.trim().toLowerCase();
1988
+ return k.startsWith("metrics.") || k === "metrics";
1989
+ }).length;
1990
+ const summaryCount = hidden.filter(([key]) => {
1991
+ const k = key.trim().toLowerCase();
1992
+ return k.startsWith("summaries.") || k === "summaries";
1993
+ }).length;
1994
+ const details = document.createElement("button");
1995
+ details.type = "button";
1996
+ details.setAttribute("aria-label", "Show property details");
1997
+ details.setAttribute("aria-expanded", "false");
1998
+ details.className = "flex w-full flex-wrap items-center gap-x-2 gap-y-1 px-3 py-2 text-left text-[11px] text-muted-foreground transition-colors hover:bg-muted/20 hover:text-foreground cursor-pointer";
1999
+ const count2 = document.createElement("span");
2000
+ count2.textContent = `${hidden.length} more ${hidden.length === 1 ? "property" : "properties"}`;
2001
+ details.appendChild(count2);
2002
+ if (metricCount > 0) {
2003
+ const metrics = document.createElement("span");
2004
+ metrics.textContent = `\xB7 ${metricCount} custom ${metricCount === 1 ? "metric" : "metrics"}`;
2005
+ details.appendChild(metrics);
2006
+ }
2007
+ if (summaryCount > 0) {
2008
+ const summaries = document.createElement("span");
2009
+ summaries.textContent = `\xB7 ${summaryCount} ${summaryCount === 1 ? "summary" : "summaries"}`;
2010
+ details.appendChild(summaries);
2011
+ }
2012
+ const disclosure = document.createElement("span");
2013
+ disclosure.className = "ml-auto";
2014
+ disclosure.setAttribute("aria-hidden", "true");
2015
+ disclosure.textContent = "\u25BE";
2016
+ details.appendChild(disclosure);
2017
+ details.addEventListener("click", onShowDetails);
2018
+ summary.appendChild(details);
2019
+ return summary;
2020
+ }
1787
2021
  var DefaultFrontmatterWidget = class extends view.WidgetType {
1788
2022
  sectionId;
1789
2023
  rawContent;
1790
2024
  readOnly;
1791
- constructor(sectionId, rawContent, readOnly = false) {
2025
+ detailed;
2026
+ constructor(sectionId, rawContent, readOnly = false, detailed = false) {
1792
2027
  super();
1793
2028
  this.sectionId = sectionId;
1794
2029
  this.rawContent = rawContent ?? "";
1795
2030
  this.readOnly = readOnly;
2031
+ this.detailed = detailed;
1796
2032
  }
1797
2033
  eq(other) {
1798
- return this.sectionId === other.sectionId && this.rawContent === other.rawContent && this.readOnly === other.readOnly;
2034
+ return this.sectionId === other.sectionId && this.rawContent === other.rawContent && this.readOnly === other.readOnly && this.detailed === other.detailed;
1799
2035
  }
1800
2036
  ignoreEvent() {
1801
2037
  return true;
@@ -1805,10 +2041,25 @@ var DefaultFrontmatterWidget = class extends view.WidgetType {
1805
2041
  widgetCleanup.set(root, []);
1806
2042
  root.className = "cm-frontmatter-preview py-2 font-sans select-none";
1807
2043
  const heading = document.createElement("div");
1808
- heading.className = "text-xs font-medium text-muted-foreground mb-1.5 px-0.5";
1809
- heading.textContent = "Properties";
2044
+ heading.className = "mb-1.5 flex items-center px-0.5 text-xs font-medium text-muted-foreground";
2045
+ const headingLabel = document.createElement("span");
2046
+ headingLabel.textContent = "Properties";
2047
+ heading.appendChild(headingLabel);
2048
+ if (this.detailed) {
2049
+ const summaryButton = document.createElement("button");
2050
+ summaryButton.type = "button";
2051
+ summaryButton.setAttribute("aria-label", "Show property summary");
2052
+ summaryButton.setAttribute("aria-expanded", "true");
2053
+ summaryButton.className = "ml-auto px-1 py-0.5 text-[11px] font-normal hover:text-foreground cursor-pointer";
2054
+ summaryButton.textContent = "Hide details \u25B4";
2055
+ summaryButton.addEventListener("click", () => {
2056
+ view.dispatch({ effects: setFrontmatterDetailed.of(false) });
2057
+ });
2058
+ heading.appendChild(summaryButton);
2059
+ }
1810
2060
  root.appendChild(heading);
1811
2061
  const box = document.createElement("div");
2062
+ box.dataset.frontmatterProperties = "true";
1812
2063
  box.className = "rounded-lg border border-border/80 bg-background/50 text-xs shadow-xs";
1813
2064
  root.appendChild(box);
1814
2065
  const body = this.rawContent.replace(/^---\r?\n?/, "").replace(/\r?\n?---\r?$/, "");
@@ -1816,329 +2067,335 @@ var DefaultFrontmatterWidget = class extends view.WidgetType {
1816
2067
  const entries = Object.entries(meta);
1817
2068
  const docKind = detectDefinitionKind(meta);
1818
2069
  const referenceProvider = view.state.facet(frontmatterReferenceIndex)?.() ?? null;
1819
- const base = docKind === "effort" ? readBaseAttributes(body) : {};
1820
- box.appendChild(createColumnHeader());
1821
- if (entries.length === 0) {
1822
- const emptyRow = document.createElement("div");
1823
- emptyRow.className = "px-3 py-2 text-xs text-muted-foreground/60 italic";
1824
- emptyRow.textContent = "No properties";
1825
- box.appendChild(emptyRow);
1826
- }
1827
- for (const [key, value] of entries) {
1828
- const isTags = isTagProperty(key);
1829
- const valueKind = referenceValueKind(key, docKind);
1830
- const isReferenceList = (isReferenceKey(key, docKind) || key === "aliases") && Array.isArray(value);
1831
- const nested = value === "" && ownsNestedBlock(body, key);
1832
- const row = document.createElement("div");
1833
- row.className = "flex items-center min-h-[34px] border-b border-dashed border-border last:border-b-0 group hover:bg-muted/10 transition-colors max-[479px]:flex-col max-[479px]:items-stretch";
1834
- row.dataset.frontmatterRow = key;
1835
- box.appendChild(row);
1836
- const keyCol = document.createElement("div");
1837
- keyCol.className = "flex items-center gap-2 px-3 py-1.5 w-36 shrink-0 border-r border-dashed border-border text-muted-foreground max-[479px]:w-auto max-[479px]:border-r-0 max-[479px]:border-b";
1838
- row.appendChild(keyCol);
1839
- keyCol.appendChild(isTags || isReferenceList ? createTagIcon() : createListIcon());
1840
- if (this.readOnly || nested) {
1841
- const keyLabel = document.createElement("span");
1842
- keyLabel.className = "truncate font-medium text-foreground/90";
1843
- keyLabel.textContent = key;
1844
- keyCol.appendChild(keyLabel);
1845
- } else {
1846
- const keyInput = document.createElement("input");
1847
- keyInput.className = "w-full bg-transparent text-xs text-foreground/90 font-medium outline-none truncate hover:bg-muted/30 focus:bg-background focus:ring-1 focus:ring-ring rounded px-1 -mx-1";
1848
- keyInput.value = key;
1849
- keyInput.spellcheck = false;
1850
- keyInput.setAttribute("aria-label", `Property key: ${key}`);
1851
- keyInput.dataset.frontmatterField = "key";
1852
- keyInput.dataset.frontmatterProperty = key;
1853
- keyInput.setAttribute(
1854
- "aria-describedby",
1855
- appendFieldHelper(
1856
- keyCol,
1857
- "Property name. Press Enter to apply. Arrow keys browse suggested property names."
1858
- ).id
1859
- );
1860
- let currentKey = key;
1861
- const commitKey = () => {
1862
- const nextKey = keyInput.value.trim();
1863
- if (!nextKey || nextKey === currentKey) {
1864
- keyInput.value = currentKey;
1865
- return;
1866
- }
1867
- if (!/^[A-Za-z_][\w.-]*$/.test(nextKey) || nextKey in meta) {
1868
- keyInput.value = currentKey;
1869
- return;
1870
- }
1871
- renamePropertyInView(view, this.sectionId, currentKey, nextKey, value);
1872
- delete meta[currentKey];
1873
- meta[nextKey] = value;
1874
- currentKey = nextKey;
1875
- };
1876
- const commitKeyIfAttached = () => queueMicrotask(() => {
1877
- if (keyInput.isConnected) commitKey();
1878
- });
1879
- keyInput.addEventListener("change", commitKeyIfAttached);
1880
- keyCol.appendChild(keyInput);
1881
- attachSuggestions(keyInput, view, root, "key", key, entries.map(([name]) => name), commitKey);
1882
- keyInput.addEventListener("blur", commitKeyIfAttached);
1883
- keyInput.addEventListener("keydown", (e) => {
1884
- if (e.defaultPrevented) return;
1885
- if (e.key === "Enter") {
1886
- e.preventDefault();
1887
- keyInput.blur();
1888
- }
1889
- });
1890
- const focus = pendingFocus.get(view);
1891
- if (focus?.kind === "key" && focus.key === key) {
1892
- setTimeout(() => {
1893
- if (!keyInput.isConnected) return;
1894
- keyInput.focus();
1895
- keyInput.select();
1896
- }, 0);
1897
- pendingFocus.delete(view);
1898
- }
1899
- }
1900
- const valCol = document.createElement("div");
1901
- valCol.className = "flex items-center flex-wrap gap-1.5 px-3 py-1.5 flex-1 min-w-0";
1902
- row.appendChild(valCol);
1903
- const commitList = (next) => {
1904
- meta[key] = next;
1905
- patchPropertyInView(view, this.sectionId, [key], next.length > 0 ? next : "");
1906
- };
1907
- const refMatch = (token) => {
1908
- if (!referenceProvider) return null;
1909
- return valueKind === "effort" ? referenceProvider.resolveEffort(token) : valueKind === "equipment" ? referenceProvider.resolveEquipment(token) : null;
1910
- };
1911
- if (isTags || isReferenceList) {
1912
- const chips = parseTagList(value);
1913
- const chipIsReference = valueKind !== "tags";
1914
- for (let idx = 0; idx < chips.length; idx++) {
1915
- const chip = chips[idx];
1916
- const match = chipIsReference ? refMatch(chip) : null;
1917
- const pill = document.createElement("span");
1918
- pill.className = match ? "inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-medium bg-muted/60 text-foreground border border-border/60" : chipIsReference ? "inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-medium bg-destructive/10 text-destructive border border-destructive/30" : "inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-medium bg-amber-500/10 text-amber-800 dark:text-amber-300 border border-amber-500/20 dark:border-amber-500/30";
1919
- const pillText = document.createElement("span");
1920
- pillText.textContent = match ? match.label : chip;
1921
- pill.appendChild(pillText);
1922
- if (match && referenceProvider) {
1923
- const openLink = document.createElement("a");
1924
- openLink.href = referenceProvider.openHref(valueKind, match.slug);
1925
- openLink.title = `Open ${match.label} page`;
1926
- openLink.setAttribute("aria-label", `Open ${match.label} page`);
1927
- openLink.className = "inline-flex items-center justify-center text-muted-foreground/60 hover:text-foreground cursor-pointer";
1928
- openLink.appendChild(createExternalIcon());
1929
- pill.appendChild(openLink);
1930
- }
1931
- if (!this.readOnly) {
1932
- const rmBtn = document.createElement("button");
1933
- rmBtn.type = "button";
1934
- rmBtn.setAttribute("aria-label", isTags ? `Remove tag ${chip}` : `Remove ${chip}`);
1935
- rmBtn.className = "inline-flex items-center justify-center text-muted-foreground/60 hover:text-foreground dark:hover:text-foreground cursor-pointer";
1936
- rmBtn.appendChild(createCloseIcon("sm"));
1937
- rmBtn.addEventListener("click", (e) => {
1938
- e.preventDefault();
1939
- e.stopPropagation();
1940
- commitList(chips.filter((_, i) => i !== idx));
1941
- });
1942
- pill.appendChild(rmBtn);
1943
- }
1944
- valCol.appendChild(pill);
1945
- }
1946
- if (!this.readOnly) {
1947
- const chipInput = document.createElement("input");
1948
- chipInput.className = "flex-none w-44 max-w-full rounded-md border border-field-border bg-background/60 px-2 py-1 text-xs text-foreground outline-none placeholder:text-field-placeholder focus:border-ring focus:ring-1 focus:ring-ring";
1949
- chipInput.placeholder = chips.length === 0 ? isReferenceList ? `Add ${valueKind === "effort" ? "effort" : "equipment"}\u2026` : "Add tags\u2026" : isTags ? "Add tag\u2026" : "Add\u2026";
1950
- chipInput.setAttribute("aria-label", `Value for ${key}`);
1951
- chipInput.dataset.frontmatterField = "value";
1952
- chipInput.dataset.frontmatterProperty = key;
1953
- chipInput.setAttribute(
2070
+ const base = docKind === "effort" ? readBaseAttributes(body, meta) : {};
2071
+ if (!this.detailed) {
2072
+ box.appendChild(createFrontmatterSummary(entries, () => {
2073
+ view.dispatch({ effects: setFrontmatterDetailed.of(true) });
2074
+ }));
2075
+ } else {
2076
+ box.appendChild(createColumnHeader());
2077
+ if (entries.length === 0) {
2078
+ const emptyRow = document.createElement("div");
2079
+ emptyRow.className = "px-3 py-2 text-xs text-muted-foreground/60 italic";
2080
+ emptyRow.textContent = "No properties";
2081
+ box.appendChild(emptyRow);
2082
+ }
2083
+ for (const [key, value] of entries) {
2084
+ const isTags = isTagProperty(key);
2085
+ const valueKind = referenceValueKind(key, docKind);
2086
+ const isReferenceList = (isReferenceKey(key, docKind) || key === "aliases") && Array.isArray(value);
2087
+ const nested = value === "" && ownsNestedBlock(body, key);
2088
+ const row = document.createElement("div");
2089
+ row.className = "flex items-center min-h-[34px] border-b border-dashed border-border last:border-b-0 group hover:bg-muted/10 transition-colors max-[479px]:flex-col max-[479px]:items-stretch";
2090
+ row.dataset.frontmatterRow = key;
2091
+ box.appendChild(row);
2092
+ const keyCol = document.createElement("div");
2093
+ keyCol.className = "flex items-center gap-2 px-3 py-1.5 w-36 shrink-0 border-r border-dashed border-border text-muted-foreground max-[479px]:w-auto max-[479px]:border-r-0 max-[479px]:border-b";
2094
+ row.appendChild(keyCol);
2095
+ keyCol.appendChild(isTags || isReferenceList ? createTagIcon() : createListIcon());
2096
+ if (this.readOnly || nested) {
2097
+ const keyLabel = document.createElement("span");
2098
+ keyLabel.className = "truncate font-medium text-foreground/90";
2099
+ keyLabel.textContent = key;
2100
+ keyCol.appendChild(keyLabel);
2101
+ } else {
2102
+ const keyInput = document.createElement("input");
2103
+ keyInput.className = "w-full bg-transparent text-xs text-foreground/90 font-medium outline-none truncate hover:bg-muted/30 focus:bg-background focus:ring-1 focus:ring-ring rounded px-1 -mx-1";
2104
+ keyInput.value = key;
2105
+ keyInput.spellcheck = false;
2106
+ keyInput.setAttribute("aria-label", `Property key: ${key}`);
2107
+ keyInput.dataset.frontmatterField = "key";
2108
+ keyInput.dataset.frontmatterProperty = key;
2109
+ keyInput.setAttribute(
1954
2110
  "aria-describedby",
1955
2111
  appendFieldHelper(
1956
- valCol,
1957
- "Press Enter or comma to add a value. Arrow keys browse the suggestions."
2112
+ keyCol,
2113
+ "Property name. Press Enter to apply. Arrow keys browse suggested property names."
1958
2114
  ).id
1959
2115
  );
1960
- const commitNewChip = () => {
1961
- const val = chipInput.value.trim().replace(/^,|,$/g, "");
1962
- if (val && !chips.some((chip) => chip.toLowerCase() === val.toLowerCase())) {
1963
- chipInput.value = "";
1964
- pendingFocus.set(view, { kind: "tag", key });
1965
- commitList([...chips, val]);
2116
+ let currentKey = key;
2117
+ const commitKey = () => {
2118
+ const nextKey = keyInput.value.trim();
2119
+ if (!nextKey || nextKey === currentKey) {
2120
+ keyInput.value = currentKey;
2121
+ return;
2122
+ }
2123
+ if (!/^[A-Za-z_][\w.-]*$/.test(nextKey) || nextKey in meta) {
2124
+ keyInput.value = currentKey;
2125
+ return;
1966
2126
  }
2127
+ renamePropertyInView(view, this.sectionId, currentKey, nextKey, value);
2128
+ delete meta[currentKey];
2129
+ meta[nextKey] = value;
2130
+ currentKey = nextKey;
1967
2131
  };
1968
- valCol.appendChild(chipInput);
1969
- attachSuggestions(chipInput, view, root, "value", key, chips, commitNewChip, valueKind);
1970
- chipInput.addEventListener("keydown", (e) => {
2132
+ const commitKeyIfAttached = () => queueMicrotask(() => {
2133
+ if (keyInput.isConnected) commitKey();
2134
+ });
2135
+ keyInput.addEventListener("change", commitKeyIfAttached);
2136
+ keyCol.appendChild(keyInput);
2137
+ attachSuggestions(keyInput, view, root, "key", key, entries.map(([name]) => name), commitKey);
2138
+ keyInput.addEventListener("blur", commitKeyIfAttached);
2139
+ keyInput.addEventListener("keydown", (e) => {
1971
2140
  if (e.defaultPrevented) return;
1972
- if (e.key === "Enter" || e.key === ",") {
1973
- e.preventDefault();
1974
- commitNewChip();
1975
- } else if (e.key === "Backspace" && !chipInput.value && chips.length > 0) {
2141
+ if (e.key === "Enter") {
1976
2142
  e.preventDefault();
1977
- pendingFocus.set(view, { kind: "tag", key });
1978
- commitList(chips.slice(0, -1));
2143
+ keyInput.blur();
1979
2144
  }
1980
2145
  });
1981
- chipInput.addEventListener("blur", () => queueMicrotask(() => {
1982
- if (chipInput.isConnected && chipInput.value.trim()) commitNewChip();
1983
- }));
1984
2146
  const focus = pendingFocus.get(view);
1985
- if (focus?.kind === "tag" && focus.key === key) {
2147
+ if (focus?.kind === "key" && focus.key === key) {
1986
2148
  setTimeout(() => {
1987
- if (chipInput.isConnected) chipInput.focus();
2149
+ if (!keyInput.isConnected) return;
2150
+ keyInput.focus();
2151
+ keyInput.select();
1988
2152
  }, 0);
1989
2153
  pendingFocus.delete(view);
1990
2154
  }
1991
- if (chipIsReference && referenceProvider) {
1992
- const unknown = chips.filter((chip) => !refMatch(chip));
1993
- if (unknown.length > 0) {
1994
- const diag = document.createElement("div");
1995
- diag.setAttribute("data-testid", "editor-ref-unknown");
1996
- diag.className = "w-full basis-full flex flex-wrap items-center gap-2 text-[10px] text-destructive";
1997
- const label = document.createElement("span");
1998
- label.textContent = `Unknown ${valueKind === "effort" ? "effort" : "equipment"}: ${unknown.join(", ")}`;
1999
- diag.appendChild(label);
2000
- const selectBtn = document.createElement("button");
2001
- selectBtn.type = "button";
2002
- selectBtn.textContent = "Select existing";
2003
- selectBtn.className = "underline hover:text-foreground cursor-pointer";
2004
- selectBtn.addEventListener("click", (e) => {
2005
- e.preventDefault();
2006
- chipInput.focus();
2007
- });
2008
- diag.appendChild(selectBtn);
2009
- const createLink = document.createElement("a");
2010
- createLink.href = referenceProvider.createHref(valueKind);
2011
- createLink.textContent = "Create page";
2012
- createLink.className = "underline hover:text-foreground";
2013
- diag.appendChild(createLink);
2014
- valCol.appendChild(diag);
2015
- }
2016
- }
2017
2155
  }
2018
- } else if (nested) {
2019
- const valSpan = document.createElement("span");
2020
- valSpan.className = "text-xs text-muted-foreground/60 italic py-0.5 truncate";
2021
- valSpan.textContent = "Nested block \u2014 edit in YAML";
2022
- valCol.appendChild(valSpan);
2023
- } else {
2024
- const strVal = Array.isArray(value) ? value.join(", ") : String(value ?? "");
2025
- if (this.readOnly) {
2026
- const valSpan = document.createElement("span");
2027
- valSpan.className = "text-xs text-foreground py-0.5 truncate";
2028
- valSpan.textContent = strVal;
2029
- valCol.appendChild(valSpan);
2030
- } else {
2031
- const valInput = document.createElement("input");
2032
- valInput.className = "w-full rounded-md border border-field-border bg-background/60 px-2 py-1 text-xs text-foreground outline-none placeholder:text-field-placeholder focus:border-ring focus:ring-1 focus:ring-ring transition-colors";
2033
- valInput.value = strVal;
2034
- valInput.spellcheck = false;
2035
- valInput.setAttribute("aria-label", `Value for ${key}`);
2036
- valInput.dataset.frontmatterField = "value";
2037
- valInput.dataset.frontmatterProperty = key;
2038
- valInput.setAttribute(
2039
- "aria-describedby",
2040
- appendFieldHelper(valCol, "Property value. Press Enter to apply.").id
2041
- );
2042
- let committedVal = strVal;
2043
- const commitVal = () => {
2044
- const cur = valInput.value.trim();
2045
- if (cur !== committedVal) {
2046
- committedVal = cur;
2047
- const num = Number(cur);
2048
- const next = cur !== "" && !isNaN(num) ? num : cur;
2049
- meta[key] = next;
2050
- patchPropertyInView(view, this.sectionId, [key], next);
2051
- }
2052
- };
2053
- const commitValIfAttached = () => queueMicrotask(() => {
2054
- if (valInput.isConnected) commitVal();
2055
- });
2056
- valInput.addEventListener("change", commitValIfAttached);
2057
- valCol.appendChild(valInput);
2058
- attachSuggestions(
2059
- valInput,
2060
- view,
2061
- root,
2062
- "value",
2063
- key,
2064
- [strVal],
2065
- commitVal,
2066
- key.trim().toLowerCase() === "metrics.effort" ? "effort" : "tags"
2067
- );
2068
- valInput.addEventListener("blur", commitValIfAttached);
2069
- valInput.addEventListener("keydown", (e) => {
2070
- if (e.defaultPrevented) return;
2071
- if (e.key === "Enter") {
2072
- e.preventDefault();
2073
- valInput.blur();
2074
- }
2075
- });
2076
- if (key.trim().toLowerCase() === "metrics.effort" && referenceProvider) {
2077
- const match = referenceProvider.resolveEffort(strVal);
2078
- if (match) {
2079
- const pill = document.createElement("span");
2080
- pill.className = "inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-medium bg-muted/60 text-foreground border border-border/60 shrink-0";
2081
- const pillText = document.createElement("span");
2082
- pillText.textContent = match.label;
2083
- pill.appendChild(pillText);
2156
+ const valCol = document.createElement("div");
2157
+ valCol.className = "flex items-center flex-wrap gap-1.5 px-3 py-1.5 flex-1 min-w-0";
2158
+ row.appendChild(valCol);
2159
+ const commitList = (next) => {
2160
+ meta[key] = next;
2161
+ patchPropertyInView(view, this.sectionId, [key], next.length > 0 ? next : "");
2162
+ };
2163
+ const refMatch = (token) => {
2164
+ if (!referenceProvider) return null;
2165
+ return valueKind === "effort" ? referenceProvider.resolveEffort(token) : valueKind === "equipment" ? referenceProvider.resolveEquipment(token) : null;
2166
+ };
2167
+ if (isTags || isReferenceList) {
2168
+ const chips = parseTagList(value);
2169
+ const chipIsReference = valueKind !== "tags";
2170
+ for (let idx = 0; idx < chips.length; idx++) {
2171
+ const chip = chips[idx];
2172
+ const match = chipIsReference ? refMatch(chip) : null;
2173
+ const pill = document.createElement("span");
2174
+ pill.className = match ? "inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-medium bg-muted/60 text-foreground border border-border/60" : chipIsReference ? "inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-medium bg-destructive/10 text-destructive border border-destructive/30" : "inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-medium bg-amber-500/10 text-amber-800 dark:text-amber-300 border border-amber-500/20 dark:border-amber-500/30";
2175
+ const pillText = document.createElement("span");
2176
+ pillText.textContent = match ? match.label : chip;
2177
+ pill.appendChild(pillText);
2178
+ if (match && referenceProvider) {
2084
2179
  const openLink = document.createElement("a");
2085
- openLink.href = referenceProvider.openHref("effort", match.slug);
2180
+ openLink.href = referenceProvider.openHref(valueKind, match.slug);
2086
2181
  openLink.title = `Open ${match.label} page`;
2087
2182
  openLink.setAttribute("aria-label", `Open ${match.label} page`);
2088
2183
  openLink.className = "inline-flex items-center justify-center text-muted-foreground/60 hover:text-foreground cursor-pointer";
2089
2184
  openLink.appendChild(createExternalIcon());
2090
2185
  pill.appendChild(openLink);
2091
- valCol.insertBefore(pill, valInput);
2092
- valInput.className = "flex-1 min-w-[70px] rounded-md border border-field-border bg-background/60 px-2 py-1 text-xs text-foreground outline-none placeholder:text-field-placeholder focus:border-ring focus:ring-1 focus:ring-ring transition-colors";
2093
2186
  }
2094
- const diag = renderEffortValueDiagnostics(
2095
- referenceProvider,
2096
- strVal,
2097
- (slug) => {
2098
- meta[key] = slug;
2099
- patchPropertyInView(view, this.sectionId, [key], slug);
2187
+ if (!this.readOnly) {
2188
+ const rmBtn = document.createElement("button");
2189
+ rmBtn.type = "button";
2190
+ rmBtn.setAttribute("aria-label", isTags ? `Remove tag ${chip}` : `Remove ${chip}`);
2191
+ rmBtn.className = "inline-flex items-center justify-center text-muted-foreground/60 hover:text-foreground dark:hover:text-foreground cursor-pointer";
2192
+ rmBtn.appendChild(createCloseIcon("sm"));
2193
+ rmBtn.addEventListener("click", (e) => {
2194
+ e.preventDefault();
2195
+ e.stopPropagation();
2196
+ commitList(chips.filter((_, i) => i !== idx));
2197
+ });
2198
+ pill.appendChild(rmBtn);
2199
+ }
2200
+ valCol.appendChild(pill);
2201
+ }
2202
+ if (!this.readOnly) {
2203
+ const chipInput = document.createElement("input");
2204
+ chipInput.className = "flex-none w-44 max-w-full rounded-md border border-field-border bg-background/60 px-2 py-1 text-xs text-foreground outline-none placeholder:text-field-placeholder focus:border-ring focus:ring-1 focus:ring-ring";
2205
+ chipInput.placeholder = chips.length === 0 ? isReferenceList ? `Add ${valueKind === "effort" ? "effort" : "equipment"}\u2026` : "Add tags\u2026" : isTags ? "Add tag\u2026" : "Add\u2026";
2206
+ chipInput.setAttribute("aria-label", `Value for ${key}`);
2207
+ chipInput.dataset.frontmatterField = "value";
2208
+ chipInput.dataset.frontmatterProperty = key;
2209
+ chipInput.setAttribute(
2210
+ "aria-describedby",
2211
+ appendFieldHelper(
2212
+ valCol,
2213
+ "Press Enter or comma to add a value. Arrow keys browse the suggestions."
2214
+ ).id
2215
+ );
2216
+ const commitNewChip = () => {
2217
+ const val = chipInput.value.trim().replace(/^,|,$/g, "");
2218
+ if (val && !chips.some((chip) => chip.toLowerCase() === val.toLowerCase())) {
2219
+ chipInput.value = "";
2220
+ pendingFocus.set(view, { kind: "tag", key });
2221
+ commitList([...chips, val]);
2222
+ }
2223
+ };
2224
+ valCol.appendChild(chipInput);
2225
+ attachSuggestions(chipInput, view, root, "value", key, chips, commitNewChip, valueKind);
2226
+ chipInput.addEventListener("keydown", (e) => {
2227
+ if (e.defaultPrevented) return;
2228
+ if (e.key === "Enter" || e.key === ",") {
2229
+ e.preventDefault();
2230
+ commitNewChip();
2231
+ } else if (e.key === "Backspace" && !chipInput.value && chips.length > 0) {
2232
+ e.preventDefault();
2233
+ pendingFocus.set(view, { kind: "tag", key });
2234
+ commitList(chips.slice(0, -1));
2100
2235
  }
2236
+ });
2237
+ chipInput.addEventListener("blur", () => queueMicrotask(() => {
2238
+ if (chipInput.isConnected && chipInput.value.trim()) commitNewChip();
2239
+ }));
2240
+ const focus = pendingFocus.get(view);
2241
+ if (focus?.kind === "tag" && focus.key === key) {
2242
+ setTimeout(() => {
2243
+ if (chipInput.isConnected) chipInput.focus();
2244
+ }, 0);
2245
+ pendingFocus.delete(view);
2246
+ }
2247
+ if (chipIsReference && referenceProvider) {
2248
+ const unknown = chips.filter((chip) => !refMatch(chip));
2249
+ if (unknown.length > 0) {
2250
+ const diag = document.createElement("div");
2251
+ diag.setAttribute("data-testid", "editor-ref-unknown");
2252
+ diag.className = "w-full basis-full flex flex-wrap items-center gap-2 text-[10px] text-destructive";
2253
+ const label = document.createElement("span");
2254
+ label.textContent = `Unknown ${valueKind === "effort" ? "effort" : "equipment"}: ${unknown.join(", ")}`;
2255
+ diag.appendChild(label);
2256
+ const selectBtn = document.createElement("button");
2257
+ selectBtn.type = "button";
2258
+ selectBtn.textContent = "Select existing";
2259
+ selectBtn.className = "underline hover:text-foreground cursor-pointer";
2260
+ selectBtn.addEventListener("click", (e) => {
2261
+ e.preventDefault();
2262
+ chipInput.focus();
2263
+ });
2264
+ diag.appendChild(selectBtn);
2265
+ const createLink = document.createElement("a");
2266
+ createLink.href = referenceProvider.createHref(valueKind);
2267
+ createLink.textContent = "Create page";
2268
+ createLink.className = "underline hover:text-foreground";
2269
+ diag.appendChild(createLink);
2270
+ valCol.appendChild(diag);
2271
+ }
2272
+ }
2273
+ }
2274
+ } else if (nested) {
2275
+ const valSpan = document.createElement("span");
2276
+ valSpan.className = "text-xs text-muted-foreground/60 italic py-0.5 truncate";
2277
+ valSpan.textContent = "Nested block \u2014 edit in YAML";
2278
+ valCol.appendChild(valSpan);
2279
+ } else {
2280
+ const strVal = Array.isArray(value) ? value.join(", ") : String(value ?? "");
2281
+ if (this.readOnly) {
2282
+ const valSpan = document.createElement("span");
2283
+ valSpan.className = "text-xs text-foreground py-0.5 truncate";
2284
+ valSpan.textContent = strVal;
2285
+ valCol.appendChild(valSpan);
2286
+ } else {
2287
+ const valInput = document.createElement("input");
2288
+ valInput.className = "w-full rounded-md border border-field-border bg-background/60 px-2 py-1 text-xs text-foreground outline-none placeholder:text-field-placeholder focus:border-ring focus:ring-1 focus:ring-ring transition-colors";
2289
+ valInput.value = strVal;
2290
+ valInput.spellcheck = false;
2291
+ valInput.setAttribute("aria-label", `Value for ${key}`);
2292
+ valInput.dataset.frontmatterField = "value";
2293
+ valInput.dataset.frontmatterProperty = key;
2294
+ valInput.setAttribute(
2295
+ "aria-describedby",
2296
+ appendFieldHelper(valCol, "Property value. Press Enter to apply.").id
2297
+ );
2298
+ let committedVal = strVal;
2299
+ const commitVal = () => {
2300
+ const cur = valInput.value.trim();
2301
+ if (cur !== committedVal) {
2302
+ committedVal = cur;
2303
+ const num = Number(cur);
2304
+ const next = cur !== "" && !isNaN(num) ? num : cur;
2305
+ meta[key] = next;
2306
+ patchPropertyInView(view, this.sectionId, [key], next);
2307
+ }
2308
+ };
2309
+ const commitValIfAttached = () => queueMicrotask(() => {
2310
+ if (valInput.isConnected) commitVal();
2311
+ });
2312
+ valInput.addEventListener("change", commitValIfAttached);
2313
+ valCol.appendChild(valInput);
2314
+ attachSuggestions(
2315
+ valInput,
2316
+ view,
2317
+ root,
2318
+ "value",
2319
+ key,
2320
+ [strVal],
2321
+ commitVal,
2322
+ key.trim().toLowerCase() === "metrics.effort" ? "effort" : "tags"
2101
2323
  );
2102
- if (diag) valCol.appendChild(diag);
2324
+ valInput.addEventListener("blur", commitValIfAttached);
2325
+ valInput.addEventListener("keydown", (e) => {
2326
+ if (e.defaultPrevented) return;
2327
+ if (e.key === "Enter") {
2328
+ e.preventDefault();
2329
+ valInput.blur();
2330
+ }
2331
+ });
2332
+ if (key.trim().toLowerCase() === "metrics.effort" && referenceProvider) {
2333
+ const match = referenceProvider.resolveEffort(strVal);
2334
+ if (match) {
2335
+ const pill = document.createElement("span");
2336
+ pill.className = "inline-flex items-center gap-1 px-2 py-0.5 rounded-md text-xs font-medium bg-muted/60 text-foreground border border-border/60 shrink-0";
2337
+ const pillText = document.createElement("span");
2338
+ pillText.textContent = match.label;
2339
+ pill.appendChild(pillText);
2340
+ const openLink = document.createElement("a");
2341
+ openLink.href = referenceProvider.openHref("effort", match.slug);
2342
+ openLink.title = `Open ${match.label} page`;
2343
+ openLink.setAttribute("aria-label", `Open ${match.label} page`);
2344
+ openLink.className = "inline-flex items-center justify-center text-muted-foreground/60 hover:text-foreground cursor-pointer";
2345
+ openLink.appendChild(createExternalIcon());
2346
+ pill.appendChild(openLink);
2347
+ valCol.insertBefore(pill, valInput);
2348
+ valInput.className = "flex-1 min-w-[70px] rounded-md border border-field-border bg-background/60 px-2 py-1 text-xs text-foreground outline-none placeholder:text-field-placeholder focus:border-ring focus:ring-1 focus:ring-ring transition-colors";
2349
+ }
2350
+ const diag = renderEffortValueDiagnostics(
2351
+ referenceProvider,
2352
+ strVal,
2353
+ (slug) => {
2354
+ meta[key] = slug;
2355
+ patchPropertyInView(view, this.sectionId, [key], slug);
2356
+ }
2357
+ );
2358
+ if (diag) valCol.appendChild(diag);
2359
+ }
2360
+ }
2361
+ if (/^https?:\/\/\S+$/i.test(strVal)) {
2362
+ const openLink = document.createElement("a");
2363
+ openLink.href = strVal;
2364
+ openLink.target = "_blank";
2365
+ openLink.rel = "noopener noreferrer";
2366
+ openLink.title = strVal;
2367
+ openLink.setAttribute("aria-label", `Open ${key} in new tab`);
2368
+ openLink.className = "shrink-0 inline-flex items-center justify-center text-muted-foreground/60 hover:text-foreground transition-colors cursor-pointer";
2369
+ openLink.appendChild(createExternalIcon());
2370
+ valCol.appendChild(openLink);
2103
2371
  }
2104
2372
  }
2105
- if (/^https?:\/\/\S+$/i.test(strVal)) {
2106
- const openLink = document.createElement("a");
2107
- openLink.href = strVal;
2108
- openLink.target = "_blank";
2109
- openLink.rel = "noopener noreferrer";
2110
- openLink.title = strVal;
2111
- openLink.setAttribute("aria-label", `Open ${key} in new tab`);
2112
- openLink.className = "shrink-0 inline-flex items-center justify-center text-muted-foreground/60 hover:text-foreground transition-colors cursor-pointer";
2113
- openLink.appendChild(createExternalIcon());
2114
- valCol.appendChild(openLink);
2373
+ if (!this.readOnly && !nested) {
2374
+ const actionsCol = document.createElement("div");
2375
+ actionsCol.className = "flex items-center pr-2";
2376
+ const delBtn = document.createElement("button");
2377
+ delBtn.type = "button";
2378
+ delBtn.setAttribute("aria-label", `Remove property ${key}`);
2379
+ delBtn.className = "opacity-0 group-hover:opacity-100 focus-visible:opacity-100 text-muted-foreground/70 hover:text-destructive p-1 rounded transition-opacity cursor-pointer";
2380
+ delBtn.appendChild(createCloseIcon("md"));
2381
+ delBtn.addEventListener("click", (e) => {
2382
+ e.preventDefault();
2383
+ e.stopPropagation();
2384
+ delete meta[key];
2385
+ patchPropertyInView(view, this.sectionId, [key], null);
2386
+ });
2387
+ actionsCol.appendChild(delBtn);
2388
+ row.appendChild(actionsCol);
2115
2389
  }
2116
2390
  }
2117
- if (!this.readOnly && !nested) {
2118
- const actionsCol = document.createElement("div");
2119
- actionsCol.className = "flex items-center pr-2";
2120
- const delBtn = document.createElement("button");
2121
- delBtn.type = "button";
2122
- delBtn.setAttribute("aria-label", `Remove property ${key}`);
2123
- delBtn.className = "opacity-0 group-hover:opacity-100 focus-visible:opacity-100 text-muted-foreground/70 hover:text-destructive p-1 rounded transition-opacity cursor-pointer";
2124
- delBtn.appendChild(createCloseIcon("md"));
2125
- delBtn.addEventListener("click", (e) => {
2126
- e.preventDefault();
2127
- e.stopPropagation();
2128
- delete meta[key];
2129
- patchPropertyInView(view, this.sectionId, [key], null);
2130
- });
2131
- actionsCol.appendChild(delBtn);
2132
- row.appendChild(actionsCol);
2391
+ if (docKind === "effort" && !this.readOnly) {
2392
+ box.appendChild(this.buildDefinitionRows(view, base));
2133
2393
  }
2134
2394
  }
2135
- if (docKind === "effort" && !this.readOnly) {
2136
- box.appendChild(this.buildDefinitionRows(view, base));
2137
- }
2138
2395
  if (!this.readOnly) {
2139
2396
  const existingTagKey = entries.find(([key]) => key.trim().toLowerCase() === "tags")?.[0] ?? entries.find(([key]) => isTagProperty(key))?.[0];
2140
2397
  const actionsRow = document.createElement("div");
2141
- actionsRow.className = "mt-2 flex items-center gap-3";
2398
+ actionsRow.className = "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1";
2142
2399
  actionsRow.dataset.frontmatterActions = "true";
2143
2400
  const addBtn = document.createElement("button");
2144
2401
  addBtn.type = "button";
@@ -2158,7 +2415,12 @@ var DefaultFrontmatterWidget = class extends view.WidgetType {
2158
2415
  newKey = `property_${count2}`;
2159
2416
  }
2160
2417
  pendingFocus.set(view, { kind: "key", key: newKey });
2161
- patchPropertyInView(view, this.sectionId, [newKey], "");
2418
+ patchPropertyInView(view, this.sectionId, [newKey], "", !this.detailed);
2419
+ const keyField = view.dom.querySelector(
2420
+ `[data-frontmatter-row="${newKey}"] [data-frontmatter-field="key"]`
2421
+ );
2422
+ keyField?.focus();
2423
+ keyField?.select();
2162
2424
  });
2163
2425
  actionsRow.appendChild(addBtn);
2164
2426
  {
@@ -2174,20 +2436,57 @@ var DefaultFrontmatterWidget = class extends view.WidgetType {
2174
2436
  addMetricBtn.addEventListener("click", (e) => {
2175
2437
  e.preventDefault();
2176
2438
  e.stopPropagation();
2177
- const existing = root.querySelector("[data-frontmatter-metric-draft] input");
2439
+ if (!this.detailed) {
2440
+ view.dispatch({ effects: setFrontmatterDetailed.of(true) });
2441
+ }
2442
+ const activeRoot = view.dom.querySelector(".cm-frontmatter-preview") ?? root;
2443
+ const activeBox = activeRoot.querySelector("[data-frontmatter-properties]") ?? box;
2444
+ const existing = activeRoot.querySelector("[data-frontmatter-metric-draft] input");
2178
2445
  if (existing) {
2179
2446
  existing.focus();
2180
2447
  return;
2181
2448
  }
2182
- const draftRow = createMetricDraftRow(view, root, referenceProvider, (path, value) => {
2449
+ const draftRow = createMetricDraftRow(view, activeRoot, referenceProvider, (path, value) => {
2183
2450
  patchPropertyInView(view, this.sectionId, path, value);
2184
2451
  });
2185
- box.appendChild(draftRow);
2452
+ activeBox.appendChild(draftRow);
2186
2453
  draftRow.querySelector("input")?.focus();
2187
2454
  view.requestMeasure();
2188
2455
  });
2189
2456
  actionsRow.appendChild(addMetricBtn);
2190
2457
  }
2458
+ {
2459
+ const addSummaryBtn = document.createElement("button");
2460
+ addSummaryBtn.type = "button";
2461
+ addSummaryBtn.setAttribute("aria-label", "Add summary");
2462
+ addSummaryBtn.setAttribute("data-testid", "editor-add-summary-note");
2463
+ addSummaryBtn.className = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground px-1 py-1 cursor-pointer transition-colors select-none";
2464
+ addSummaryBtn.appendChild(createPlusIcon());
2465
+ const addSummaryLabel = document.createElement("span");
2466
+ addSummaryLabel.textContent = "Add summary";
2467
+ addSummaryBtn.appendChild(addSummaryLabel);
2468
+ addSummaryBtn.addEventListener("click", (e) => {
2469
+ e.preventDefault();
2470
+ e.stopPropagation();
2471
+ if (!this.detailed) {
2472
+ view.dispatch({ effects: setFrontmatterDetailed.of(true) });
2473
+ }
2474
+ const activeRoot = view.dom.querySelector(".cm-frontmatter-preview") ?? root;
2475
+ const activeBox = activeRoot.querySelector("[data-frontmatter-properties]") ?? box;
2476
+ const existing = activeRoot.querySelector("[data-frontmatter-summary-draft] input");
2477
+ if (existing) {
2478
+ existing.focus();
2479
+ return;
2480
+ }
2481
+ const draftRow = createSummaryDraftRow(view, (path, value) => {
2482
+ patchPropertyInView(view, this.sectionId, path, value);
2483
+ });
2484
+ activeBox.appendChild(draftRow);
2485
+ draftRow.querySelector("input")?.focus();
2486
+ view.requestMeasure();
2487
+ });
2488
+ actionsRow.appendChild(addSummaryBtn);
2489
+ }
2191
2490
  {
2192
2491
  const addTagBtn = document.createElement("button");
2193
2492
  addTagBtn.type = "button";
@@ -2200,23 +2499,28 @@ var DefaultFrontmatterWidget = class extends view.WidgetType {
2200
2499
  addTagBtn.addEventListener("click", (e) => {
2201
2500
  e.preventDefault();
2202
2501
  e.stopPropagation();
2502
+ if (!this.detailed) {
2503
+ view.dispatch({ effects: setFrontmatterDetailed.of(true) });
2504
+ }
2505
+ const activeRoot = view.dom.querySelector(".cm-frontmatter-preview") ?? root;
2506
+ const activeBox = activeRoot.querySelector("[data-frontmatter-properties]") ?? box;
2203
2507
  if (existingTagKey) {
2204
- const input = Array.from(root.querySelectorAll(
2508
+ const input = Array.from(activeRoot.querySelectorAll(
2205
2509
  '[data-frontmatter-field="value"]'
2206
2510
  )).find((element) => element.dataset.frontmatterProperty === existingTagKey);
2207
2511
  input?.focus();
2208
2512
  return;
2209
2513
  }
2210
- const existingDraft = root.querySelector("[data-frontmatter-tag-draft] input");
2514
+ const existingDraft = activeRoot.querySelector("[data-frontmatter-tag-draft] input");
2211
2515
  if (existingDraft) {
2212
2516
  existingDraft.focus();
2213
2517
  return;
2214
2518
  }
2215
- const draftRow = createTagDraftRow(view, root, "category", (val) => {
2519
+ const draftRow = createTagDraftRow(view, activeRoot, "category", (val) => {
2216
2520
  pendingFocus.set(view, { kind: "tag", key: "category" });
2217
2521
  patchPropertyInView(view, this.sectionId, ["category"], [val]);
2218
2522
  });
2219
- box.appendChild(draftRow);
2523
+ activeBox.appendChild(draftRow);
2220
2524
  draftRow.querySelector("input")?.focus();
2221
2525
  view.requestMeasure();
2222
2526
  });
@@ -2237,7 +2541,11 @@ var DefaultFrontmatterWidget = class extends view.WidgetType {
2237
2541
  const { sections: currentSections } = view.state.field(sectionField);
2238
2542
  const section = currentSections.find((s) => s.id === this.sectionId && s.type === "frontmatter") || currentSections.find((s) => s.type === "frontmatter");
2239
2543
  if (!section) return;
2240
- view.dispatch({ selection: { anchor: section.contentFrom ?? section.from }, scrollIntoView: true });
2544
+ view.dispatch({
2545
+ selection: { anchor: section.from },
2546
+ effects: setFrontmatterSource.of(true),
2547
+ scrollIntoView: true
2548
+ });
2241
2549
  view.focus();
2242
2550
  });
2243
2551
  actionsRow.appendChild(editYamlBtn);
@@ -2262,10 +2570,12 @@ var DefaultFrontmatterWidget = class extends view.WidgetType {
2262
2570
  const commitField = (field, raw) => {
2263
2571
  if (field === "met") {
2264
2572
  const num = Number(raw);
2265
- patchPropertyInView(view, this.sectionId, ["baseAttributes", "met"], raw !== "" && !Number.isNaN(num) ? num : null);
2573
+ const path2 = base.hasBaseBlock ? ["baseAttributes", "met"] : ["metrics.met"];
2574
+ patchPropertyInView(view, this.sectionId, path2, raw !== "" && !Number.isNaN(num) ? num : null);
2266
2575
  return;
2267
2576
  }
2268
- patchPropertyInView(view, this.sectionId, ["baseAttributes", field], raw === "" ? null : raw);
2577
+ const path = base.hasBaseBlock ? ["baseAttributes", field] : [field];
2578
+ patchPropertyInView(view, this.sectionId, path, raw === "" ? null : raw);
2269
2579
  };
2270
2580
  const fieldRow = (label, child) => {
2271
2581
  const row = document.createElement("label");
@@ -2328,8 +2638,13 @@ var DefaultFrontmatterWidget = class extends view.WidgetType {
2328
2638
  }
2329
2639
  };
2330
2640
  var EmptyFrontmatterWidget = class extends view.WidgetType {
2331
- eq(_other) {
2332
- return true;
2641
+ constructor(detailed = false) {
2642
+ super();
2643
+ this.detailed = detailed;
2644
+ }
2645
+ detailed;
2646
+ eq(other) {
2647
+ return this.detailed === other.detailed;
2333
2648
  }
2334
2649
  ignoreEvent() {
2335
2650
  return true;
@@ -2342,7 +2657,7 @@ var EmptyFrontmatterWidget = class extends view.WidgetType {
2342
2657
  heading.textContent = "Properties";
2343
2658
  root.appendChild(heading);
2344
2659
  const actionsRow = document.createElement("div");
2345
- actionsRow.className = "flex items-center gap-3";
2660
+ actionsRow.className = "flex flex-wrap items-center gap-x-3 gap-y-1";
2346
2661
  actionsRow.dataset.frontmatterActions = "true";
2347
2662
  const addBtn = document.createElement("button");
2348
2663
  addBtn.type = "button";
@@ -2356,9 +2671,71 @@ var EmptyFrontmatterWidget = class extends view.WidgetType {
2356
2671
  e.preventDefault();
2357
2672
  e.stopPropagation();
2358
2673
  pendingFocus.set(view, { kind: "key", key: "property" });
2359
- patchPropertyInView(view, "new", ["property"], "");
2674
+ patchPropertyInView(view, "new", ["property"], "", true);
2675
+ const keyField = view.dom.querySelector(
2676
+ '[data-frontmatter-row="property"] [data-frontmatter-field="key"]'
2677
+ );
2678
+ keyField?.focus();
2679
+ keyField?.select();
2360
2680
  });
2361
2681
  actionsRow.appendChild(addBtn);
2682
+ const addMetricBtn = document.createElement("button");
2683
+ addMetricBtn.type = "button";
2684
+ addMetricBtn.setAttribute("aria-label", "Add metric");
2685
+ addMetricBtn.setAttribute("data-testid", "editor-add-metric-note");
2686
+ addMetricBtn.className = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground px-1 py-1 cursor-pointer transition-colors select-none";
2687
+ addMetricBtn.appendChild(createPlusIcon());
2688
+ const addMetricLabel = document.createElement("span");
2689
+ addMetricLabel.textContent = "Add metric";
2690
+ addMetricBtn.appendChild(addMetricLabel);
2691
+ addMetricBtn.addEventListener("click", (e) => {
2692
+ e.preventDefault();
2693
+ e.stopPropagation();
2694
+ if (!this.detailed) view.dispatch({ effects: setFrontmatterDetailed.of(true) });
2695
+ const activeRoot = view.dom.querySelector(".cm-frontmatter-preview") ?? root;
2696
+ const existing = activeRoot.querySelector("[data-frontmatter-metric-draft] input");
2697
+ if (existing) {
2698
+ existing.focus();
2699
+ return;
2700
+ }
2701
+ const referenceProvider = view.state.facet(frontmatterReferenceIndex)?.() ?? null;
2702
+ const draftRow = createMetricDraftRow(view, activeRoot, referenceProvider, (path, value) => {
2703
+ patchPropertyInView(view, "new", path, value, true);
2704
+ });
2705
+ activeRoot.appendChild(draftRow);
2706
+ ensureColumnHeader(activeRoot, draftRow);
2707
+ draftRow.querySelector("input")?.focus();
2708
+ view.requestMeasure();
2709
+ });
2710
+ actionsRow.appendChild(addMetricBtn);
2711
+ const addSummaryBtn = document.createElement("button");
2712
+ addSummaryBtn.type = "button";
2713
+ addSummaryBtn.setAttribute("aria-label", "Add summary");
2714
+ addSummaryBtn.setAttribute("data-testid", "editor-add-summary-note");
2715
+ addSummaryBtn.className = "inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground px-1 py-1 cursor-pointer transition-colors select-none";
2716
+ addSummaryBtn.appendChild(createPlusIcon());
2717
+ const addSummaryLabel = document.createElement("span");
2718
+ addSummaryLabel.textContent = "Add summary";
2719
+ addSummaryBtn.appendChild(addSummaryLabel);
2720
+ addSummaryBtn.addEventListener("click", (e) => {
2721
+ e.preventDefault();
2722
+ e.stopPropagation();
2723
+ if (!this.detailed) view.dispatch({ effects: setFrontmatterDetailed.of(true) });
2724
+ const activeRoot = view.dom.querySelector(".cm-frontmatter-preview") ?? root;
2725
+ const existing = activeRoot.querySelector("[data-frontmatter-summary-draft] input");
2726
+ if (existing) {
2727
+ existing.focus();
2728
+ return;
2729
+ }
2730
+ const draftRow = createSummaryDraftRow(view, (path, value) => {
2731
+ patchPropertyInView(view, "new", path, value, true);
2732
+ });
2733
+ activeRoot.appendChild(draftRow);
2734
+ ensureColumnHeader(activeRoot, draftRow);
2735
+ draftRow.querySelector("input")?.focus();
2736
+ view.requestMeasure();
2737
+ });
2738
+ actionsRow.appendChild(addSummaryBtn);
2362
2739
  const addTagBtn = document.createElement("button");
2363
2740
  addTagBtn.type = "button";
2364
2741
  addTagBtn.setAttribute("aria-label", "Add tag");
@@ -2370,17 +2747,19 @@ var EmptyFrontmatterWidget = class extends view.WidgetType {
2370
2747
  addTagBtn.addEventListener("click", (e) => {
2371
2748
  e.preventDefault();
2372
2749
  e.stopPropagation();
2373
- const existingDraft = root.querySelector("[data-frontmatter-tag-draft] input");
2750
+ if (!this.detailed) view.dispatch({ effects: setFrontmatterDetailed.of(true) });
2751
+ const activeRoot = view.dom.querySelector(".cm-frontmatter-preview") ?? root;
2752
+ const existingDraft = activeRoot.querySelector("[data-frontmatter-tag-draft] input");
2374
2753
  if (existingDraft) {
2375
2754
  existingDraft.focus();
2376
2755
  return;
2377
2756
  }
2378
- const draftRow = createTagDraftRow(view, root, "category", (val) => {
2757
+ const draftRow = createTagDraftRow(view, activeRoot, "category", (val) => {
2379
2758
  pendingFocus.set(view, { kind: "tag", key: "category" });
2380
- patchPropertyInView(view, "new", ["category"], [val]);
2759
+ patchPropertyInView(view, "new", ["category"], [val], true);
2381
2760
  });
2382
- root.appendChild(draftRow);
2383
- ensureColumnHeader(root, draftRow);
2761
+ activeRoot.appendChild(draftRow);
2762
+ ensureColumnHeader(activeRoot, draftRow);
2384
2763
  draftRow.querySelector("input")?.focus();
2385
2764
  view.requestMeasure();
2386
2765
  });
@@ -2402,14 +2781,14 @@ function buildFrontmatterDecos(state$1) {
2402
2781
  builder.add(section.from, to, view.Decoration.replace({ block: true }));
2403
2782
  continue;
2404
2783
  }
2405
- const selected = !state$1.readOnly && state$1.selection.ranges.some((range) => range.from < section.to && range.to > section.from);
2784
+ const selected = !state$1.readOnly && (state$1.field(frontmatterSource) || state$1.selection.ranges.some((range) => range.from < section.to && range.to > section.from));
2406
2785
  if (selected) continue;
2407
2786
  const rawContent = state$1.doc.sliceString(section.from, section.to);
2408
2787
  builder.add(
2409
2788
  section.from,
2410
2789
  section.to,
2411
2790
  view.Decoration.replace({
2412
- widget: new DefaultFrontmatterWidget(section.id, rawContent, state$1.readOnly),
2791
+ widget: new DefaultFrontmatterWidget(section.id, rawContent, state$1.readOnly, state$1.field(frontmatterDetailed)),
2413
2792
  block: true
2414
2793
  })
2415
2794
  );
@@ -2419,7 +2798,7 @@ function buildFrontmatterDecos(state$1) {
2419
2798
  0,
2420
2799
  0,
2421
2800
  view.Decoration.widget({
2422
- widget: new EmptyFrontmatterWidget(),
2801
+ widget: new EmptyFrontmatterWidget(state$1.field(frontmatterDetailed)),
2423
2802
  side: -1,
2424
2803
  block: true
2425
2804
  })
@@ -2432,7 +2811,7 @@ var frontmatterPreviewField = state.StateField.define({
2432
2811
  return buildFrontmatterDecos(state);
2433
2812
  },
2434
2813
  update(deco, tr) {
2435
- if (tr.docChanged || tr.reconfigured || tr.selection || tr.effects.some((e) => e.is(referenceIndexReload))) {
2814
+ if (tr.docChanged || tr.reconfigured || tr.selection || tr.effects.some((e) => e.is(referenceIndexReload) || e.is(setFrontmatterDetailed) || e.is(setFrontmatterSource))) {
2436
2815
  return buildFrontmatterDecos(tr.state);
2437
2816
  }
2438
2817
  return deco;
@@ -2444,7 +2823,7 @@ var frontmatterPreviewField = state.StateField.define({
2444
2823
  )
2445
2824
  ]
2446
2825
  });
2447
- var frontmatterPreview = [frontmatterPreviewField];
2826
+ var frontmatterPreview = [frontmatterDetailed, frontmatterSource, frontmatterPreviewField];
2448
2827
  var HEADING_RE = /^(#{1,6}) /;
2449
2828
  var BOLD_RE = /\*\*([^*\n]+)\*\*/g;
2450
2829
  var ITALIC_STAR_RE = /(?<!\*)\*([^*\n]+)\*(?!\*)/g;
@@ -4990,7 +5369,9 @@ var CLAUSE_META = {
4990
5369
  note: { label: "Note", inputType: "freetext", placeholder: "note id\u2026", placeholderText: "note: [id]", icon: "\u{1F4D3}", description: "Scope to one note", prefix: "note:" },
4991
5370
  page: { label: "Page", inputType: "select", placeholder: "slug, date, true\u2026", placeholderText: "page: [slug|date]", icon: "\u{1F4C4}", description: "On :page \u2014 the page slug or YYYY-MM-DD date; on :note \u2014 page-ness (true/false)", prefix: "page:" },
4992
5371
  plane: { label: "Output Plane", inputType: "select", placeholder: "segment, load, event\u2026", placeholderText: "plane: [type]", icon: "\u{1F4CB}", description: "Output-statement plane on :session", prefix: "plane:" },
4993
- pipes: { label: "Pipes", inputType: "freetext", placeholder: "| order by date | limit 10", placeholderText: "| select | order | limit", icon: "\u21E5", description: "Presentation pipes \u2014 select/order/limit/offset" }
5372
+ pipes: { label: "Pipes", inputType: "freetext", placeholder: "| order by date | limit 10", placeholderText: "| select | order | limit", icon: "\u21E5", description: "Presentation pipes \u2014 select/order/limit/offset" },
5373
+ feed: { label: "Feed", inputType: "select", placeholder: "core, local, feed id\u2026", placeholderText: "feed: [id]", icon: "\u{1F4E1}", description: "Filter by publishing feed source", prefix: "feed:" },
5374
+ subscribed: { label: "Subscribed", inputType: "select", placeholder: "true, false", placeholderText: "subscribed: [true|false]", icon: "\u2B50", description: "Filter by notebook subscription state", prefix: "subscribed:" }
4994
5375
  };
4995
5376
  var CUSTOM_FALLBACK_ICON = "\u{1F9E9}";
4996
5377
  function getClauseMeta(type) {
@@ -12832,9 +13213,11 @@ exports.fenceCompletion = fenceCompletion;
12832
13213
  exports.findQueryBlockSection = findQueryBlockSection;
12833
13214
  exports.findWorkoutErrors = findWorkoutErrors;
12834
13215
  exports.forceSectionParse = forceSectionParse;
13216
+ exports.frontmatterDetailed = frontmatterDetailed;
12835
13217
  exports.frontmatterPreview = frontmatterPreview;
12836
13218
  exports.frontmatterPreviewField = frontmatterPreviewField;
12837
13219
  exports.frontmatterReferenceIndex = frontmatterReferenceIndex;
13220
+ exports.frontmatterSource = frontmatterSource;
12838
13221
  exports.frontmatterSuggestions = frontmatterSuggestions;
12839
13222
  exports.frontmatterVisibility = frontmatterVisibility;
12840
13223
  exports.getCursorFocusState = getCursorFocusState;
@@ -12869,6 +13252,8 @@ exports.sectionRestore = sectionRestore;
12869
13252
  exports.sectionSaved = sectionSaved;
12870
13253
  exports.sessionQueryInsert = sessionQueryInsert;
12871
13254
  exports.sessionQueryWql = sessionQueryWql;
13255
+ exports.setFrontmatterDetailed = setFrontmatterDetailed;
13256
+ exports.setFrontmatterSource = setFrontmatterSource;
12872
13257
  exports.setGutterHighlights = setGutterHighlights;
12873
13258
  exports.smartIncrement = smartIncrement;
12874
13259
  exports.toggleWidgetEdit = toggleWidgetEdit;