@xenosystem/blocks 0.13.16 → 0.13.17

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.
@@ -1488,9 +1488,9 @@ function LayersPanelView({ controller }) {
1488
1488
  const getSnapshot = useCallback(() => controller.getRevision(), [controller]);
1489
1489
  useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1490
1490
  const view = layersViewState(controller);
1491
- const ui6 = (intent) => applyLayersUi(controller, intent);
1491
+ const ui7 = (intent) => applyLayersUi(controller, intent);
1492
1492
  return /* @__PURE__ */ jsxs(Column, { className: "xeno-layers-panel", children: [
1493
- view.searchShown ? /* @__PURE__ */ jsx2(Toolbar, { children: /* @__PURE__ */ jsx2(SearchField, { value: view.filter, onChange: (v) => ui6({ kind: "filter", value: v }), placeholder: "Search layers", label: "Search layers" }) }) : null,
1493
+ view.searchShown ? /* @__PURE__ */ jsx2(Toolbar, { children: /* @__PURE__ */ jsx2(SearchField, { value: view.filter, onChange: (v) => ui7({ kind: "filter", value: v }), placeholder: "Search layers", label: "Search layers" }) }) : null,
1494
1494
  /* @__PURE__ */ jsxs(LayersScroller, { controller, children: [
1495
1495
  /* @__PURE__ */ jsx2(RowList, { role: "tree", "aria-label": "Layers", children: /* @__PURE__ */ jsx2(VirtualWindow, { totalHeight: view.totalHeight, offsetTop: view.offsetTop, children: view.rows.map((row2) => /* @__PURE__ */ jsx2(LayerRow, { row: row2, thumbnailSize: view.thumbnailSize, controller }, row2.id)) }) }),
1496
1496
  view.empty ? /* @__PURE__ */ jsx2(EmptyState, { title: "No layers" }) : null
@@ -1603,7 +1603,7 @@ function LayersThumbnail({ thumbnailKey, size, controller }) {
1603
1603
  return /* @__PURE__ */ jsx2(Thumbnail, { size, data: controller.getThumbnail(thumbnailKey) });
1604
1604
  }
1605
1605
  function LayerRow({ row: row2, thumbnailSize, controller }) {
1606
- const ui6 = (intent) => applyLayersUi(controller, intent);
1606
+ const ui7 = (intent) => applyLayersUi(controller, intent);
1607
1607
  return /* @__PURE__ */ jsx2(
1608
1608
  Row,
1609
1609
  {
@@ -1618,11 +1618,11 @@ function LayerRow({ row: row2, thumbnailSize, controller }) {
1618
1618
  title: row2.title,
1619
1619
  icon: row2.hasThumbnail ? /* @__PURE__ */ jsx2(LayersThumbnail, { thumbnailKey: row2.thumbnailKey, size: thumbnailSize, controller }) : /* @__PURE__ */ jsx2(KindGlyph, {}),
1620
1620
  leading: /* @__PURE__ */ jsxs(Fragment, { children: [
1621
- row2.showVisibility ? /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(VisibilityIcon, { open: row2.visible }), label: row2.visibilityLabel, active: row2.visible, onClick: () => ui6({ kind: "toggleProp", id: row2.id, prop: "visible" }) }) : null,
1622
- row2.showLock ? /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(LockStateIcon, { locked: row2.locked }), label: row2.lockLabel, active: row2.locked, onClick: () => ui6({ kind: "toggleProp", id: row2.id, prop: "locked" }) }) : null,
1623
- row2.showSolo ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "S", label: "Solo", on: row2.solo, onChange: () => ui6({ kind: "toggleProp", id: row2.id, prop: "solo" }) }) : null,
1624
- row2.showMute ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "M", label: "Mute", on: row2.muted, onChange: () => ui6({ kind: "toggleProp", id: row2.id, prop: "muted" }) }) : null,
1625
- row2.showArm ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "R", label: "Arm", on: row2.armed, onChange: () => ui6({ kind: "toggleProp", id: row2.id, prop: "armed" }) }) : null,
1621
+ row2.showVisibility ? /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(VisibilityIcon, { open: row2.visible }), label: row2.visibilityLabel, active: row2.visible, onClick: () => ui7({ kind: "toggleProp", id: row2.id, prop: "visible" }) }) : null,
1622
+ row2.showLock ? /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(LockStateIcon, { locked: row2.locked }), label: row2.lockLabel, active: row2.locked, onClick: () => ui7({ kind: "toggleProp", id: row2.id, prop: "locked" }) }) : null,
1623
+ row2.showSolo ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "S", label: "Solo", on: row2.solo, onChange: () => ui7({ kind: "toggleProp", id: row2.id, prop: "solo" }) }) : null,
1624
+ row2.showMute ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "M", label: "Mute", on: row2.muted, onChange: () => ui7({ kind: "toggleProp", id: row2.id, prop: "muted" }) }) : null,
1625
+ row2.showArm ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "R", label: "Arm", on: row2.armed, onChange: () => ui7({ kind: "toggleProp", id: row2.id, prop: "armed" }) }) : null,
1626
1626
  row2.showColor ? /* @__PURE__ */ jsx2(ColorTag, { color: row2.colorLabel }) : null
1627
1627
  ] }),
1628
1628
  label: row2.renaming ? /* @__PURE__ */ jsx2(LayersRenameField, { id: row2.id, controller }) : row2.name,
@@ -1630,9 +1630,9 @@ function LayerRow({ row: row2, thumbnailSize, controller }) {
1630
1630
  row2.adornments.map((a) => /* @__PURE__ */ jsx2(Badge, { size: "sm", title: a.title, children: a.text }, a.key)),
1631
1631
  row2.showOpacity ? /* @__PURE__ */ jsx2(Text, { role: "number", children: row2.opacity }) : null
1632
1632
  ] }),
1633
- onExpandedChange: () => ui6({ kind: "toggleExpanded", id: row2.id }),
1634
- onDoubleClick: () => ui6({ kind: "beginRename", id: row2.id }),
1635
- onContextMenuAt: (x, y) => ui6({ kind: "contextMenu", id: row2.id, x, y })
1633
+ onExpandedChange: () => ui7({ kind: "toggleExpanded", id: row2.id }),
1634
+ onDoubleClick: () => ui7({ kind: "beginRename", id: row2.id }),
1635
+ onContextMenuAt: (x, y) => ui7({ kind: "contextMenu", id: row2.id, x, y })
1636
1636
  }
1637
1637
  );
1638
1638
  }
@@ -2024,8 +2024,8 @@ function constraintsOf(field) {
2024
2024
  scale: field.scale
2025
2025
  };
2026
2026
  }
2027
- function parseNumeric(text6) {
2028
- const trimmed = String(text6).trim();
2027
+ function parseNumeric(text7) {
2028
+ const trimmed = String(text7).trim();
2029
2029
  if (trimmed.length === 0) return { ok: false, reason: "empty" };
2030
2030
  const match = /^[+-]?(\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(trimmed);
2031
2031
  if (!match) return { ok: false, reason: "nan" };
@@ -2050,8 +2050,8 @@ function normalizeNumeric(value, c) {
2050
2050
  function formatNumeric(value, field) {
2051
2051
  if (!Number.isFinite(value)) return "";
2052
2052
  const precision = field.precision;
2053
- const text6 = typeof precision === "number" && Number.isFinite(precision) ? value.toFixed(Math.max(0, Math.floor(precision))) : String(value);
2054
- return field.unit ? `${text6}${field.unit}` : text6;
2053
+ const text7 = typeof precision === "number" && Number.isFinite(precision) ? value.toFixed(Math.max(0, Math.floor(precision))) : String(value);
2054
+ return field.unit ? `${text7}${field.unit}` : text7;
2055
2055
  }
2056
2056
  function usesLogScale(c) {
2057
2057
  return c.scale === "log" && typeof c.min === "number" && typeof c.max === "number" && c.min > 0 && c.max > c.min;
@@ -2385,44 +2385,44 @@ var InspectorPanelController = class {
2385
2385
  if (!field || !isFieldEditable(field)) return;
2386
2386
  const mixed = isFieldMixed(field);
2387
2387
  const value = this.getFieldValue(fieldId);
2388
- const text6 = mixed ? "" : field.type === "number" && typeof value === "number" ? formatNumeric(value, field) : value === void 0 || value === null ? "" : String(value);
2389
- this.drafts.set(fieldId, { text: text6, original: value });
2388
+ const text7 = mixed ? "" : field.type === "number" && typeof value === "number" ? formatNumeric(value, field) : value === void 0 || value === null ? "" : String(value);
2389
+ this.drafts.set(fieldId, { text: text7, original: value });
2390
2390
  this.focusedFieldId = fieldId;
2391
2391
  this.emitFieldFocus(fieldId);
2392
2392
  this.notify();
2393
2393
  }
2394
2394
  /** Update the draft text (typing). Emits nothing under the `blur` policy. */
2395
- setDraft(fieldId, text6) {
2395
+ setDraft(fieldId, text7) {
2396
2396
  const draft = this.drafts.get(fieldId);
2397
2397
  if (!draft) {
2398
- this.drafts.set(fieldId, { text: text6, original: this.getFieldValue(fieldId) });
2398
+ this.drafts.set(fieldId, { text: text7, original: this.getFieldValue(fieldId) });
2399
2399
  } else {
2400
- draft.text = text6;
2400
+ draft.text = text7;
2401
2401
  }
2402
2402
  const field = this.getField(fieldId);
2403
2403
  if (field && commitPolicyOf(field) === "live") {
2404
- const parsed = this.parseDraft(fieldId, text6);
2404
+ const parsed = this.parseDraft(fieldId, text7);
2405
2405
  if (parsed.ok) this.emitSet(fieldId, parsed.value);
2406
2406
  }
2407
2407
  this.notify();
2408
2408
  }
2409
2409
  /** Parse a draft string into a field-appropriate value. */
2410
- parseDraft(fieldId, text6) {
2410
+ parseDraft(fieldId, text7) {
2411
2411
  const field = this.getField(fieldId);
2412
2412
  if (!field) return { ok: false };
2413
2413
  if (field.type === "number") {
2414
- const parsed = parseNumeric(text6);
2414
+ const parsed = parseNumeric(text7);
2415
2415
  if (!parsed.ok) return { ok: false };
2416
2416
  return { ok: true, value: normalizeNumeric(parsed.value, constraintsOf(field)) };
2417
2417
  }
2418
2418
  if (field.type === "json") {
2419
2419
  try {
2420
- return { ok: true, value: JSON.parse(text6) };
2420
+ return { ok: true, value: JSON.parse(text7) };
2421
2421
  } catch {
2422
2422
  return { ok: false };
2423
2423
  }
2424
2424
  }
2425
- return { ok: true, value: text6 };
2425
+ return { ok: true, value: text7 };
2426
2426
  }
2427
2427
  /**
2428
2428
  * Commit the draft (Enter / blur). Invalid input reverts instead of committing garbage.
@@ -3348,7 +3348,7 @@ function NumberWidget({ field, controller, component }) {
3348
3348
  },
3349
3349
  [controller, editable, field.id, component]
3350
3350
  );
3351
- const text6 = component !== void 0 ? (() => {
3351
+ const text7 = component !== void 0 ? (() => {
3352
3352
  const raw = controller.getFieldValue(field.id);
3353
3353
  const v = Array.isArray(raw) ? raw[component] : void 0;
3354
3354
  return typeof v === "number" ? formatNumeric(v, field) : "";
@@ -3360,7 +3360,7 @@ function NumberWidget({ field, controller, component }) {
3360
3360
  Input2,
3361
3361
  {
3362
3362
  inputMode: "decimal",
3363
- value: text6,
3363
+ value: text7,
3364
3364
  placeholder: mixed ? controller.getMixedPlaceholder() : field.placeholder,
3365
3365
  mixed,
3366
3366
  readOnly: !editable,
@@ -3578,7 +3578,7 @@ function ActionGlyph({ glyph }) {
3578
3578
  return /* @__PURE__ */ jsx4("span", { "aria-hidden": true, children: glyph });
3579
3579
  }
3580
3580
  function SectionView({ section, controller }) {
3581
- const ui6 = (intent) => applyInspectorUi(controller, intent);
3581
+ const ui7 = (intent) => applyInspectorUi(controller, intent);
3582
3582
  const hasActions = section.actions.length > 0 || section.hasAddMenu;
3583
3583
  return /* @__PURE__ */ jsxs3(
3584
3584
  Section,
@@ -3587,13 +3587,13 @@ function SectionView({ section, controller }) {
3587
3587
  flush: true,
3588
3588
  collapsible: section.collapsible,
3589
3589
  collapsed: section.collapsed,
3590
- onCollapsedChange: () => ui6({ kind: "toggleSectionCollapsed", sectionId: section.id }),
3590
+ onCollapsedChange: () => ui7({ kind: "toggleSectionCollapsed", sectionId: section.id }),
3591
3591
  enabled: section.enabled,
3592
- onEnabledChange: () => ui6({ kind: "toggleSectionEnabled", sectionId: section.id }),
3592
+ onEnabledChange: () => ui7({ kind: "toggleSectionEnabled", sectionId: section.id }),
3593
3593
  badge: section.badge ? /* @__PURE__ */ jsx4(Badge2, { children: section.badge }) : null,
3594
3594
  actions: hasActions ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
3595
- section.actions.map((action) => /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ActionGlyph, { glyph: action.glyph }), label: action.label, disabled: action.disabled, onClick: () => ui6({ kind: "runAction", sectionId: section.id, actionId: action.id }) }, action.id)),
3596
- section.hasAddMenu ? /* @__PURE__ */ jsx4(Menu, { size: "sm", trigger: "\uFF0B", items: section.addItems, label: section.addLabel, align: "end", onSelect: (id) => ui6({ kind: "addItem", sectionId: section.id, itemId: id }) }) : null
3595
+ section.actions.map((action) => /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ActionGlyph, { glyph: action.glyph }), label: action.label, disabled: action.disabled, onClick: () => ui7({ kind: "runAction", sectionId: section.id, actionId: action.id }) }, action.id)),
3596
+ section.hasAddMenu ? /* @__PURE__ */ jsx4(Menu, { size: "sm", trigger: "\uFF0B", items: section.addItems, label: section.addLabel, align: "end", onSelect: (id) => ui7({ kind: "addItem", sectionId: section.id, itemId: id }) }) : null
3597
3597
  ] }) : null,
3598
3598
  children: [
3599
3599
  section.showEmptyHint ? /* @__PURE__ */ jsx4(Stack2, { pad: "inset", children: /* @__PURE__ */ jsx4(Text3, { role: "caption", children: section.emptyHint }) }) : null,
@@ -3603,7 +3603,7 @@ function SectionView({ section, controller }) {
3603
3603
  );
3604
3604
  }
3605
3605
  function FieldRow({ row: row2, controller }) {
3606
- const ui6 = (intent) => applyInspectorUi(controller, intent);
3606
+ const ui7 = (intent) => applyInspectorUi(controller, intent);
3607
3607
  return /* @__PURE__ */ jsxs3(Fragment3, { children: [
3608
3608
  /* @__PURE__ */ jsxs3(
3609
3609
  Field,
@@ -3611,19 +3611,19 @@ function FieldRow({ row: row2, controller }) {
3611
3611
  label: row2.label,
3612
3612
  title: row2.title,
3613
3613
  focused: row2.focused,
3614
- onLabelClick: () => ui6({ kind: "focusField", fieldId: row2.id }),
3615
- onContextMenu: (x, y) => ui6({ kind: "contextMenu", fieldId: row2.id, x, y }),
3614
+ onLabelClick: () => ui7({ kind: "focusField", fieldId: row2.id }),
3615
+ onContextMenu: (x, y) => ui7({ kind: "contextMenu", fieldId: row2.id, x, y }),
3616
3616
  leading: row2.showKeyframes ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
3617
- /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(StopwatchIcon, {}), label: "Toggle animation", active: row2.animated, onClick: () => ui6({ kind: "keyframe", fieldId: row2.id, op: "toggleAnim" }) }),
3618
- row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(PrevIcon, {}), label: "Previous keyframe", disabled: row2.prevDisabled, onClick: () => ui6({ kind: "keyframe", fieldId: row2.id, op: "prev" }) }) : null,
3619
- row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(DiamondIcon, { filled: row2.onKeyframe }), label: row2.keyframeLabel, onClick: () => ui6({ kind: "keyframe", fieldId: row2.id, op: row2.keyframeOp }) }) : null,
3620
- row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(NextIcon, {}), label: "Next keyframe", disabled: row2.nextDisabled, onClick: () => ui6({ kind: "keyframe", fieldId: row2.id, op: "next" }) }) : null
3617
+ /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(StopwatchIcon, {}), label: "Toggle animation", active: row2.animated, onClick: () => ui7({ kind: "keyframe", fieldId: row2.id, op: "toggleAnim" }) }),
3618
+ row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(PrevIcon, {}), label: "Previous keyframe", disabled: row2.prevDisabled, onClick: () => ui7({ kind: "keyframe", fieldId: row2.id, op: "prev" }) }) : null,
3619
+ row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(DiamondIcon, { filled: row2.onKeyframe }), label: row2.keyframeLabel, onClick: () => ui7({ kind: "keyframe", fieldId: row2.id, op: row2.keyframeOp }) }) : null,
3620
+ row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(NextIcon, {}), label: "Next keyframe", disabled: row2.nextDisabled, onClick: () => ui7({ kind: "keyframe", fieldId: row2.id, op: "next" }) }) : null
3621
3621
  ] }) : null,
3622
3622
  trailing: /* @__PURE__ */ jsxs3(Fragment3, { children: [
3623
- row2.showEasing ? /* @__PURE__ */ jsx4(Select2, { size: "sm", options: row2.easingOptions, value: row2.easing, label: "Easing", onChange: (v) => ui6({ kind: "setEasing", fieldId: row2.id, easing: v }) }) : null,
3623
+ row2.showEasing ? /* @__PURE__ */ jsx4(Select2, { size: "sm", options: row2.easingOptions, value: row2.easing, label: "Easing", onChange: (v) => ui7({ kind: "setEasing", fieldId: row2.id, easing: v }) }) : null,
3624
3624
  row2.showKeyframes ? /* @__PURE__ */ jsx4(Badge2, { size: "sm", children: row2.keyframeChip }) : null,
3625
- row2.showToken ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(LinkIcon, {}), label: row2.tokenActionLabel, active: row2.bound, onClick: () => ui6({ kind: "bindToken", fieldId: row2.id }) }) : null,
3626
- row2.showReset ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ResetIcon, {}), label: "Reset to default", onClick: () => ui6({ kind: "resetField", fieldId: row2.id }) }) : null
3625
+ row2.showToken ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(LinkIcon, {}), label: row2.tokenActionLabel, active: row2.bound, onClick: () => ui7({ kind: "bindToken", fieldId: row2.id }) }) : null,
3626
+ row2.showReset ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ResetIcon, {}), label: "Reset to default", onClick: () => ui7({ kind: "resetField", fieldId: row2.id }) }) : null
3627
3627
  ] }),
3628
3628
  children: [
3629
3629
  row2.bound ? /* @__PURE__ */ jsx4(Badge2, { title: row2.tokenTitle, children: row2.tokenLabel }) : null,
@@ -4876,23 +4876,23 @@ function HistoryPanelView({ controller }) {
4876
4876
  const getSnapshot = useCallback4(() => controller.getRevision(), [controller]);
4877
4877
  useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
4878
4878
  const view = historyViewState(controller);
4879
- const ui6 = (intent) => applyHistoryUi(controller, intent);
4879
+ const ui7 = (intent) => applyHistoryUi(controller, intent);
4880
4880
  return /* @__PURE__ */ jsxs4(Column3, { className: "xeno-history-panel", children: [
4881
4881
  /* @__PURE__ */ jsx5(
4882
4882
  Toolbar2,
4883
4883
  {
4884
4884
  left: /* @__PURE__ */ jsxs4(Fragment4, { children: [
4885
- /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(UndoIcon, {}), label: "Undo", disabled: !view.canUndo, onClick: () => ui6({ kind: "undo" }) }),
4886
- /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(RedoIcon, {}), label: "Redo", disabled: !view.canRedo, onClick: () => ui6({ kind: "redo" }) })
4885
+ /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(UndoIcon, {}), label: "Undo", disabled: !view.canUndo, onClick: () => ui7({ kind: "undo" }) }),
4886
+ /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(RedoIcon, {}), label: "Redo", disabled: !view.canRedo, onClick: () => ui7({ kind: "redo" }) })
4887
4887
  ] }),
4888
4888
  right: /* @__PURE__ */ jsxs4(Fragment4, { children: [
4889
4889
  /* @__PURE__ */ jsx5(Text4, { role: "number", children: view.counter }),
4890
- view.clearAllowed ? /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(TrashIcon, {}), label: "Clear history", onClick: () => ui6({ kind: "requestClear" }) }) : null
4890
+ view.clearAllowed ? /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(TrashIcon, {}), label: "Clear history", onClick: () => ui7({ kind: "requestClear" }) }) : null
4891
4891
  ] }),
4892
- children: view.searchShown ? /* @__PURE__ */ jsx5(SearchField3, { value: view.filter, onChange: (v) => ui6({ kind: "filter", value: v }), placeholder: "Search history", label: "Search history" }) : null
4892
+ children: view.searchShown ? /* @__PURE__ */ jsx5(SearchField3, { value: view.filter, onChange: (v) => ui7({ kind: "filter", value: v }), placeholder: "Search history", label: "Search history" }) : null
4893
4893
  }
4894
4894
  ),
4895
- view.confirmingClear ? /* @__PURE__ */ jsx5(InlineConfirm, { message: "Clear all history?", label: "Confirm clear history", confirmLabel: "Clear", onConfirm: () => ui6({ kind: "confirmClear" }), onCancel: () => ui6({ kind: "cancelClear" }) }) : null,
4895
+ view.confirmingClear ? /* @__PURE__ */ jsx5(InlineConfirm, { message: "Clear all history?", label: "Confirm clear history", confirmLabel: "Clear", onConfirm: () => ui7({ kind: "confirmClear" }), onCancel: () => ui7({ kind: "cancelClear" }) }) : null,
4896
4896
  view.pruned ? /* @__PURE__ */ jsx5(Text4, { role: "caption", children: "Earlier history discarded (limit reached)" }) : null,
4897
4897
  /* @__PURE__ */ jsxs4(HistoryScroller, { controller, children: [
4898
4898
  /* @__PURE__ */ jsx5(RowList2, { role: "listbox", "aria-label": "History", children: /* @__PURE__ */ jsx5(VirtualWindow2, { totalHeight: view.totalHeight, offsetTop: view.offsetTop, children: view.rows.map((row2) => /* @__PURE__ */ jsx5(HistoryRowView, { row: row2, controller }, row2.key)) }) }),
@@ -4951,7 +4951,7 @@ function HistoryCancelButton({ entryId, controller }) {
4951
4951
  );
4952
4952
  }
4953
4953
  function HistoryRowView({ row: row2, controller }) {
4954
- const ui6 = (intent) => applyHistoryUi(controller, intent);
4954
+ const ui7 = (intent) => applyHistoryUi(controller, intent);
4955
4955
  return /* @__PURE__ */ jsx5(
4956
4956
  Row2,
4957
4957
  {
@@ -4974,9 +4974,9 @@ function HistoryRowView({ row: row2, controller }) {
4974
4974
  row2.showTime ? /* @__PURE__ */ jsx5(Text4, { role: "meta", children: row2.time }) : null,
4975
4975
  row2.pending ? /* @__PURE__ */ jsx5(HistoryCancelButton, { entryId: row2.cancelId, controller }) : null
4976
4976
  ] }),
4977
- onClick: () => ui6({ kind: "jump", entryId: row2.entryId }),
4978
- onMouseEnter: () => ui6({ kind: "hover", entryId: row2.entryId }),
4979
- onContextMenuAt: (x, y) => ui6({ kind: "contextMenu", entryId: row2.entryId, x, y })
4977
+ onClick: () => ui7({ kind: "jump", entryId: row2.entryId }),
4978
+ onMouseEnter: () => ui7({ kind: "hover", entryId: row2.entryId }),
4979
+ onContextMenuAt: (x, y) => ui7({ kind: "contextMenu", entryId: row2.entryId, x, y })
4980
4980
  }
4981
4981
  );
4982
4982
  }
@@ -6262,27 +6262,27 @@ function ColorPanelView({ controller }) {
6262
6262
  const getSnapshot = useCallback5(() => controller.getRevision(), [controller]);
6263
6263
  useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
6264
6264
  const view = colorViewState(controller);
6265
- const ui6 = (intent) => applyColorUi(controller, intent);
6265
+ const ui7 = (intent) => applyColorUi(controller, intent);
6266
6266
  return /* @__PURE__ */ jsx6(Column4, { className: "xeno-color-panel", children: /* @__PURE__ */ jsxs5(Stack3, { gap: 8, children: [
6267
6267
  view.fgBg ? /* @__PURE__ */ jsxs5(Stack3, { direction: "row", pad: "none", children: [
6268
- /* @__PURE__ */ jsx6(ColorChip, { color: view.hex, label: "Foreground", pressed: view.fgActive, onClick: () => ui6({ kind: "setChannel", channel: "fg" }) }),
6269
- /* @__PURE__ */ jsx6(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx6(SwapIcon, {}), label: "Swap FG/BG (X)", onClick: () => ui6({ kind: "swapChannels" }) }),
6270
- /* @__PURE__ */ jsx6(ColorChip, { color: null, label: "Background", pressed: view.bgActive, onClick: () => ui6({ kind: "setChannel", channel: "bg" }) })
6268
+ /* @__PURE__ */ jsx6(ColorChip, { color: view.hex, label: "Foreground", pressed: view.fgActive, onClick: () => ui7({ kind: "setChannel", channel: "fg" }) }),
6269
+ /* @__PURE__ */ jsx6(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx6(SwapIcon, {}), label: "Swap FG/BG (X)", onClick: () => ui7({ kind: "swapChannels" }) }),
6270
+ /* @__PURE__ */ jsx6(ColorChip, { color: null, label: "Background", pressed: view.bgActive, onClick: () => ui7({ kind: "setChannel", channel: "bg" }) })
6271
6271
  ] }) : null,
6272
6272
  /* @__PURE__ */ jsx6(ColorPicker, { controller }),
6273
6273
  /* @__PURE__ */ jsxs5(Stack3, { direction: "row", pad: "none", gap: 4, children: [
6274
- /* @__PURE__ */ jsx6(Select3, { size: "sm", label: "Color model", value: view.space, options: view.spaceOptions, onChange: (v) => ui6({ kind: "setSpace", space: v }) }),
6275
- view.isHex ? /* @__PURE__ */ jsx6(Input3, { variant: "mono", label: "Hex", value: view.hex, onChange: (v) => ui6({ kind: "setHex", value: v }), disabled: view.bound }) : null,
6276
- view.components.map((comp) => /* @__PURE__ */ jsx6(NumberInput, { caption: comp.label, value: comp.value, onChange: (n) => ui6({ kind: "setComponent", index: comp.index, value: n }), disabled: view.bound }, comp.key)),
6277
- view.alphaShown ? /* @__PURE__ */ jsx6(NumberInput, { caption: "A", label: "Alpha", min: 0, max: 100, value: view.alphaPercent, onChange: (n) => ui6({ kind: "setAlpha", percent: n }), disabled: view.bound }) : null,
6278
- /* @__PURE__ */ jsx6(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx6(EyedropperIcon, {}), label: "Pick from screen", onClick: () => ui6({ kind: "eyedropper" }) })
6274
+ /* @__PURE__ */ jsx6(Select3, { size: "sm", label: "Color model", value: view.space, options: view.spaceOptions, onChange: (v) => ui7({ kind: "setSpace", space: v }) }),
6275
+ view.isHex ? /* @__PURE__ */ jsx6(Input3, { variant: "mono", label: "Hex", value: view.hex, onChange: (v) => ui7({ kind: "setHex", value: v }), disabled: view.bound }) : null,
6276
+ view.components.map((comp) => /* @__PURE__ */ jsx6(NumberInput, { caption: comp.label, value: comp.value, onChange: (n) => ui7({ kind: "setComponent", index: comp.index, value: n }), disabled: view.bound }, comp.key)),
6277
+ view.alphaShown ? /* @__PURE__ */ jsx6(NumberInput, { caption: "A", label: "Alpha", min: 0, max: 100, value: view.alphaPercent, onChange: (n) => ui7({ kind: "setAlpha", percent: n }), disabled: view.bound }) : null,
6278
+ /* @__PURE__ */ jsx6(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx6(EyedropperIcon, {}), label: "Pick from screen", onClick: () => ui7({ kind: "eyedropper" }) })
6279
6279
  ] }),
6280
6280
  view.hasTokens ? /* @__PURE__ */ jsx6(Field2, { label: "Token", htmlFor: "xeno-color-token", children: /* @__PURE__ */ jsxs5(Stack3, { direction: "row", pad: "none", gap: 4, children: [
6281
- /* @__PURE__ */ jsx6(Select3, { size: "sm", label: "Bind token", value: view.boundTokenId, placeholder: "\u2014 raw \u2014", options: view.tokenOptions, onChange: (id) => ui6({ kind: "bindToken", tokenId: id }) }),
6282
- view.bound ? /* @__PURE__ */ jsx6(TextButton2, { onClick: () => ui6({ kind: "unbindToken" }), children: "Unbind" }) : null
6281
+ /* @__PURE__ */ jsx6(Select3, { size: "sm", label: "Bind token", value: view.boundTokenId, placeholder: "\u2014 raw \u2014", options: view.tokenOptions, onChange: (id) => ui7({ kind: "bindToken", tokenId: id }) }),
6282
+ view.bound ? /* @__PURE__ */ jsx6(TextButton2, { onClick: () => ui7({ kind: "unbindToken" }), children: "Unbind" }) : null
6283
6283
  ] }) }) : null,
6284
- /* @__PURE__ */ jsx6(Section2, { title: "Swatches", flush: true, actions: /* @__PURE__ */ jsx6(TextButton2, { onClick: () => ui6({ kind: "addSwatch" }), children: "+ Save" }), children: /* @__PURE__ */ jsx6(SwatchGrid, { label: "Swatches", children: view.swatches.map((sw) => /* @__PURE__ */ jsx6(ColorChip, { size: "grid", color: sw.hex, label: sw.hex, onClick: () => ui6({ kind: "selectColor", hex: sw.hex }), onContextMenu: () => ui6({ kind: "removeSwatch", hex: sw.hex }) }, sw.key)) }) }),
6285
- view.hasRecent ? /* @__PURE__ */ jsx6(Section2, { title: "Recent", flush: true, children: /* @__PURE__ */ jsx6(SwatchGrid, { label: "Recent", children: view.recent.map((r) => /* @__PURE__ */ jsx6(ColorChip, { size: "grid", color: r.hex, label: r.hex, onClick: () => ui6({ kind: "selectColor", hex: r.hex }) }, r.key)) }) }) : null
6284
+ /* @__PURE__ */ jsx6(Section2, { title: "Swatches", flush: true, actions: /* @__PURE__ */ jsx6(TextButton2, { onClick: () => ui7({ kind: "addSwatch" }), children: "+ Save" }), children: /* @__PURE__ */ jsx6(SwatchGrid, { label: "Swatches", children: view.swatches.map((sw) => /* @__PURE__ */ jsx6(ColorChip, { size: "grid", color: sw.hex, label: sw.hex, onClick: () => ui7({ kind: "selectColor", hex: sw.hex }), onContextMenu: () => ui7({ kind: "removeSwatch", hex: sw.hex }) }, sw.key)) }) }),
6285
+ view.hasRecent ? /* @__PURE__ */ jsx6(Section2, { title: "Recent", flush: true, children: /* @__PURE__ */ jsx6(SwatchGrid, { label: "Recent", children: view.recent.map((r) => /* @__PURE__ */ jsx6(ColorChip, { size: "grid", color: r.hex, label: r.hex, onClick: () => ui7({ kind: "selectColor", hex: r.hex }) }, r.key)) }) }) : null
6286
6286
  ] }) });
6287
6287
  }
6288
6288
  function ColorPicker({ controller }) {
@@ -8944,11 +8944,11 @@ var FindController = class {
8944
8944
  * @param text - The new query text.
8945
8945
  * @param immediate - Skip the debounce (a paste, or pressing Enter).
8946
8946
  */
8947
- setText(text6, immediate = false) {
8948
- if (text6 === this.text) return;
8949
- this.text = text6;
8947
+ setText(text7, immediate = false) {
8948
+ if (text7 === this.text) return;
8949
+ this.text = text7;
8950
8950
  this.index = -1;
8951
- if (text6 === "") {
8951
+ if (text7 === "") {
8952
8952
  this.clearResults("idle");
8953
8953
  this.emitQuery(true);
8954
8954
  return;
@@ -8976,8 +8976,8 @@ var FindController = class {
8976
8976
  this.notify();
8977
8977
  }
8978
8978
  /** The replacement text. Never emitted on its own — a replacement is not a search. */
8979
- setReplaceText(text6) {
8980
- this.replaceText = text6;
8979
+ setReplaceText(text7) {
8980
+ this.replaceText = text7;
8981
8981
  this.notify();
8982
8982
  }
8983
8983
  /** Show or hide the replace row. */
@@ -9626,7 +9626,7 @@ function findRuntimeComponents(controller) {
9626
9626
  function FindPanelView({ controller, showResults, placeholder }) {
9627
9627
  useSyncExternalStore7(controller.subscribe, controller.getState, controller.getState);
9628
9628
  const view = findViewState(controller, { showResults, placeholder });
9629
- const ui6 = (intent) => applyFindUi(controller, intent);
9629
+ const ui7 = (intent) => applyFindUi(controller, intent);
9630
9630
  const renderRow = (row2) => /* @__PURE__ */ jsx9(
9631
9631
  Row3,
9632
9632
  {
@@ -9634,8 +9634,8 @@ function FindPanelView({ controller, showResults, placeholder }) {
9634
9634
  selected: row2.current,
9635
9635
  meta: row2.location,
9636
9636
  label: /* @__PURE__ */ jsx9(Highlight, { segments: row2.segments }),
9637
- onClick: () => ui6({ kind: "select", matchId: row2.id }),
9638
- onContextMenuAt: (x, y) => ui6({ kind: "contextMenu", matchId: row2.id, x, y })
9637
+ onClick: () => ui7({ kind: "select", matchId: row2.id }),
9638
+ onContextMenuAt: (x, y) => ui7({ kind: "contextMenu", matchId: row2.id, x, y })
9639
9639
  },
9640
9640
  row2.id
9641
9641
  );
@@ -9643,11 +9643,11 @@ function FindPanelView({ controller, showResults, placeholder }) {
9643
9643
  /* @__PURE__ */ jsx9(
9644
9644
  Toolbar4,
9645
9645
  {
9646
- left: /* @__PURE__ */ jsx9(SearchField4, { value: view.text, placeholder: view.placeholder, onChange: (value) => ui6({ kind: "text", value }) }),
9646
+ left: /* @__PURE__ */ jsx9(SearchField4, { value: view.text, placeholder: view.placeholder, onChange: (value) => ui7({ kind: "text", value }) }),
9647
9647
  right: /* @__PURE__ */ jsxs8(ToolbarGroup2, { end: true, children: [
9648
- /* @__PURE__ */ jsx9(IconButton6, { label: "Match case", active: view.caseSensitive, icon: /* @__PURE__ */ jsx9(CaseIcon, { size: 12 }), onClick: () => ui6({ kind: "option", option: "caseSensitive" }) }),
9649
- /* @__PURE__ */ jsx9(IconButton6, { label: "Whole word", active: view.wholeWord, icon: /* @__PURE__ */ jsx9(WordIcon, { size: 12 }), onClick: () => ui6({ kind: "option", option: "wholeWord" }) }),
9650
- /* @__PURE__ */ jsx9(IconButton6, { label: "Regular expression", active: view.regex, icon: /* @__PURE__ */ jsx9(RegexIcon, { size: 12 }), onClick: () => ui6({ kind: "option", option: "regex" }) })
9648
+ /* @__PURE__ */ jsx9(IconButton6, { label: "Match case", active: view.caseSensitive, icon: /* @__PURE__ */ jsx9(CaseIcon, { size: 12 }), onClick: () => ui7({ kind: "option", option: "caseSensitive" }) }),
9649
+ /* @__PURE__ */ jsx9(IconButton6, { label: "Whole word", active: view.wholeWord, icon: /* @__PURE__ */ jsx9(WordIcon, { size: 12 }), onClick: () => ui7({ kind: "option", option: "wholeWord" }) }),
9650
+ /* @__PURE__ */ jsx9(IconButton6, { label: "Regular expression", active: view.regex, icon: /* @__PURE__ */ jsx9(RegexIcon, { size: 12 }), onClick: () => ui7({ kind: "option", option: "regex" }) })
9651
9651
  ] })
9652
9652
  }
9653
9653
  ),
@@ -9662,9 +9662,9 @@ function FindPanelView({ controller, showResults, placeholder }) {
9662
9662
  view.countShown ? /* @__PURE__ */ jsx9(Text5, { role: "number", children: view.counter }) : null
9663
9663
  ] }),
9664
9664
  right: /* @__PURE__ */ jsxs8(ToolbarGroup2, { end: true, children: [
9665
- /* @__PURE__ */ jsx9(IconButton6, { label: "Previous match", disabled: !view.hasMatches, icon: /* @__PURE__ */ jsx9(Chevron, { direction: "up", size: 12 }), onClick: () => ui6({ kind: "previous" }) }),
9666
- /* @__PURE__ */ jsx9(IconButton6, { label: "Next match", disabled: !view.hasMatches, icon: /* @__PURE__ */ jsx9(Chevron, { direction: "down", size: 12 }), onClick: () => ui6({ kind: "next" }) }),
9667
- /* @__PURE__ */ jsx9(TextButton3, { onClick: () => ui6({ kind: "toggleReplace" }), children: view.replaceLabel })
9665
+ /* @__PURE__ */ jsx9(IconButton6, { label: "Previous match", disabled: !view.hasMatches, icon: /* @__PURE__ */ jsx9(Chevron, { direction: "up", size: 12 }), onClick: () => ui7({ kind: "previous" }) }),
9666
+ /* @__PURE__ */ jsx9(IconButton6, { label: "Next match", disabled: !view.hasMatches, icon: /* @__PURE__ */ jsx9(Chevron, { direction: "down", size: 12 }), onClick: () => ui7({ kind: "next" }) }),
9667
+ /* @__PURE__ */ jsx9(TextButton3, { onClick: () => ui7({ kind: "toggleReplace" }), children: view.replaceLabel })
9668
9668
  ] })
9669
9669
  }
9670
9670
  ),
@@ -9672,10 +9672,10 @@ function FindPanelView({ controller, showResults, placeholder }) {
9672
9672
  view.replaceVisible ? /* @__PURE__ */ jsx9(
9673
9673
  Toolbar4,
9674
9674
  {
9675
- left: /* @__PURE__ */ jsx9(SearchField4, { value: view.replaceText, placeholder: "Replace with", onChange: (value) => ui6({ kind: "replaceText", value }) }),
9675
+ left: /* @__PURE__ */ jsx9(SearchField4, { value: view.replaceText, placeholder: "Replace with", onChange: (value) => ui7({ kind: "replaceText", value }) }),
9676
9676
  right: /* @__PURE__ */ jsxs8(ToolbarGroup2, { end: true, children: [
9677
- /* @__PURE__ */ jsx9(TextButton3, { disabled: !view.canReplace, onClick: () => ui6({ kind: "replaceCurrent" }), children: "Replace" }),
9678
- /* @__PURE__ */ jsx9(TextButton3, { disabled: !view.canReplace, onClick: () => ui6({ kind: "replaceAll" }), children: view.allLabel })
9677
+ /* @__PURE__ */ jsx9(TextButton3, { disabled: !view.canReplace, onClick: () => ui7({ kind: "replaceCurrent" }), children: "Replace" }),
9678
+ /* @__PURE__ */ jsx9(TextButton3, { disabled: !view.canReplace, onClick: () => ui7({ kind: "replaceAll" }), children: view.allLabel })
9679
9679
  ] })
9680
9680
  }
9681
9681
  ) : null,
@@ -10333,279 +10333,290 @@ var commentsManifest = {
10333
10333
  import { useSyncExternalStore as useSyncExternalStore8 } from "react";
10334
10334
  import {
10335
10335
  Badge as Badge6,
10336
+ Column as Column6,
10336
10337
  EmptyState as EmptyState6,
10338
+ Input as Input4,
10339
+ Message,
10340
+ Note,
10341
+ Quote,
10337
10342
  ScrollArea as ScrollArea5,
10338
10343
  Section as Section4,
10339
10344
  SegmentedControl as SegmentedControl2,
10345
+ Stack as Stack4,
10340
10346
  StatusBar as StatusBar6,
10347
+ Text as Text6,
10348
+ TextArea as TextArea2,
10341
10349
  TextButton as TextButton4,
10350
+ ThreadCard,
10342
10351
  Toolbar as Toolbar5,
10343
10352
  ToolbarGroup as ToolbarGroup3
10344
10353
  } from "@xenosystem/components/chrome";
10345
- import { Fragment as Fragment7, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
10354
+
10355
+ // src/edit/comments/declaration.ts
10346
10356
  var ANCHOR_BADGE = {
10347
10357
  live: null,
10348
- stale: {
10349
- label: "edited",
10350
- tone: "warning",
10351
- title: "The content this comment is about has changed since it was written."
10352
- },
10353
- orphaned: {
10354
- label: "content deleted",
10355
- tone: "error",
10356
- title: "The content this comment was about no longer exists. The comment is kept."
10358
+ stale: { label: "edited", tone: "warning", title: "The content this comment is about has changed since it was written." },
10359
+ orphaned: { label: "content deleted", tone: "error", title: "The content this comment was about no longer exists. The comment is kept." }
10360
+ };
10361
+ function commentsViewState(c, options = {}) {
10362
+ const s = c.getState();
10363
+ const now = c.clock();
10364
+ const showResolved = options.showResolved ?? true;
10365
+ const allowReactions = options.allowReactions ?? false;
10366
+ const me = s.currentUser?.id ?? "";
10367
+ const thread = (v) => {
10368
+ const t = v.thread;
10369
+ const badge = ANCHOR_BADGE[v.anchorState];
10370
+ const replying = s.replyingTo === t.id;
10371
+ return {
10372
+ id: t.id,
10373
+ active: v.active,
10374
+ resolved: t.resolved === true,
10375
+ hasAnchorBadge: badge !== null,
10376
+ anchorLabel: badge?.label,
10377
+ anchorTone: badge?.tone ?? "neutral",
10378
+ anchorTitle: badge?.title,
10379
+ age: formatAge(t.createdAt, now),
10380
+ hasQuote: !!t.anchorLabel,
10381
+ quote: t.anchorLabel,
10382
+ orphaned: v.anchorState === "orphaned",
10383
+ messages: v.messages.map((m) => ({ id: m.id, author: m.authorName ?? m.authorId, age: formatAge(m.createdAt, now), edited: !!m.editedAt, body: m.body })),
10384
+ hasReactions: allowReactions && !!t.reactions?.length,
10385
+ reactions: allowReactions ? (t.reactions ?? []).map((r) => ({ threadId: t.id, emoji: r.emoji, label: `${r.emoji} ${r.userIds.length}`, mine: r.userIds.includes(me), reactTo: !r.userIds.includes(me) })) : [],
10386
+ replying,
10387
+ replyDraft: replying ? s.replyDraft : "",
10388
+ idle: !replying,
10389
+ resolveLabel: t.resolved ? "Reopen" : "Resolve",
10390
+ resolveTo: !t.resolved
10391
+ };
10392
+ };
10393
+ const open = s.threads.filter((v) => !v.thread.resolved).map(thread);
10394
+ const resolved = s.threads.filter((v) => v.thread.resolved).map(thread);
10395
+ const o = s.totals.orphaned;
10396
+ return {
10397
+ show: s.filter.show ?? "open",
10398
+ shows: [{ value: "open", label: "Open" }, { value: "resolved", label: "Resolved" }, { value: "all", label: "All" }],
10399
+ sort: s.sort,
10400
+ sorts: [{ value: "document", label: "Doc" }, { value: "newest", label: "New" }, { value: "oldest", label: "Old" }],
10401
+ orphanedNotice: o > 0 ? `${o} comment${o === 1 ? "" : "s"} lost the content${o === 1 ? " it was" : " they were"} about.` : void 0,
10402
+ orphanedHidden: o > 0 && s.filter.includeOrphaned === false,
10403
+ filteredEmpty: s.threads.length === 0 && s.filteredEmpty,
10404
+ filteredHint: `${s.totals.all} comment${s.totals.all === 1 ? "" : "s"} on this document.`,
10405
+ empty: s.threads.length === 0 && !s.filteredEmpty,
10406
+ emptyHint: options.emptyHint ?? "Select something and add a comment.",
10407
+ listed: s.threads.length > 0,
10408
+ open,
10409
+ hasResolved: showResolved && resolved.length > 0,
10410
+ resolvedTitle: `Resolved (${resolved.length})`,
10411
+ resolved: showResolved ? resolved : [],
10412
+ draft: s.draft,
10413
+ placeholder: s.activeAnchor === null ? "Select something in the document to comment on it" : "Add a comment\u2026",
10414
+ composerDisabled: s.activeAnchor === null,
10415
+ commentDisabled: s.activeAnchor === null || s.draft.trim() === "",
10416
+ countLabel: `${s.totals.open} open`,
10417
+ staleLabel: s.totals.stale > 0 ? `${s.totals.stale} edited` : void 0
10418
+ };
10419
+ }
10420
+ function applyCommentsUi(c, intent) {
10421
+ switch (intent.kind) {
10422
+ case "show":
10423
+ c.setFilter({ show: intent.show });
10424
+ break;
10425
+ case "sort":
10426
+ c.setSort(intent.sort);
10427
+ break;
10428
+ case "showOrphaned":
10429
+ c.setFilter({ includeOrphaned: true });
10430
+ break;
10431
+ case "activate":
10432
+ c.activate(intent.id);
10433
+ break;
10434
+ case "contextMenu":
10435
+ c.requestContextMenu(intent.id, intent.x, intent.y);
10436
+ break;
10437
+ case "react":
10438
+ c.react(intent.id, intent.emoji, intent.on);
10439
+ break;
10440
+ case "reply":
10441
+ c.reply(intent.id);
10442
+ break;
10443
+ case "replyDraft":
10444
+ c.setReplyDraft(intent.value);
10445
+ break;
10446
+ case "replyKey":
10447
+ if (intent.key === "Enter" && !intent.shift) c.reply(intent.id);
10448
+ else if (intent.key === "Escape") c.setReplyingTo(null);
10449
+ break;
10450
+ case "startReply":
10451
+ c.setReplyingTo(intent.id);
10452
+ break;
10453
+ case "resolve":
10454
+ c.resolve(intent.id, intent.resolved);
10455
+ break;
10456
+ case "remove":
10457
+ c.remove(intent.id);
10458
+ break;
10459
+ case "draft":
10460
+ c.setDraft(intent.value);
10461
+ break;
10462
+ case "draftKey":
10463
+ if (intent.key === "Enter" && (intent.ctrl || intent.meta)) c.create();
10464
+ break;
10465
+ case "create":
10466
+ c.create();
10467
+ break;
10357
10468
  }
10469
+ }
10470
+ var commentsViewManifest = {
10471
+ id: commentsManifest.id,
10472
+ version: "1.0.0",
10473
+ title: "Comments",
10474
+ category: "edit",
10475
+ layout: { slot: "side" },
10476
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The CommentsViewModel the runtime derives from the controller on every draw." }],
10477
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, \u2026 } \u2014 applied by the runtime to the controller." }],
10478
+ commands: []
10358
10479
  };
10359
- function ThreadCard({
10360
- view,
10361
- controller,
10362
- allowReactions,
10363
- now
10364
- }) {
10365
- const { thread, anchorState } = view;
10366
- const badge = ANCHOR_BADGE[anchorState];
10367
- const replying = controller.getState().replyingTo === thread.id;
10368
- const orphaned = anchorState === "orphaned";
10369
- return /* @__PURE__ */ jsx10(
10370
- "div",
10480
+ var EMPTY6 = { show: "open", shows: [], sort: "document", sorts: [], orphanedHidden: false, filteredEmpty: false, filteredHint: "", empty: true, emptyHint: "Select something and add a comment.", listed: false, open: [], hasResolved: false, resolvedTitle: "", resolved: [], draft: "", placeholder: "", composerDisabled: true, commentDisabled: true, countLabel: "0 open" };
10481
+ var BEHAVIOURS6 = {
10482
+ "edit/commentsPanelView": (inputs) => inputs.view ?? EMPTY6
10483
+ };
10484
+ var COMMENTS_RUNTIME_UNITS = [];
10485
+ var text6 = (b) => ({ component: "text", props: { text: b } });
10486
+ var ui6 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
10487
+ var threadCard = (input) => ({
10488
+ component: "chrome/ThreadCard",
10489
+ each: { input, key: "id" },
10490
+ props: { active: { item: "active" }, resolved: { item: "resolved" } },
10491
+ on: { onContextMenuAt: ui6("contextMenu", { id: { item: "id" }, x: { arg: "", index: 0 }, y: { arg: "", index: 1 } }) },
10492
+ children: [
10493
+ {
10494
+ component: "chrome/Stack",
10495
+ props: { direction: { value: "row" }, gap: { value: 4 }, pad: { value: "none" } },
10496
+ children: [
10497
+ { component: "chrome/Badge", when: { item: "hasAnchorBadge" }, props: { tone: { item: "anchorTone" }, title: { item: "anchorTitle" } }, children: [text6({ item: "anchorLabel" })] },
10498
+ { component: "chrome/Badge", when: { item: "resolved" }, children: [text6({ value: "resolved" })] },
10499
+ { component: "chrome/Text", props: { role: { value: "caption" } }, children: [text6({ item: "age" })] }
10500
+ ]
10501
+ },
10502
+ { component: "chrome/Quote", when: { item: "hasQuote" }, props: { orphaned: { item: "orphaned" } }, on: { onActivate: ui6("activate", { id: { item: "id" } }) }, children: [text6({ item: "quote" })] },
10503
+ { component: "chrome/Message", each: { item: "messages", key: "id" }, props: { author: { item: "author" }, age: { item: "age" }, edited: { item: "edited" } }, children: [text6({ item: "body" })] },
10371
10504
  {
10372
- style: {
10373
- borderBottom: "1px solid var(--xeno-border-subtle)",
10374
- // Resolved dims. Orphaned does NOT — a comment whose content was deleted is the one most
10375
- // likely to matter, and dimming it would finish the job the deletion started.
10376
- opacity: thread.resolved ? 0.55 : 1,
10377
- background: view.active ? "var(--xeno-border-subtle)" : void 0
10505
+ component: "chrome/Stack",
10506
+ when: { item: "hasReactions" },
10507
+ props: { direction: { value: "row" }, gap: { value: 4 }, pad: { value: "none" } },
10508
+ children: [{ component: "chrome/TextButton", each: { item: "reactions", key: "emoji" }, on: { onClick: ui6("react", { id: { item: "threadId" }, emoji: { item: "emoji" }, on: { item: "reactTo" } }) }, children: [text6({ item: "label" })] }]
10509
+ },
10510
+ {
10511
+ component: "chrome/Stack",
10512
+ when: { item: "replying" },
10513
+ props: { direction: { value: "row" }, gap: { value: 4 }, pad: { value: "none" } },
10514
+ children: [
10515
+ { component: "chrome/Input", props: { value: { item: "replyDraft" }, placeholder: { value: "Reply" }, autoFocus: { value: true }, label: { value: "Reply" } }, on: { onChange: ui6("replyDraft", { value: { arg: "" } }), onKeyDown: ui6("replyKey", { id: { item: "id" }, key: { arg: "key" }, shift: { arg: "shiftKey" } }) } },
10516
+ { component: "chrome/TextButton", on: { onClick: ui6("reply", { id: { item: "id" } }) }, children: [text6({ value: "Send" })] }
10517
+ ]
10518
+ },
10519
+ {
10520
+ component: "chrome/Stack",
10521
+ when: { item: "idle" },
10522
+ props: { direction: { value: "row" }, gap: { value: 6 }, pad: { value: "none" } },
10523
+ children: [
10524
+ { component: "chrome/TextButton", on: { onClick: ui6("startReply", { id: { item: "id" } }) }, children: [text6({ value: "Reply" })] },
10525
+ { component: "chrome/TextButton", on: { onClick: ui6("resolve", { id: { item: "id" }, resolved: { item: "resolveTo" } }) }, children: [text6({ item: "resolveLabel" })] },
10526
+ { component: "chrome/TextButton", on: { onClick: ui6("remove", { id: { item: "id" } }) }, children: [text6({ value: "Delete" })] }
10527
+ ]
10528
+ }
10529
+ ]
10530
+ });
10531
+ var COMMENTS_DECLARATION = {
10532
+ format: "xeno.block@1",
10533
+ manifest: commentsViewManifest,
10534
+ behaviour: "edit/commentsPanelView",
10535
+ composition: {
10536
+ component: "chrome/Column",
10537
+ children: [
10538
+ {
10539
+ component: "chrome/Toolbar",
10540
+ props: {
10541
+ left: { node: { component: "chrome/SegmentedControl", props: { label: { value: "Show" }, options: { input: "view.shows" }, value: { input: "view.show" } }, on: { onChange: ui6("show", { show: { arg: "" } }) } } },
10542
+ right: { node: { component: "chrome/ToolbarGroup", props: { end: { value: true } }, children: [{ component: "chrome/SegmentedControl", props: { label: { value: "Sort" }, options: { input: "view.sorts" }, value: { input: "view.sort" } }, on: { onChange: ui6("sort", { sort: { arg: "" } }) } }] } }
10543
+ }
10378
10544
  },
10379
- onContextMenu: (event) => {
10380
- event.preventDefault();
10381
- controller.requestContextMenu(thread.id, event.clientX, event.clientY);
10545
+ {
10546
+ component: "chrome/Note",
10547
+ when: { input: "view.orphanedNotice" },
10548
+ children: [text6({ input: "view.orphanedNotice" }), { component: "chrome/TextButton", when: { input: "view.orphanedHidden" }, on: { onClick: ui6("showOrphaned") }, children: [text6({ value: "Show" })] }]
10382
10549
  },
10383
- children: /* @__PURE__ */ jsxs9("div", { style: { padding: "6px 8px", display: "flex", flexDirection: "column", gap: 4 }, children: [
10384
- /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
10385
- badge ? /* @__PURE__ */ jsx10(Badge6, { tone: badge.tone, title: badge.title, children: badge.label }) : null,
10386
- thread.resolved ? /* @__PURE__ */ jsx10(Badge6, { children: "resolved" }) : null,
10387
- /* @__PURE__ */ jsx10("span", { style: { marginLeft: "auto", fontSize: 9, opacity: 0.4 }, children: formatAge(thread.createdAt, now) })
10388
- ] }),
10389
- thread.anchorLabel ? /* @__PURE__ */ jsx10(
10390
- "button",
10391
- {
10392
- type: "button",
10393
- onClick: () => controller.activate(thread.id),
10394
- style: {
10395
- textAlign: "left",
10396
- fontSize: 10,
10397
- opacity: orphaned ? 0.4 : 0.6,
10398
- borderLeft: "2px solid var(--xeno-control)",
10399
- paddingLeft: 6,
10400
- textDecoration: orphaned ? "line-through" : void 0,
10401
- background: "none",
10402
- border: "none",
10403
- borderLeftWidth: 2,
10404
- borderLeftStyle: "solid",
10405
- borderLeftColor: "var(--xeno-control)",
10406
- cursor: "pointer",
10407
- font: "inherit"
10408
- },
10409
- children: thread.anchorLabel
10410
- }
10411
- ) : null,
10412
- view.messages.map((message) => /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexDirection: "column", gap: 1 }, children: [
10413
- /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "baseline", gap: 4 }, children: [
10414
- /* @__PURE__ */ jsx10("span", { style: { fontSize: 10, fontWeight: 600 }, children: message.authorName ?? message.authorId }),
10415
- /* @__PURE__ */ jsx10("span", { style: { fontSize: 9, opacity: 0.35 }, children: formatAge(message.createdAt, now) }),
10416
- message.editedAt ? /* @__PURE__ */ jsx10("span", { style: { fontSize: 9, opacity: 0.3 }, children: "edited" }) : null
10417
- ] }),
10418
- /* @__PURE__ */ jsx10("p", { style: { fontSize: 11, opacity: 0.75, margin: 0, whiteSpace: "pre-wrap" }, children: message.body })
10419
- ] }, message.id)),
10420
- allowReactions && thread.reactions?.length ? /* @__PURE__ */ jsx10("div", { style: { display: "flex", gap: 4 }, children: thread.reactions.map((reaction) => /* @__PURE__ */ jsxs9(
10421
- TextButton4,
10422
- {
10423
- onClick: () => controller.react(
10424
- thread.id,
10425
- reaction.emoji,
10426
- !reaction.userIds.includes(controller.getState().currentUser?.id ?? "")
10427
- ),
10428
- children: [
10429
- reaction.emoji,
10430
- " ",
10431
- reaction.userIds.length
10432
- ]
10433
- },
10434
- reaction.emoji
10435
- )) }) : null,
10436
- replying ? /* @__PURE__ */ jsxs9("div", { style: { display: "flex", gap: 4 }, children: [
10437
- /* @__PURE__ */ jsx10(
10438
- "input",
10439
- {
10440
- value: controller.getState().replyDraft,
10441
- onChange: (event) => controller.setReplyDraft(event.target.value),
10442
- onKeyDown: (event) => {
10443
- if (event.key === "Enter" && !event.shiftKey) {
10444
- event.preventDefault();
10445
- controller.reply(thread.id);
10446
- } else if (event.key === "Escape") {
10447
- controller.setReplyingTo(null);
10448
- }
10449
- },
10450
- placeholder: "Reply",
10451
- autoFocus: true,
10452
- style: {
10453
- flex: 1,
10454
- fontSize: 10,
10455
- background: "var(--xeno-canvas)",
10456
- border: "1px solid var(--xeno-border)",
10457
- borderRadius: 2,
10458
- padding: "2px 4px",
10459
- color: "inherit",
10460
- outline: "none"
10461
- }
10462
- }
10463
- ),
10464
- /* @__PURE__ */ jsx10(TextButton4, { onClick: () => controller.reply(thread.id), children: "Send" })
10465
- ] }) : /* @__PURE__ */ jsxs9("div", { style: { display: "flex", gap: 6 }, children: [
10466
- /* @__PURE__ */ jsx10(TextButton4, { onClick: () => controller.setReplyingTo(thread.id), children: "Reply" }),
10467
- /* @__PURE__ */ jsx10(TextButton4, { onClick: () => controller.resolve(thread.id, !thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
10468
- /* @__PURE__ */ jsx10(TextButton4, { onClick: () => controller.remove(thread.id), children: "Delete" })
10469
- ] })
10470
- ] })
10471
- }
10472
- );
10473
- }
10474
- function CommentsPanelView({
10475
- controller,
10476
- showResolved = true,
10477
- allowReactions = false,
10478
- emptyHint
10479
- }) {
10480
- const state = useSyncExternalStore8(controller.subscribe, controller.getState, controller.getState);
10481
- const now = controller.clock();
10482
- const open = state.threads.filter((t) => !t.thread.resolved);
10483
- const resolved = state.threads.filter((t) => t.thread.resolved);
10484
- return /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
10550
+ {
10551
+ component: "chrome/ScrollArea",
10552
+ children: [
10553
+ { component: "chrome/EmptyState", when: { input: "view.filteredEmpty" }, props: { title: { value: "Nothing matches this filter" }, hint: { input: "view.filteredHint" } } },
10554
+ { component: "chrome/EmptyState", when: { input: "view.empty" }, props: { title: { value: "No comments" }, hint: { input: "view.emptyHint" } } },
10555
+ threadCard("view.open"),
10556
+ { component: "chrome/Section", when: { input: "view.hasResolved" }, props: { title: { input: "view.resolvedTitle" }, flush: { value: true } }, children: [threadCard("view.resolved")] }
10557
+ ]
10558
+ },
10559
+ {
10560
+ component: "chrome/Stack",
10561
+ props: { gap: { value: 2 }, pad: { value: "gutter" }, className: { value: "xeno-comments-composer" } },
10562
+ children: [
10563
+ { component: "chrome/TextArea", props: { value: { input: "view.draft" }, placeholder: { input: "view.placeholder" }, disabled: { input: "view.composerDisabled" }, rows: { value: 2 }, label: { value: "New comment" } }, on: { onChange: ui6("draft", { value: { arg: "" } }), onKeyDown: ui6("draftKey", { key: { arg: "key" }, ctrl: { arg: "ctrlKey" }, meta: { arg: "metaKey" } }) } },
10564
+ { component: "chrome/Toolbar", props: { divided: { value: false }, right: { node: { component: "chrome/ToolbarGroup", props: { end: { value: true } }, children: [{ component: "chrome/TextButton", props: { strong: { value: true }, disabled: { input: "view.commentDisabled" } }, on: { onClick: ui6("create") }, children: [text6({ value: "Comment" })] }] } } } }
10565
+ ]
10566
+ },
10567
+ { component: "chrome/StatusBar", props: { left: { input: "view.countLabel" }, right: { input: "view.staleLabel" } } }
10568
+ ]
10569
+ }
10570
+ };
10571
+
10572
+ // src/edit/comments/react/CommentsPanelView.tsx
10573
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
10574
+ function CommentsPanelView({ controller, ...options }) {
10575
+ useSyncExternalStore8(controller.subscribe, controller.getState, controller.getState);
10576
+ const view = commentsViewState(controller, options);
10577
+ const ui7 = (intent) => applyCommentsUi(controller, intent);
10578
+ const card = (t) => /* @__PURE__ */ jsxs9(ThreadCard, { active: t.active, resolved: t.resolved, onContextMenuAt: (x, y) => ui7({ kind: "contextMenu", id: t.id, x, y }), children: [
10579
+ /* @__PURE__ */ jsxs9(Stack4, { direction: "row", gap: 4, pad: "none", children: [
10580
+ t.hasAnchorBadge ? /* @__PURE__ */ jsx10(Badge6, { tone: t.anchorTone, title: t.anchorTitle, children: t.anchorLabel }) : null,
10581
+ t.resolved ? /* @__PURE__ */ jsx10(Badge6, { children: "resolved" }) : null,
10582
+ /* @__PURE__ */ jsx10(Text6, { role: "caption", children: t.age })
10583
+ ] }),
10584
+ t.hasQuote ? /* @__PURE__ */ jsx10(Quote, { orphaned: t.orphaned, onActivate: () => ui7({ kind: "activate", id: t.id }), children: t.quote }) : null,
10585
+ t.messages.map((m) => /* @__PURE__ */ jsx10(Message, { author: m.author, age: m.age, edited: m.edited, children: m.body }, m.id)),
10586
+ t.hasReactions ? /* @__PURE__ */ jsx10(Stack4, { direction: "row", gap: 4, pad: "none", children: t.reactions.map((r) => /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui7({ kind: "react", id: r.threadId, emoji: r.emoji, on: r.reactTo }), children: r.label }, r.emoji)) }) : null,
10587
+ t.replying ? /* @__PURE__ */ jsxs9(Stack4, { direction: "row", gap: 4, pad: "none", children: [
10588
+ /* @__PURE__ */ jsx10(Input4, { value: t.replyDraft, placeholder: "Reply", autoFocus: true, label: "Reply", onChange: (value) => ui7({ kind: "replyDraft", value }), onKeyDown: (e) => ui7({ kind: "replyKey", id: t.id, key: e.key, shift: e.shiftKey }) }),
10589
+ /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui7({ kind: "reply", id: t.id }), children: "Send" })
10590
+ ] }) : null,
10591
+ t.idle ? /* @__PURE__ */ jsxs9(Stack4, { direction: "row", gap: 6, pad: "none", children: [
10592
+ /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui7({ kind: "startReply", id: t.id }), children: "Reply" }),
10593
+ /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui7({ kind: "resolve", id: t.id, resolved: t.resolveTo }), children: t.resolveLabel }),
10594
+ /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui7({ kind: "remove", id: t.id }), children: "Delete" })
10595
+ ] }) : null
10596
+ ] }, t.id);
10597
+ return /* @__PURE__ */ jsxs9(Column6, { children: [
10485
10598
  /* @__PURE__ */ jsx10(
10486
10599
  Toolbar5,
10487
10600
  {
10488
- left: /* @__PURE__ */ jsx10(
10489
- SegmentedControl2,
10490
- {
10491
- label: "Show",
10492
- value: state.filter.show ?? "open",
10493
- onChange: (value) => controller.setFilter({ show: value }),
10494
- options: [
10495
- { label: "Open", value: "open" },
10496
- { label: "Resolved", value: "resolved" },
10497
- { label: "All", value: "all" }
10498
- ]
10499
- }
10500
- ),
10501
- right: /* @__PURE__ */ jsx10(ToolbarGroup3, { end: true, children: /* @__PURE__ */ jsx10(
10502
- SegmentedControl2,
10503
- {
10504
- label: "Sort",
10505
- value: state.sort,
10506
- onChange: (value) => controller.setSort(value),
10507
- options: [
10508
- { label: "Doc", value: "document" },
10509
- { label: "New", value: "newest" },
10510
- { label: "Old", value: "oldest" }
10511
- ]
10512
- }
10513
- ) })
10601
+ left: /* @__PURE__ */ jsx10(SegmentedControl2, { label: "Show", options: view.shows, value: view.show, onChange: (show) => ui7({ kind: "show", show }) }),
10602
+ right: /* @__PURE__ */ jsx10(ToolbarGroup3, { end: true, children: /* @__PURE__ */ jsx10(SegmentedControl2, { label: "Sort", options: view.sorts, value: view.sort, onChange: (sort) => ui7({ kind: "sort", sort }) }) })
10514
10603
  }
10515
10604
  ),
10516
- state.totals.orphaned > 0 ? /* @__PURE__ */ jsxs9("div", { style: { padding: "2px 8px", fontSize: 9, opacity: 0.6 }, children: [
10517
- state.totals.orphaned,
10518
- " comment",
10519
- state.totals.orphaned === 1 ? "" : "s",
10520
- " lost the content",
10521
- state.totals.orphaned === 1 ? " it was" : " they were",
10522
- " about.",
10523
- state.filter.includeOrphaned === false ? /* @__PURE__ */ jsx10(TextButton4, { onClick: () => controller.setFilter({ includeOrphaned: true }), children: "Show" }) : null
10605
+ view.orphanedNotice ? /* @__PURE__ */ jsxs9(Note, { children: [
10606
+ view.orphanedNotice,
10607
+ view.orphanedHidden ? /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui7({ kind: "showOrphaned" }), children: "Show" }) : null
10524
10608
  ] }) : null,
10525
- /* @__PURE__ */ jsx10(ScrollArea5, { children: state.threads.length === 0 ? state.filteredEmpty ? (
10526
- // NOT "no comments". There are comments; this filter hides them, and telling a
10527
- // reviewer their feedback is gone is worse than telling them nothing.
10528
- /* @__PURE__ */ jsx10(
10529
- EmptyState6,
10530
- {
10531
- title: "Nothing matches this filter",
10532
- hint: `${state.totals.all} comment${state.totals.all === 1 ? "" : "s"} on this document.`
10533
- }
10534
- )
10535
- ) : /* @__PURE__ */ jsx10(
10536
- EmptyState6,
10537
- {
10538
- title: "No comments",
10539
- hint: emptyHint ?? "Select something and add a comment."
10540
- }
10541
- ) : /* @__PURE__ */ jsxs9(Fragment7, { children: [
10542
- open.map((view) => /* @__PURE__ */ jsx10(
10543
- ThreadCard,
10544
- {
10545
- view,
10546
- controller,
10547
- allowReactions,
10548
- now
10549
- },
10550
- view.thread.id
10551
- )),
10552
- showResolved && resolved.length > 0 ? /* @__PURE__ */ jsx10(Section4, { title: `Resolved (${resolved.length})`, flush: true, children: resolved.map((view) => /* @__PURE__ */ jsx10(
10553
- ThreadCard,
10554
- {
10555
- view,
10556
- controller,
10557
- allowReactions,
10558
- now
10559
- },
10560
- view.thread.id
10561
- )) }) : null
10562
- ] }) }),
10563
- /* @__PURE__ */ jsxs9("div", { style: { borderTop: "1px solid var(--xeno-border-subtle)", padding: 6 }, children: [
10564
- /* @__PURE__ */ jsx10(
10565
- "textarea",
10566
- {
10567
- value: state.draft,
10568
- onChange: (event) => controller.setDraft(event.target.value),
10569
- onKeyDown: (event) => {
10570
- if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
10571
- event.preventDefault();
10572
- controller.create();
10573
- }
10574
- },
10575
- placeholder: state.activeAnchor === null ? "Select something in the document to comment on it" : "Add a comment\u2026",
10576
- disabled: state.activeAnchor === null,
10577
- rows: 2,
10578
- style: {
10579
- width: "100%",
10580
- fontSize: 11,
10581
- background: "var(--xeno-canvas)",
10582
- border: "1px solid var(--xeno-border)",
10583
- borderRadius: 2,
10584
- padding: 4,
10585
- color: "inherit",
10586
- outline: "none",
10587
- resize: "none",
10588
- opacity: state.activeAnchor === null ? 0.5 : 1
10589
- }
10590
- }
10591
- ),
10592
- /* @__PURE__ */ jsx10("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 2 }, children: /* @__PURE__ */ jsx10(
10593
- TextButton4,
10594
- {
10595
- strong: true,
10596
- disabled: state.activeAnchor === null || state.draft.trim() === "",
10597
- onClick: () => controller.create(),
10598
- children: "Comment"
10599
- }
10600
- ) })
10609
+ /* @__PURE__ */ jsxs9(ScrollArea5, { children: [
10610
+ view.filteredEmpty ? /* @__PURE__ */ jsx10(EmptyState6, { title: "Nothing matches this filter", hint: view.filteredHint }) : null,
10611
+ view.empty ? /* @__PURE__ */ jsx10(EmptyState6, { title: "No comments", hint: view.emptyHint }) : null,
10612
+ view.open.map(card),
10613
+ view.hasResolved ? /* @__PURE__ */ jsx10(Section4, { title: view.resolvedTitle, flush: true, children: view.resolved.map(card) }) : null
10601
10614
  ] }),
10602
- /* @__PURE__ */ jsx10(
10603
- StatusBar6,
10604
- {
10605
- left: `${state.totals.open} open`,
10606
- right: state.totals.stale > 0 ? `${state.totals.stale} edited` : void 0
10607
- }
10608
- )
10615
+ /* @__PURE__ */ jsxs9(Stack4, { gap: 2, pad: "gutter", className: "xeno-comments-composer", children: [
10616
+ /* @__PURE__ */ jsx10(TextArea2, { value: view.draft, placeholder: view.placeholder, disabled: view.composerDisabled, rows: 2, label: "New comment", onChange: (value) => ui7({ kind: "draft", value }), onKeyDown: (e) => ui7({ kind: "draftKey", key: e.key, ctrl: e.ctrlKey, meta: e.metaKey }) }),
10617
+ /* @__PURE__ */ jsx10(Toolbar5, { divided: false, right: /* @__PURE__ */ jsx10(ToolbarGroup3, { end: true, children: /* @__PURE__ */ jsx10(TextButton4, { strong: true, disabled: view.commentDisabled, onClick: () => ui7({ kind: "create" }), children: "Comment" }) }) })
10618
+ ] }),
10619
+ /* @__PURE__ */ jsx10(StatusBar6, { left: view.countLabel, right: view.staleLabel })
10609
10620
  ] });
10610
10621
  }
10611
10622
 
@@ -11773,7 +11784,7 @@ var nodegraphManifest = {
11773
11784
  // src/edit/nodegraph/react/NodeGraphPanelView.tsx
11774
11785
  import { useCallback as useCallback7, useEffect as useEffect7, useRef as useRef8, useSyncExternalStore as useSyncExternalStore9 } from "react";
11775
11786
  import { EmptyState as EmptyState7, StatusBar as StatusBar7, Toolbar as Toolbar6, ToolbarGroup as ToolbarGroup4 } from "@xenosystem/components/chrome";
11776
- import { Fragment as Fragment8, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
11787
+ import { Fragment as Fragment7, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
11777
11788
  var HEADER_H2 = 24;
11778
11789
  var PORT_ROW_H2 = 18;
11779
11790
  function NodeGraphPanelView({ controller }) {
@@ -11996,7 +12007,7 @@ function NodeRect({ node, selected, zoom }) {
11996
12007
  children: node.title
11997
12008
  }
11998
12009
  ),
11999
- node.collapsed ? null : /* @__PURE__ */ jsxs10(Fragment8, { children: [
12010
+ node.collapsed ? null : /* @__PURE__ */ jsxs10(Fragment7, { children: [
12000
12011
  node.inputs.map((port, i) => /* @__PURE__ */ jsx11(
12001
12012
  Port,
12002
12013
  {
@@ -12729,12 +12740,12 @@ var TreeController = class {
12729
12740
  }
12730
12741
  if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
12731
12742
  const fresh = now - this.typeAhead.at > this.typeAheadMs;
12732
- const text6 = (fresh ? "" : this.typeAhead.text) + event.key.toLowerCase();
12733
- this.typeAhead = { text: text6, at: now };
12743
+ const text7 = (fresh ? "" : this.typeAhead.text) + event.key.toLowerCase();
12744
+ this.typeAhead = { text: text7, at: now };
12734
12745
  const from = fresh ? current : current + 1;
12735
12746
  for (let i = 0; i < rows.length; i += 1) {
12736
12747
  const candidate = rows[(from + i) % rows.length];
12737
- if (candidate.node.label.toLowerCase().startsWith(text6)) {
12748
+ if (candidate.node.label.toLowerCase().startsWith(text7)) {
12738
12749
  this.focusedId = candidate.id;
12739
12750
  this.select([candidate.id]);
12740
12751
  return true;
@@ -12760,9 +12771,9 @@ var TreeController = class {
12760
12771
  return true;
12761
12772
  }
12762
12773
  /** Update the rename draft. */
12763
- updateRenameDraft(text6) {
12774
+ updateRenameDraft(text7) {
12764
12775
  if (!this.renaming) return;
12765
- this.renaming = { ...this.renaming, draft: text6 };
12776
+ this.renaming = { ...this.renaming, draft: text7 };
12766
12777
  this.notify();
12767
12778
  }
12768
12779
  /**
@@ -13314,7 +13325,7 @@ import {
13314
13325
  ToolbarGroup as ToolbarGroup5
13315
13326
  } from "@xenosystem/components/chrome";
13316
13327
  import { computeWindow as computeWindow3 } from "@xenosystem/tree-core";
13317
- import { Fragment as Fragment9, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
13328
+ import { Fragment as Fragment8, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
13318
13329
  function TreePanelView({
13319
13330
  controller,
13320
13331
  rowHeight = 24,
@@ -13531,7 +13542,7 @@ function TreeRowView({
13531
13542
  onPointerUp: () => {
13532
13543
  if (state.drag) controller.endDrag();
13533
13544
  },
13534
- icon: /* @__PURE__ */ jsxs11(Fragment9, { children: [
13545
+ icon: /* @__PURE__ */ jsxs11(Fragment8, { children: [
13535
13546
  twisty,
13536
13547
  /* @__PURE__ */ jsx12(NodeIcon, { icon: node.icon ?? defaultIconFor(node.kind) })
13537
13548
  ] }),
@@ -13561,7 +13572,7 @@ function TreeRowView({
13561
13572
  }
13562
13573
  ) : node.label,
13563
13574
  meta: /* @__PURE__ */ jsx12(ChildStateChip, { row: row2, controller }),
13564
- trailing: /* @__PURE__ */ jsxs11(Fragment9, { children: [
13575
+ trailing: /* @__PURE__ */ jsxs11(Fragment8, { children: [
13565
13576
  columns?.map((columnId) => /* @__PURE__ */ jsx12("span", { style: { opacity: 0.7, fontVariantNumeric: "tabular-nums" }, children: node.columns?.[columnId] ?? "" }, columnId)),
13566
13577
  node.glyphs?.map((glyph) => /* @__PURE__ */ jsx12("span", { title: glyph.title, style: { display: "flex", opacity: 0.7 }, children: /* @__PURE__ */ jsx12(NodeIcon, { icon: glyph.icon }) }, glyph.id)),
13567
13578
  node.badges?.map((badge) => (
@@ -13832,7 +13843,10 @@ export {
13832
13843
  COLOR_DECLARATION,
13833
13844
  COLOR_RUNTIME_UNITS,
13834
13845
  COLOR_SPACES,
13846
+ BEHAVIOURS6 as COMMENTS_BEHAVIOURS,
13847
+ COMMENTS_DECLARATION,
13835
13848
  COMMENTS_PANEL_ID,
13849
+ COMMENTS_RUNTIME_UNITS,
13836
13850
  CURVE_PANEL_ID,
13837
13851
  ColorPanelController,
13838
13852
  ColorPanelView,
@@ -13899,6 +13913,7 @@ export {
13899
13913
  anchorStateOf,
13900
13914
  appliedCount,
13901
13915
  applyColorUi,
13916
+ applyCommentsUi,
13902
13917
  applyFindUi,
13903
13918
  applyHistoryUi,
13904
13919
  applyInspectorUi,
@@ -13922,6 +13937,8 @@ export {
13922
13937
  colorViewState,
13923
13938
  commentsManifest,
13924
13939
  commentsPanel,
13940
+ commentsViewManifest,
13941
+ commentsViewState,
13925
13942
  commitPolicyOf,
13926
13943
  componentsForSpace,
13927
13944
  computeSurfaceSize,