@openspecui/web 3.5.1 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/assets/CanvasRenderer-DdAQFilo.js +1 -0
  2. package/dist/assets/WebGLRenderer-D0xPb_sN.js +1 -0
  3. package/dist/assets/WebGPURenderer-DOHdaBx2.js +1 -0
  4. package/dist/assets/browserAll-r2hAe-_H.js +1 -0
  5. package/dist/assets/{dist-BdfEVdSD.js → dist-BCNuV7xG.js} +1 -1
  6. package/dist/assets/dist-BIdWjo7l.js +1 -0
  7. package/dist/assets/{dist-Bhy8KkMf.js → dist-BVK0Rssu.js} +1 -1
  8. package/dist/assets/{dist-DcDz_e4d.js → dist-BozUQC43.js} +1 -1
  9. package/dist/assets/{dist-C5q6KJx5.js → dist-CDVL3YEX.js} +1 -1
  10. package/dist/assets/{dist-Bi2cQNT3.js → dist-CLV0HJPY.js} +1 -1
  11. package/dist/assets/{dist-BbID--pe.js → dist-CTzhdbTq.js} +1 -1
  12. package/dist/assets/dist-CaLzADt5.js +1 -0
  13. package/dist/assets/{dist-CwQDZ7pq.js → dist-CiOmMqf9.js} +1 -1
  14. package/dist/assets/{dist-BPxWbdzo.js → dist-CnERwHuC.js} +1 -1
  15. package/dist/assets/dist-DOlIviuv.js +1 -0
  16. package/dist/assets/{dist-CsiaRd98.js → dist-YiF0YY_U.js} +1 -1
  17. package/dist/assets/{ghostty-web-DsRWwk5O.js → ghostty-web-CZWq-tLQ.js} +1 -1
  18. package/dist/assets/{index-Cu3RSC5X.js → index-4c2vKipy.js} +257 -192
  19. package/dist/assets/index-BWGHsSSL.css +1 -0
  20. package/dist/assets/{init-CHYX1BvT.js → init-DIWiFCrW.js} +1 -1
  21. package/dist/assets/trpc-sHsseX53.js +1 -0
  22. package/dist/assets/webworkerAll-C2ADhLOV.js +1 -0
  23. package/dist/index.html +2 -2
  24. package/dist-ssg/client/.vite/ssr-manifest.json +33 -15
  25. package/dist-ssg/client/assets/CanvasRenderer-BnHuf5TM.js +1 -0
  26. package/dist-ssg/client/assets/WebGLRenderer-QYh372d6.js +1 -0
  27. package/dist-ssg/client/assets/WebGPURenderer-By0AaAH2.js +1 -0
  28. package/dist-ssg/client/assets/browserAll-DPwpu3rh.js +1 -0
  29. package/dist-ssg/client/assets/dist-BEN6F4c9.js +1 -0
  30. package/dist-ssg/client/assets/{dist-CnuoVdCe.js → dist-B_W8Dqsb.js} +1 -1
  31. package/dist-ssg/client/assets/{dist-BcmGqqhm.js → dist-BcdNCEyE.js} +1 -1
  32. package/dist-ssg/client/assets/{dist-BBxB0BsU.js → dist-Be-EJt6Y.js} +1 -1
  33. package/dist-ssg/client/assets/{dist-Cc-hzu-d.js → dist-ByMJ_rO0.js} +1 -1
  34. package/dist-ssg/client/assets/{dist-B7BRp2AD.js → dist-CMm3Nmq-.js} +1 -1
  35. package/dist-ssg/client/assets/{dist-C-b45p6-.js → dist-CcH1wf1k.js} +1 -1
  36. package/dist-ssg/client/assets/dist-D1b5s3ht.js +1 -0
  37. package/dist-ssg/client/assets/dist-DPvPfazk.js +1 -0
  38. package/dist-ssg/client/assets/{dist-D5h2ifzs.js → dist-DVuQHEv_.js} +1 -1
  39. package/dist-ssg/client/assets/{dist-CEnArSkR.js → dist-fM5ujDIS.js} +1 -1
  40. package/dist-ssg/client/assets/{dist-B0OSa9m8.js → dist-oT5Lx9wx.js} +1 -1
  41. package/dist-ssg/client/assets/{ghostty-web-Cuh3nJnk.js → ghostty-web-DSO4nFSe.js} +1 -1
  42. package/dist-ssg/client/assets/index-CGJJczvl.css +1 -0
  43. package/dist-ssg/client/assets/{index.ssg-C4AALSK6.js → index.ssg-ph-ofjeJ.js} +231 -166
  44. package/dist-ssg/client/assets/{init-DSQMJRar.js → init-DOhQsgqw.js} +1 -1
  45. package/dist-ssg/client/assets/trpc-DvRhEguu.js +1 -0
  46. package/dist-ssg/client/assets/webworkerAll-J8DER0Bd.js +1 -0
  47. package/dist-ssg/client/index.ssg.html +2 -2
  48. package/dist-ssg/server/entry-server.js +1651 -163
  49. package/package.json +1 -1
  50. package/dist/assets/CanvasRenderer-4LnzB23C.js +0 -1
  51. package/dist/assets/WebGLRenderer-C7T0ni7u.js +0 -1
  52. package/dist/assets/WebGPURenderer-Bfz3G3Ra.js +0 -1
  53. package/dist/assets/browserAll-D1GPsRxQ.js +0 -1
  54. package/dist/assets/dist-BbOkQMo2.js +0 -1
  55. package/dist/assets/dist-Cndawm3q.js +0 -1
  56. package/dist/assets/dist-tU6RymPC.js +0 -1
  57. package/dist/assets/index-DYcfk_P1.css +0 -1
  58. package/dist/assets/trpc-BwqMLlvD.js +0 -1
  59. package/dist/assets/webworkerAll-DTZDj7Ch.js +0 -1
  60. package/dist-ssg/client/assets/CanvasRenderer-BRusEeYW.js +0 -1
  61. package/dist-ssg/client/assets/WebGLRenderer-CYFxsByj.js +0 -1
  62. package/dist-ssg/client/assets/WebGPURenderer-BC6Mc9fr.js +0 -1
  63. package/dist-ssg/client/assets/browserAll-DwGvMVOE.js +0 -1
  64. package/dist-ssg/client/assets/dist-B-JL-RdI.js +0 -1
  65. package/dist-ssg/client/assets/dist-D6Kcyhv0.js +0 -1
  66. package/dist-ssg/client/assets/dist-iGOlT260.js +0 -1
  67. package/dist-ssg/client/assets/index-D3bHMPOK.css +0 -1
  68. package/dist-ssg/client/assets/trpc-38H3yLz8.js +0 -1
  69. package/dist-ssg/client/assets/webworkerAll-C6L_ZFEk.js +0 -1
@@ -38436,13 +38436,6 @@ function writeLocalStorage(layout, key = getLocalStorageKey()) {
38436
38436
  localStorage.setItem(key, JSON.stringify(layout));
38437
38437
  } catch {}
38438
38438
  }
38439
- function collapseProjectDetailLocation(location) {
38440
- const tabId = pathToTabId(location.pathname);
38441
- if (!tabId) return location;
38442
- if (location.pathname === tabId) return location;
38443
- if (tabId === "/changes" || tabId === "/specs" || tabId === "/archive") return parseHref(tabId, location.state);
38444
- return location;
38445
- }
38446
38439
  var carryActiveOnMovePlugin = ({ prevState, nextState, event }) => {
38447
38440
  if (event.type !== "MOVE_TAB") return nextState;
38448
38441
  const sourceArea = prevState.bottomTabs.includes(event.tabId) ? "bottom" : "main";
@@ -39078,8 +39071,6 @@ var NavController = class {
39078
39071
  }
39079
39072
  this.state = normalizeState({
39080
39073
  ...this.state,
39081
- mainLocation: collapseProjectDetailLocation(this.state.mainLocation),
39082
- bottomLocation: collapseProjectDetailLocation(this.state.bottomLocation),
39083
39074
  updatedAt: 0
39084
39075
  });
39085
39076
  this.normalizeUrl();
@@ -50003,6 +49994,15 @@ async function getSpec(id) {
50003
49994
  return snapshotSpecToSpec(snapSpec);
50004
49995
  }
50005
49996
  /**
49997
+ * Get raw spec content (markdown)
49998
+ */
49999
+ async function getSpecRaw(id) {
50000
+ const snapshot = await loadSnapshot();
50001
+ if (!snapshot) return null;
50002
+ const spec = snapshot.specs.find((s) => s.id === id);
50003
+ return spec?.content ?? spec?.sourceContent ?? null;
50004
+ }
50005
+ /**
50006
50006
  * Get all changes metadata
50007
50007
  */
50008
50008
  async function getChanges() {
@@ -52924,6 +52924,12 @@ function useSpecSubscription(id) {
52924
52924
  onError: callbacks.onError
52925
52925
  }), () => getSpec(id), [id], getSpecSubscriptionCacheKey(id));
52926
52926
  }
52927
+ function useSpecRawSubscription(id) {
52928
+ return useSubscription((callbacks) => trpcClient.spec.subscribeRaw.subscribe({ id }, {
52929
+ onData: callbacks.onData,
52930
+ onError: callbacks.onError
52931
+ }), () => getSpecRaw(id), [id], `spec.subscribeRaw:${id}`);
52932
+ }
52927
52933
  function useChangesSubscription() {
52928
52934
  return useSubscription((callbacks) => trpcClient.change.subscribe.subscribe(void 0, {
52929
52935
  onData: callbacks.onData,
@@ -55415,6 +55421,42 @@ function useManualReconnect() {
55415
55421
  }, []);
55416
55422
  }
55417
55423
  //#endregion
55424
+ //#region src/components/badge.tsx
55425
+ var toneClassNames = {
55426
+ primary: "bg-primary text-primary-foreground",
55427
+ subtle: "border border-primary/35 bg-primary/10 text-primary",
55428
+ muted: "border border-border bg-muted text-muted-foreground",
55429
+ custom: ""
55430
+ };
55431
+ var sizeClassNames$1 = {
55432
+ dot: "h-1.5 w-1.5 min-w-0 p-0",
55433
+ xs: "h-4 min-w-4 px-1 text-[10px] leading-none",
55434
+ sm: "h-5 min-w-5 px-1.5 text-[11px] leading-none"
55435
+ };
55436
+ var shapeClassNames = {
55437
+ pill: "rounded-full",
55438
+ box: "rounded"
55439
+ };
55440
+ function Badge({ tone = "primary", size = "xs", shape = "pill", className, ...props }) {
55441
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
55442
+ "data-ui-badge": "true",
55443
+ className: cn$1("inline-flex shrink-0 items-center justify-center gap-0.5 whitespace-nowrap font-semibold", toneClassNames[tone], sizeClassNames$1[size], shapeClassNames[shape], className),
55444
+ ...props
55445
+ });
55446
+ }
55447
+ function formatCountBadgeValue(count, max = 99) {
55448
+ return count > max ? `${max}+` : String(count);
55449
+ }
55450
+ function CountBadge({ count, max = 99, hideWhenZero = false, title, ...props }) {
55451
+ if (hideWhenZero && count <= 0) return null;
55452
+ const value = formatCountBadgeValue(count, max);
55453
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
55454
+ title,
55455
+ ...props,
55456
+ children: value
55457
+ });
55458
+ }
55459
+ //#endregion
55418
55460
  //#region ../core/src/pty-protocol.ts
55419
55461
  var PositiveInt = numberType().int().positive();
55420
55462
  var PtyPlatformSchema = enumType([
@@ -67053,6 +67095,7 @@ var MIN_WIDTH_PX = 300;
67053
67095
  var MIN_HEIGHT_PX = 150;
67054
67096
  var MAX_WIDTH_PCT = 95;
67055
67097
  var MAX_HEIGHT_PCT = 85;
67098
+ var MOVE_BAR_PROTRUSION_PX = 10;
67056
67099
  var SETTINGS_KEY$1 = "xtermInputPanelSettings";
67057
67100
  function mergeSettings(updates) {
67058
67101
  try {
@@ -67129,6 +67172,8 @@ var InputPanel = class extends i {
67129
67172
  --muted-foreground: var(--_ip-muted-fg);
67130
67173
  --terminal: var(--_ip-bg);
67131
67174
  --terminal-foreground: var(--_ip-fg);
67175
+ --move-bar-height: 10px;
67176
+ --move-bar-protrusion: 10px;
67132
67177
  font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
67133
67178
  font-size: 13px;
67134
67179
  color: var(--foreground, #fff);
@@ -67251,7 +67296,7 @@ var InputPanel = class extends i {
67251
67296
  border-radius: 8px;
67252
67297
  background: var(--background, #1a1a1a);
67253
67298
  color: var(--foreground, #fff);
67254
- overflow: hidden;
67299
+ overflow: visible;
67255
67300
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
67256
67301
  display: flex;
67257
67302
  flex-direction: column;
@@ -67263,6 +67308,54 @@ var InputPanel = class extends i {
67263
67308
  z-index: 9999;
67264
67309
  }
67265
67310
 
67311
+ .panel-body {
67312
+ width: 100%;
67313
+ height: 100%;
67314
+ display: flex;
67315
+ flex-direction: column;
67316
+ overflow: hidden;
67317
+ border-radius: inherit;
67318
+ }
67319
+
67320
+ .move-bar {
67321
+ position: absolute;
67322
+ top: calc(-1 * var(--move-bar-protrusion));
67323
+ left: 50%;
67324
+ width: 68px;
67325
+ height: var(--move-bar-height);
67326
+ transform: translateX(-50%);
67327
+ border: 1px solid var(--primary, #e04a2f);
67328
+ border-bottom: 0;
67329
+ border-radius: 10px 10px 0 0;
67330
+ background: var(--background, #1a1a1a);
67331
+ color: var(--muted-foreground, #888);
67332
+ touch-action: none;
67333
+ cursor: grab;
67334
+ z-index: 12;
67335
+ display: flex;
67336
+ align-items: center;
67337
+ justify-content: center;
67338
+ box-shadow: 0 -6px 12px rgba(0, 0, 0, 0.12);
67339
+ }
67340
+
67341
+ .move-bar::before {
67342
+ content: '';
67343
+ width: 34px;
67344
+ height: 4px;
67345
+ border-radius: 999px;
67346
+ background: currentColor;
67347
+ opacity: 0.72;
67348
+ }
67349
+
67350
+ .move-bar:hover,
67351
+ :host([data-interacting]) .move-bar {
67352
+ color: var(--primary, #e04a2f);
67353
+ }
67354
+
67355
+ :host([data-interacting]) .move-bar {
67356
+ cursor: grabbing;
67357
+ }
67358
+
67266
67359
  /* --- Resize handles --- */
67267
67360
  .resize-handle {
67268
67361
  position: absolute;
@@ -67360,6 +67453,7 @@ var InputPanel = class extends i {
67360
67453
  }
67361
67454
  disconnectedCallback() {
67362
67455
  super.disconnectedCallback();
67456
+ this._closeFloatingDialog();
67363
67457
  this.dispatchEvent(new CustomEvent("input-panel:disconnected", {
67364
67458
  bubbles: true,
67365
67459
  composed: true
@@ -67416,7 +67510,7 @@ var InputPanel = class extends i {
67416
67510
  const maxOverX = wPx / 3, maxOverY = hPx / 3;
67417
67511
  return {
67418
67512
  left: Math.max(-maxOverX, Math.min(vw - wPx + maxOverX, leftPx)),
67419
- top: Math.max(0, Math.min(vh - hPx + maxOverY, topPx))
67513
+ top: Math.max(MOVE_BAR_PROTRUSION_PX, Math.min(vh - hPx + maxOverY, topPx))
67420
67514
  };
67421
67515
  }
67422
67516
  _applyGeometry(dialog) {
@@ -67444,6 +67538,43 @@ var InputPanel = class extends i {
67444
67538
  const dialog = this.shadowRoot?.querySelector(".panel-dialog");
67445
67539
  if (dialog) this._applyGeometry(dialog);
67446
67540
  }
67541
+ _isPopoverOpen(dialog) {
67542
+ try {
67543
+ return dialog.matches(":popover-open");
67544
+ } catch {
67545
+ return false;
67546
+ }
67547
+ }
67548
+ _isFloatingDialogOpen(dialog) {
67549
+ return dialog.open || this._isPopoverOpen(dialog);
67550
+ }
67551
+ _showFloatingDialog(dialog) {
67552
+ if (this._isFloatingDialogOpen(dialog)) return;
67553
+ if (!dialog.isConnected) {
67554
+ requestAnimationFrame(() => {
67555
+ if (this.layout === "floating" && dialog.isConnected && !this._isFloatingDialogOpen(dialog)) {
67556
+ this._showFloatingDialog(dialog);
67557
+ this._applyGeometry(dialog);
67558
+ }
67559
+ });
67560
+ return;
67561
+ }
67562
+ const popoverDialog = dialog;
67563
+ if (typeof popoverDialog.showPopover === "function") {
67564
+ popoverDialog.showPopover();
67565
+ return;
67566
+ }
67567
+ dialog.show();
67568
+ }
67569
+ _closeFloatingDialog() {
67570
+ const dialog = this.shadowRoot?.querySelector(".panel-dialog");
67571
+ if (!dialog) return;
67572
+ if (this._isPopoverOpen(dialog) && typeof dialog.hidePopover === "function") {
67573
+ dialog.hidePopover();
67574
+ return;
67575
+ }
67576
+ if (dialog.open) dialog.close();
67577
+ }
67447
67578
  _switchTab(tab) {
67448
67579
  this.activeTab = tab;
67449
67580
  this.dispatchEvent(new CustomEvent("input-panel:tab-change", {
@@ -67455,6 +67586,7 @@ var InputPanel = class extends i {
67455
67586
  }
67456
67587
  _toggleLayout() {
67457
67588
  this.layout = this.layout === "fixed" ? "floating" : "fixed";
67589
+ if (this.layout === "fixed") this._closeFloatingDialog();
67458
67590
  this.dispatchEvent(new CustomEvent("input-panel:layout-change", {
67459
67591
  detail: { layout: this.layout },
67460
67592
  bubbles: true,
@@ -67463,8 +67595,7 @@ var InputPanel = class extends i {
67463
67595
  this.requestUpdate();
67464
67596
  }
67465
67597
  _close() {
67466
- const dialog = this.shadowRoot?.querySelector(".panel-dialog");
67467
- if (dialog?.open) dialog.close();
67598
+ this._closeFloatingDialog();
67468
67599
  this.dispatchEvent(new CustomEvent("input-panel:close", {
67469
67600
  bubbles: true,
67470
67601
  composed: true
@@ -67474,8 +67605,8 @@ var InputPanel = class extends i {
67474
67605
  super.firstUpdated(changed);
67475
67606
  if (this.layout === "floating") {
67476
67607
  const dialog = this.shadowRoot?.querySelector(".panel-dialog");
67477
- if (dialog && !dialog.open) {
67478
- dialog.show();
67608
+ if (dialog && !this._isFloatingDialogOpen(dialog)) {
67609
+ this._showFloatingDialog(dialog);
67479
67610
  this._applyGeometry(dialog);
67480
67611
  }
67481
67612
  }
@@ -67486,14 +67617,17 @@ var InputPanel = class extends i {
67486
67617
  if (this.layout === "floating") {
67487
67618
  const dialog = this.shadowRoot?.querySelector(".panel-dialog");
67488
67619
  if (dialog) {
67489
- if (changed.has("layout") && !dialog.open) dialog.show();
67490
- if (dialog.open) this._applyGeometry(dialog);
67620
+ if (changed.has("layout") && !this._isFloatingDialogOpen(dialog)) this._showFloatingDialog(dialog);
67621
+ if (this._isFloatingDialogOpen(dialog)) this._applyGeometry(dialog);
67491
67622
  }
67492
67623
  }
67493
67624
  }
67494
67625
  _onToolbarPointerDown(e) {
67495
- if (this.layout !== "floating") return;
67496
67626
  if (e.target.closest("button")) return;
67627
+ this._onDragPointerDown(e);
67628
+ }
67629
+ _onDragPointerDown(e) {
67630
+ if (this.layout !== "floating") return;
67497
67631
  const dialog = this.shadowRoot?.querySelector(".panel-dialog");
67498
67632
  if (!dialog) return;
67499
67633
  e.stopPropagation();
@@ -67506,9 +67640,12 @@ var InputPanel = class extends i {
67506
67640
  origTop: rect.top
67507
67641
  };
67508
67642
  this.setAttribute("data-interacting", "");
67509
- e.target.setPointerCapture(e.pointerId);
67643
+ const handle = e.currentTarget;
67644
+ try {
67645
+ handle.setPointerCapture(e.pointerId);
67646
+ } catch {}
67510
67647
  }
67511
- _onToolbarPointerMove(e) {
67648
+ _onDragPointerMove(e) {
67512
67649
  if (!this._dragState) return;
67513
67650
  e.stopPropagation();
67514
67651
  e.preventDefault();
@@ -67524,8 +67661,10 @@ var InputPanel = class extends i {
67524
67661
  dialog.style.top = `${top}px`;
67525
67662
  dialog.style.bottom = "auto";
67526
67663
  }
67527
- _onToolbarPointerUp() {
67664
+ _onDragPointerUp(e) {
67528
67665
  if (!this._dragState) return;
67666
+ e.stopPropagation();
67667
+ e.preventDefault();
67529
67668
  this._dragState = null;
67530
67669
  this.removeAttribute("data-interacting");
67531
67670
  const dialog = this.shadowRoot?.querySelector(".panel-dialog");
@@ -67645,8 +67784,9 @@ var InputPanel = class extends i {
67645
67784
  class="toolbar"
67646
67785
  part="toolbar"
67647
67786
  @pointerdown=${(e) => this._onToolbarPointerDown(e)}
67648
- @pointermove=${(e) => this._onToolbarPointerMove(e)}
67649
- @pointerup=${() => this._onToolbarPointerUp()}
67787
+ @pointermove=${(e) => this._onDragPointerMove(e)}
67788
+ @pointerup=${(e) => this._onDragPointerUp(e)}
67789
+ @pointercancel=${(e) => this._onDragPointerUp(e)}
67650
67790
  >
67651
67791
  <div class="tab-group">
67652
67792
  ${[
@@ -67706,7 +67846,16 @@ var InputPanel = class extends i {
67706
67846
  ></input-panel-settings>` : T`<slot name=${this.activeTab}></slot>`}
67707
67847
  </div>
67708
67848
  `;
67709
- if (this.layout === "floating") return T` <dialog class="panel-dialog">
67849
+ if (this.layout === "floating") return T` <dialog class="panel-dialog" popover="manual">
67850
+ <div
67851
+ class="move-bar"
67852
+ part="move-bar"
67853
+ aria-label="Move input panel"
67854
+ @pointerdown=${(e) => this._onDragPointerDown(e)}
67855
+ @pointermove=${(e) => this._onDragPointerMove(e)}
67856
+ @pointerup=${(e) => this._onDragPointerUp(e)}
67857
+ @pointercancel=${(e) => this._onDragPointerUp(e)}
67858
+ ></div>
67710
67859
  <div
67711
67860
  class="resize-handle resize-tl"
67712
67861
  @pointerdown=${(e) => this._onResizeStart(e, "tl")}
@@ -67723,7 +67872,7 @@ var InputPanel = class extends i {
67723
67872
  class="resize-handle resize-br"
67724
67873
  @pointerdown=${(e) => this._onResizeStart(e, "br")}
67725
67874
  ></div>
67726
- ${inner}
67875
+ <div class="panel-body">${inner}</div>
67727
67876
  </dialog>`;
67728
67877
  return inner;
67729
67878
  }
@@ -68341,11 +68490,11 @@ function assertPath$1(path2) {
68341
68490
  function removeUrlParams(url) {
68342
68491
  return url.split("?")[0].split("#")[0];
68343
68492
  }
68344
- function escapeRegExp(string) {
68493
+ function escapeRegExp$1(string) {
68345
68494
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
68346
68495
  }
68347
68496
  function replaceAll$1(str, find, replace) {
68348
- return str.replace(new RegExp(escapeRegExp(find), "g"), replace);
68497
+ return str.replace(new RegExp(escapeRegExp$1(find), "g"), replace);
68349
68498
  }
68350
68499
  function normalizeStringPosix(path2, allowAboveRoot) {
68351
68500
  let res = "";
@@ -94320,10 +94469,10 @@ function NotificationEntryButton({ className, badgeClassName, iconClassName }) {
94320
94469
  "aria-label": label,
94321
94470
  title: label,
94322
94471
  "data-notification-entry-button": "true",
94323
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Bell, { className: cn$1("h-4 w-4", iconClassName) }), unreadCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
94324
- className: cn$1("bg-primary ring-background text-primary-foreground absolute -right-1.5 -top-1.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] leading-4 ring-2", badgeClassName),
94325
- "aria-hidden": "true",
94326
- children: unreadCount > 99 ? "99+" : unreadCount
94472
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Bell, { className: cn$1("h-4 w-4", iconClassName) }), unreadCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CountBadge, {
94473
+ count: unreadCount,
94474
+ className: cn$1("ring-background absolute -right-1.5 -top-1.5 ring-2", badgeClassName),
94475
+ "aria-hidden": "true"
94327
94476
  })]
94328
94477
  })
94329
94478
  });
@@ -126052,12 +126201,15 @@ var { codeToHtml, codeToHast, codeToTokens, codeToTokensBase, codeToTokensWithTh
126052
126201
  * Simple markdown renderer with GFM support and shiki code highlighting.
126053
126202
  * For full markdown viewing with ToC, use MarkdownViewer instead.
126054
126203
  */
126055
- function MarkdownContent({ children, className = "", components }) {
126204
+ function MarkdownContent({ children, className = "", components, inlineTextAnnotations = [], blockAnnotations = [] }) {
126205
+ const blockAnnotationByOffset = (0, import_react.useMemo)(() => new Map(blockAnnotations.map((annotation) => [createBlockAnnotationKey$1(annotation), annotation])), [blockAnnotations]);
126206
+ const annotationComponents = (0, import_react.useMemo)(() => createAnnotationComponents(inlineTextAnnotations, blockAnnotationByOffset), [inlineTextAnnotations, blockAnnotationByOffset]);
126056
126207
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
126057
126208
  className: `markdown-content ${className}`,
126058
126209
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Markdown, {
126059
126210
  remarkPlugins: [remarkGfm],
126060
126211
  components: {
126212
+ ...annotationComponents,
126061
126213
  code: CodeBlock,
126062
126214
  pre: ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children }),
126063
126215
  ...components
@@ -126066,6 +126218,156 @@ function MarkdownContent({ children, className = "", components }) {
126066
126218
  })
126067
126219
  });
126068
126220
  }
126221
+ function renderInlineAnnotatedChildren(children, annotations) {
126222
+ if (annotations.length === 0) return children;
126223
+ if (typeof children === "string" || typeof children === "number") return renderInlineAnnotatedText(String(children), annotations);
126224
+ if (Array.isArray(children)) return children.map((child, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Fragment, { children: renderInlineAnnotatedChildren(child, annotations) }, `inline-node-${index}`));
126225
+ return children;
126226
+ }
126227
+ function renderInlineAnnotatedText(text, annotations) {
126228
+ const pattern = createInlineAnnotationPattern(annotations);
126229
+ if (!pattern) return text;
126230
+ const annotationByText = new Map(annotations.map((annotation) => [annotation.text, annotation]));
126231
+ const nodes = [];
126232
+ let lastIndex = 0;
126233
+ for (const match of text.matchAll(pattern)) {
126234
+ const matchText = match[0];
126235
+ const start = match.index ?? 0;
126236
+ const annotation = annotationByText.get(matchText);
126237
+ if (!annotation) continue;
126238
+ if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
126239
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
126240
+ className: annotation.className,
126241
+ ...annotation.dataAttributes,
126242
+ children: matchText
126243
+ }, `inline-annotation-${start}-${matchText}`));
126244
+ lastIndex = start + matchText.length;
126245
+ }
126246
+ if (lastIndex === 0) return text;
126247
+ if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
126248
+ return nodes;
126249
+ }
126250
+ function createInlineAnnotationPattern(annotations) {
126251
+ const terms = Array.from(new Set(annotations.map((annotation) => annotation.text.trim()))).filter(Boolean).sort((left, right) => right.length - left.length);
126252
+ if (terms.length === 0) return void 0;
126253
+ return new RegExp(`\\b(${terms.map(escapeRegExp).join("|")})\\b`, "g");
126254
+ }
126255
+ function escapeRegExp(value) {
126256
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
126257
+ }
126258
+ function getMarkdownNodeSourceStartOffset(node) {
126259
+ if (!node || typeof node !== "object" || !("position" in node)) return void 0;
126260
+ const position = node.position;
126261
+ if (!position || typeof position !== "object" || !("start" in position)) return void 0;
126262
+ const start = position.start;
126263
+ if (!start || typeof start !== "object" || !("offset" in start)) return void 0;
126264
+ const offset = start.offset;
126265
+ return typeof offset === "number" ? offset : void 0;
126266
+ }
126267
+ function getBlockAnnotation(node, annotationByOffset, sourceKind) {
126268
+ const sourceStartOffset = getMarkdownNodeSourceStartOffset(node);
126269
+ if (sourceStartOffset === void 0) return void 0;
126270
+ return (sourceKind ? annotationByOffset.get(createBlockAnnotationKey$1({
126271
+ sourceStartOffset,
126272
+ sourceKind
126273
+ })) : void 0) ?? annotationByOffset.get(createBlockAnnotationKey$1({ sourceStartOffset }));
126274
+ }
126275
+ function createBlockAnnotationKey$1(annotation) {
126276
+ return `${annotation.sourceStartOffset}:${annotation.sourceKind ?? "*"}`;
126277
+ }
126278
+ function mergeClassName(...classNames) {
126279
+ return classNames.filter(Boolean).join(" ") || void 0;
126280
+ }
126281
+ function createAnnotationComponents(inlineAnnotations, blockAnnotationByOffset) {
126282
+ if (inlineAnnotations.length === 0 && blockAnnotationByOffset.size === 0) return {};
126283
+ const render = (children) => renderInlineAnnotatedChildren(children, inlineAnnotations);
126284
+ return {
126285
+ p: ({ children, node, ...props }) => {
126286
+ const annotation = getBlockAnnotation(node, blockAnnotationByOffset, "paragraph");
126287
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
126288
+ ...props,
126289
+ ...annotation?.dataAttributes,
126290
+ className: mergeClassName(props.className, annotation?.className),
126291
+ children: render(children)
126292
+ });
126293
+ },
126294
+ ul: ({ children, node, ...props }) => {
126295
+ const annotation = getBlockAnnotation(node, blockAnnotationByOffset, "list");
126296
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
126297
+ ...props,
126298
+ ...annotation?.dataAttributes,
126299
+ className: mergeClassName(props.className, annotation?.className),
126300
+ children
126301
+ });
126302
+ },
126303
+ ol: ({ children, node, ...props }) => {
126304
+ const annotation = getBlockAnnotation(node, blockAnnotationByOffset, "list");
126305
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", {
126306
+ ...props,
126307
+ ...annotation?.dataAttributes,
126308
+ className: mergeClassName(props.className, annotation?.className),
126309
+ children
126310
+ });
126311
+ },
126312
+ li: ({ children, node, className, ...props }) => {
126313
+ const annotation = getBlockAnnotation(node, blockAnnotationByOffset, "listItem");
126314
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", {
126315
+ ...props,
126316
+ ...annotation?.dataAttributes,
126317
+ className: mergeClassName(className, annotation?.className),
126318
+ children: render(children)
126319
+ });
126320
+ },
126321
+ strong: ({ children, node, ...props }) => {
126322
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", {
126323
+ ...props,
126324
+ children: render(children)
126325
+ });
126326
+ },
126327
+ em: ({ children, node, ...props }) => {
126328
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("em", {
126329
+ ...props,
126330
+ children: render(children)
126331
+ });
126332
+ },
126333
+ a: ({ children, node, ...props }) => {
126334
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
126335
+ ...props,
126336
+ children: render(children)
126337
+ });
126338
+ },
126339
+ blockquote: ({ children, node, ...props }) => {
126340
+ const annotation = getBlockAnnotation(node, blockAnnotationByOffset, "blockquote");
126341
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("blockquote", {
126342
+ ...props,
126343
+ ...annotation?.dataAttributes,
126344
+ className: mergeClassName(props.className, annotation?.className),
126345
+ children
126346
+ });
126347
+ },
126348
+ table: ({ children, node, ...props }) => {
126349
+ const annotation = getBlockAnnotation(node, blockAnnotationByOffset, "table");
126350
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", {
126351
+ ...props,
126352
+ ...annotation?.dataAttributes,
126353
+ className: mergeClassName(props.className, annotation?.className),
126354
+ children
126355
+ });
126356
+ },
126357
+ th: ({ children, node, ...props }) => {
126358
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
126359
+ ...props,
126360
+ children: render(children)
126361
+ });
126362
+ },
126363
+ td: ({ children, node, ...props }) => {
126364
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
126365
+ ...props,
126366
+ children: render(children)
126367
+ });
126368
+ }
126369
+ };
126370
+ }
126069
126371
  /** Shared code block component with shiki syntax highlighting */
126070
126372
  function CodeBlock({ children, className }) {
126071
126373
  const [html, setHtml] = (0, import_react.useState)(null);
@@ -126169,32 +126471,38 @@ function Toc({ items, defaultCollapsed = true, className = "" }) {
126169
126471
  const tree = (0, import_react.useMemo)(() => buildTocTree(items), [items]);
126170
126472
  if (items.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("aside", { className: `hidden ${className}` });
126171
126473
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
126172
- className: `toc-root scrollbar-none sticky top-0 z-10 max-h-[calc(100cqh-3rem)] self-start overflow-y-auto ${className}`,
126474
+ className: `toc-root sticky top-0 z-10 h-10 w-full min-w-0 max-w-full self-start ${className}`,
126173
126475
  children: [
126174
126476
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: tocStyles }),
126175
126477
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
126176
- className: "toc-narrow border-border bg-background overflow-hidden rounded border",
126478
+ className: "toc-narrow border-border bg-background/60 overflow-hidden rounded border backdrop-blur-sm",
126177
126479
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
126178
126480
  onClick: () => setCollapsed(!collapsed),
126179
- className: `text-foreground flex w-full items-center gap-2 px-3 py-2 ${collapsed ? "" : "border-border border-b"}`,
126481
+ className: `text-foreground flex w-full min-w-0 items-center gap-2 px-3 py-2 ${collapsed ? "" : "border-border border-b"}`,
126180
126482
  "aria-label": collapsed ? "Show table of contents" : "Hide table of contents",
126181
126483
  children: [
126182
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { className: "h-4 w-4" }),
126484
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { className: "h-4 w-4 shrink-0" }),
126183
126485
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
126184
- className: "text-sm",
126486
+ className: "min-w-0 truncate text-sm",
126185
126487
  children: "Contents"
126186
126488
  }),
126187
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, { className: `ml-auto h-4 w-4 transition-transform ${collapsed ? "" : "rotate-180"}` })
126489
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, { className: `ml-auto h-4 w-4 shrink-0 transition-transform ${collapsed ? "" : "rotate-180"}` })
126188
126490
  ]
126189
- }), !collapsed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TocTree, { nodes: tree })]
126491
+ }), !collapsed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
126492
+ className: "toc-scroll toc-narrow-scroll scrollbar-thin scrollbar-track-transparent min-h-0 overflow-y-auto overscroll-contain p-2",
126493
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TocTree, { nodes: tree })
126494
+ })]
126190
126495
  }),
126191
126496
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("nav", {
126192
- className: "toc-wide flex flex-col",
126497
+ className: "toc-wide min-w-0 max-w-full flex-col overflow-hidden",
126193
126498
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
126194
- className: "text-muted-foreground flex items-center gap-2 px-3 py-2 text-xs font-medium uppercase tracking-wide",
126195
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "On this page" })]
126499
+ className: "text-muted-foreground flex min-w-0 items-center gap-2 px-3 py-2 text-xs font-medium uppercase tracking-wide",
126500
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { className: "h-3.5 w-3.5 shrink-0" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
126501
+ className: "min-w-0 truncate",
126502
+ children: "On this page"
126503
+ })]
126196
126504
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
126197
- className: "scrollbar-thin scrollbar-track-transparent min-h-0 flex-1 overflow-y-auto p-2",
126505
+ className: "toc-scroll toc-wide-scroll scrollbar-thin scrollbar-track-transparent min-h-0 flex-1 overflow-y-auto overscroll-contain p-2",
126198
126506
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TocTree, { nodes: tree })
126199
126507
  })]
126200
126508
  })
@@ -126205,7 +126513,7 @@ function Toc({ items, defaultCollapsed = true, className = "" }) {
126205
126513
  * Find the nearest scrollable ancestor element.
126206
126514
  */
126207
126515
  function findScrollableParent(element) {
126208
- return element.closest(".toc-root");
126516
+ return element.closest(".toc-scroll") ?? element.closest(".toc-root");
126209
126517
  }
126210
126518
  /**
126211
126519
  * Scroll element into view within its scrollable container using scrollTo.
@@ -126267,10 +126575,18 @@ function TocTree({ nodes, depth = 0 }) {
126267
126575
  var tocStyles = String.raw`
126268
126576
  /* Default: narrow mode (collapsible) */
126269
126577
  .toc-narrow {
126270
- display: block;
126578
+ display: flex;
126579
+ flex-direction: column;
126580
+ max-height: min(20rem, calc(100cqh - 2rem), calc(100svh - 2rem));
126581
+ max-width: 100%;
126582
+ min-width: 0;
126583
+ }
126584
+ .toc-narrow-scroll {
126585
+ max-height: min(18rem, calc(100cqh - 5rem), calc(100svh - 5rem));
126271
126586
  }
126272
126587
  .toc-wide {
126273
126588
  display: none;
126589
+ max-height: min(calc(100cqh - 3rem), calc(100svh - 3rem));
126274
126590
  }
126275
126591
 
126276
126592
  /* Wide container: show sidebar mode */
@@ -126279,7 +126595,19 @@ var tocStyles = String.raw`
126279
126595
  display: none;
126280
126596
  }
126281
126597
  .toc-wide {
126282
- display: block;
126598
+ display: flex;
126599
+ }
126600
+ }
126601
+
126602
+ @supports not (height: 100cqh) {
126603
+ .toc-narrow {
126604
+ max-height: min(20rem, calc(100svh - 2rem));
126605
+ }
126606
+ .toc-narrow-scroll {
126607
+ max-height: min(18rem, calc(100svh - 5rem));
126608
+ }
126609
+ .toc-wide {
126610
+ max-height: calc(100svh - 3rem);
126283
126611
  }
126284
126612
  }
126285
126613
 
@@ -126475,26 +126803,41 @@ function useSectionTimeline() {
126475
126803
  *
126476
126804
  * 嵌套时自动检测父级 Context,只渲染内容不显示 ToC sidebar。
126477
126805
  */
126478
- function MarkdownViewer({ markdown, className = "", onReady, collectToc = true }) {
126806
+ function MarkdownViewer({ markdown, className = "", onReady, collectToc = true, headingTransform, inlineTextAnnotations, blockAnnotations }) {
126479
126807
  const isNested = !!useTocContext();
126480
126808
  if (!collectToc) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlainMarkdownViewer, {
126481
126809
  markdown,
126482
- className
126810
+ className,
126811
+ headingTransform,
126812
+ inlineTextAnnotations,
126813
+ blockAnnotations
126483
126814
  });
126484
126815
  if (isNested) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NestedMarkdownViewer, {
126485
126816
  markdown,
126486
- className
126817
+ className,
126818
+ headingTransform,
126819
+ inlineTextAnnotations,
126820
+ blockAnnotations
126487
126821
  });
126488
126822
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RootMarkdownViewer, {
126489
126823
  markdown,
126490
126824
  className,
126491
- onReady
126825
+ onReady,
126826
+ headingTransform,
126827
+ inlineTextAnnotations,
126828
+ blockAnnotations
126492
126829
  });
126493
126830
  }
126494
- function PlainMarkdownViewer({ markdown, className = "" }) {
126495
- if (typeof markdown === "string") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownContent, {
126831
+ function PlainMarkdownViewer({ markdown, className = "", headingTransform, inlineTextAnnotations, blockAnnotations }) {
126832
+ if (typeof markdown === "string") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StringMarkdownContent, {
126833
+ markdown,
126496
126834
  className,
126497
- children: markdown
126835
+ collector: new TocCollector(),
126836
+ levelOffset: 0,
126837
+ headingTransform,
126838
+ inlineTextAnnotations,
126839
+ blockAnnotations,
126840
+ collectToc: false
126498
126841
  });
126499
126842
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlainBuilderMarkdownContent, {
126500
126843
  builder: markdown,
@@ -126537,7 +126880,7 @@ function PlainBuilderMarkdownContent({ builder, className }) {
126537
126880
  children: builder(components)
126538
126881
  });
126539
126882
  }
126540
- function RootMarkdownViewer({ markdown, className, onReady }) {
126883
+ function RootMarkdownViewer({ markdown, className, onReady, headingTransform, inlineTextAnnotations, blockAnnotations }) {
126541
126884
  const [tocItems, setTocItems] = (0, import_react.useState)([]);
126542
126885
  const collectorRef = (0, import_react.useRef)(null);
126543
126886
  const readyCalledRef = (0, import_react.useRef)(false);
@@ -126559,7 +126902,10 @@ function RootMarkdownViewer({ markdown, className, onReady }) {
126559
126902
  const content = typeof markdown === "string" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StringMarkdownContent, {
126560
126903
  markdown,
126561
126904
  collector,
126562
- levelOffset: 0
126905
+ levelOffset: 0,
126906
+ headingTransform,
126907
+ inlineTextAnnotations,
126908
+ blockAnnotations
126563
126909
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BuilderMarkdownContent, {
126564
126910
  builder: markdown,
126565
126911
  collector,
@@ -126572,27 +126918,30 @@ function RootMarkdownViewer({ markdown, className, onReady }) {
126572
126918
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
126573
126919
  className: `@container-[size] h-full ${className}`,
126574
126920
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: viewerStyles }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(MarkdownContainer, {
126575
- className: "viewer-scroll viewer-layout gap-6",
126921
+ className: "viewer-scroll toc-page-layout viewer-layout gap-6",
126576
126922
  timelineScope,
126577
126923
  enableHashNavigation: true,
126578
126924
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Toc, {
126579
126925
  items: tocItems,
126580
- className: "viewer-toc"
126926
+ className: "toc-page-sidebar viewer-toc"
126581
126927
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
126582
- className: "viewer-content min-w-0",
126928
+ className: "toc-page-content viewer-content min-w-0",
126583
126929
  children: content
126584
126930
  })]
126585
126931
  })]
126586
126932
  })
126587
126933
  });
126588
126934
  }
126589
- function NestedMarkdownViewer({ markdown, className }) {
126935
+ function NestedMarkdownViewer({ markdown, className, headingTransform, inlineTextAnnotations, blockAnnotations }) {
126590
126936
  const { collector, levelOffset } = useTocContext();
126591
126937
  return typeof markdown === "string" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StringMarkdownContent, {
126592
126938
  markdown,
126593
126939
  className,
126594
126940
  collector,
126595
- levelOffset
126941
+ levelOffset,
126942
+ headingTransform,
126943
+ inlineTextAnnotations,
126944
+ blockAnnotations
126596
126945
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BuilderMarkdownContent, {
126597
126946
  builder: markdown,
126598
126947
  className,
@@ -126600,21 +126949,33 @@ function NestedMarkdownViewer({ markdown, className }) {
126600
126949
  levelOffset
126601
126950
  });
126602
126951
  }
126603
- function StringMarkdownContent({ markdown, collector, levelOffset, className }) {
126952
+ function StringMarkdownContent({ markdown, collector, levelOffset, className, headingTransform, inlineTextAnnotations, blockAnnotations, collectToc = true }) {
126604
126953
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownContent, {
126605
126954
  className,
126606
126955
  components: (0, import_react.useMemo)(() => {
126607
126956
  const createHeading = (level) => {
126608
- return function Heading({ children }) {
126957
+ return function Heading({ children, node }) {
126609
126958
  const text = extractTextFromChildren(children);
126610
126959
  const sectionTimelineIndex = useSectionTimeline();
126960
+ const sourceRange = getMarkdownNodeSourceRange(node);
126611
126961
  const adjustedLevel = Math.min(level + levelOffset, 6);
126612
- const registration = sectionTimelineIndex === null ? collector.add(text, adjustedLevel) : collector.bindSectionHeading(sectionTimelineIndex, text, adjustedLevel);
126962
+ const transform = headingTransform?.({
126963
+ sourceLevel: level,
126964
+ level: adjustedLevel,
126965
+ text,
126966
+ ...sourceRange.start !== void 0 ? { sourceStartOffset: sourceRange.start } : {},
126967
+ ...sourceRange.end !== void 0 ? { sourceEndOffset: sourceRange.end } : {}
126968
+ });
126969
+ const tocLabel = transform?.tocLabel ?? text;
126970
+ const registration = collectToc === false ? collector.add(tocLabel, adjustedLevel, transform?.id) : sectionTimelineIndex === null ? collector.add(tocLabel, adjustedLevel, transform?.id) : collector.bindSectionHeading(sectionTimelineIndex, tocLabel, adjustedLevel, transform?.id);
126613
126971
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HeadingElement, {
126614
126972
  level: adjustedLevel,
126615
126973
  id: registration.id,
126616
126974
  timelineIndex: registration.timelineIndex,
126617
- bindTimeline: registration.binding === "heading",
126975
+ bindTimeline: collectToc !== false && registration.binding === "heading",
126976
+ className: transform?.className,
126977
+ dataAttributes: transform?.dataAttributes,
126978
+ suffix: transform?.suffix,
126618
126979
  children
126619
126980
  });
126620
126981
  };
@@ -126627,19 +126988,27 @@ function StringMarkdownContent({ markdown, collector, levelOffset, className })
126627
126988
  h5: createHeading(5),
126628
126989
  h6: createHeading(6)
126629
126990
  };
126630
- }, [collector, levelOffset]),
126991
+ }, [
126992
+ collector,
126993
+ levelOffset,
126994
+ headingTransform,
126995
+ collectToc
126996
+ ]),
126997
+ inlineTextAnnotations,
126998
+ blockAnnotations,
126631
126999
  children: markdown
126632
127000
  });
126633
127001
  }
126634
127002
  function BuilderMarkdownContent({ builder, collector, levelOffset, className }) {
126635
127003
  const components = (0, import_react.useMemo)(() => {
126636
127004
  const createHeading = (level) => {
126637
- return function Heading({ id: fixedId, className, children }) {
127005
+ return function Heading({ id: fixedId, className, children, tocLabel }) {
126638
127006
  const currentLevelOffset = useTocContext()?.levelOffset ?? levelOffset;
126639
127007
  const sectionTimelineIndex = useSectionTimeline();
126640
127008
  const text = extractTextFromChildren(children);
127009
+ const label = tocLabel ?? text;
126641
127010
  const adjustedLevel = Math.min(level + currentLevelOffset, 6);
126642
- const registration = sectionTimelineIndex === null ? collector.add(text, adjustedLevel, fixedId) : collector.bindSectionHeading(sectionTimelineIndex, text, adjustedLevel, fixedId);
127011
+ const registration = sectionTimelineIndex === null ? collector.add(label, adjustedLevel, fixedId) : collector.bindSectionHeading(sectionTimelineIndex, label, adjustedLevel, fixedId);
126643
127012
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HeadingElement, {
126644
127013
  level: adjustedLevel,
126645
127014
  id: registration.id,
@@ -126686,44 +127055,74 @@ function SectionElement({ timelineIndex, children, className }) {
126686
127055
  children
126687
127056
  });
126688
127057
  }
126689
- function HeadingElement({ level, id, timelineIndex, bindTimeline = false, children, className }) {
127058
+ function HeadingElement({ level, id, timelineIndex, bindTimeline = false, children, suffix, className, dataAttributes }) {
126690
127059
  const style = bindTimeline && timelineIndex !== void 0 ? { viewTimelineName: `--toc-${timelineIndex}` } : void 0;
126691
127060
  switch (level) {
126692
- case 1: return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
127061
+ case 1: return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h1", {
126693
127062
  id,
126694
127063
  className,
126695
127064
  style,
126696
- children
127065
+ ...dataAttributes,
127066
+ children: [
127067
+ children,
127068
+ suffix ? " " : null,
127069
+ suffix
127070
+ ]
126697
127071
  });
126698
- case 2: return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
127072
+ case 2: return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h2", {
126699
127073
  id,
126700
127074
  className,
126701
127075
  style,
126702
- children
127076
+ ...dataAttributes,
127077
+ children: [
127078
+ children,
127079
+ suffix ? " " : null,
127080
+ suffix
127081
+ ]
126703
127082
  });
126704
- case 3: return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
127083
+ case 3: return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", {
126705
127084
  id,
126706
127085
  className,
126707
127086
  style,
126708
- children
127087
+ ...dataAttributes,
127088
+ children: [
127089
+ children,
127090
+ suffix ? " " : null,
127091
+ suffix
127092
+ ]
126709
127093
  });
126710
- case 4: return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", {
127094
+ case 4: return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h4", {
126711
127095
  id,
126712
127096
  className,
126713
127097
  style,
126714
- children
127098
+ ...dataAttributes,
127099
+ children: [
127100
+ children,
127101
+ suffix ? " " : null,
127102
+ suffix
127103
+ ]
126715
127104
  });
126716
- case 5: return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h5", {
127105
+ case 5: return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h5", {
126717
127106
  id,
126718
127107
  className,
126719
127108
  style,
126720
- children
127109
+ ...dataAttributes,
127110
+ children: [
127111
+ children,
127112
+ suffix ? " " : null,
127113
+ suffix
127114
+ ]
126721
127115
  });
126722
- case 6: return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h6", {
127116
+ case 6: return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h6", {
126723
127117
  id,
126724
127118
  className,
126725
127119
  style,
126726
- children
127120
+ ...dataAttributes,
127121
+ children: [
127122
+ children,
127123
+ suffix ? " " : null,
127124
+ suffix
127125
+ ]
126727
127126
  });
126728
127127
  }
126729
127128
  }
@@ -126759,6 +127158,24 @@ function extractTextFromChildren(children) {
126759
127158
  if (typeof children === "object" && "props" in children) return extractTextFromChildren(children.props?.children);
126760
127159
  return "";
126761
127160
  }
127161
+ function getMarkdownNodeSourceRange(node) {
127162
+ if (!node || typeof node !== "object" || !("position" in node)) return {};
127163
+ const position = node.position;
127164
+ if (!position || typeof position !== "object") return {};
127165
+ const start = readMarkdownNodeOffset(position, "start");
127166
+ const end = readMarkdownNodeOffset(position, "end");
127167
+ return {
127168
+ ...start !== void 0 ? { start } : {},
127169
+ ...end !== void 0 ? { end } : {}
127170
+ };
127171
+ }
127172
+ function readMarkdownNodeOffset(position, key) {
127173
+ if (!(key in position)) return void 0;
127174
+ const point = position[key];
127175
+ if (!point || typeof point !== "object" || !("offset" in point)) return void 0;
127176
+ const offset = point.offset;
127177
+ return typeof offset === "number" ? offset : void 0;
127178
+ }
126762
127179
  /** 比较两个 TocItem 数组是否相等 */
126763
127180
  function arraysEqual(a, b) {
126764
127181
  if (a.length !== b.length) return false;
@@ -126766,28 +127183,7 @@ function arraysEqual(a, b) {
126766
127183
  }
126767
127184
  /** CSS for container queries layout */
126768
127185
  var viewerStyles = String.raw`
126769
- /* Container query based layout */
126770
- .viewer-layout {
126771
- display: block;
126772
- }
126773
- .viewer-toc {
126774
- margin-bottom: 1rem;
126775
- }
126776
-
126777
- /* Wide container: grid layout with ToC on right */
126778
- @container (min-width: 768px) {
126779
- .viewer-layout {
126780
- display: grid;
126781
- grid-template-columns: minmax(0, 1fr) 180px;
126782
- }
126783
- .viewer-toc {
126784
- order: 2;
126785
- margin-bottom: 0;
126786
- }
126787
- .viewer-content {
126788
- order: 1;
126789
- }
126790
- }
127186
+ /* MarkdownViewer keeps layout hooks local; shared ToC geometry lives in index.css. */
126791
127187
  `;
126792
127188
  //#endregion
126793
127189
  //#region src/components/tabs.tsx
@@ -146156,8 +146552,11 @@ function ChangeList() {
146156
146552
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
146157
146553
  className: "flex flex-col items-end gap-1 text-right text-sm",
146158
146554
  children: [
146159
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
146160
- className: `rounded border px-1.5 py-0.5 text-[11px] font-medium ${phase.toneClass}`,
146555
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
146556
+ tone: "custom",
146557
+ size: "sm",
146558
+ shape: "box",
146559
+ className: `border ${phase.toneClass}`,
146161
146560
  children: phase.label
146162
146561
  }),
146163
146562
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -149566,7 +149965,7 @@ function Switch({ checked, onCheckedChange, ariaLabel, id, name, required, disab
149566
149965
  className: cn$1("inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border border-transparent p-0.5 outline-none transition-colors", "focus-visible:ring-primary focus-visible:ring-1", checked ? "border-primary bg-primary" : "bg-muted-foreground/30 hover:bg-muted-foreground/40", disabled && "cursor-not-allowed opacity-50", readOnly && !disabled && "cursor-default", className),
149567
149966
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
149568
149967
  "aria-hidden": "true",
149569
- className: cn$1("pointer-events-none block h-5 w-5 rounded-full bg-white shadow-sm transition-transform", checked ? "translate-x-5" : "translate-x-0", thumbClassName)
149968
+ className: cn$1("pointer-events-none block h-5 w-5 rounded-full bg-white transition-transform", checked ? "translate-x-5" : "translate-x-0", thumbClassName)
149570
149969
  })
149571
149970
  })] });
149572
149971
  }
@@ -152565,8 +152964,11 @@ function DiffStat({ diff, className = "" }) {
152565
152964
  }
152566
152965
  function GitFilesBadge({ files }) {
152567
152966
  if (files <= 0) return null;
152568
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
152569
- className: "inline-flex items-center rounded border border-zinc-500/35 bg-zinc-500/10 px-[0.15rem] py-0 font-mono text-[10px] text-zinc-700 dark:border-zinc-300/40 dark:bg-zinc-300/15 dark:text-zinc-100",
152967
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
152968
+ tone: "custom",
152969
+ size: "xs",
152970
+ shape: "box",
152971
+ className: "h-auto min-w-0 border border-zinc-500/35 bg-zinc-500/10 px-[0.15rem] py-0 font-mono text-[10px] font-normal text-zinc-700 dark:border-zinc-300/40 dark:bg-zinc-300/15 dark:text-zinc-100",
152570
152972
  children: [files, "f"]
152571
152973
  });
152572
152974
  }
@@ -153054,8 +153456,11 @@ function Dashboard() {
153054
153456
  className: "text-xs font-semibold",
153055
153457
  children: schema.schemaName
153056
153458
  })
153057
- }), schema.readyToArchive > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
153058
- className: "text-muted-foreground rounded border px-1 py-0.5 text-[10px]",
153459
+ }), schema.readyToArchive > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
153460
+ tone: "custom",
153461
+ size: "xs",
153462
+ shape: "box",
153463
+ className: "text-muted-foreground border",
153059
153464
  children: ["archive-ready ", schema.readyToArchive]
153060
153465
  }) : null]
153061
153466
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -153351,8 +153756,11 @@ function Dashboard() {
153351
153756
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
153352
153757
  className: "shrink-0 text-right text-sm",
153353
153758
  children: [
153354
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
153355
- className: `rounded border px-1.5 py-0.5 text-[11px] font-medium ${phase.toneClass}`,
153759
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
153760
+ tone: "custom",
153761
+ size: "sm",
153762
+ shape: "box",
153763
+ className: `border ${phase.toneClass}`,
153356
153764
  children: phase.label
153357
153765
  }),
153358
153766
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -153802,6 +154210,1129 @@ function SpecList() {
153802
154210
  });
153803
154211
  }
153804
154212
  //#endregion
154213
+ //#region ../core/src/markdown-facts.ts
154214
+ function toMarkdownFactKind(type) {
154215
+ switch (type) {
154216
+ case "root":
154217
+ case "heading":
154218
+ case "paragraph":
154219
+ case "list":
154220
+ case "listItem":
154221
+ case "blockquote":
154222
+ case "table":
154223
+ case "tableRow":
154224
+ case "tableCell":
154225
+ case "code":
154226
+ case "thematicBreak":
154227
+ case "html":
154228
+ case "definition":
154229
+ case "footnoteDefinition": return type;
154230
+ default: return "unknown";
154231
+ }
154232
+ }
154233
+ function parseMarkdownFacts(sourceMarkdown) {
154234
+ const root = fromMarkdown(sourceMarkdown, {
154235
+ extensions: [gfm()],
154236
+ mdastExtensions: [gfmFromMarkdown()]
154237
+ });
154238
+ const facts = [];
154239
+ const nodeIdByNode = /* @__PURE__ */ new Map();
154240
+ const visit = (node, parentId) => {
154241
+ const fact = createFact(node, sourceMarkdown, facts.length, parentId);
154242
+ facts.push(fact);
154243
+ nodeIdByNode.set(node, fact.id);
154244
+ if (isParentNode(node)) for (const child of node.children) {
154245
+ if (!isSupportedNode(child)) continue;
154246
+ visit(child, fact.id);
154247
+ const childId = nodeIdByNode.get(child);
154248
+ if (childId) fact.children.push(childId);
154249
+ }
154250
+ };
154251
+ visit(root);
154252
+ return {
154253
+ sourceMarkdown,
154254
+ rootId: "md-1",
154255
+ facts
154256
+ };
154257
+ }
154258
+ function createFact(node, sourceMarkdown, index, parentId) {
154259
+ const base = {
154260
+ id: `md-${index + 1}`,
154261
+ kind: toMarkdownFactKind(node.type),
154262
+ mdastType: node.type,
154263
+ text: getNodeText(node),
154264
+ children: [],
154265
+ range: toSourceRange(sourceMarkdown, node.position),
154266
+ ...parentId ? { parentId } : {}
154267
+ };
154268
+ switch (node.type) {
154269
+ case "heading": return {
154270
+ ...base,
154271
+ depth: node.depth
154272
+ };
154273
+ case "list": return {
154274
+ ...base,
154275
+ ordered: Boolean(node.ordered)
154276
+ };
154277
+ case "listItem": return {
154278
+ ...base,
154279
+ ...typeof node.checked === "boolean" ? { checked: node.checked } : {}
154280
+ };
154281
+ case "code": return {
154282
+ ...base,
154283
+ text: node.value,
154284
+ value: node.value,
154285
+ ...node.lang ? { language: node.lang } : {}
154286
+ };
154287
+ case "html": return {
154288
+ ...base,
154289
+ value: node.value
154290
+ };
154291
+ default: return base;
154292
+ }
154293
+ }
154294
+ function toSourceRange(sourceMarkdown, position) {
154295
+ if (!position) return void 0;
154296
+ const startOffset = position.start.offset;
154297
+ const endOffset = position.end.offset;
154298
+ const rawMarkdown = typeof startOffset === "number" && typeof endOffset === "number" ? sourceMarkdown.slice(startOffset, endOffset) : "";
154299
+ return {
154300
+ start: {
154301
+ line: position.start.line,
154302
+ column: position.start.column,
154303
+ ...typeof startOffset === "number" ? { offset: startOffset } : {}
154304
+ },
154305
+ end: {
154306
+ line: position.end.line,
154307
+ column: position.end.column,
154308
+ ...typeof endOffset === "number" ? { offset: endOffset } : {}
154309
+ },
154310
+ rawMarkdown
154311
+ };
154312
+ }
154313
+ function getNodeText(node) {
154314
+ if (node.type === "code" || node.type === "html") return node.value;
154315
+ return toString$1(node, {
154316
+ includeHtml: true,
154317
+ includeImageAlt: true
154318
+ }).trim();
154319
+ }
154320
+ function isParentNode(node) {
154321
+ return Array.isArray(node.children);
154322
+ }
154323
+ function isSupportedNode(node) {
154324
+ switch (node.type) {
154325
+ case "blockquote":
154326
+ case "code":
154327
+ case "definition":
154328
+ case "footnoteDefinition":
154329
+ case "heading":
154330
+ case "html":
154331
+ case "list":
154332
+ case "listItem":
154333
+ case "paragraph":
154334
+ case "table":
154335
+ case "tableRow":
154336
+ case "tableCell":
154337
+ case "thematicBreak": return true;
154338
+ default: return isParentNode(node);
154339
+ }
154340
+ }
154341
+ //#endregion
154342
+ //#region ../core/src/markdown-reading.ts
154343
+ function createMarkdownReadingDocument(sourceMarkdown, plugins = []) {
154344
+ return createMarkdownReadingDocumentFromFacts(parseMarkdownFacts(sourceMarkdown), plugins);
154345
+ }
154346
+ function createMarkdownReadingDocumentFromFacts(factsDocument, plugins = []) {
154347
+ const lookup = createLookup(factsDocument);
154348
+ const annotations = [];
154349
+ for (const rule of plugins.flatMap((plugin) => plugin.annotationRules ?? [])) {
154350
+ const context = createAnnotationContext(lookup, annotations);
154351
+ for (const input of rule.annotate(context)) annotations.push({
154352
+ id: `${rule.id}:${annotations.length + 1}`,
154353
+ ruleId: rule.id,
154354
+ kind: input.kind,
154355
+ targetFactId: input.targetFactId,
154356
+ ...input.sourceSpan ? { sourceSpan: input.sourceSpan } : {},
154357
+ ...input.textSpan ? { textSpan: input.textSpan } : {},
154358
+ confidence: input.confidence,
154359
+ ...input.metadata ? { metadata: input.metadata } : {}
154360
+ });
154361
+ }
154362
+ const projections = {};
154363
+ for (const rule of plugins.flatMap((plugin) => plugin.projectionRules ?? [])) {
154364
+ const context = createProjectionContext(lookup, annotations, projections);
154365
+ const output = rule.project(context);
154366
+ if (output !== void 0) projections[rule.id] = output;
154367
+ }
154368
+ return {
154369
+ ...factsDocument,
154370
+ annotations,
154371
+ projections
154372
+ };
154373
+ }
154374
+ function getMarkdownFactSpan(fact) {
154375
+ const start = fact.range?.start.offset;
154376
+ const end = fact.range?.end.offset;
154377
+ if (typeof start !== "number" || typeof end !== "number") return void 0;
154378
+ return {
154379
+ start,
154380
+ end
154381
+ };
154382
+ }
154383
+ function trimMarkdownSlice(sourceMarkdown, start, end) {
154384
+ return sourceMarkdown.slice(start, Math.max(start, end)).trim();
154385
+ }
154386
+ function getMarkdownHeadingFacts(document) {
154387
+ return document.facts.filter((fact) => fact.kind === "heading" && typeof fact.depth === "number");
154388
+ }
154389
+ function getMarkdownHeadingEnd(headings, index, sourceLength) {
154390
+ const heading = headings[index];
154391
+ if (!heading) return sourceLength;
154392
+ const headingDepth = heading.depth ?? 6;
154393
+ for (let i = index + 1; i < headings.length; i++) {
154394
+ const next = headings[i];
154395
+ if (next && (next.depth ?? 6) <= headingDepth) return getMarkdownFactSpan(next)?.start ?? sourceLength;
154396
+ }
154397
+ return sourceLength;
154398
+ }
154399
+ function getMarkdownAnnotationsForFact(annotations, factId, kind) {
154400
+ return annotations.filter((annotation) => annotation.targetFactId === factId && (!kind || annotation.kind === kind));
154401
+ }
154402
+ function getMarkdownAnnotation(annotations, factId, kind) {
154403
+ return annotations.find((annotation) => annotation.targetFactId === factId && annotation.kind === kind);
154404
+ }
154405
+ function buildMarkdownParentMap(facts) {
154406
+ const factById = new Map(facts.map((fact) => [fact.id, fact]));
154407
+ const parentById = /* @__PURE__ */ new Map();
154408
+ for (const fact of facts) {
154409
+ if (!fact.parentId) continue;
154410
+ const parent = factById.get(fact.parentId);
154411
+ if (parent) parentById.set(fact.id, parent);
154412
+ }
154413
+ return parentById;
154414
+ }
154415
+ function createLookup(document) {
154416
+ return {
154417
+ sourceMarkdown: document.sourceMarkdown,
154418
+ rootId: document.rootId,
154419
+ facts: document.facts,
154420
+ factById: new Map(document.facts.map((fact) => [fact.id, fact])),
154421
+ parentById: buildMarkdownParentMap(document.facts)
154422
+ };
154423
+ }
154424
+ function createAnnotationContext(lookup, previousAnnotations) {
154425
+ return {
154426
+ ...lookup,
154427
+ previousAnnotations,
154428
+ getAnnotationsForFact: (factId, kind) => getMarkdownAnnotationsForFact(previousAnnotations, factId, kind),
154429
+ getAnnotation: (factId, kind) => getMarkdownAnnotation(previousAnnotations, factId, kind)
154430
+ };
154431
+ }
154432
+ function createProjectionContext(lookup, annotations, projections) {
154433
+ return {
154434
+ ...lookup,
154435
+ annotations,
154436
+ projections,
154437
+ getAnnotationsForFact: (factId, kind) => getMarkdownAnnotationsForFact(annotations, factId, kind),
154438
+ getAnnotation: (factId, kind) => getMarkdownAnnotation(annotations, factId, kind)
154439
+ };
154440
+ }
154441
+ //#endregion
154442
+ //#region ../core/src/openspec-annotations.ts
154443
+ var OPEN_SPEC_ANNOTATION_RULES = {
154444
+ documentTitle: "openspec.heading.document-title.v2",
154445
+ purposeSection: "openspec.heading.purpose-section.v2",
154446
+ requirementsSection: "openspec.heading.requirements-section.v2",
154447
+ requirementPrefix: "openspec.heading.requirement-prefix.v2",
154448
+ requirementUnderSection: "openspec.heading.requirement-under-section.v2",
154449
+ requirementCapabilityPrefix: "openspec.heading.capability-prefix.v2",
154450
+ requirementCapabilityText: "openspec.heading.capability-text.v2",
154451
+ scenarioPrefix: "openspec.heading.scenario-prefix.v2",
154452
+ scenarioExamplePrefix: "openspec.heading.example-prefix.v2",
154453
+ scenarioStepHeading: "openspec.heading.step-backed-scenario.v2",
154454
+ scenarioStep: "openspec.list-item.scenario-step.v2",
154455
+ keyword: "openspec.inline.keyword.v2"
154456
+ };
154457
+ var REQUIREMENT_PREFIX_PATTERN = /^(?:Requirement|Capability):\s*/i;
154458
+ var SCENARIO_PREFIX_PATTERN = /^(?:Scenario|Example):\s*/i;
154459
+ var SCENARIO_STEP_KEYWORDS = [
154460
+ "GIVEN",
154461
+ "WHEN",
154462
+ "THEN",
154463
+ "AND",
154464
+ "BUT"
154465
+ ];
154466
+ var REQUIREMENT_KEYWORD_PATTERN = new RegExp(`\\b(${[
154467
+ "SHALL",
154468
+ "MUST",
154469
+ "SHOULD",
154470
+ "MAY"
154471
+ ].join("|")})\\b`, "g");
154472
+ var SCENARIO_STEP_PATTERN = new RegExp(`^\\s*(?:[-*+]\\s+)?(?:\\[[ xX]\\]\\s+)?(?:\\*\\*)?(${SCENARIO_STEP_KEYWORDS.join("|")})\\b(?:\\*\\*)?\\s*:?\\s*(.+?)\\s*$`, "i");
154473
+ var REQUIREMENT_SECTION_TERMS = [
154474
+ "requirement",
154475
+ "specification",
154476
+ "capability",
154477
+ "capabilities"
154478
+ ];
154479
+ var PURPOSE_SECTION_TERMS = [
154480
+ "purpose",
154481
+ "overview",
154482
+ "objective",
154483
+ "goal",
154484
+ "goals"
154485
+ ];
154486
+ var REQUIREMENT_BODY_SIGNAL_PATTERN = /\b(SHALL|MUST|SHOULD|MAY|CAN|WILL)\b/i;
154487
+ var NON_SCENARIO_HEADING_PATTERN = /^(notes?|details?|rationale|reason|migration|examples?|open questions?)$/i;
154488
+ var builtinOpenSpecReadingPlugin = {
154489
+ id: "openspec.builtin-reading.v2",
154490
+ annotationRules: [
154491
+ {
154492
+ id: OPEN_SPEC_ANNOTATION_RULES.documentTitle,
154493
+ annotate(context) {
154494
+ return context.facts.flatMap((fact) => {
154495
+ if (fact.kind !== "heading" || fact.depth !== 1) return [];
154496
+ return [{
154497
+ kind: "document-title",
154498
+ targetFactId: fact.id,
154499
+ confidence: "strong",
154500
+ metadata: { title: fact.text }
154501
+ }];
154502
+ });
154503
+ }
154504
+ },
154505
+ {
154506
+ id: OPEN_SPEC_ANNOTATION_RULES.purposeSection,
154507
+ annotate(context) {
154508
+ return context.facts.flatMap((fact) => {
154509
+ if (fact.kind !== "heading" || fact.depth !== 2) return [];
154510
+ if (!matchesAnyTerm(fact.text, PURPOSE_SECTION_TERMS)) return [];
154511
+ return [{
154512
+ kind: "purpose-section",
154513
+ targetFactId: fact.id,
154514
+ confidence: isExactTerm(fact.text, ["Purpose", "Overview"]) ? "strong" : "weak",
154515
+ metadata: { title: fact.text }
154516
+ }];
154517
+ });
154518
+ }
154519
+ },
154520
+ {
154521
+ id: OPEN_SPEC_ANNOTATION_RULES.requirementsSection,
154522
+ annotate(context) {
154523
+ return context.facts.flatMap((fact) => {
154524
+ if (fact.kind !== "heading" || fact.depth !== 2) return [];
154525
+ if (!matchesAnyTerm(fact.text, REQUIREMENT_SECTION_TERMS)) return [];
154526
+ return [{
154527
+ kind: "requirements-section",
154528
+ targetFactId: fact.id,
154529
+ confidence: fact.text.toLowerCase().includes("requirement") ? "strong" : "weak",
154530
+ metadata: { title: fact.text }
154531
+ }];
154532
+ });
154533
+ }
154534
+ },
154535
+ {
154536
+ id: OPEN_SPEC_ANNOTATION_RULES.requirementPrefix,
154537
+ annotate(context) {
154538
+ return context.facts.flatMap((fact) => {
154539
+ if (fact.kind !== "heading" || fact.depth !== 3) return [];
154540
+ if (!/^Requirement:\s*/i.test(fact.text)) return [];
154541
+ return [createRequirementAnnotation(fact, "strong")];
154542
+ });
154543
+ }
154544
+ },
154545
+ {
154546
+ id: OPEN_SPEC_ANNOTATION_RULES.requirementCapabilityPrefix,
154547
+ annotate(context) {
154548
+ return context.facts.flatMap((fact) => {
154549
+ if (fact.kind !== "heading" || fact.depth !== 3) return [];
154550
+ if (!/^Capability:\s*/i.test(fact.text)) return [];
154551
+ return [createRequirementAnnotation(fact, "weak")];
154552
+ });
154553
+ }
154554
+ },
154555
+ {
154556
+ id: OPEN_SPEC_ANNOTATION_RULES.requirementUnderSection,
154557
+ annotate(context) {
154558
+ return getRequirementSections(context).flatMap((section) => {
154559
+ return getChildHeadings(context, section, 3).flatMap((fact) => {
154560
+ if (context.getAnnotation(fact.id, "requirement")) return [];
154561
+ if (REQUIREMENT_PREFIX_PATTERN.test(fact.text)) return [];
154562
+ if (!hasRequirementBodySignals(context, fact, section.end)) return [];
154563
+ return [{
154564
+ kind: "requirement",
154565
+ targetFactId: fact.id,
154566
+ confidence: "weak",
154567
+ metadata: { title: fact.text }
154568
+ }];
154569
+ });
154570
+ });
154571
+ }
154572
+ },
154573
+ {
154574
+ id: OPEN_SPEC_ANNOTATION_RULES.requirementCapabilityText,
154575
+ annotate(context) {
154576
+ return getRequirementSections(context).flatMap((section) => {
154577
+ return getChildHeadings(context, section, 3).flatMap((fact) => {
154578
+ if (context.getAnnotation(fact.id, "requirement")) return [];
154579
+ if (!matchesAnyTerm(fact.text, [
154580
+ "capability",
154581
+ "feature",
154582
+ "behavior"
154583
+ ])) return [];
154584
+ return [{
154585
+ kind: "requirement",
154586
+ targetFactId: fact.id,
154587
+ confidence: "weak",
154588
+ metadata: { title: stripRequirementPrefix(fact.text) || fact.text }
154589
+ }];
154590
+ });
154591
+ });
154592
+ }
154593
+ },
154594
+ {
154595
+ id: OPEN_SPEC_ANNOTATION_RULES.scenarioPrefix,
154596
+ annotate(context) {
154597
+ return getRequirementHeadings(context).flatMap((requirement) => {
154598
+ return getNestedHeadings(context, requirement).flatMap((fact) => {
154599
+ if (fact.depth !== 4 || !/^Scenario:\s*/i.test(fact.text)) return [];
154600
+ return [createScenarioAnnotation(fact, "strong")];
154601
+ });
154602
+ });
154603
+ }
154604
+ },
154605
+ {
154606
+ id: OPEN_SPEC_ANNOTATION_RULES.scenarioExamplePrefix,
154607
+ annotate(context) {
154608
+ return getRequirementHeadings(context).flatMap((requirement) => {
154609
+ return getNestedHeadings(context, requirement).flatMap((fact) => {
154610
+ if (fact.depth !== 4 || !/^Example:\s*/i.test(fact.text)) return [];
154611
+ return [createScenarioAnnotation(fact, "weak")];
154612
+ });
154613
+ });
154614
+ }
154615
+ },
154616
+ {
154617
+ id: OPEN_SPEC_ANNOTATION_RULES.scenarioStepHeading,
154618
+ annotate(context) {
154619
+ return getRequirementHeadings(context).flatMap((requirement) => {
154620
+ return getNestedHeadings(context, requirement).flatMap((fact) => {
154621
+ if (fact.depth !== 4) return [];
154622
+ if (context.getAnnotation(fact.id, "scenario")) return [];
154623
+ if (NON_SCENARIO_HEADING_PATTERN.test(fact.text)) return [];
154624
+ if (!hasScenarioStepSignals(context, fact)) return [];
154625
+ return [{
154626
+ kind: "scenario",
154627
+ targetFactId: fact.id,
154628
+ confidence: "weak",
154629
+ metadata: { title: stripScenarioPrefix(fact.text) || fact.text }
154630
+ }];
154631
+ });
154632
+ });
154633
+ }
154634
+ },
154635
+ {
154636
+ id: OPEN_SPEC_ANNOTATION_RULES.scenarioStep,
154637
+ annotate(context) {
154638
+ const scenarioSections = getScenarioSections(context);
154639
+ return context.facts.flatMap((fact) => {
154640
+ if (fact.kind !== "listItem") return [];
154641
+ if (!isWithinAnySection(fact, scenarioSections)) return [];
154642
+ const metadata = parseScenarioStepFact(fact);
154643
+ if (!metadata) return [];
154644
+ return [{
154645
+ kind: "scenario-step",
154646
+ targetFactId: fact.id,
154647
+ confidence: "strong",
154648
+ metadata
154649
+ }];
154650
+ });
154651
+ }
154652
+ },
154653
+ {
154654
+ id: OPEN_SPEC_ANNOTATION_RULES.keyword,
154655
+ annotate(context) {
154656
+ const scenarioKeywordAnnotations = context.previousAnnotations.flatMap((annotation) => {
154657
+ if (annotation.kind !== "scenario-step") return [];
154658
+ const fact = context.factById.get(annotation.targetFactId);
154659
+ if (!fact) return [];
154660
+ return findScenarioStepKeyword(fact, readAnnotationKeyword(annotation.metadata));
154661
+ });
154662
+ const requirementKeywordAnnotations = context.facts.flatMap((fact) => {
154663
+ if (!canAnnotateInlineKeywords(fact)) return [];
154664
+ return findRequirementKeywords(fact);
154665
+ });
154666
+ return [...scenarioKeywordAnnotations, ...requirementKeywordAnnotations];
154667
+ }
154668
+ }
154669
+ ]
154670
+ };
154671
+ function createRequirementAnnotation(fact, confidence) {
154672
+ return {
154673
+ kind: "requirement",
154674
+ targetFactId: fact.id,
154675
+ confidence,
154676
+ metadata: { title: stripRequirementPrefix(fact.text) || fact.text }
154677
+ };
154678
+ }
154679
+ function createScenarioAnnotation(fact, confidence) {
154680
+ return {
154681
+ kind: "scenario",
154682
+ targetFactId: fact.id,
154683
+ confidence,
154684
+ metadata: { title: stripScenarioPrefix(fact.text) || "Scenario" }
154685
+ };
154686
+ }
154687
+ function matchesAnyTerm(text, terms) {
154688
+ const normalized = text.toLowerCase();
154689
+ return terms.some((term) => normalized.includes(term));
154690
+ }
154691
+ function isExactTerm(text, terms) {
154692
+ const normalized = text.trim().toLowerCase();
154693
+ return terms.some((term) => normalized === term.toLowerCase());
154694
+ }
154695
+ function stripRequirementPrefix(text) {
154696
+ return text.replace(REQUIREMENT_PREFIX_PATTERN, "").trim();
154697
+ }
154698
+ function stripScenarioPrefix(text) {
154699
+ return text.replace(SCENARIO_PREFIX_PATTERN, "").trim();
154700
+ }
154701
+ function getHeadingSections(context) {
154702
+ const headings = getMarkdownHeadingFacts(context);
154703
+ return headings.reduce((sections, fact, index) => {
154704
+ const span = getMarkdownFactSpan(fact);
154705
+ if (!span) return sections;
154706
+ sections.push({
154707
+ fact,
154708
+ start: span.start,
154709
+ end: getMarkdownHeadingEnd(headings, index, context.sourceMarkdown.length)
154710
+ });
154711
+ return sections;
154712
+ }, []);
154713
+ }
154714
+ function getRequirementSections(context) {
154715
+ return getHeadingSections(context).filter((section) => context.getAnnotation(section.fact.id, "requirements-section"));
154716
+ }
154717
+ function getChildHeadings(context, section, depth) {
154718
+ return getMarkdownHeadingFacts(context).filter((fact) => {
154719
+ if (fact.depth !== depth) return false;
154720
+ const span = getMarkdownFactSpan(fact);
154721
+ return !!span && span.start > section.start && span.start < section.end;
154722
+ });
154723
+ }
154724
+ function getRequirementHeadings(context) {
154725
+ return getMarkdownHeadingFacts(context).filter((fact) => context.getAnnotation(fact.id, "requirement"));
154726
+ }
154727
+ function getNestedHeadings(context, parentHeading) {
154728
+ const headings = getMarkdownHeadingFacts(context);
154729
+ const parentIndex = headings.findIndex((fact) => fact.id === parentHeading.id);
154730
+ const parentSpan = getMarkdownFactSpan(parentHeading);
154731
+ if (parentIndex < 0 || !parentSpan) return [];
154732
+ const end = getMarkdownHeadingEnd(headings, parentIndex, context.sourceMarkdown.length);
154733
+ return headings.filter((fact) => {
154734
+ const span = getMarkdownFactSpan(fact);
154735
+ return !!span && span.start > parentSpan.start && span.start < end;
154736
+ });
154737
+ }
154738
+ function getScenarioSections(context) {
154739
+ const headings = getMarkdownHeadingFacts(context);
154740
+ return getHeadingSections(context).filter((section) => {
154741
+ if (!context.getAnnotation(section.fact.id, "scenario")) return false;
154742
+ if (NON_SCENARIO_HEADING_PATTERN.test(section.fact.text)) return false;
154743
+ return headings.findIndex((fact) => fact.id === section.fact.id) >= 0;
154744
+ });
154745
+ }
154746
+ function hasRequirementBodySignals(context, requirementHeading, sectionEnd) {
154747
+ const span = getMarkdownFactSpan(requirementHeading);
154748
+ if (!span) return false;
154749
+ const headings = getMarkdownHeadingFacts(context);
154750
+ const index = headings.findIndex((fact) => fact.id === requirementHeading.id);
154751
+ const end = index >= 0 ? getMarkdownHeadingEnd(headings, index, sectionEnd) : sectionEnd;
154752
+ const body = context.sourceMarkdown.slice(span.end, Math.min(end, sectionEnd));
154753
+ return REQUIREMENT_BODY_SIGNAL_PATTERN.test(body);
154754
+ }
154755
+ function hasScenarioStepSignals(context, scenarioHeading) {
154756
+ const span = getMarkdownFactSpan(scenarioHeading);
154757
+ if (!span) return false;
154758
+ const headings = getMarkdownHeadingFacts(context);
154759
+ const index = headings.findIndex((fact) => fact.id === scenarioHeading.id);
154760
+ const end = index >= 0 ? getMarkdownHeadingEnd(headings, index, context.sourceMarkdown.length) : context.sourceMarkdown.length;
154761
+ return context.sourceMarkdown.slice(span.end, end).split("\n").some((line) => SCENARIO_STEP_PATTERN.test(line));
154762
+ }
154763
+ function isWithinAnySection(fact, sections) {
154764
+ const span = getMarkdownFactSpan(fact);
154765
+ if (!span) return false;
154766
+ return sections.some((section) => span.start > section.start && span.start < section.end);
154767
+ }
154768
+ function parseScenarioStepFact(fact) {
154769
+ const rawMarkdown = fact.range?.rawMarkdown.trim() || fact.text;
154770
+ const match = rawMarkdown.match(SCENARIO_STEP_PATTERN) ?? fact.text.match(SCENARIO_STEP_PATTERN);
154771
+ if (!match) return void 0;
154772
+ return {
154773
+ keyword: match[1].toUpperCase(),
154774
+ contentMarkdown: match[2].trim(),
154775
+ rawMarkdown
154776
+ };
154777
+ }
154778
+ function canAnnotateInlineKeywords(fact) {
154779
+ return fact.kind === "paragraph" || fact.kind === "listItem" || fact.kind === "heading" || fact.kind === "tableCell";
154780
+ }
154781
+ function findScenarioStepKeyword(fact, keyword) {
154782
+ if (!isScenarioStepKeywordValue(keyword)) return [];
154783
+ const match = fact.text.match(new RegExp(`^\\s*(${SCENARIO_STEP_KEYWORDS.join("|")})\\b`, "i"));
154784
+ if (!match) return [];
154785
+ const text = match[1];
154786
+ const textStart = match.index ?? 0;
154787
+ const sourceSpan = findSourceSpanForFactText(fact, text);
154788
+ return [{
154789
+ kind: "keyword",
154790
+ targetFactId: fact.id,
154791
+ ...sourceSpan ? { sourceSpan } : {},
154792
+ textSpan: {
154793
+ start: textStart,
154794
+ end: textStart + text.length
154795
+ },
154796
+ confidence: "strong",
154797
+ metadata: {
154798
+ keyword,
154799
+ keywordText: text,
154800
+ keywordRole: "scenario-step"
154801
+ }
154802
+ }];
154803
+ }
154804
+ function findRequirementKeywords(fact) {
154805
+ const span = getMarkdownFactSpan(fact);
154806
+ const text = fact.text;
154807
+ if (!span || !text) return [];
154808
+ return Array.from(text.matchAll(REQUIREMENT_KEYWORD_PATTERN), (match) => {
154809
+ const keyword = match[1].toUpperCase();
154810
+ const textStart = match.index;
154811
+ const textEnd = textStart + match[1].length;
154812
+ const sourceSpan = findSourceSpanForFactText(fact, match[1], textStart);
154813
+ return {
154814
+ kind: "keyword",
154815
+ targetFactId: fact.id,
154816
+ ...sourceSpan ? { sourceSpan } : {},
154817
+ textSpan: {
154818
+ start: textStart,
154819
+ end: textEnd
154820
+ },
154821
+ confidence: "strong",
154822
+ metadata: {
154823
+ keyword,
154824
+ keywordText: match[1],
154825
+ keywordRole: isScenarioStepKeyword$1(keyword) ? "scenario-step" : "requirement-modal"
154826
+ }
154827
+ };
154828
+ });
154829
+ }
154830
+ function isScenarioStepKeywordValue(value) {
154831
+ return typeof value === "string" && SCENARIO_STEP_KEYWORDS.some((stepKeyword) => stepKeyword === value);
154832
+ }
154833
+ function isScenarioStepKeyword$1(keyword) {
154834
+ return SCENARIO_STEP_KEYWORDS.some((stepKeyword) => stepKeyword === keyword);
154835
+ }
154836
+ function readAnnotationKeyword(metadata) {
154837
+ return metadata && "keyword" in metadata ? metadata.keyword : void 0;
154838
+ }
154839
+ function findSourceSpanForFactText(fact, text, textStart = 0) {
154840
+ const factSpan = getMarkdownFactSpan(fact);
154841
+ const rawMarkdown = fact.range?.rawMarkdown;
154842
+ if (!factSpan || !rawMarkdown) return void 0;
154843
+ const rawStart = rawMarkdown.indexOf(text, Math.min(textStart, rawMarkdown.length));
154844
+ const index = rawStart >= 0 ? rawStart : rawMarkdown.indexOf(text);
154845
+ if (index < 0) return void 0;
154846
+ const start = factSpan.start + index;
154847
+ return {
154848
+ start,
154849
+ end: start + text.length
154850
+ };
154851
+ }
154852
+ //#endregion
154853
+ //#region ../core/src/openspec-projection.ts
154854
+ var OPEN_SPEC_SPEC_PROJECTION_ID = "openspec.projection.spec.v2";
154855
+ var OPEN_SPEC_READING_SECTIONS_PROJECTION_ID = "openspec.projection.reading-sections.v2";
154856
+ function createOpenSpecReadingPlugin(options) {
154857
+ return {
154858
+ ...builtinOpenSpecReadingPlugin,
154859
+ id: "openspec.builtin-reading-with-projections.v2",
154860
+ projectionRules: [createOpenSpecReadingSectionsProjectionRule(), createOpenSpecSpecProjectionRule(options)]
154861
+ };
154862
+ }
154863
+ function projectOpenSpecMarkdown(sourceMarkdown, options, plugins = [createOpenSpecReadingPlugin(options)]) {
154864
+ return toProjectedOpenSpecDocument(createMarkdownReadingDocument(sourceMarkdown, plugins));
154865
+ }
154866
+ function getOpenSpecProjectionAnnotation(annotations, factId, kind) {
154867
+ return annotations.find((annotation) => annotation.targetFactId === factId && annotation.kind === kind);
154868
+ }
154869
+ function createOpenSpecReadingSectionsProjectionRule() {
154870
+ return {
154871
+ id: OPEN_SPEC_READING_SECTIONS_PROJECTION_ID,
154872
+ project(context) {
154873
+ return {
154874
+ sections: collectSpecSections(toOpenSpecProjectionContext(context)),
154875
+ requirements: collectRequirementBlocks(toOpenSpecProjectionContext(context))
154876
+ };
154877
+ }
154878
+ };
154879
+ }
154880
+ function createOpenSpecSpecProjectionRule(options) {
154881
+ return {
154882
+ id: OPEN_SPEC_SPEC_PROJECTION_ID,
154883
+ project(context) {
154884
+ return projectOpenSpecContextToSpec(toOpenSpecProjectionContext(context), options);
154885
+ }
154886
+ };
154887
+ }
154888
+ function projectOpenSpecContextToSpec(context, options) {
154889
+ const name = context.annotations.find((annotation) => annotation.kind === "document-title")?.metadata?.title || options.specId;
154890
+ const overviewSection = collectSpecSections(context).find((section) => section.kind === "overview");
154891
+ const overview = overviewSection ? trimMarkdownSlice(context.sourceMarkdown, getContentStartAfterHeading(context, overviewSection.factId, overviewSection.start), overviewSection.end) : "";
154892
+ const requirements = collectRequirementBlocks(context).map((requirement) => projectRequirement(context, requirement));
154893
+ return {
154894
+ id: options.specId,
154895
+ name: name || options.specId,
154896
+ overview: overview.trim(),
154897
+ requirements,
154898
+ metadata: {
154899
+ version: "1.0.0",
154900
+ format: "openspec"
154901
+ }
154902
+ };
154903
+ }
154904
+ function createRequirementText(title, bodyMarkdown, scenarioText) {
154905
+ return [
154906
+ title,
154907
+ bodyMarkdown,
154908
+ scenarioText
154909
+ ].filter((part) => part.trim()).join("\n\n");
154910
+ }
154911
+ function collectSpecSections(context) {
154912
+ const headings = getMarkdownHeadingFacts(context);
154913
+ return headings.reduce((sections, fact, index) => {
154914
+ if (fact.depth !== 2) return sections;
154915
+ const span = getFactSpan(fact);
154916
+ if (!span) return sections;
154917
+ sections.push({
154918
+ id: fact.id,
154919
+ title: fact.text,
154920
+ kind: getSectionKind(context.annotations, fact.id),
154921
+ factId: fact.id,
154922
+ start: span.start,
154923
+ end: getMarkdownHeadingEnd(headings, index, context.sourceMarkdown.length)
154924
+ });
154925
+ return sections;
154926
+ }, []);
154927
+ }
154928
+ function collectRequirementBlocks(context) {
154929
+ const headings = getMarkdownHeadingFacts(context);
154930
+ let reqIndex = 0;
154931
+ return headings.reduce((requirements, fact, index) => {
154932
+ const annotation = getOpenSpecProjectionAnnotation(context.annotations, fact.id, "requirement");
154933
+ if (!annotation) return requirements;
154934
+ const span = getFactSpan(fact);
154935
+ if (!span) return requirements;
154936
+ reqIndex++;
154937
+ const end = getMarkdownHeadingEnd(headings, index, context.sourceMarkdown.length);
154938
+ const title = annotation.metadata?.title?.trim() || fact.text;
154939
+ requirements.push({
154940
+ id: `req-${reqIndex}`,
154941
+ title,
154942
+ factId: fact.id,
154943
+ start: span.start,
154944
+ end,
154945
+ scenarios: collectScenarioBlocks(context, headings, fact, end)
154946
+ });
154947
+ return requirements;
154948
+ }, []);
154949
+ }
154950
+ function collectScenarioBlocks(context, headings, requirementFact, requirementEnd) {
154951
+ const requirementSpan = getFactSpan(requirementFact);
154952
+ if (!requirementSpan) return [];
154953
+ return headings.reduce((scenarios, fact, index) => {
154954
+ const annotation = getOpenSpecProjectionAnnotation(context.annotations, fact.id, "scenario");
154955
+ if (!annotation) return scenarios;
154956
+ const span = getFactSpan(fact);
154957
+ if (!span || span.start <= requirementSpan.start || span.start >= requirementEnd) return scenarios;
154958
+ scenarios.push({
154959
+ title: annotation.metadata?.title?.trim() || fact.text,
154960
+ factId: fact.id,
154961
+ start: span.start,
154962
+ end: Math.min(getScenarioEnd(context, headings, index, requirementEnd), requirementEnd)
154963
+ });
154964
+ return scenarios;
154965
+ }, []);
154966
+ }
154967
+ function getScenarioEnd(context, headings, scenarioIndex, requirementEnd) {
154968
+ for (let i = scenarioIndex + 1; i < headings.length; i++) {
154969
+ const next = headings[i];
154970
+ const nextSpan = getFactSpan(next);
154971
+ if (!nextSpan || nextSpan.start >= requirementEnd) return requirementEnd;
154972
+ if ((next.depth ?? 6) <= 3 || getOpenSpecProjectionAnnotation(context.annotations, next.id, "scenario")) return nextSpan.start;
154973
+ }
154974
+ return requirementEnd;
154975
+ }
154976
+ function getContentStartAfterHeading(context, factId, fallback) {
154977
+ const fact = context.factById.get(factId);
154978
+ return fact ? getFactSpan(fact)?.end ?? fallback : fallback;
154979
+ }
154980
+ function projectScenario(context, scenario) {
154981
+ const bodyMarkdown = trimMarkdownSlice(context.sourceMarkdown, getContentStartAfterHeading(context, scenario.factId, scenario.start), scenario.end);
154982
+ const rawText = [scenario.title, bodyMarkdown].filter((part) => part.trim()).join("\n");
154983
+ return {
154984
+ title: scenario.title,
154985
+ bodyMarkdown,
154986
+ rawText,
154987
+ steps: getScenarioStepsFromAnnotations(context, scenario)
154988
+ };
154989
+ }
154990
+ function getScenarioStepsFromAnnotations(context, scenario) {
154991
+ return context.annotations.reduce((steps, annotation) => {
154992
+ if (annotation.kind !== "scenario-step") return steps;
154993
+ const fact = context.factById.get(annotation.targetFactId);
154994
+ const span = fact ? getFactSpan(fact) : void 0;
154995
+ if (!span || span.start <= scenario.start || span.start >= scenario.end) return steps;
154996
+ const keyword = annotation.metadata?.keyword;
154997
+ const contentMarkdown = annotation.metadata?.contentMarkdown;
154998
+ const rawText = annotation.metadata?.rawMarkdown;
154999
+ if (!isScenarioStepKeyword(keyword) || !contentMarkdown || !rawText) return steps;
155000
+ steps.push({
155001
+ keyword,
155002
+ contentMarkdown,
155003
+ rawText
155004
+ });
155005
+ return steps;
155006
+ }, []);
155007
+ }
155008
+ function isScenarioStepKeyword(value) {
155009
+ return value === "GIVEN" || value === "WHEN" || value === "THEN" || value === "AND" || value === "BUT";
155010
+ }
155011
+ function projectRequirement(context, requirement) {
155012
+ const bodyEnd = requirement.scenarios.map((scenario) => scenario.start).sort((left, right) => left - right)[0] ?? requirement.end;
155013
+ const bodyMarkdown = trimMarkdownSlice(context.sourceMarkdown, getContentStartAfterHeading(context, requirement.factId, requirement.start), bodyEnd);
155014
+ const scenarios = requirement.scenarios.map((scenario) => projectScenario(context, scenario));
155015
+ const scenarioText = scenarios.map((scenario) => scenario.rawText).join("\n\n");
155016
+ return {
155017
+ id: requirement.id,
155018
+ title: requirement.title,
155019
+ bodyMarkdown,
155020
+ text: createRequirementText(requirement.title, bodyMarkdown, scenarioText),
155021
+ scenarios
155022
+ };
155023
+ }
155024
+ function getFactSpan(fact) {
155025
+ return getMarkdownFactSpan(fact);
155026
+ }
155027
+ function getSectionKind(annotations, factId) {
155028
+ if (getOpenSpecProjectionAnnotation(annotations, factId, "purpose-section")) return "overview";
155029
+ if (getOpenSpecProjectionAnnotation(annotations, factId, "requirements-section")) return "requirements";
155030
+ return "other";
155031
+ }
155032
+ function toOpenSpecProjectionContext(context) {
155033
+ return {
155034
+ sourceMarkdown: context.sourceMarkdown,
155035
+ facts: context.facts,
155036
+ factById: context.factById,
155037
+ annotations: context.annotations.filter(isOpenSpecAnnotation)
155038
+ };
155039
+ }
155040
+ function toProjectedOpenSpecDocument(document) {
155041
+ return {
155042
+ ...document,
155043
+ annotations: document.annotations.filter(isOpenSpecAnnotation)
155044
+ };
155045
+ }
155046
+ function isOpenSpecAnnotation(annotation) {
155047
+ return annotation.kind === "document-title" || annotation.kind === "purpose-section" || annotation.kind === "requirements-section" || annotation.kind === "requirement" || annotation.kind === "scenario" || annotation.kind === "scenario-step" || annotation.kind === "keyword";
155048
+ }
155049
+ //#endregion
155050
+ //#region src/components/spec-markdown-document.tsx
155051
+ var OPENSPEC_PREFIXES = {
155052
+ requirement: /^(?:Requirement|Capability):\s*/i,
155053
+ scenario: /^(?:Scenario|Example):\s*/i
155054
+ };
155055
+ var OPENSPEC_INLINE_KEYWORD_CLASS = "openspec-inline-keyword";
155056
+ var OPENSPEC_SCENARIO_STEP_CLASS = "spec-scenario-step";
155057
+ var OPENSPEC_BLOCK_FACT_KINDS = new Set([
155058
+ "paragraph",
155059
+ "list",
155060
+ "listItem",
155061
+ "blockquote",
155062
+ "table"
155063
+ ]);
155064
+ function stripPrefix(text, prefix) {
155065
+ return text.replace(prefix, "").trim();
155066
+ }
155067
+ function describeOpenSpecHeading(sourceLevel, text) {
155068
+ if (sourceLevel === 1) return {
155069
+ kind: "spec",
155070
+ id: slugify(text) || "spec",
155071
+ title: text,
155072
+ tocLabel: text
155073
+ };
155074
+ if (sourceLevel === 2) return {
155075
+ kind: "section",
155076
+ id: slugify(text) || "section",
155077
+ title: text,
155078
+ tocLabel: text
155079
+ };
155080
+ if (sourceLevel === 3 && OPENSPEC_PREFIXES.requirement.test(text)) {
155081
+ const title = stripPrefix(text, OPENSPEC_PREFIXES.requirement);
155082
+ return {
155083
+ kind: "requirement",
155084
+ id: `requirement-${slugify(title) || "item"}`,
155085
+ title,
155086
+ tocLabel: title
155087
+ };
155088
+ }
155089
+ if (sourceLevel === 4 && OPENSPEC_PREFIXES.scenario.test(text)) {
155090
+ const title = stripPrefix(text, OPENSPEC_PREFIXES.scenario);
155091
+ return {
155092
+ kind: "scenario",
155093
+ id: `scenario-${slugify(title) || "item"}`,
155094
+ title,
155095
+ tocLabel: title
155096
+ };
155097
+ }
155098
+ }
155099
+ function describeAnnotatedOpenSpecHeading(document, headingFact, sourceLevel, text) {
155100
+ if (!headingFact) return describeOpenSpecHeading(sourceLevel, text);
155101
+ const readingProjection = document.projections[OPEN_SPEC_READING_SECTIONS_PROJECTION_ID];
155102
+ if (sourceLevel === 2) {
155103
+ const section = readingProjection?.sections.find((section) => section.factId === headingFact.id);
155104
+ return {
155105
+ kind: "section",
155106
+ id: slugify(text) || "section",
155107
+ title: text,
155108
+ tocLabel: text,
155109
+ ...section ? { sectionKind: section.kind } : {}
155110
+ };
155111
+ }
155112
+ const requirement = getOpenSpecProjectionAnnotation(document.annotations, headingFact.id, "requirement");
155113
+ if (requirement) {
155114
+ const title = requirement.metadata?.title?.trim() || text;
155115
+ const requirementIndex = readingProjection?.requirements.findIndex((block) => block.factId === headingFact.id) ?? -1;
155116
+ return {
155117
+ kind: "requirement",
155118
+ id: `requirement-${slugify(title) || "item"}`,
155119
+ title,
155120
+ tocLabel: title,
155121
+ label: requirementIndex >= 0 ? formatRequirementLabel(requirementIndex + 1) : "Requirement"
155122
+ };
155123
+ }
155124
+ const scenario = getOpenSpecProjectionAnnotation(document.annotations, headingFact.id, "scenario");
155125
+ if (scenario) {
155126
+ const title = scenario.metadata?.title?.trim() || text;
155127
+ return {
155128
+ kind: "scenario",
155129
+ id: `scenario-${slugify(title) || "item"}`,
155130
+ title,
155131
+ tocLabel: title,
155132
+ label: "Scenario"
155133
+ };
155134
+ }
155135
+ return describeOpenSpecHeading(sourceLevel, text);
155136
+ }
155137
+ function createAnnotatedHeadingTransform(document, requirementCount) {
155138
+ const headingByStartOffset = /* @__PURE__ */ new Map();
155139
+ for (const fact of document.facts) {
155140
+ if (fact.kind !== "heading" || typeof fact.depth !== "number") continue;
155141
+ const span = getMarkdownFactSpan(fact);
155142
+ if (span) headingByStartOffset.set(span.start, fact);
155143
+ }
155144
+ return ({ sourceLevel, text, sourceStartOffset }) => {
155145
+ const heading = describeAnnotatedOpenSpecHeading(document, sourceStartOffset === void 0 ? void 0 : headingByStartOffset.get(sourceStartOffset), sourceLevel, text);
155146
+ if (!heading) return void 0;
155147
+ return {
155148
+ id: heading.id,
155149
+ tocLabel: heading.tocLabel,
155150
+ className: createHeadingClassName(heading, requirementCount),
155151
+ suffix: createHeadingSuffix(heading, requirementCount),
155152
+ dataAttributes: {
155153
+ "data-openspec-kind": heading.kind,
155154
+ "data-openspec-title": heading.title,
155155
+ ...heading.label ? { "data-openspec-label": heading.label } : {},
155156
+ ...heading.sectionKind ? { "data-openspec-section-kind": heading.sectionKind } : {}
155157
+ }
155158
+ };
155159
+ };
155160
+ }
155161
+ function createHeadingClassName(heading, requirementCount) {
155162
+ if (heading.kind !== "section" || heading.title !== "Requirements") return void 0;
155163
+ if (requirementCount === void 0) return void 0;
155164
+ return "openspec-heading-with-chip";
155165
+ }
155166
+ function createHeadingSuffix(heading, requirementCount) {
155167
+ if (heading.kind !== "section" || heading.title !== "Requirements") return void 0;
155168
+ if (requirementCount === void 0) return void 0;
155169
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CountBadge, {
155170
+ count: requirementCount,
155171
+ tone: "subtle",
155172
+ size: "sm",
155173
+ shape: "box",
155174
+ className: "openspec-heading-chip",
155175
+ "aria-label": String(requirementCount),
155176
+ title: `${requirementCount} requirements`
155177
+ });
155178
+ }
155179
+ /**
155180
+ * Renders the processed spec Markdown as the visual source while attaching
155181
+ * OpenSpec structure metadata for styling, anchors, and ToC alignment.
155182
+ */
155183
+ function SpecMarkdownDocument({ markdown, spec, requirementCount, className = "" }) {
155184
+ const resolvedRequirementCount = requirementCount ?? spec?.requirements.length;
155185
+ const document = (0, import_react.useMemo)(() => projectOpenSpecMarkdown(markdown, { specId: spec?.id ?? "inline" }), [markdown, spec?.id]);
155186
+ const headingTransform = (0, import_react.useMemo)(() => createAnnotatedHeadingTransform(document, resolvedRequirementCount), [document, resolvedRequirementCount]);
155187
+ const inlineTextAnnotations = (0, import_react.useMemo)(() => createOpenSpecInlineTextAnnotations(document), [document]);
155188
+ const blockAnnotations = (0, import_react.useMemo)(() => createOpenSpecBlockAnnotations(document), [document]);
155189
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownViewer, {
155190
+ className: `spec-markdown-document spec-reading-document ${className}`,
155191
+ markdown,
155192
+ headingTransform,
155193
+ inlineTextAnnotations,
155194
+ blockAnnotations
155195
+ });
155196
+ }
155197
+ function createOpenSpecBlockAnnotations(document) {
155198
+ const factById = new Map(document.facts.map((fact) => [fact.id, fact]));
155199
+ const annotationByOffset = /* @__PURE__ */ new Map();
155200
+ const readingProjection = document.projections[OPEN_SPEC_READING_SECTIONS_PROJECTION_ID];
155201
+ if (readingProjection) {
155202
+ for (const section of readingProjection.sections) {
155203
+ if (section.kind !== "overview") continue;
155204
+ addRangeBlockAnnotations(document, annotationByOffset, {
155205
+ start: getFactEnd(factById, section.factId, section.start),
155206
+ end: section.end,
155207
+ dataAttributes: {
155208
+ "data-openspec-zone": "purpose",
155209
+ "data-openspec-section-kind": section.kind,
155210
+ "data-openspec-section-title": section.title
155211
+ }
155212
+ });
155213
+ }
155214
+ const requirementsSection = readingProjection.sections.find((section) => section.kind === "requirements");
155215
+ if (requirementsSection) {
155216
+ const firstRequirementStart = readingProjection.requirements.map((requirement) => requirement.start).sort((left, right) => left - right)[0];
155217
+ addRangeBlockAnnotations(document, annotationByOffset, {
155218
+ start: getFactEnd(factById, requirementsSection.factId, requirementsSection.start),
155219
+ end: firstRequirementStart ?? requirementsSection.end,
155220
+ dataAttributes: {
155221
+ "data-openspec-zone": "requirements-intro",
155222
+ "data-openspec-section-kind": requirementsSection.kind,
155223
+ "data-openspec-section-title": requirementsSection.title
155224
+ }
155225
+ });
155226
+ }
155227
+ for (const [index, requirement] of readingProjection.requirements.entries()) {
155228
+ const requirementLabel = formatRequirementLabel(index + 1);
155229
+ const firstScenarioStart = requirement.scenarios.map((scenario) => scenario.start).sort((left, right) => left - right)[0];
155230
+ const requirementData = {
155231
+ "data-openspec-requirement-id": requirement.id,
155232
+ "data-openspec-requirement-label": requirementLabel,
155233
+ "data-openspec-requirement-title": requirement.title
155234
+ };
155235
+ addRangeBlockAnnotations(document, annotationByOffset, {
155236
+ start: getFactEnd(factById, requirement.factId, requirement.start),
155237
+ end: firstScenarioStart ?? requirement.end,
155238
+ dataAttributes: {
155239
+ "data-openspec-zone": "requirement-body",
155240
+ ...requirementData
155241
+ }
155242
+ });
155243
+ for (const scenario of requirement.scenarios) addRangeBlockAnnotations(document, annotationByOffset, {
155244
+ start: getFactEnd(factById, scenario.factId, scenario.start),
155245
+ end: scenario.end,
155246
+ dataAttributes: {
155247
+ "data-openspec-zone": "scenario-body",
155248
+ ...requirementData,
155249
+ "data-openspec-scenario-title": scenario.title
155250
+ }
155251
+ });
155252
+ }
155253
+ }
155254
+ for (const annotation of document.annotations) {
155255
+ if (annotation.kind !== "scenario-step") continue;
155256
+ const fact = factById.get(annotation.targetFactId);
155257
+ const span = fact ? getMarkdownFactSpan(fact) : void 0;
155258
+ if (!fact || !span) continue;
155259
+ const keyword = annotation.metadata?.keyword;
155260
+ upsertBlockAnnotation(annotationByOffset, {
155261
+ sourceStartOffset: span.start,
155262
+ sourceKind: fact.mdastType,
155263
+ className: OPENSPEC_SCENARIO_STEP_CLASS,
155264
+ dataAttributes: {
155265
+ "data-openspec-kind": "scenario-step",
155266
+ ...keyword ? { "data-openspec-step-keyword": keyword } : {}
155267
+ }
155268
+ });
155269
+ }
155270
+ return Array.from(annotationByOffset.values());
155271
+ }
155272
+ function addRangeBlockAnnotations(document, annotationByOffset, range) {
155273
+ for (const fact of document.facts) {
155274
+ if (!OPENSPEC_BLOCK_FACT_KINDS.has(fact.kind)) continue;
155275
+ const span = getMarkdownFactSpan(fact);
155276
+ if (!span || span.start < range.start || span.start >= range.end) continue;
155277
+ upsertBlockAnnotation(annotationByOffset, {
155278
+ sourceStartOffset: span.start,
155279
+ sourceKind: fact.mdastType,
155280
+ dataAttributes: {
155281
+ "data-openspec-block-kind": fact.kind,
155282
+ ...range.dataAttributes
155283
+ }
155284
+ });
155285
+ }
155286
+ }
155287
+ function upsertBlockAnnotation(annotationByOffset, annotation) {
155288
+ const key = createBlockAnnotationKey(annotation);
155289
+ const previous = annotationByOffset.get(key);
155290
+ if (!previous) {
155291
+ annotationByOffset.set(key, annotation);
155292
+ return;
155293
+ }
155294
+ annotationByOffset.set(key, {
155295
+ sourceStartOffset: annotation.sourceStartOffset,
155296
+ sourceKind: annotation.sourceKind,
155297
+ className: [previous.className, annotation.className].filter(Boolean).join(" ") || void 0,
155298
+ dataAttributes: {
155299
+ ...previous.dataAttributes,
155300
+ ...annotation.dataAttributes
155301
+ }
155302
+ });
155303
+ }
155304
+ function createBlockAnnotationKey(annotation) {
155305
+ return `${annotation.sourceStartOffset}:${annotation.sourceKind ?? "*"}`;
155306
+ }
155307
+ function getFactEnd(factById, factId, fallback) {
155308
+ const fact = factById.get(factId);
155309
+ return fact ? getMarkdownFactSpan(fact)?.end ?? fallback : fallback;
155310
+ }
155311
+ function formatRequirementLabel(index) {
155312
+ return `REQ-${String(index).padStart(2, "0")}`;
155313
+ }
155314
+ function createOpenSpecInlineTextAnnotations(document) {
155315
+ const terms = /* @__PURE__ */ new Map();
155316
+ for (const annotation of document.annotations) {
155317
+ if (annotation.kind !== "keyword") continue;
155318
+ const { keyword, keywordText, keywordRole } = annotation.metadata ?? {};
155319
+ const text = keywordText ?? keyword;
155320
+ if (!keyword || !text) continue;
155321
+ terms.set(text, {
155322
+ text,
155323
+ className: OPENSPEC_INLINE_KEYWORD_CLASS,
155324
+ dataAttributes: createOpenSpecKeywordDataAttributes(keyword, keywordRole)
155325
+ });
155326
+ }
155327
+ return Array.from(terms.values());
155328
+ }
155329
+ function createOpenSpecKeywordDataAttributes(keyword, keywordRole) {
155330
+ return {
155331
+ "data-openspec-keyword": keyword,
155332
+ ...keywordRole ? { "data-openspec-keyword-role": keywordRole } : {}
155333
+ };
155334
+ }
155335
+ //#endregion
153805
155336
  //#region src/routes/spec-view.tsx
153806
155337
  function SpecView() {
153807
155338
  const { specId } = useParams({ from: "/specs/$specId" });
@@ -153811,8 +155342,9 @@ function SpecView() {
153811
155342
  entityId: specId
153812
155343
  }), [specId]);
153813
155344
  const { data: spec, isLoading } = useSpecSubscription(specId);
155345
+ const { data: rawMarkdown, isLoading: isRawLoading } = useSpecRawSubscription(specId);
153814
155346
  const validation = null;
153815
- if (isLoading && !spec) {
155347
+ if (isLoading && !spec || isRawLoading && !rawMarkdown) {
153816
155348
  if (handoff) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
153817
155349
  className: "flex min-h-0 flex-1 flex-col gap-6 p-4",
153818
155350
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
@@ -153854,10 +155386,11 @@ function SpecView() {
153854
155386
  });
153855
155387
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpecContent, {
153856
155388
  spec,
155389
+ rawMarkdown: rawMarkdown ?? "",
153857
155390
  validation
153858
155391
  });
153859
155392
  }
153860
- function SpecContent({ spec, validation }) {
155393
+ function SpecContent({ spec, rawMarkdown, validation }) {
153861
155394
  const headerRef = (0, import_react.useRef)(null);
153862
155395
  const sharedDescriptor = (0, import_react.useMemo)(() => ({
153863
155396
  family: "specs",
@@ -153895,56 +155428,11 @@ function SpecContent({ spec, validation }) {
153895
155428
  })]
153896
155429
  }),
153897
155430
  validation && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ValidationStatus, { validation }),
153898
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownViewer, {
153899
- className: "vt-detail-content min-h-0 flex-1",
153900
- markdown: ({ H1, H2, Section }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
153901
- className: "space-y-6",
153902
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Section, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(H1, {
153903
- id: "overview",
153904
- children: "Overview"
153905
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
153906
- className: "bg-muted/30 mt-2 rounded-lg p-4",
153907
- children: spec.overview ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownViewer, { markdown: spec.overview }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
153908
- className: "text-muted-foreground",
153909
- children: "No overview"
153910
- })
153911
- })] }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Section, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(H1, {
153912
- id: "requirements",
153913
- children: [
153914
- "Requirements (",
153915
- spec.requirements.length,
153916
- ")"
153917
- ]
153918
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
153919
- className: "mt-3 space-y-4",
153920
- children: [spec.requirements.map((req) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Section, {
153921
- className: "border-border rounded-lg border p-4",
153922
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(H2, {
153923
- className: "text-base",
153924
- id: `req-${req.id}`,
153925
- children: req.text
153926
- }), req.scenarios.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
153927
- className: "mt-3",
153928
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
153929
- className: "text-muted-foreground mb-2 text-sm font-medium",
153930
- children: [
153931
- "Scenarios (",
153932
- req.scenarios.length,
153933
- ")"
153934
- ]
153935
- }), req.scenarios.map((scenario, i) => {
153936
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
153937
- className: "bg-muted/50 rounded-md p-3",
153938
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownViewer, { markdown: scenario.rawText.replace(/^---\n?/, "").replace(/\n?---$/, "").trim() })
153939
- }, i);
153940
- })]
153941
- })]
153942
- }, req.id)), spec.requirements.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
153943
- className: "text-muted-foreground",
153944
- children: "No requirements defined"
153945
- })]
153946
- })] })]
153947
- })
155431
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpecMarkdownDocument, {
155432
+ markdown: rawMarkdown,
155433
+ spec,
155434
+ requirementCount: spec.requirements.length,
155435
+ className: "vt-detail-content min-h-0 flex-1"
153948
155436
  })
153949
155437
  ]
153950
155438
  });