@industry-theme/file-city-panel 0.5.72 → 0.5.74

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.
@@ -254529,7 +254529,7 @@ const NoteCard$1 = ({ note, onDelete }) => {
254529
254529
  alignItems: "flex-start"
254530
254530
  },
254531
254531
  children: [
254532
- /* @__PURE__ */ jsx(Avatar$1, { name: author, theme: theme2, size: 22 }),
254532
+ /* @__PURE__ */ jsx(Avatar$2, { name: author, theme: theme2, size: 22 }),
254533
254533
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
254534
254534
  /* @__PURE__ */ jsxs(
254535
254535
  "div",
@@ -254693,7 +254693,7 @@ const ComposerForm$1 = ({
254693
254693
  }
254694
254694
  );
254695
254695
  };
254696
- const Avatar$1 = ({ name: name2, theme: theme2, size }) => /* @__PURE__ */ jsx(
254696
+ const Avatar$2 = ({ name: name2, theme: theme2, size }) => /* @__PURE__ */ jsx(
254697
254697
  "span",
254698
254698
  {
254699
254699
  "aria-hidden": true,
@@ -256173,7 +256173,7 @@ const NoteCard = ({ note, onDelete }) => {
256173
256173
  const { theme: theme2 } = useTheme();
256174
256174
  const author = note.author ?? "Anonymous";
256175
256175
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "flex-start" }, children: [
256176
- /* @__PURE__ */ jsx(Avatar, { name: author, theme: theme2, size: 22 }),
256176
+ /* @__PURE__ */ jsx(Avatar$1, { name: author, theme: theme2, size: 22 }),
256177
256177
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
256178
256178
  /* @__PURE__ */ jsxs(
256179
256179
  "div",
@@ -256291,7 +256291,7 @@ const ComposerForm = ({
256291
256291
  }
256292
256292
  );
256293
256293
  };
256294
- const Avatar = ({ name: name2, theme: theme2, size }) => /* @__PURE__ */ jsx(
256294
+ const Avatar$1 = ({ name: name2, theme: theme2, size }) => /* @__PURE__ */ jsx(
256295
256295
  "span",
256296
256296
  {
256297
256297
  "aria-hidden": true,
@@ -256898,14 +256898,14 @@ const SequenceLeaderLine = React.forwardRef(function SequenceLeaderLine2({ conta
256898
256898
  )
256899
256899
  ] });
256900
256900
  });
256901
- const DRAFT_MARKDOWN_ANNOTATION_ID = "__draft-md__";
256901
+ const DRAFT_MARKDOWN_ANNOTATION_ID$1 = "__draft-md__";
256902
256902
  const sameMarkdownScope = (a, b) => {
256903
256903
  if (a.kind === "summary" && b.kind === "summary") return true;
256904
256904
  if (a.kind === "description" && b.kind === "description")
256905
256905
  return a.eventId === b.eventId;
256906
256906
  return false;
256907
256907
  };
256908
- const toMarkdownUiNote = (n) => {
256908
+ const toMarkdownUiNote$1 = (n) => {
256909
256909
  if (n.kind !== "markdown") return null;
256910
256910
  return {
256911
256911
  id: n.id,
@@ -257096,7 +257096,7 @@ const FileCitySequenceExplorerPanel = ({
257096
257096
  if (!(sequencePayload == null ? void 0 : sequencePayload.notes) || !markdownScope) return [];
257097
257097
  return sequencePayload.notes.filter(
257098
257098
  (n) => n.kind === "markdown" && sameMarkdownScope(n.scope, markdownScope)
257099
- ).map(toMarkdownUiNote).filter((n) => n != null);
257099
+ ).map(toMarkdownUiNote$1).filter((n) => n != null);
257100
257100
  }, [sequencePayload == null ? void 0 : sequencePayload.notes, markdownScope]);
257101
257101
  const [markdownNotesSelection, setMarkdownNotesSelection] = React.useState(null);
257102
257102
  const scopeKey = markdownScope ? markdownScope.kind === "description" ? `d-${markdownScope.eventId}` : "s" : "-";
@@ -257126,7 +257126,7 @@ const FileCitySequenceExplorerPanel = ({
257126
257126
  }
257127
257127
  if ((markdownNotesSelection == null ? void 0 : markdownNotesSelection.kind) === "composer") {
257128
257128
  result.push({
257129
- id: DRAFT_MARKDOWN_ANNOTATION_ID,
257129
+ id: DRAFT_MARKDOWN_ANNOTATION_ID$1,
257130
257130
  anchor: {
257131
257131
  exact: markdownNotesSelection.anchor.exact,
257132
257132
  prefix: markdownNotesSelection.anchor.prefix,
@@ -257139,7 +257139,7 @@ const FileCitySequenceExplorerPanel = ({
257139
257139
  const activeMarkdownAnnotationId = React.useMemo(() => {
257140
257140
  if (!markdownNotesSelection) return null;
257141
257141
  if (markdownNotesSelection.kind === "composer") {
257142
- return DRAFT_MARKDOWN_ANNOTATION_ID;
257142
+ return DRAFT_MARKDOWN_ANNOTATION_ID$1;
257143
257143
  }
257144
257144
  const head = markdownNotes.find(
257145
257145
  (n) => n.id === markdownNotesSelection.noteId
@@ -257152,7 +257152,7 @@ const FileCitySequenceExplorerPanel = ({
257152
257152
  }, [markdownNotesSelection, markdownNotes]);
257153
257153
  const handleMarkdownAnnotationClick = React.useCallback(
257154
257154
  (annotationId) => {
257155
- if (annotationId === DRAFT_MARKDOWN_ANNOTATION_ID) return;
257155
+ if (annotationId === DRAFT_MARKDOWN_ANNOTATION_ID$1) return;
257156
257156
  setMarkdownNotesSelection((prev) => {
257157
257157
  if ((prev == null ? void 0 : prev.kind) === "thread" && prev.noteId === annotationId) {
257158
257158
  return null;
@@ -258269,248 +258269,1156 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
258269
258269
  }
258270
258270
  );
258271
258271
  });
258272
- const PANEL_WIDTH_PCT = 38;
258273
- const FLOAT_INSET = 16;
258274
- const MIN_WIDTH_PX = 360;
258275
- const RESIZE_HANDLE_WIDTH = 6;
258276
- const TrailMarkdownOverlay = ({
258277
- eyebrow,
258272
+ const modeSegmentStyle = (theme2, active, side) => ({
258273
+ flexShrink: 0,
258274
+ fontFamily: theme2.fonts.body,
258275
+ fontSize: theme2.fontSizes[1],
258276
+ fontWeight: theme2.fontWeights.medium,
258277
+ color: active ? theme2.colors.background : theme2.colors.text,
258278
+ background: active ? theme2.colors.accent : "transparent",
258279
+ border: `1px solid ${active ? theme2.colors.accent : theme2.colors.muted}`,
258280
+ borderTopLeftRadius: side === "left" ? theme2.radii[3] : 0,
258281
+ borderBottomLeftRadius: side === "left" ? theme2.radii[3] : 0,
258282
+ borderTopRightRadius: side === "right" ? theme2.radii[3] : 0,
258283
+ borderBottomRightRadius: side === "right" ? theme2.radii[3] : 0,
258284
+ marginLeft: side === "right" ? -1 : 0,
258285
+ padding: "6px 14px",
258286
+ cursor: "pointer",
258287
+ lineHeight: 1.2
258288
+ });
258289
+ const TrailRequestIntroModal = ({
258290
+ body,
258291
+ request,
258278
258292
  title,
258279
- subtitle,
258280
- markdown: markdown2,
258281
- slideIdPrefix,
258282
- bottomOffset,
258283
- containerRef: externalContainerRef,
258284
- disableBottomTransition,
258285
- nav,
258286
- files
258293
+ author,
258294
+ createdAt,
258295
+ markerCount,
258296
+ repoCount,
258297
+ fileCount,
258298
+ steps,
258299
+ slideIdPrefix = "trail-request-intro",
258300
+ defaultMode,
258301
+ onBegin,
258302
+ onBeginAtStep,
258303
+ onDismiss,
258304
+ signOffs,
258305
+ signedByCurrentUser = false,
258306
+ seenBy,
258307
+ notesByAuthor
258287
258308
  }) => {
258288
258309
  const { theme: theme2 } = useTheme();
258289
- const body = markdown2.trim();
258290
- const bottomOffsetCss = typeof bottomOffset === "number" ? `${bottomOffset}px` : bottomOffset;
258291
- const [hasEntered, setHasEntered] = React.useState(false);
258292
- const [widthPx, setWidthPx] = React.useState(null);
258293
- const [isResizing, setIsResizing] = React.useState(false);
258294
- const containerRef = React.useRef(null);
258295
- const setContainerRef = React.useCallback(
258296
- (node2) => {
258297
- containerRef.current = node2;
258298
- if (typeof externalContainerRef === "function") {
258299
- externalContainerRef(node2);
258300
- } else if (externalContainerRef) {
258301
- externalContainerRef.current = node2;
258302
- }
258303
- },
258304
- [externalContainerRef]
258305
- );
258306
- const dragStateRef = React.useRef(null);
258307
- const handleResizePointerDown = React.useCallback(
258308
- (e) => {
258309
- if (e.button !== 0) return;
258310
- const el = containerRef.current;
258311
- if (!el) return;
258312
- e.preventDefault();
258313
- dragStateRef.current = {
258314
- startX: e.clientX,
258315
- startWidth: el.getBoundingClientRect().width
258316
- };
258317
- setIsResizing(true);
258318
- e.currentTarget.setPointerCapture(e.pointerId);
258319
- },
258320
- []
258310
+ const isInformative = !request || request.trim().length === 0;
258311
+ const eyebrowLabel = isInformative ? "Light Reading" : "Request";
258312
+ const headingLine = isInformative ? title : request;
258313
+ const stampText = isInformative ? "ACK" : "LGTM";
258314
+ const [showManifest, setShowManifest] = React.useState(false);
258315
+ const canExpandManifest = !!(steps && steps.length > 0);
258316
+ const [manifestRowHover, setManifestRowHover] = React.useState(false);
258317
+ const [noteAuthorFilter, setNoteAuthorFilter] = React.useState(
258318
+ null
258321
258319
  );
258322
- const handleResizePointerMove = React.useCallback(
258323
- (e) => {
258324
- var _a;
258325
- const drag2 = dragStateRef.current;
258326
- if (!drag2) return;
258327
- const parent = (_a = containerRef.current) == null ? void 0 : _a.parentElement;
258328
- const parentWidth = (parent == null ? void 0 : parent.clientWidth) ?? window.innerWidth;
258329
- const maxWidth = Math.max(MIN_WIDTH_PX, parentWidth - FLOAT_INSET);
258330
- const dx = e.clientX - drag2.startX;
258331
- const next2 = Math.min(maxWidth, Math.max(MIN_WIDTH_PX, drag2.startWidth + dx));
258332
- setWidthPx(next2);
258320
+ const filteredMarkerIds = React.useMemo(() => {
258321
+ if (!noteAuthorFilter) return null;
258322
+ const list2 = notesByAuthor == null ? void 0 : notesByAuthor[noteAuthorFilter];
258323
+ if (!list2 || list2.length === 0) return null;
258324
+ return new Set(list2);
258325
+ }, [noteAuthorFilter, notesByAuthor]);
258326
+ const handleAvatarClick = React.useCallback(
258327
+ (author2) => {
258328
+ const markerIds = notesByAuthor == null ? void 0 : notesByAuthor[author2];
258329
+ if (!markerIds || markerIds.length === 0) return;
258330
+ setNoteAuthorFilter((prev) => prev === author2 ? null : author2);
258331
+ setShowManifest(true);
258333
258332
  },
258334
- []
258333
+ [notesByAuthor]
258335
258334
  );
258336
- const handleResizePointerUp = React.useCallback(
258337
- (e) => {
258338
- if (!dragStateRef.current) return;
258339
- dragStateRef.current = null;
258340
- setIsResizing(false);
258341
- if (e.currentTarget.hasPointerCapture(e.pointerId)) {
258342
- e.currentTarget.releasePointerCapture(e.pointerId);
258343
- }
258344
- },
258345
- []
258335
+ React.useEffect(() => {
258336
+ if (!showManifest) setNoteAuthorFilter(null);
258337
+ }, [showManifest]);
258338
+ const [selectedMode, setSelectedMode] = React.useState(
258339
+ defaultMode
258346
258340
  );
258347
- if (!body) return null;
258348
- return /* @__PURE__ */ jsxs(
258341
+ const dateStamp = React.useMemo(() => {
258342
+ if (!createdAt) return null;
258343
+ const d = new Date(createdAt);
258344
+ if (Number.isNaN(d.getTime())) return null;
258345
+ const diffMs = Date.now() - d.getTime();
258346
+ const sec = Math.round(diffMs / 1e3);
258347
+ if (sec < 45) return "just now";
258348
+ const min = Math.round(sec / 60);
258349
+ if (min < 60) return `${min}m ago`;
258350
+ const hr = Math.round(min / 60);
258351
+ if (hr < 24) return `${hr}h ago`;
258352
+ const day = Math.round(hr / 24);
258353
+ if (day < 7) return `${day}d ago`;
258354
+ const wk = Math.round(day / 7);
258355
+ if (wk < 5) return `${wk}w ago`;
258356
+ const mo = Math.round(day / 30);
258357
+ if (mo < 12) return `${mo}mo ago`;
258358
+ const yr = Math.round(day / 365);
258359
+ return `${yr}y ago`;
258360
+ }, [createdAt]);
258361
+ const manifestParts = [];
258362
+ if (fileCount && fileCount > 0) {
258363
+ manifestParts.push(`${fileCount} ${fileCount === 1 ? "file" : "files"}`);
258364
+ }
258365
+ manifestParts.push(`${markerCount} ${markerCount === 1 ? "step" : "steps"}`);
258366
+ if (repoCount && repoCount > 1) {
258367
+ manifestParts.push(`${repoCount} repos`);
258368
+ }
258369
+ React.useEffect(() => {
258370
+ const onKey = (e) => {
258371
+ if (e.key === "Escape") onDismiss();
258372
+ };
258373
+ window.addEventListener("keydown", onKey);
258374
+ return () => window.removeEventListener("keydown", onKey);
258375
+ }, [onDismiss]);
258376
+ const trimmedBody = body.trim();
258377
+ return /* @__PURE__ */ jsx(
258349
258378
  "div",
258350
258379
  {
258351
- ref: setContainerRef,
258352
- onAnimationEnd: () => setHasEntered(true),
258380
+ onClick: onDismiss,
258381
+ role: "dialog",
258382
+ "aria-modal": "true",
258383
+ "aria-label": "Trail request",
258353
258384
  style: {
258354
258385
  position: "absolute",
258355
- top: 0,
258356
- left: 0,
258357
- // 1px gap above the drawer's top edge so the drawer reads
258358
- // cleanly through any subpixel rounding / transition flicker.
258359
- bottom: `calc(${bottomOffsetCss} + 1px)`,
258360
- // Match the sequence drawer's slide animation so the
258361
- // overlay's bottom edge tracks the drawer's height instead
258362
- // of snapping when the host toggles `bottomOffset`. Skipped
258363
- // while the drawer is being live-resized so the bottom edge
258364
- // tracks the cursor without lag.
258365
- transition: disableBottomTransition ? void 0 : "bottom 280ms ease",
258366
- width: widthPx != null ? widthPx : `${PANEL_WIDTH_PCT}%`,
258367
- minWidth: MIN_WIDTH_PX,
258368
- backgroundColor: theme2.colors.background,
258369
- // Flush against the city container's left/top/bottom edges;
258370
- // only the right edge separates from the canvas, so we draw
258371
- // the border there only.
258372
- borderRight: `1px solid ${theme2.colors.border}`,
258373
- overflow: "hidden",
258386
+ inset: 0,
258387
+ // Fully opaque so the city, markdown overlay, and snippet
258388
+ // pane behind the modal don't bleed through and compete with
258389
+ // the request for attention.
258390
+ background: theme2.colors.background,
258374
258391
  display: "flex",
258375
- flexDirection: "column",
258376
- zIndex: 1900,
258377
- animation: hasEntered || isResizing ? void 0 : "trailMarkdownOverlaySlideIn 220ms ease-out",
258378
- userSelect: isResizing ? "none" : void 0
258392
+ alignItems: "center",
258393
+ justifyContent: "center",
258394
+ // Above every other trail overlay markdown overlay sits at
258395
+ // 1900 and the leader line at 1901, so the intro must clear
258396
+ // both or the left edge bleeds through the backdrop.
258397
+ zIndex: 2e3,
258398
+ fontFamily: theme2.fonts.body,
258399
+ padding: theme2.space[4]
258379
258400
  },
258380
- children: [
258381
- /* @__PURE__ */ jsx("style", { children: `
258382
- @keyframes trailMarkdownOverlaySlideIn {
258383
- from { transform: translateX(-100%); }
258384
- to { transform: translateX(0); }
258385
- }
258386
- ` }),
258387
- eyebrow || title || subtitle ? /* @__PURE__ */ jsxs(
258388
- "div",
258389
- {
258390
- style: {
258391
- padding: "10px 14px",
258392
- borderBottom: `1px solid ${theme2.colors.border}`,
258393
- display: "flex",
258394
- flexDirection: "column",
258395
- color: theme2.colors.text,
258396
- fontFamily: theme2.fonts.body,
258397
- flexShrink: 0,
258398
- minWidth: 0
258399
- },
258400
- children: [
258401
- eyebrow ? /* @__PURE__ */ jsx(
258402
- "span",
258403
- {
258404
- style: {
258405
- fontSize: theme2.fontSizes[0],
258406
- color: theme2.colors.textSecondary,
258407
- letterSpacing: 0.4,
258408
- textTransform: "uppercase"
258409
- },
258410
- children: eyebrow
258411
- }
258412
- ) : null,
258413
- title ? /* @__PURE__ */ jsx(
258414
- "span",
258415
- {
258416
- style: {
258417
- fontSize: theme2.fontSizes[1],
258418
- fontWeight: 600,
258419
- overflow: "hidden",
258420
- textOverflow: "ellipsis",
258421
- whiteSpace: "nowrap"
258422
- },
258423
- title,
258424
- children: title
258425
- }
258426
- ) : null,
258427
- subtitle ? /* @__PURE__ */ jsx(
258428
- "span",
258429
- {
258430
- style: {
258431
- fontSize: theme2.fontSizes[0],
258432
- color: theme2.colors.textSecondary,
258433
- fontFamily: theme2.fonts.monospace,
258434
- overflow: "hidden",
258435
- textOverflow: "ellipsis",
258436
- whiteSpace: "nowrap",
258437
- marginTop: 2
258438
- },
258439
- title: subtitle,
258440
- children: subtitle
258441
- }
258442
- ) : null
258443
- ]
258444
- }
258445
- ) : null,
258446
- /* @__PURE__ */ jsxs(
258447
- "div",
258448
- {
258449
- style: {
258450
- flex: 1,
258451
- minHeight: 0,
258452
- overflowY: "auto",
258453
- display: "flex",
258454
- flexDirection: "column"
258455
- },
258456
- children: [
258457
- /* @__PURE__ */ jsx("div", { style: { flex: "0 0 auto" }, children: /* @__PURE__ */ jsx(
258458
- IndustryMarkdownSlide,
258459
- {
258460
- content: body,
258461
- slideIdPrefix,
258462
- slideIndex: 0,
258463
- isVisible: true,
258464
- theme: theme2,
258465
- transparentBackground: true,
258466
- enableKeyboardScrolling: false,
258467
- disableScroll: true
258468
- }
258469
- ) }),
258470
- nav ? /* @__PURE__ */ jsx(TrailMarkdownOverlayFooter, { nav }) : null,
258471
- files && files.length > 0 ? /* @__PURE__ */ jsx(TrailFilesList, { files }) : null
258472
- ]
258473
- }
258474
- ),
258475
- /* @__PURE__ */ jsx(
258476
- "div",
258477
- {
258478
- role: "separator",
258479
- "aria-orientation": "vertical",
258480
- "aria-label": "Resize markdown panel",
258481
- onPointerDown: handleResizePointerDown,
258482
- onPointerMove: handleResizePointerMove,
258483
- onPointerUp: handleResizePointerUp,
258484
- onPointerCancel: handleResizePointerUp,
258485
- style: {
258486
- position: "absolute",
258487
- top: 0,
258488
- right: 0,
258489
- bottom: 0,
258490
- width: RESIZE_HANDLE_WIDTH,
258491
- cursor: "ew-resize",
258492
- backgroundColor: isResizing ? theme2.colors.accent : "transparent",
258493
- transition: isResizing ? void 0 : "background-color 120ms ease",
258494
- touchAction: "none"
258495
- },
258496
- onMouseEnter: (e) => {
258497
- if (isResizing) return;
258498
- e.currentTarget.style.backgroundColor = theme2.colors.border;
258499
- },
258500
- onMouseLeave: (e) => {
258501
- if (isResizing) return;
258502
- e.currentTarget.style.backgroundColor = "transparent";
258503
- }
258504
- }
258505
- )
258506
- ]
258507
- }
258508
- );
258509
- };
258510
- const TrailFilesList = ({ files }) => {
258511
- const { theme: theme2 } = useTheme();
258512
- return /* @__PURE__ */ jsxs(
258513
- "div",
258401
+ children: /* @__PURE__ */ jsxs(
258402
+ "div",
258403
+ {
258404
+ onClick: (e) => e.stopPropagation(),
258405
+ style: {
258406
+ width: "min(640px, 100%)",
258407
+ maxHeight: "100%",
258408
+ display: "flex",
258409
+ flexDirection: "column",
258410
+ minHeight: 0,
258411
+ position: "relative"
258412
+ },
258413
+ children: [
258414
+ /* @__PURE__ */ jsxs(
258415
+ "div",
258416
+ {
258417
+ style: {
258418
+ maxHeight: "min(70vh, 640px)",
258419
+ flexShrink: 0,
258420
+ display: "flex",
258421
+ flexDirection: "column",
258422
+ // Card background is the secondary shade so it still reads
258423
+ // as a card against the opaque panel-background backdrop.
258424
+ background: theme2.colors.backgroundSecondary,
258425
+ color: theme2.colors.text,
258426
+ borderRadius: theme2.radii[4],
258427
+ border: `1px solid ${theme2.colors.border}`,
258428
+ boxShadow: theme2.shadows[4],
258429
+ overflow: "hidden"
258430
+ },
258431
+ children: [
258432
+ /* @__PURE__ */ jsxs(
258433
+ "div",
258434
+ {
258435
+ style: {
258436
+ padding: "14px 18px",
258437
+ borderBottom: `1px solid ${theme2.colors.border}`,
258438
+ display: "flex",
258439
+ justifyContent: "space-between",
258440
+ alignItems: "flex-start",
258441
+ gap: theme2.space[3]
258442
+ },
258443
+ children: [
258444
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
258445
+ /* @__PURE__ */ jsx(
258446
+ "div",
258447
+ {
258448
+ style: {
258449
+ fontFamily: theme2.fonts.monospace,
258450
+ fontSize: theme2.fontSizes[0],
258451
+ color: theme2.colors.textTertiary,
258452
+ textTransform: "uppercase",
258453
+ letterSpacing: "0.08em"
258454
+ },
258455
+ children: eyebrowLabel
258456
+ }
258457
+ ),
258458
+ /* @__PURE__ */ jsx(
258459
+ "div",
258460
+ {
258461
+ style: {
258462
+ fontFamily: theme2.fonts.body,
258463
+ fontSize: theme2.fontSizes[3],
258464
+ fontWeight: theme2.fontWeights.medium,
258465
+ color: theme2.colors.text,
258466
+ marginTop: 2
258467
+ },
258468
+ children: headingLine
258469
+ }
258470
+ ),
258471
+ /* @__PURE__ */ jsxs(
258472
+ "button",
258473
+ {
258474
+ type: "button",
258475
+ onClick: () => {
258476
+ if (canExpandManifest) setShowManifest((v) => !v);
258477
+ },
258478
+ onMouseEnter: () => setManifestRowHover(true),
258479
+ onMouseLeave: () => setManifestRowHover(false),
258480
+ disabled: !canExpandManifest,
258481
+ "aria-expanded": canExpandManifest ? showManifest : void 0,
258482
+ style: {
258483
+ fontFamily: theme2.fonts.monospace,
258484
+ fontSize: theme2.fontSizes[0],
258485
+ color: showManifest ? theme2.colors.text : theme2.colors.textTertiary,
258486
+ marginTop: 8,
258487
+ marginLeft: -6,
258488
+ marginRight: -6,
258489
+ letterSpacing: "0.04em",
258490
+ background: canExpandManifest && (showManifest || manifestRowHover) ? theme2.colors.backgroundTertiary ?? theme2.colors.background : "transparent",
258491
+ border: "none",
258492
+ padding: "4px 6px",
258493
+ borderRadius: theme2.radii[1],
258494
+ cursor: canExpandManifest ? "pointer" : "default",
258495
+ textAlign: "left",
258496
+ display: "block",
258497
+ width: "calc(100% + 12px)",
258498
+ transition: "background 120ms ease, color 120ms ease"
258499
+ },
258500
+ children: [
258501
+ "MANIFEST",
258502
+ " ",
258503
+ /* @__PURE__ */ jsx(
258504
+ "span",
258505
+ {
258506
+ style: {
258507
+ color: showManifest ? theme2.colors.text : theme2.colors.textSecondary
258508
+ },
258509
+ children: manifestParts.join(" · ")
258510
+ }
258511
+ )
258512
+ ]
258513
+ }
258514
+ ),
258515
+ /* @__PURE__ */ jsxs(
258516
+ "div",
258517
+ {
258518
+ style: {
258519
+ fontFamily: theme2.fonts.monospace,
258520
+ fontSize: theme2.fontSizes[0],
258521
+ color: theme2.colors.textTertiary,
258522
+ marginTop: 2,
258523
+ letterSpacing: "0.04em",
258524
+ display: "flex",
258525
+ flexWrap: "wrap",
258526
+ gap: "0 10px",
258527
+ lineHeight: 1.55
258528
+ },
258529
+ children: [
258530
+ author && author.trim().length > 0 ? /* @__PURE__ */ jsxs("span", { children: [
258531
+ "FROM",
258532
+ " ",
258533
+ /* @__PURE__ */ jsx("span", { style: { color: theme2.colors.textSecondary }, children: author })
258534
+ ] }) : null,
258535
+ dateStamp ? /* @__PURE__ */ jsx("span", { children: dateStamp }) : null
258536
+ ]
258537
+ }
258538
+ )
258539
+ ] }),
258540
+ /* @__PURE__ */ jsxs(
258541
+ "div",
258542
+ {
258543
+ "aria-hidden": true,
258544
+ style: {
258545
+ flexShrink: 0,
258546
+ alignSelf: "center",
258547
+ width: 80,
258548
+ height: 80,
258549
+ position: "relative",
258550
+ background: theme2.colors.background,
258551
+ borderRadius: theme2.radii[1],
258552
+ display: "flex",
258553
+ alignItems: "center",
258554
+ justifyContent: "center",
258555
+ fontFamily: theme2.fonts.monospace,
258556
+ fontSize: theme2.fontSizes[0],
258557
+ color: theme2.colors.textTertiary,
258558
+ letterSpacing: "0.08em",
258559
+ userSelect: "none"
258560
+ },
258561
+ children: [
258562
+ /* @__PURE__ */ jsx(
258563
+ "svg",
258564
+ {
258565
+ style: {
258566
+ position: "absolute",
258567
+ inset: 0,
258568
+ width: "100%",
258569
+ height: "100%",
258570
+ pointerEvents: "none"
258571
+ },
258572
+ viewBox: "0 0 80 80",
258573
+ preserveAspectRatio: "none",
258574
+ "aria-hidden": true,
258575
+ children: /* @__PURE__ */ jsx(
258576
+ "rect",
258577
+ {
258578
+ x: "1",
258579
+ y: "1",
258580
+ width: "78",
258581
+ height: "78",
258582
+ rx: "3",
258583
+ ry: "3",
258584
+ fill: "none",
258585
+ stroke: theme2.colors.textTertiary,
258586
+ strokeWidth: 2,
258587
+ pathLength: 16,
258588
+ strokeDasharray: "1 1",
258589
+ strokeDashoffset: -0.5,
258590
+ strokeLinecap: "butt"
258591
+ }
258592
+ )
258593
+ }
258594
+ ),
258595
+ signedByCurrentUser ? /* @__PURE__ */ jsx(
258596
+ "div",
258597
+ {
258598
+ style: {
258599
+ position: "absolute",
258600
+ inset: 0,
258601
+ display: "flex",
258602
+ alignItems: "center",
258603
+ justifyContent: "center"
258604
+ },
258605
+ children: /* @__PURE__ */ jsx(LgtmStamp, { theme: theme2, size: 80, rotated: false, text: stampText })
258606
+ }
258607
+ ) : /* @__PURE__ */ jsxs(
258608
+ "span",
258609
+ {
258610
+ style: {
258611
+ position: "relative",
258612
+ display: "flex",
258613
+ flexDirection: "column",
258614
+ alignItems: "center",
258615
+ gap: 1,
258616
+ lineHeight: 1.1,
258617
+ fontSize: "0.75em"
258618
+ },
258619
+ children: [
258620
+ /* @__PURE__ */ jsx("span", { children: "TRAIL" }),
258621
+ /* @__PURE__ */ jsx("span", { children: "UNREVIEWED" })
258622
+ ]
258623
+ }
258624
+ )
258625
+ ]
258626
+ }
258627
+ )
258628
+ ]
258629
+ }
258630
+ ),
258631
+ /* @__PURE__ */ jsxs(
258632
+ "div",
258633
+ {
258634
+ style: {
258635
+ flex: 1,
258636
+ minHeight: 0,
258637
+ overflowY: "auto",
258638
+ padding: 0,
258639
+ position: "relative"
258640
+ },
258641
+ children: [
258642
+ /* @__PURE__ */ jsx(
258643
+ IndustryMarkdownSlide,
258644
+ {
258645
+ content: trimmedBody,
258646
+ slideIdPrefix,
258647
+ slideIndex: 0,
258648
+ isVisible: true,
258649
+ theme: theme2,
258650
+ transparentBackground: true,
258651
+ enableKeyboardScrolling: false,
258652
+ disableScroll: true
258653
+ }
258654
+ ),
258655
+ showManifest && steps && steps.length > 0 ? /* @__PURE__ */ jsxs(
258656
+ "div",
258657
+ {
258658
+ style: {
258659
+ position: "absolute",
258660
+ inset: 0,
258661
+ background: theme2.colors.backgroundSecondary,
258662
+ display: "flex",
258663
+ flexDirection: "column"
258664
+ },
258665
+ children: [
258666
+ noteAuthorFilter ? /* @__PURE__ */ jsxs(
258667
+ "div",
258668
+ {
258669
+ style: {
258670
+ padding: "8px 18px",
258671
+ borderBottom: `1px solid ${theme2.colors.border}`,
258672
+ display: "flex",
258673
+ alignItems: "center",
258674
+ gap: 8,
258675
+ fontFamily: theme2.fonts.monospace,
258676
+ fontSize: theme2.fontSizes[0],
258677
+ color: theme2.colors.textTertiary,
258678
+ letterSpacing: "0.04em",
258679
+ flexShrink: 0
258680
+ },
258681
+ children: [
258682
+ /* @__PURE__ */ jsxs("span", { children: [
258683
+ "NOTES BY",
258684
+ " ",
258685
+ /* @__PURE__ */ jsx("span", { style: { color: theme2.colors.text }, children: noteAuthorFilter })
258686
+ ] }),
258687
+ /* @__PURE__ */ jsx(
258688
+ "button",
258689
+ {
258690
+ type: "button",
258691
+ onClick: () => setNoteAuthorFilter(null),
258692
+ "aria-label": "Clear filter",
258693
+ title: "Clear filter",
258694
+ style: {
258695
+ marginLeft: "auto",
258696
+ background: "transparent",
258697
+ border: "none",
258698
+ color: theme2.colors.textTertiary,
258699
+ cursor: "pointer",
258700
+ padding: "0 4px",
258701
+ fontSize: theme2.fontSizes[1],
258702
+ lineHeight: 1
258703
+ },
258704
+ children: "×"
258705
+ }
258706
+ )
258707
+ ]
258708
+ }
258709
+ ) : null,
258710
+ /* @__PURE__ */ jsx(
258711
+ "ol",
258712
+ {
258713
+ style: {
258714
+ listStyle: "none",
258715
+ margin: 0,
258716
+ padding: "18px 18px 18px",
258717
+ fontFamily: theme2.fonts.monospace,
258718
+ fontSize: theme2.fontSizes[1],
258719
+ lineHeight: 1.6,
258720
+ color: theme2.colors.textSecondary,
258721
+ overflowY: "auto",
258722
+ flex: 1,
258723
+ minHeight: 0
258724
+ },
258725
+ children: steps.map((step, i) => {
258726
+ const clickable = !!onBeginAtStep;
258727
+ const matchesFilter = filteredMarkerIds != null && step.id != null && filteredMarkerIds.has(step.id);
258728
+ const dimmed = filteredMarkerIds != null && !matchesFilter;
258729
+ return /* @__PURE__ */ jsx(
258730
+ "li",
258731
+ {
258732
+ style: {
258733
+ borderBottom: i < steps.length - 1 ? `1px dashed ${theme2.colors.border}` : "none",
258734
+ opacity: dimmed ? 0.4 : 1,
258735
+ transition: "opacity 120ms ease"
258736
+ },
258737
+ children: /* @__PURE__ */ jsxs(
258738
+ "button",
258739
+ {
258740
+ type: "button",
258741
+ onClick: () => {
258742
+ if (onBeginAtStep) onBeginAtStep(i, selectedMode);
258743
+ },
258744
+ disabled: !clickable,
258745
+ style: {
258746
+ all: "unset",
258747
+ boxSizing: "border-box",
258748
+ display: "flex",
258749
+ gap: 12,
258750
+ alignItems: "baseline",
258751
+ width: "100%",
258752
+ padding: "6px 8px",
258753
+ margin: "0 -8px",
258754
+ borderRadius: theme2.radii[1],
258755
+ cursor: clickable ? "pointer" : "default",
258756
+ fontFamily: "inherit",
258757
+ fontSize: "inherit",
258758
+ lineHeight: "inherit",
258759
+ color: "inherit",
258760
+ transition: "background 100ms ease",
258761
+ background: matchesFilter ? `color-mix(in srgb, ${theme2.colors.accent} 18%, transparent)` : "transparent",
258762
+ borderLeft: matchesFilter ? `3px solid ${theme2.colors.accent}` : "3px solid transparent",
258763
+ paddingLeft: 8
258764
+ },
258765
+ onMouseEnter: (e) => {
258766
+ if (clickable && !matchesFilter) {
258767
+ e.currentTarget.style.background = theme2.colors.backgroundTertiary ?? theme2.colors.background;
258768
+ }
258769
+ },
258770
+ onMouseLeave: (e) => {
258771
+ e.currentTarget.style.background = matchesFilter ? `color-mix(in srgb, ${theme2.colors.accent} 18%, transparent)` : "transparent";
258772
+ },
258773
+ title: clickable ? `Start walkthrough at step ${i + 1}` : void 0,
258774
+ children: [
258775
+ /* @__PURE__ */ jsx(
258776
+ "span",
258777
+ {
258778
+ style: {
258779
+ color: theme2.colors.textTertiary,
258780
+ flexShrink: 0,
258781
+ minWidth: 24
258782
+ },
258783
+ children: String(i + 1).padStart(2, "0")
258784
+ }
258785
+ ),
258786
+ /* @__PURE__ */ jsx(
258787
+ "span",
258788
+ {
258789
+ style: {
258790
+ flex: 1,
258791
+ minWidth: 0,
258792
+ color: theme2.colors.text
258793
+ },
258794
+ children: step.label
258795
+ }
258796
+ ),
258797
+ step.sourcePath ? /* @__PURE__ */ jsx(
258798
+ "span",
258799
+ {
258800
+ style: {
258801
+ color: theme2.colors.textTertiary,
258802
+ flexShrink: 1,
258803
+ minWidth: 0,
258804
+ overflow: "hidden",
258805
+ textOverflow: "ellipsis",
258806
+ whiteSpace: "nowrap"
258807
+ },
258808
+ title: step.sourcePath,
258809
+ children: step.sourcePath
258810
+ }
258811
+ ) : null
258812
+ ]
258813
+ }
258814
+ )
258815
+ },
258816
+ i
258817
+ );
258818
+ })
258819
+ }
258820
+ )
258821
+ ]
258822
+ }
258823
+ ) : null
258824
+ ]
258825
+ }
258826
+ ),
258827
+ /* @__PURE__ */ jsxs(
258828
+ "div",
258829
+ {
258830
+ style: {
258831
+ padding: "14px 18px",
258832
+ borderTop: `1px solid ${theme2.colors.border}`,
258833
+ display: "grid",
258834
+ // Three-column grid so the switch sits in the true center
258835
+ // of the footer regardless of the OPEN AS label width or
258836
+ // the Start button width. Left/right columns share `1fr`
258837
+ // so the center stays balanced.
258838
+ gridTemplateColumns: "1fr auto 1fr",
258839
+ alignItems: "center",
258840
+ gap: theme2.space[3]
258841
+ },
258842
+ children: [
258843
+ /* @__PURE__ */ jsx(
258844
+ "span",
258845
+ {
258846
+ style: {
258847
+ justifySelf: "start",
258848
+ fontFamily: theme2.fonts.monospace,
258849
+ fontSize: theme2.fontSizes[0],
258850
+ color: theme2.colors.textTertiary,
258851
+ letterSpacing: "0.04em"
258852
+ },
258853
+ children: "OPEN AS"
258854
+ }
258855
+ ),
258856
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifySelf: "center" }, children: [
258857
+ /* @__PURE__ */ jsx(
258858
+ "button",
258859
+ {
258860
+ type: "button",
258861
+ onClick: () => setSelectedMode("city"),
258862
+ "aria-pressed": selectedMode === "city",
258863
+ title: "City view — 3D buildings + sequence drawer",
258864
+ style: {
258865
+ ...modeSegmentStyle(theme2, selectedMode === "city", "left"),
258866
+ // Both segments share a min-width so the switch reads
258867
+ // as two equal halves regardless of label length.
258868
+ minWidth: 110,
258869
+ textAlign: "center"
258870
+ },
258871
+ children: "Walkthrough"
258872
+ }
258873
+ ),
258874
+ /* @__PURE__ */ jsx(
258875
+ "button",
258876
+ {
258877
+ type: "button",
258878
+ onClick: () => setSelectedMode("doc"),
258879
+ "aria-pressed": selectedMode === "doc",
258880
+ title: "Document view — scroll all markers as one page",
258881
+ style: {
258882
+ ...modeSegmentStyle(theme2, selectedMode === "doc", "right"),
258883
+ minWidth: 110,
258884
+ textAlign: "center"
258885
+ },
258886
+ children: "Doc"
258887
+ }
258888
+ )
258889
+ ] }),
258890
+ /* @__PURE__ */ jsx(
258891
+ "button",
258892
+ {
258893
+ type: "button",
258894
+ onClick: () => onBegin(selectedMode),
258895
+ autoFocus: true,
258896
+ title: "Start the trail in the chosen format",
258897
+ style: {
258898
+ justifySelf: "end",
258899
+ fontFamily: theme2.fonts.body,
258900
+ fontSize: theme2.fontSizes[1],
258901
+ fontWeight: theme2.fontWeights.semibold,
258902
+ color: theme2.colors.background,
258903
+ background: theme2.colors.accent,
258904
+ border: `1px solid ${theme2.colors.accent}`,
258905
+ borderRadius: theme2.radii[3],
258906
+ padding: "6px 18px",
258907
+ cursor: "pointer",
258908
+ lineHeight: 1.2
258909
+ },
258910
+ children: "Start"
258911
+ }
258912
+ )
258913
+ ]
258914
+ }
258915
+ )
258916
+ ]
258917
+ }
258918
+ ),
258919
+ signOffs && signOffs.length > 0 ? /* @__PURE__ */ jsx(
258920
+ AvatarStrip,
258921
+ {
258922
+ theme: theme2,
258923
+ label: "REVIEWED BY",
258924
+ ringed: true,
258925
+ entries: signOffs.map((s2) => {
258926
+ var _a;
258927
+ const hasNotes = (((_a = notesByAuthor == null ? void 0 : notesByAuthor[s2.author]) == null ? void 0 : _a.length) ?? 0) > 0;
258928
+ return {
258929
+ key: s2.id,
258930
+ name: s2.author,
258931
+ tooltip: `${s2.author} · ${s2.signedAt.slice(0, 10)}${s2.comment ? `
258932
+ ${s2.comment}` : ""}${hasNotes ? "\n(click to highlight their notes on the manifest)" : ""}`,
258933
+ onClick: hasNotes ? () => handleAvatarClick(s2.author) : void 0,
258934
+ active: noteAuthorFilter === s2.author
258935
+ };
258936
+ })
258937
+ }
258938
+ ) : null,
258939
+ seenBy && seenBy.length > 0 ? /* @__PURE__ */ jsx(
258940
+ AvatarStrip,
258941
+ {
258942
+ theme: theme2,
258943
+ label: "NOTES BY",
258944
+ entries: seenBy.map((name2) => {
258945
+ var _a;
258946
+ const hasNotes = (((_a = notesByAuthor == null ? void 0 : notesByAuthor[name2]) == null ? void 0 : _a.length) ?? 0) > 0;
258947
+ return {
258948
+ key: name2,
258949
+ name: name2,
258950
+ tooltip: hasNotes ? `${name2} — click to highlight their notes on the manifest` : name2,
258951
+ onClick: hasNotes ? () => handleAvatarClick(name2) : void 0,
258952
+ active: noteAuthorFilter === name2
258953
+ };
258954
+ })
258955
+ }
258956
+ ) : null
258957
+ ]
258958
+ }
258959
+ )
258960
+ }
258961
+ );
258962
+ };
258963
+ const Avatar = ({ theme: theme2, name: name2, size, ringed }) => {
258964
+ const initials2 = (() => {
258965
+ var _a, _b;
258966
+ const parts = name2.trim().split(/\s+/);
258967
+ return ((((_a = parts[0]) == null ? void 0 : _a[0]) ?? "") + (((_b = parts[1]) == null ? void 0 : _b[0]) ?? "")).toUpperCase();
258968
+ })();
258969
+ const borderWidth = ringed ? 2 : 0;
258970
+ return /* @__PURE__ */ jsx(
258971
+ "span",
258972
+ {
258973
+ "aria-hidden": true,
258974
+ style: {
258975
+ display: "inline-flex",
258976
+ alignItems: "center",
258977
+ justifyContent: "center",
258978
+ width: size,
258979
+ height: size,
258980
+ boxSizing: "border-box",
258981
+ flexShrink: 0,
258982
+ borderRadius: "50%",
258983
+ border: ringed ? `${borderWidth}px solid ${theme2.colors.success}` : "none",
258984
+ background: `color-mix(in srgb, ${theme2.colors.accent} 30%, ${theme2.colors.background})`,
258985
+ color: theme2.colors.text,
258986
+ fontFamily: theme2.fonts.body,
258987
+ fontSize: Math.max(10, Math.round(size * 0.4)),
258988
+ fontWeight: theme2.fontWeights.bold,
258989
+ letterSpacing: 0.4
258990
+ },
258991
+ children: initials2 || "?"
258992
+ }
258993
+ );
258994
+ };
258995
+ const AvatarStrip = ({ theme: theme2, label, entries, ringed = false }) => {
258996
+ return /* @__PURE__ */ jsxs(
258997
+ "div",
258998
+ {
258999
+ style: {
259000
+ marginTop: 10,
259001
+ padding: "8px 14px",
259002
+ background: theme2.colors.backgroundSecondary,
259003
+ border: `1px solid ${theme2.colors.border}`,
259004
+ borderRadius: theme2.radii[3],
259005
+ display: "flex",
259006
+ alignItems: "center",
259007
+ gap: 10,
259008
+ flexShrink: 0,
259009
+ boxShadow: theme2.shadows[2]
259010
+ },
259011
+ children: [
259012
+ /* @__PURE__ */ jsx(
259013
+ "span",
259014
+ {
259015
+ style: {
259016
+ fontFamily: theme2.fonts.monospace,
259017
+ fontSize: theme2.fontSizes[0],
259018
+ color: theme2.colors.textTertiary,
259019
+ letterSpacing: "0.04em",
259020
+ flexShrink: 0
259021
+ },
259022
+ children: label
259023
+ }
259024
+ ),
259025
+ /* @__PURE__ */ jsx(
259026
+ "div",
259027
+ {
259028
+ style: {
259029
+ display: "flex",
259030
+ alignItems: "center",
259031
+ gap: 6,
259032
+ flex: 1,
259033
+ minWidth: 0,
259034
+ overflowX: "auto"
259035
+ },
259036
+ children: entries.map((e) => {
259037
+ const Tag2 = e.onClick ? "button" : "span";
259038
+ return /* @__PURE__ */ jsxs(
259039
+ Tag2,
259040
+ {
259041
+ type: e.onClick ? "button" : void 0,
259042
+ onClick: e.onClick,
259043
+ title: e.tooltip ?? e.name,
259044
+ style: {
259045
+ display: "inline-flex",
259046
+ alignItems: "center",
259047
+ gap: 6,
259048
+ flexShrink: 0,
259049
+ background: e.active ? `color-mix(in srgb, ${theme2.colors.accent} 18%, transparent)` : "transparent",
259050
+ border: e.active ? `1px solid ${theme2.colors.accent}` : `1px solid transparent`,
259051
+ borderRadius: 999,
259052
+ padding: e.onClick ? "2px 8px 2px 2px" : 0,
259053
+ cursor: e.onClick ? "pointer" : "default",
259054
+ color: "inherit",
259055
+ fontFamily: "inherit",
259056
+ fontSize: "inherit"
259057
+ },
259058
+ children: [
259059
+ /* @__PURE__ */ jsx(Avatar, { theme: theme2, name: e.name, size: 24, ringed }),
259060
+ /* @__PURE__ */ jsx(
259061
+ "span",
259062
+ {
259063
+ style: {
259064
+ fontFamily: theme2.fonts.body,
259065
+ fontSize: theme2.fontSizes[0],
259066
+ color: theme2.colors.textSecondary
259067
+ },
259068
+ children: e.name
259069
+ }
259070
+ )
259071
+ ]
259072
+ },
259073
+ e.key
259074
+ );
259075
+ })
259076
+ }
259077
+ )
259078
+ ]
259079
+ }
259080
+ );
259081
+ };
259082
+ const LgtmStamp = ({ theme: theme2, size, rotated = true, text: text2 = "LGTM" }) => {
259083
+ const inkColor = theme2.colors.success;
259084
+ const borderWidth = Math.max(2, Math.round(size / 30));
259085
+ return /* @__PURE__ */ jsxs(
259086
+ "div",
259087
+ {
259088
+ "aria-hidden": true,
259089
+ style: {
259090
+ width: size,
259091
+ height: size,
259092
+ transform: rotated ? "rotate(-8deg)" : "none",
259093
+ border: `${borderWidth}px double ${inkColor}`,
259094
+ borderRadius: theme2.radii[1],
259095
+ display: "flex",
259096
+ flexDirection: "column",
259097
+ alignItems: "center",
259098
+ justifyContent: "center",
259099
+ fontFamily: theme2.fonts.monospace,
259100
+ color: inkColor,
259101
+ letterSpacing: "0.1em",
259102
+ opacity: 0.9,
259103
+ userSelect: "none",
259104
+ padding: size * 0.05,
259105
+ boxSizing: "border-box"
259106
+ },
259107
+ children: [
259108
+ /* @__PURE__ */ jsx(
259109
+ "div",
259110
+ {
259111
+ style: {
259112
+ fontSize: size * 0.26,
259113
+ fontWeight: theme2.fontWeights.bold,
259114
+ lineHeight: 1
259115
+ },
259116
+ children: text2
259117
+ }
259118
+ ),
259119
+ /* @__PURE__ */ jsx(
259120
+ "div",
259121
+ {
259122
+ style: {
259123
+ fontSize: size * 0.1,
259124
+ marginTop: size * 0.05,
259125
+ letterSpacing: "0.08em"
259126
+ },
259127
+ children: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
259128
+ }
259129
+ )
259130
+ ]
259131
+ }
259132
+ );
259133
+ };
259134
+ const PANEL_WIDTH_PCT = 38;
259135
+ const FLOAT_INSET = 16;
259136
+ const MIN_WIDTH_PX = 360;
259137
+ const RESIZE_HANDLE_WIDTH = 6;
259138
+ const TrailMarkdownOverlay = ({
259139
+ eyebrow,
259140
+ title,
259141
+ subtitle,
259142
+ markdown: markdown2,
259143
+ slideIdPrefix,
259144
+ bottomOffset,
259145
+ containerRef: externalContainerRef,
259146
+ disableBottomTransition,
259147
+ nav,
259148
+ files,
259149
+ annotations,
259150
+ activeAnnotationId,
259151
+ onAnnotationClick,
259152
+ onCreateNoteForSelection,
259153
+ composerOpen
259154
+ }) => {
259155
+ const { theme: theme2 } = useTheme();
259156
+ const body = markdown2.trim();
259157
+ const [pendingSelection, setPendingSelection] = React.useState(null);
259158
+ const handleSelectionChange = React.useCallback(
259159
+ (selection2) => {
259160
+ setPendingSelection(selection2);
259161
+ },
259162
+ []
259163
+ );
259164
+ const handleAnnotationClick = React.useCallback(
259165
+ (annotationId) => {
259166
+ onAnnotationClick == null ? void 0 : onAnnotationClick(annotationId);
259167
+ },
259168
+ [onAnnotationClick]
259169
+ );
259170
+ const annotationStyleVars = React.useMemo(
259171
+ () => ({
259172
+ backgroundColor: `color-mix(in srgb, ${theme2.colors.accent} 22%, transparent)`,
259173
+ activeBackgroundColor: `color-mix(in srgb, ${theme2.colors.accent} 45%, transparent)`
259174
+ }),
259175
+ [theme2.colors.accent]
259176
+ );
259177
+ const bottomOffsetCss = typeof bottomOffset === "number" ? `${bottomOffset}px` : bottomOffset;
259178
+ const [hasEntered, setHasEntered] = React.useState(false);
259179
+ const [widthPx, setWidthPx] = React.useState(null);
259180
+ const [isResizing, setIsResizing] = React.useState(false);
259181
+ const containerRef = React.useRef(null);
259182
+ const setContainerRef = React.useCallback(
259183
+ (node2) => {
259184
+ containerRef.current = node2;
259185
+ if (typeof externalContainerRef === "function") {
259186
+ externalContainerRef(node2);
259187
+ } else if (externalContainerRef) {
259188
+ externalContainerRef.current = node2;
259189
+ }
259190
+ },
259191
+ [externalContainerRef]
259192
+ );
259193
+ const dragStateRef = React.useRef(null);
259194
+ const handleResizePointerDown = React.useCallback(
259195
+ (e) => {
259196
+ if (e.button !== 0) return;
259197
+ const el = containerRef.current;
259198
+ if (!el) return;
259199
+ e.preventDefault();
259200
+ dragStateRef.current = {
259201
+ startX: e.clientX,
259202
+ startWidth: el.getBoundingClientRect().width
259203
+ };
259204
+ setIsResizing(true);
259205
+ e.currentTarget.setPointerCapture(e.pointerId);
259206
+ },
259207
+ []
259208
+ );
259209
+ const handleResizePointerMove = React.useCallback(
259210
+ (e) => {
259211
+ var _a;
259212
+ const drag2 = dragStateRef.current;
259213
+ if (!drag2) return;
259214
+ const parent = (_a = containerRef.current) == null ? void 0 : _a.parentElement;
259215
+ const parentWidth = (parent == null ? void 0 : parent.clientWidth) ?? window.innerWidth;
259216
+ const maxWidth = Math.max(MIN_WIDTH_PX, parentWidth - FLOAT_INSET);
259217
+ const dx = e.clientX - drag2.startX;
259218
+ const next2 = Math.min(maxWidth, Math.max(MIN_WIDTH_PX, drag2.startWidth + dx));
259219
+ setWidthPx(next2);
259220
+ },
259221
+ []
259222
+ );
259223
+ const handleResizePointerUp = React.useCallback(
259224
+ (e) => {
259225
+ if (!dragStateRef.current) return;
259226
+ dragStateRef.current = null;
259227
+ setIsResizing(false);
259228
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) {
259229
+ e.currentTarget.releasePointerCapture(e.pointerId);
259230
+ }
259231
+ },
259232
+ []
259233
+ );
259234
+ if (!body) return null;
259235
+ return /* @__PURE__ */ jsxs(
259236
+ "div",
259237
+ {
259238
+ ref: setContainerRef,
259239
+ onAnimationEnd: () => setHasEntered(true),
259240
+ style: {
259241
+ position: "absolute",
259242
+ top: 0,
259243
+ left: 0,
259244
+ // 1px gap above the drawer's top edge so the drawer reads
259245
+ // cleanly through any subpixel rounding / transition flicker.
259246
+ bottom: `calc(${bottomOffsetCss} + 1px)`,
259247
+ // Match the sequence drawer's slide animation so the
259248
+ // overlay's bottom edge tracks the drawer's height instead
259249
+ // of snapping when the host toggles `bottomOffset`. Skipped
259250
+ // while the drawer is being live-resized so the bottom edge
259251
+ // tracks the cursor without lag.
259252
+ transition: disableBottomTransition ? void 0 : "bottom 280ms ease",
259253
+ width: widthPx != null ? widthPx : `${PANEL_WIDTH_PCT}%`,
259254
+ minWidth: MIN_WIDTH_PX,
259255
+ backgroundColor: theme2.colors.background,
259256
+ // Flush against the city container's left/top/bottom edges;
259257
+ // only the right edge separates from the canvas, so we draw
259258
+ // the border there only.
259259
+ borderRight: `1px solid ${theme2.colors.border}`,
259260
+ overflow: "hidden",
259261
+ display: "flex",
259262
+ flexDirection: "column",
259263
+ zIndex: 1900,
259264
+ animation: hasEntered || isResizing ? void 0 : "trailMarkdownOverlaySlideIn 220ms ease-out",
259265
+ userSelect: isResizing ? "none" : void 0
259266
+ },
259267
+ children: [
259268
+ /* @__PURE__ */ jsx("style", { children: `
259269
+ @keyframes trailMarkdownOverlaySlideIn {
259270
+ from { transform: translateX(-100%); }
259271
+ to { transform: translateX(0); }
259272
+ }
259273
+ ` }),
259274
+ eyebrow || title || subtitle ? /* @__PURE__ */ jsxs(
259275
+ "div",
259276
+ {
259277
+ style: {
259278
+ padding: "10px 14px",
259279
+ borderBottom: `1px solid ${theme2.colors.border}`,
259280
+ display: "flex",
259281
+ flexDirection: "column",
259282
+ color: theme2.colors.text,
259283
+ fontFamily: theme2.fonts.body,
259284
+ flexShrink: 0,
259285
+ minWidth: 0
259286
+ },
259287
+ children: [
259288
+ eyebrow ? /* @__PURE__ */ jsx(
259289
+ "span",
259290
+ {
259291
+ style: {
259292
+ fontSize: theme2.fontSizes[0],
259293
+ color: theme2.colors.textSecondary,
259294
+ letterSpacing: 0.4,
259295
+ textTransform: "uppercase"
259296
+ },
259297
+ children: eyebrow
259298
+ }
259299
+ ) : null,
259300
+ title ? /* @__PURE__ */ jsx(
259301
+ "span",
259302
+ {
259303
+ style: {
259304
+ fontSize: theme2.fontSizes[1],
259305
+ fontWeight: 600,
259306
+ overflow: "hidden",
259307
+ textOverflow: "ellipsis",
259308
+ whiteSpace: "nowrap"
259309
+ },
259310
+ title,
259311
+ children: title
259312
+ }
259313
+ ) : null,
259314
+ subtitle ? /* @__PURE__ */ jsx(
259315
+ "span",
259316
+ {
259317
+ style: {
259318
+ fontSize: theme2.fontSizes[0],
259319
+ color: theme2.colors.textSecondary,
259320
+ fontFamily: theme2.fonts.monospace,
259321
+ overflow: "hidden",
259322
+ textOverflow: "ellipsis",
259323
+ whiteSpace: "nowrap",
259324
+ marginTop: 2
259325
+ },
259326
+ title: subtitle,
259327
+ children: subtitle
259328
+ }
259329
+ ) : null
259330
+ ]
259331
+ }
259332
+ ) : null,
259333
+ /* @__PURE__ */ jsxs(
259334
+ "div",
259335
+ {
259336
+ style: {
259337
+ flex: 1,
259338
+ minHeight: 0,
259339
+ overflowY: "auto",
259340
+ display: "flex",
259341
+ flexDirection: "column",
259342
+ position: "relative"
259343
+ },
259344
+ children: [
259345
+ /* @__PURE__ */ jsx("div", { style: { flex: "0 0 auto" }, children: /* @__PURE__ */ jsx(
259346
+ IndustryMarkdownSlide,
259347
+ {
259348
+ content: body,
259349
+ slideIdPrefix,
259350
+ slideIndex: 0,
259351
+ isVisible: true,
259352
+ theme: theme2,
259353
+ transparentBackground: true,
259354
+ enableKeyboardScrolling: false,
259355
+ disableScroll: true,
259356
+ annotations,
259357
+ activeAnnotationId: activeAnnotationId ?? null,
259358
+ onAnnotationClick: onAnnotationClick ? handleAnnotationClick : void 0,
259359
+ onSelectionChange: onCreateNoteForSelection ? handleSelectionChange : void 0,
259360
+ annotationStyle: annotationStyleVars
259361
+ }
259362
+ ) }),
259363
+ nav ? /* @__PURE__ */ jsx(TrailMarkdownOverlayFooter, { nav }) : null,
259364
+ files && files.length > 0 ? /* @__PURE__ */ jsx(TrailFilesList, { files }) : null,
259365
+ !composerOpen && pendingSelection && onCreateNoteForSelection && /* @__PURE__ */ jsx(
259366
+ MarkdownSelectionPill,
259367
+ {
259368
+ rect: {
259369
+ left: pendingSelection.rect.left,
259370
+ top: pendingSelection.rect.top,
259371
+ right: pendingSelection.rect.right,
259372
+ bottom: pendingSelection.rect.bottom
259373
+ },
259374
+ onClick: () => {
259375
+ onCreateNoteForSelection(pendingSelection.anchor);
259376
+ setPendingSelection(null);
259377
+ }
259378
+ }
259379
+ )
259380
+ ]
259381
+ }
259382
+ ),
259383
+ /* @__PURE__ */ jsx(
259384
+ "div",
259385
+ {
259386
+ role: "separator",
259387
+ "aria-orientation": "vertical",
259388
+ "aria-label": "Resize markdown panel",
259389
+ onPointerDown: handleResizePointerDown,
259390
+ onPointerMove: handleResizePointerMove,
259391
+ onPointerUp: handleResizePointerUp,
259392
+ onPointerCancel: handleResizePointerUp,
259393
+ style: {
259394
+ position: "absolute",
259395
+ top: 0,
259396
+ right: 0,
259397
+ bottom: 0,
259398
+ width: RESIZE_HANDLE_WIDTH,
259399
+ cursor: "ew-resize",
259400
+ backgroundColor: isResizing ? theme2.colors.accent : "transparent",
259401
+ transition: isResizing ? void 0 : "background-color 120ms ease",
259402
+ touchAction: "none"
259403
+ },
259404
+ onMouseEnter: (e) => {
259405
+ if (isResizing) return;
259406
+ e.currentTarget.style.backgroundColor = theme2.colors.border;
259407
+ },
259408
+ onMouseLeave: (e) => {
259409
+ if (isResizing) return;
259410
+ e.currentTarget.style.backgroundColor = "transparent";
259411
+ }
259412
+ }
259413
+ )
259414
+ ]
259415
+ }
259416
+ );
259417
+ };
259418
+ const TrailFilesList = ({ files }) => {
259419
+ const { theme: theme2 } = useTheme();
259420
+ return /* @__PURE__ */ jsxs(
259421
+ "div",
258514
259422
  {
258515
259423
  style: {
258516
259424
  borderTop: `1px solid ${theme2.colors.border}`,
@@ -258769,7 +259677,13 @@ const TrailSnippetView = ({
258769
259677
  focusLine,
258770
259678
  contextLines = 2,
258771
259679
  readFile,
258772
- background
259680
+ background,
259681
+ notes,
259682
+ activeThreadKey,
259683
+ composerOpen,
259684
+ onOpenThread,
259685
+ onCloseThread,
259686
+ onOpenComposer
258773
259687
  }) => {
258774
259688
  const { theme: theme2 } = useTheme();
258775
259689
  const [contents, setContents] = React.useState(null);
@@ -258830,12 +259744,96 @@ const TrailSnippetView = ({
258830
259744
  }
258831
259745
  });
258832
259746
  },
258833
- [lineNumberOffset]
259747
+ [lineNumberOffset]
259748
+ );
259749
+ const lineAnnotations = React.useMemo(() => {
259750
+ if (!slice) return [];
259751
+ const threadsByKey = /* @__PURE__ */ new Map();
259752
+ for (const n of notes ?? []) {
259753
+ const arr = threadsByKey.get(n.threadKey) ?? [];
259754
+ arr.push(n);
259755
+ threadsByKey.set(n.threadKey, arr);
259756
+ }
259757
+ const result = [];
259758
+ for (const [threadKey, threadNotes] of threadsByKey) {
259759
+ const head = threadNotes[0];
259760
+ const active = activeThreadKey === threadKey;
259761
+ const seen = /* @__PURE__ */ new Set();
259762
+ for (const range of head.ranges) {
259763
+ const key = `${range.startLine}-${range.endLine}`;
259764
+ if (seen.has(key)) continue;
259765
+ seen.add(key);
259766
+ const sliceLocal = range.startLine - slice.sliceStart + 1;
259767
+ if (sliceLocal < 1) continue;
259768
+ result.push({
259769
+ lineNumber: sliceLocal,
259770
+ metadata: {
259771
+ absoluteLine: range.startLine,
259772
+ notes: threadNotes,
259773
+ range,
259774
+ threadKey,
259775
+ active,
259776
+ disabled: !!composerOpen
259777
+ }
259778
+ });
259779
+ }
259780
+ }
259781
+ return result;
259782
+ }, [slice, notes, activeThreadKey, composerOpen]);
259783
+ const renderAnnotation = React.useCallback(
259784
+ (annotation) => {
259785
+ if (!annotation.metadata) return null;
259786
+ const {
259787
+ notes: threadNotes,
259788
+ range,
259789
+ threadKey,
259790
+ active,
259791
+ disabled
259792
+ } = annotation.metadata;
259793
+ return /* @__PURE__ */ jsx(
259794
+ SnippetNoteIndicator,
259795
+ {
259796
+ notes: threadNotes,
259797
+ range,
259798
+ active,
259799
+ disabled,
259800
+ onClick: () => {
259801
+ if (disabled) return;
259802
+ if (active) onCloseThread == null ? void 0 : onCloseThread();
259803
+ else onOpenThread == null ? void 0 : onOpenThread(threadKey);
259804
+ }
259805
+ }
259806
+ );
259807
+ },
259808
+ [onOpenThread, onCloseThread]
259809
+ );
259810
+ const onGutterUtilityClick = React.useCallback(
259811
+ (range) => {
259812
+ if (!slice || !onOpenComposer) return;
259813
+ const startAbs = range.start + slice.sliceStart - 1;
259814
+ const endAbs = range.end + slice.sliceStart - 1;
259815
+ const sliceLines = slice.contents.split("\n");
259816
+ const startLineText = sliceLines[startAbs - slice.sliceStart] ?? "";
259817
+ const endLineText = sliceLines[endAbs - slice.sliceStart] ?? "";
259818
+ onOpenComposer({
259819
+ startLine: startAbs,
259820
+ endLine: endAbs,
259821
+ startLineText,
259822
+ endLineText
259823
+ });
259824
+ },
259825
+ [slice, onOpenComposer]
258834
259826
  );
258835
259827
  const options = React.useMemo(() => {
258836
259828
  const base2 = background ? buildPierreOptions(background) : pierreOptions;
258837
- return { ...base2, onPostRender };
258838
- }, [background, onPostRender]);
259829
+ return {
259830
+ ...base2,
259831
+ onPostRender,
259832
+ enableGutterUtility: !!onOpenComposer,
259833
+ enableLineSelection: !!onOpenComposer,
259834
+ onGutterUtilityClick
259835
+ };
259836
+ }, [background, onPostRender, onOpenComposer, onGutterUtilityClick]);
258839
259837
  if (error) {
258840
259838
  return /* @__PURE__ */ jsx("div", { style: { padding: 16, color: theme2.colors.error }, children: error });
258841
259839
  }
@@ -258847,6 +259845,8 @@ const TrailSnippetView = ({
258847
259845
  {
258848
259846
  file: fileObject,
258849
259847
  options,
259848
+ lineAnnotations,
259849
+ renderAnnotation,
258850
259850
  selectedLines: slice.focusOffset != null ? { start: slice.focusOffset, end: slice.focusOffset } : void 0,
258851
259851
  style: pierreStyle$1
258852
259852
  }
@@ -259004,7 +260004,51 @@ const SNIPPET_PANE_RIGHT_GUTTER_PX = 16;
259004
260004
  const SNIPPET_PANE_LEADER_BUFFER_PX = 60;
259005
260005
  const PANEL_TRANSITION_MS = 280;
259006
260006
  const THREE_D_TOGGLE_DISABLED = true;
259007
- const FileCityTrailExplorerPanel = ({ context, actions, defaultViewMode, defaultShowSequenceDrawer }) => {
260007
+ const DRAFT_MARKDOWN_ANNOTATION_ID = "__draft-md__";
260008
+ const sameTrailMarkdownScope = (a, b) => {
260009
+ if (a.kind === "summary" && b.kind === "summary") return true;
260010
+ if (a.kind === "description" && b.kind === "description") {
260011
+ return a.markerId === b.markerId;
260012
+ }
260013
+ return false;
260014
+ };
260015
+ const toMarkdownUiNote = (n) => {
260016
+ if (n.kind !== "markdown") return null;
260017
+ return {
260018
+ id: n.id,
260019
+ anchor: {
260020
+ exact: n.anchor.exact,
260021
+ prefix: n.anchor.prefix,
260022
+ suffix: n.anchor.suffix
260023
+ },
260024
+ body: n.body,
260025
+ author: n.author,
260026
+ createdAt: new Date(n.createdAt).getTime()
260027
+ };
260028
+ };
260029
+ const toSnippetUiNote = (n) => {
260030
+ if (n.kind !== "snippet") return null;
260031
+ if (n.anchor.kind !== "slice") return null;
260032
+ const ranges = n.anchor.ranges.map((r2) => ({
260033
+ startLine: r2.startLine,
260034
+ endLine: r2.endLine
260035
+ }));
260036
+ return {
260037
+ id: n.id,
260038
+ threadKey: computeThreadKey(ranges),
260039
+ ranges,
260040
+ body: n.body,
260041
+ author: n.author,
260042
+ createdAt: new Date(n.createdAt).getTime()
260043
+ };
260044
+ };
260045
+ const FileCityTrailExplorerPanel = ({
260046
+ context,
260047
+ actions,
260048
+ defaultViewMode,
260049
+ defaultShowSequenceDrawer,
260050
+ currentAuthor
260051
+ }) => {
259008
260052
  var _a;
259009
260053
  const tree = context.fileTree.data;
259010
260054
  const lineCounts = ((_a = context.lineCounts.data) == null ? void 0 : _a.lineCounts) ?? null;
@@ -259061,6 +260105,12 @@ const FileCityTrailExplorerPanel = ({ context, actions, defaultViewMode, default
259061
260105
  cityData,
259062
260106
  hasLineCounts: lineCounts != null && Object.keys(lineCounts).length > 0,
259063
260107
  readFile: actions.readFile,
260108
+ createTrailNote: actions.createTrailNote,
260109
+ updateTrailNote: actions.updateTrailNote,
260110
+ deleteTrailNote: actions.deleteTrailNote,
260111
+ createTrailSignOff: actions.createTrailSignOff,
260112
+ deleteTrailSignOff: actions.deleteTrailSignOff,
260113
+ currentAuthor,
259064
260114
  defaultViewMode,
259065
260115
  defaultShowSequenceDrawer
259066
260116
  }
@@ -259075,10 +260125,14 @@ const FileCityTrailSequenceLayout = ({
259075
260125
  cityData,
259076
260126
  hasLineCounts,
259077
260127
  readFile,
260128
+ createTrailNote,
260129
+ deleteTrailNote,
260130
+ createTrailSignOff,
260131
+ currentAuthor = "You",
259078
260132
  defaultViewMode = "city",
259079
- defaultShowSequenceDrawer = true
260133
+ defaultShowSequenceDrawer = false
259080
260134
  }) => {
259081
- var _a, _b;
260135
+ var _a, _b, _c, _d;
259082
260136
  const { theme: theme2 } = useTheme();
259083
260137
  const [show3D, setShow3D] = React.useState(
259084
260138
  false
@@ -259087,12 +260141,32 @@ const FileCityTrailSequenceLayout = ({
259087
260141
  return;
259088
260142
  }, [hasLineCounts]);
259089
260143
  const effectiveShow3D = false;
260144
+ const [requestDismissed, setRequestDismissed] = React.useState(false);
260145
+ React.useEffect(() => {
260146
+ setRequestDismissed(false);
260147
+ }, [trail2.id]);
259090
260148
  const [viewMode, setViewMode] = React.useState(
259091
260149
  defaultViewMode
259092
260150
  );
259093
260151
  const [showSequenceDrawer, setShowSequenceDrawer] = React.useState(
259094
260152
  defaultShowSequenceDrawer
259095
260153
  );
260154
+ const [showFilesPanel, setShowFilesPanel] = React.useState(false);
260155
+ const [showNotesPanel, setShowNotesPanel] = React.useState(false);
260156
+ const isInformative = !trail2.request || trail2.request.trim().length === 0;
260157
+ const userHasSignedOff = React.useMemo(() => {
260158
+ return (trail2.signOffs ?? []).some((s2) => s2.author === currentAuthor);
260159
+ }, [trail2.signOffs, currentAuthor]);
260160
+ const [signOffAnimating, setSignOffAnimating] = React.useState(false);
260161
+ const handleSignOff = React.useCallback(() => {
260162
+ if (userHasSignedOff || signOffAnimating) return;
260163
+ setSignOffAnimating(true);
260164
+ void createTrailSignOff(trail2.id, { author: currentAuthor });
260165
+ window.setTimeout(() => {
260166
+ setSignOffAnimating(false);
260167
+ setRequestDismissed(false);
260168
+ }, 1400);
260169
+ }, [userHasSignedOff, signOffAnimating, createTrailSignOff, trail2.id, currentAuthor]);
259096
260170
  const [drawerHeightOverridePct, setDrawerHeightOverridePct] = React.useState(null);
259097
260171
  const [isResizingDrawer, setIsResizingDrawer] = React.useState(false);
259098
260172
  const drawerHeightPct = !showSequenceDrawer ? 0 : drawerHeightOverridePct ?? SEQUENCE_DRAWER_HEIGHT_PCT;
@@ -259290,6 +260364,8 @@ const FileCityTrailSequenceLayout = ({
259290
260364
  );
259291
260365
  const [snippetPaneWidth, setSnippetPaneWidth] = React.useState(null);
259292
260366
  const [containerSize, setContainerSize] = React.useState(null);
260367
+ const mainAreaRef = React.useRef(null);
260368
+ const [mainAreaSize, setMainAreaSize] = React.useState(null);
259293
260369
  const selectedBuilding = React.useMemo(() => {
259294
260370
  if (!cityData || !selectedMarker) return null;
259295
260371
  const path2 = markerCityPath(selectedMarker, trail2);
@@ -259358,6 +260434,18 @@ const FileCityTrailSequenceLayout = ({
259358
260434
  const observer = new ResizeObserver(update);
259359
260435
  observer.observe(el);
259360
260436
  return () => observer.disconnect();
260437
+ }, [viewMode]);
260438
+ React.useEffect(() => {
260439
+ const el = mainAreaRef.current;
260440
+ if (!el) return;
260441
+ const update = () => {
260442
+ const rect = el.getBoundingClientRect();
260443
+ setMainAreaSize({ width: rect.width, height: rect.height });
260444
+ };
260445
+ update();
260446
+ const observer = new ResizeObserver(update);
260447
+ observer.observe(el);
260448
+ return () => observer.disconnect();
259361
260449
  }, []);
259362
260450
  const [markdownOverlayEl, setMarkdownOverlayEl] = React.useState(null);
259363
260451
  React.useEffect(() => {
@@ -259371,6 +260459,274 @@ const FileCityTrailSequenceLayout = ({
259371
260459
  observer.observe(markdownOverlayEl);
259372
260460
  return () => observer.disconnect();
259373
260461
  }, [markdownOverlayEl]);
260462
+ const markdownScope = React.useMemo(() => {
260463
+ if (selectedMarkerId) {
260464
+ return { kind: "description", markerId: selectedMarkerId };
260465
+ }
260466
+ if (trail2.summary && trail2.summary.trim().length > 0) {
260467
+ return { kind: "summary" };
260468
+ }
260469
+ return null;
260470
+ }, [selectedMarkerId, trail2.summary]);
260471
+ const markdownNotes = React.useMemo(() => {
260472
+ if (!trail2.notes || !markdownScope) return [];
260473
+ return trail2.notes.filter(
260474
+ (n) => n.kind === "markdown" && sameTrailMarkdownScope(n.scope, markdownScope)
260475
+ ).map(toMarkdownUiNote).filter((n) => n != null);
260476
+ }, [trail2.notes, markdownScope]);
260477
+ const [markdownNotesSelection, setMarkdownNotesSelection] = React.useState(null);
260478
+ const markdownScopeKey = markdownScope ? markdownScope.kind === "description" ? `d-${markdownScope.markerId}` : "s" : "-";
260479
+ React.useEffect(() => {
260480
+ setMarkdownNotesSelection(null);
260481
+ }, [markdownScopeKey]);
260482
+ const markdownComposerOpen = (markdownNotesSelection == null ? void 0 : markdownNotesSelection.kind) === "composer";
260483
+ const markdownAnnotations = React.useMemo(() => {
260484
+ const result = [];
260485
+ const counts = /* @__PURE__ */ new Map();
260486
+ for (const n of markdownNotes) {
260487
+ counts.set(n.anchor.exact, (counts.get(n.anchor.exact) ?? 0) + 1);
260488
+ }
260489
+ const seenAnchors = /* @__PURE__ */ new Set();
260490
+ for (const n of markdownNotes) {
260491
+ if (seenAnchors.has(n.anchor.exact)) continue;
260492
+ seenAnchors.add(n.anchor.exact);
260493
+ result.push({
260494
+ id: n.id,
260495
+ anchor: {
260496
+ exact: n.anchor.exact,
260497
+ prefix: n.anchor.prefix,
260498
+ suffix: n.anchor.suffix
260499
+ },
260500
+ count: counts.get(n.anchor.exact)
260501
+ });
260502
+ }
260503
+ if ((markdownNotesSelection == null ? void 0 : markdownNotesSelection.kind) === "composer") {
260504
+ result.push({
260505
+ id: DRAFT_MARKDOWN_ANNOTATION_ID,
260506
+ anchor: {
260507
+ exact: markdownNotesSelection.anchor.exact,
260508
+ prefix: markdownNotesSelection.anchor.prefix,
260509
+ suffix: markdownNotesSelection.anchor.suffix
260510
+ }
260511
+ });
260512
+ }
260513
+ return result;
260514
+ }, [markdownNotes, markdownNotesSelection]);
260515
+ const activeMarkdownAnnotationId = React.useMemo(() => {
260516
+ if (!markdownNotesSelection) return null;
260517
+ if (markdownNotesSelection.kind === "composer") {
260518
+ return DRAFT_MARKDOWN_ANNOTATION_ID;
260519
+ }
260520
+ const head = markdownNotes.find(
260521
+ (n) => n.id === markdownNotesSelection.noteId
260522
+ );
260523
+ if (!head) return null;
260524
+ const anchorMatch = markdownNotes.find(
260525
+ (n) => n.anchor.exact === head.anchor.exact
260526
+ );
260527
+ return (anchorMatch == null ? void 0 : anchorMatch.id) ?? head.id;
260528
+ }, [markdownNotesSelection, markdownNotes]);
260529
+ const handleMarkdownAnnotationClick = React.useCallback(
260530
+ (annotationId) => {
260531
+ if (annotationId === DRAFT_MARKDOWN_ANNOTATION_ID) return;
260532
+ setMarkdownNotesSelection((prev) => {
260533
+ if ((prev == null ? void 0 : prev.kind) === "thread" && prev.noteId === annotationId) {
260534
+ return null;
260535
+ }
260536
+ return { kind: "thread", noteId: annotationId };
260537
+ });
260538
+ },
260539
+ []
260540
+ );
260541
+ const handleCreateMarkdownNoteForSelection = React.useCallback(
260542
+ (anchor) => {
260543
+ setMarkdownNotesSelection({ kind: "composer", anchor });
260544
+ },
260545
+ []
260546
+ );
260547
+ const handleSubmitMarkdownNote = React.useCallback(
260548
+ async (anchor, body) => {
260549
+ if (!markdownScope) return;
260550
+ const note = await createTrailNote(trail2.id, {
260551
+ kind: "markdown",
260552
+ scope: markdownScope,
260553
+ anchor: {
260554
+ kind: "text-quote",
260555
+ exact: anchor.exact,
260556
+ prefix: anchor.prefix,
260557
+ suffix: anchor.suffix
260558
+ },
260559
+ body,
260560
+ author: "You"
260561
+ });
260562
+ if (note) {
260563
+ setMarkdownNotesSelection({ kind: "thread", noteId: note.id });
260564
+ }
260565
+ },
260566
+ [createTrailNote, trail2.id, markdownScope]
260567
+ );
260568
+ const handleReplyMarkdownNote = React.useCallback(
260569
+ async (noteId, body) => {
260570
+ if (!markdownScope) return;
260571
+ const head = markdownNotes.find((n) => n.id === noteId);
260572
+ if (!head) return;
260573
+ await createTrailNote(trail2.id, {
260574
+ kind: "markdown",
260575
+ scope: markdownScope,
260576
+ anchor: {
260577
+ kind: "text-quote",
260578
+ exact: head.anchor.exact,
260579
+ prefix: head.anchor.prefix,
260580
+ suffix: head.anchor.suffix
260581
+ },
260582
+ body,
260583
+ author: "You"
260584
+ });
260585
+ },
260586
+ [createTrailNote, trail2.id, markdownScope, markdownNotes]
260587
+ );
260588
+ const handleDeleteMarkdownNote = React.useCallback(
260589
+ async (id2) => {
260590
+ await deleteTrailNote(trail2.id, id2);
260591
+ },
260592
+ [deleteTrailNote, trail2.id]
260593
+ );
260594
+ const snippetNotes = React.useMemo(() => {
260595
+ if (!trail2.notes || !selectedMarkerId) return [];
260596
+ return trail2.notes.filter(
260597
+ (n) => n.kind === "snippet" && n.scope.markerId === selectedMarkerId
260598
+ ).map(toSnippetUiNote).filter((n) => n != null);
260599
+ }, [trail2.notes, selectedMarkerId]);
260600
+ const [snippetNotesSelection, setSnippetNotesSelection] = React.useState(null);
260601
+ React.useEffect(() => {
260602
+ setSnippetNotesSelection(null);
260603
+ }, [selectedMarkerId]);
260604
+ const snippetComposerOpen = (snippetNotesSelection == null ? void 0 : snippetNotesSelection.kind) === "composer";
260605
+ const activeSnippetThreadKey = (snippetNotesSelection == null ? void 0 : snippetNotesSelection.kind) === "thread" ? snippetNotesSelection.threadKey : null;
260606
+ const appendSnippetComposerRange = React.useCallback(
260607
+ (range) => {
260608
+ setSnippetNotesSelection((prev) => {
260609
+ if ((prev == null ? void 0 : prev.kind) !== "composer") {
260610
+ return { kind: "composer", ranges: [range] };
260611
+ }
260612
+ const exists = prev.ranges.some(
260613
+ (r2) => r2.startLine === range.startLine && r2.endLine === range.endLine
260614
+ );
260615
+ if (exists) return prev;
260616
+ return { kind: "composer", ranges: [...prev.ranges, range] };
260617
+ });
260618
+ },
260619
+ []
260620
+ );
260621
+ const removeSnippetComposerRange = React.useCallback(
260622
+ (range) => {
260623
+ setSnippetNotesSelection((prev) => {
260624
+ if ((prev == null ? void 0 : prev.kind) !== "composer") return prev;
260625
+ const next2 = prev.ranges.filter(
260626
+ (r2) => !(r2.startLine === range.startLine && r2.endLine === range.endLine)
260627
+ );
260628
+ if (next2.length === 0) return null;
260629
+ return { kind: "composer", ranges: next2 };
260630
+ });
260631
+ },
260632
+ []
260633
+ );
260634
+ const snippetLineTextsFromThread = React.useCallback(
260635
+ (threadKey) => {
260636
+ const head = (trail2.notes ?? []).find(
260637
+ (n) => n.kind === "snippet" && n.scope.markerId === selectedMarkerId && n.anchor.kind === "slice" && computeThreadKey(
260638
+ n.anchor.ranges.map((r2) => ({
260639
+ startLine: r2.startLine,
260640
+ endLine: r2.endLine
260641
+ }))
260642
+ ) === threadKey
260643
+ );
260644
+ if (!head || head.anchor.kind !== "slice") return null;
260645
+ return head.anchor.ranges.map((r2) => ({
260646
+ startLineText: r2.startLineText,
260647
+ endLineText: r2.endLineText
260648
+ }));
260649
+ },
260650
+ [trail2.notes, selectedMarkerId]
260651
+ );
260652
+ const createSnippetNote = React.useCallback(
260653
+ async (ranges, body) => {
260654
+ if (!selectedMarkerId || ranges.length === 0) return;
260655
+ await createTrailNote(trail2.id, {
260656
+ kind: "snippet",
260657
+ scope: { markerId: selectedMarkerId },
260658
+ anchor: {
260659
+ kind: "slice",
260660
+ ranges: ranges.map((r2) => ({
260661
+ startLine: r2.startLine,
260662
+ endLine: r2.endLine,
260663
+ startLineText: r2.startLineText,
260664
+ endLineText: r2.endLineText
260665
+ }))
260666
+ },
260667
+ body,
260668
+ author: "You"
260669
+ });
260670
+ },
260671
+ [createTrailNote, trail2.id, selectedMarkerId]
260672
+ );
260673
+ const handleSubmitSnippetNote = React.useCallback(
260674
+ async (ranges, body) => {
260675
+ await createSnippetNote(ranges, body);
260676
+ const uiRanges = ranges.map((r2) => ({
260677
+ startLine: r2.startLine,
260678
+ endLine: r2.endLine
260679
+ }));
260680
+ setSnippetNotesSelection({
260681
+ kind: "thread",
260682
+ threadKey: computeThreadKey(uiRanges)
260683
+ });
260684
+ },
260685
+ [createSnippetNote]
260686
+ );
260687
+ const handleReplySnippetNote = React.useCallback(
260688
+ async (threadKey, body) => {
260689
+ const fingerprints = snippetLineTextsFromThread(threadKey);
260690
+ const head = (trail2.notes ?? []).find(
260691
+ (n) => n.kind === "snippet" && n.scope.markerId === selectedMarkerId && n.anchor.kind === "slice" && computeThreadKey(
260692
+ n.anchor.ranges.map((r2) => ({
260693
+ startLine: r2.startLine,
260694
+ endLine: r2.endLine
260695
+ }))
260696
+ ) === threadKey
260697
+ );
260698
+ if (!head || head.anchor.kind !== "slice" || !fingerprints) return;
260699
+ const ranges = head.anchor.ranges.map((r2, i) => {
260700
+ var _a2, _b2;
260701
+ return {
260702
+ startLine: r2.startLine,
260703
+ endLine: r2.endLine,
260704
+ startLineText: ((_a2 = fingerprints[i]) == null ? void 0 : _a2.startLineText) ?? r2.startLineText,
260705
+ endLineText: ((_b2 = fingerprints[i]) == null ? void 0 : _b2.endLineText) ?? r2.endLineText
260706
+ };
260707
+ });
260708
+ await createSnippetNote(ranges, body);
260709
+ },
260710
+ [snippetLineTextsFromThread, trail2.notes, selectedMarkerId, createSnippetNote]
260711
+ );
260712
+ const handleDeleteSnippetNote = React.useCallback(
260713
+ async (id2) => {
260714
+ await deleteTrailNote(trail2.id, id2);
260715
+ },
260716
+ [deleteTrailNote, trail2.id]
260717
+ );
260718
+ const handleOpenSnippetThread = React.useCallback((threadKey) => {
260719
+ setSnippetNotesSelection({ kind: "thread", threadKey });
260720
+ }, []);
260721
+ const handleCloseSnippetThread = React.useCallback(() => {
260722
+ setSnippetNotesSelection(null);
260723
+ }, []);
260724
+ const handleOpenSnippetComposer = React.useCallback(
260725
+ (input) => {
260726
+ appendSnippetComposerRange(input);
260727
+ },
260728
+ [appendSnippetComposerRange]
260729
+ );
259374
260730
  React.useEffect(() => {
259375
260731
  if (!snippetPaneEl) {
259376
260732
  setSnippetPaneWidth(null);
@@ -259482,18 +260838,15 @@ const FileCityTrailSequenceLayout = ({
259482
260838
  show3D: effectiveShow3D,
259483
260839
  onToggle3D: () => setShow3D((v) => !v),
259484
260840
  hideToggle: THREE_D_TOGGLE_DISABLED,
259485
- showSequenceDrawer,
259486
- onToggleSequenceDrawer: () => {
259487
- setDrawerHeightOverridePct(null);
259488
- setShowSequenceDrawer((v) => !v);
259489
- },
259490
260841
  canExit: (
259491
- // Drawer-open is the default start state, so it no longer
259492
- // counts as "non-idle". Exit is offered only when the user
259493
- // has drilled into something the start view doesn't show.
259494
- // The "idle" view-mode is the host-supplied default when
259495
- // the host opens the panel in 'doc', staying in 'doc' is
259496
- // idle and switching to 'city' is the non-idle drill-in.
260842
+ // The drawer (and the Files panel) are passive viewing
260843
+ // aids that the user toggles freely from the footer, so
260844
+ // their state isn't part of `canExit`. Exit is offered
260845
+ // only when the user has drilled into something the start
260846
+ // view doesn't show. The "idle" view-mode is the host-
260847
+ // supplied default when the host opens the panel in
260848
+ // 'doc', staying in 'doc' is idle and switching to 'city'
260849
+ // is the non-idle drill-in.
259497
260850
  selectedMarkerId != null || snippetExpanded || viewMode !== defaultViewMode
259498
260851
  ),
259499
260852
  onExitTrail: () => {
@@ -259501,90 +260854,253 @@ const FileCityTrailSequenceLayout = ({
259501
260854
  setDrawerHeightOverridePct(null);
259502
260855
  setSnippetExpanded(false);
259503
260856
  setViewMode(defaultViewMode);
260857
+ setRequestDismissed(false);
259504
260858
  },
259505
260859
  viewMode,
259506
- onSetViewMode: setViewMode
260860
+ onSignOff: handleSignOff,
260861
+ signedOff: userHasSignedOff,
260862
+ informative: isInformative
259507
260863
  }
259508
260864
  ),
259509
- viewMode === "doc" ? /* @__PURE__ */ jsx(
259510
- TrailDocumentView,
260865
+ trail2.summary && trail2.summary.trim().length > 0 && !requestDismissed ? /* @__PURE__ */ jsx(
260866
+ TrailRequestIntroModal,
259511
260867
  {
259512
- markers: markersForThisRepo,
259513
- summary: trail2.summary,
259514
- readFile,
259515
- selectedMarkerId,
259516
- onSelectMarker
260868
+ body: trail2.summary,
260869
+ request: trail2.request,
260870
+ title: trail2.title,
260871
+ author: trail2.author,
260872
+ createdAt: trail2.createdAt,
260873
+ markerCount: trail2.markers.length,
260874
+ repoCount: ((_c = trail2.repos) == null ? void 0 : _c.length) ?? 0,
260875
+ fileCount: new Set(
260876
+ trail2.markers.map((m) => m.sourcePath).filter((p2) => !!p2 && p2.length > 0)
260877
+ ).size,
260878
+ seenBy: (() => {
260879
+ const noteAuthors = /* @__PURE__ */ new Set();
260880
+ for (const n of trail2.notes ?? []) {
260881
+ if (!n.author || n.author === trail2.author) continue;
260882
+ noteAuthors.add(n.author);
260883
+ }
260884
+ return Array.from(noteAuthors);
260885
+ })(),
260886
+ notesByAuthor: (() => {
260887
+ const map2 = /* @__PURE__ */ new Map();
260888
+ for (const n of trail2.notes ?? []) {
260889
+ if (!n.author) continue;
260890
+ let markerId = null;
260891
+ if (n.kind === "snippet") {
260892
+ markerId = n.scope.markerId;
260893
+ } else if (n.kind === "markdown" && n.scope.kind === "description") {
260894
+ markerId = n.scope.markerId;
260895
+ }
260896
+ if (!markerId) continue;
260897
+ (map2.get(n.author) ?? map2.set(n.author, /* @__PURE__ */ new Set()).get(n.author)).add(markerId);
260898
+ }
260899
+ const result = {};
260900
+ for (const [k, set2] of map2) result[k] = Array.from(set2);
260901
+ return result;
260902
+ })(),
260903
+ steps: trail2.markers.map((m, i) => ({
260904
+ id: m.id,
260905
+ label: m.label && m.label.trim().length > 0 ? m.label : `Step ${i + 1}`,
260906
+ sourcePath: m.sourcePath
260907
+ })),
260908
+ slideIdPrefix: `trail-request-${trail2.id}`,
260909
+ defaultMode: viewMode,
260910
+ onBegin: (mode) => {
260911
+ setViewMode(mode);
260912
+ handleStartTrail();
260913
+ setRequestDismissed(true);
260914
+ },
260915
+ onBeginAtStep: (stepIndex, mode) => {
260916
+ setViewMode(mode);
260917
+ const marker = trail2.markers[stepIndex];
260918
+ if (marker) onSelectMarker(marker.id);
260919
+ setRequestDismissed(true);
260920
+ },
260921
+ onDismiss: () => setRequestDismissed(true),
260922
+ signOffs: trail2.signOffs,
260923
+ signedByCurrentUser: userHasSignedOff
259517
260924
  }
259518
- ) : /* @__PURE__ */ jsxs(
260925
+ ) : null,
260926
+ /* @__PURE__ */ jsxs(
259519
260927
  "div",
259520
260928
  {
259521
- ref: containerRef,
260929
+ ref: mainAreaRef,
259522
260930
  style: {
259523
260931
  flex: 1,
259524
260932
  position: "relative",
259525
260933
  minHeight: 0,
259526
- overflow: "hidden"
260934
+ overflow: "hidden",
260935
+ display: "flex",
260936
+ flexDirection: "column"
259527
260937
  },
259528
260938
  children: [
259529
- cityData ? /* @__PURE__ */ jsx(
259530
- FileCity3D,
259531
- {
259532
- cityData,
259533
- width: "100%",
259534
- height: "100%",
259535
- selectedPath: selectedMarker ? markerCityPath(selectedMarker, trail2) : null,
259536
- onBuildingClick: handleBuildingClick,
259537
- onCameraFrame,
259538
- showControls: false,
259539
- backgroundColor: theme2.colors.background,
259540
- textColor: theme2.colors.textMuted,
259541
- isGrown: effectiveShow3D,
259542
- highlightLayers: cityHighlightLayers,
259543
- defaultBuildingColor: trailFilesActive ? theme2.colors.textTertiary : void 0
259544
- }
259545
- ) : /* @__PURE__ */ jsx(CityLoadingPlaceholder, {}),
259546
- /* @__PURE__ */ jsx(
259547
- TrailFilePath,
260939
+ viewMode === "doc" ? /* @__PURE__ */ jsx(
260940
+ TrailDocumentView,
259548
260941
  {
259549
- ref: filePathRef,
259550
- containerRef,
259551
- buildings: trailPathBuildings,
259552
- markerTitles: trailPathTitles,
259553
- currentStepIndex: trailPathCurrentStepIndex,
259554
- cityCenter
260942
+ markers: markersForThisRepo,
260943
+ summary: trail2.summary,
260944
+ readFile,
260945
+ selectedMarkerId,
260946
+ onSelectMarker
259555
260947
  }
259556
- ),
259557
- /* @__PURE__ */ jsx(
259558
- TrailLeaderLine,
260948
+ ) : /* @__PURE__ */ jsxs(
260949
+ "div",
259559
260950
  {
259560
- ref: leaderLineRef,
259561
- containerRef,
259562
- building: selectedBuilding,
259563
- cityCenter,
259564
- targetRef: snippetPaneRef
260951
+ ref: containerRef,
260952
+ style: {
260953
+ flex: 1,
260954
+ position: "relative",
260955
+ minHeight: 0,
260956
+ overflow: "hidden"
260957
+ },
260958
+ children: [
260959
+ cityData ? /* @__PURE__ */ jsx(
260960
+ FileCity3D,
260961
+ {
260962
+ cityData,
260963
+ width: "100%",
260964
+ height: "100%",
260965
+ selectedPath: selectedMarker ? markerCityPath(selectedMarker, trail2) : null,
260966
+ onBuildingClick: handleBuildingClick,
260967
+ onCameraFrame,
260968
+ showControls: false,
260969
+ backgroundColor: theme2.colors.background,
260970
+ textColor: theme2.colors.textMuted,
260971
+ isGrown: effectiveShow3D,
260972
+ highlightLayers: cityHighlightLayers,
260973
+ defaultBuildingColor: trailFilesActive ? theme2.colors.textTertiary : void 0
260974
+ }
260975
+ ) : /* @__PURE__ */ jsx(CityLoadingPlaceholder, {}),
260976
+ /* @__PURE__ */ jsx(
260977
+ TrailFilePath,
260978
+ {
260979
+ ref: filePathRef,
260980
+ containerRef,
260981
+ buildings: trailPathBuildings,
260982
+ markerTitles: trailPathTitles,
260983
+ currentStepIndex: trailPathCurrentStepIndex,
260984
+ cityCenter
260985
+ }
260986
+ ),
260987
+ /* @__PURE__ */ jsx(
260988
+ TrailLeaderLine,
260989
+ {
260990
+ ref: leaderLineRef,
260991
+ containerRef,
260992
+ building: selectedBuilding,
260993
+ cityCenter,
260994
+ targetRef: snippetPaneRef
260995
+ }
260996
+ ),
260997
+ overlayMarkdown ? /* @__PURE__ */ jsx(
260998
+ TrailMarkdownOverlay,
260999
+ {
261000
+ eyebrow: overlayMarkdown.eyebrow,
261001
+ title: overlayMarkdown.title,
261002
+ subtitle: overlayMarkdown.subtitle,
261003
+ markdown: overlayMarkdown.markdown,
261004
+ slideIdPrefix: overlayMarkdown.slideIdPrefix,
261005
+ bottomOffset: `${drawerHeightPct}%`,
261006
+ disableBottomTransition: isResizingDrawer,
261007
+ containerRef: setMarkdownOverlayEl,
261008
+ annotations: markdownAnnotations,
261009
+ activeAnnotationId: activeMarkdownAnnotationId,
261010
+ onAnnotationClick: handleMarkdownAnnotationClick,
261011
+ onCreateNoteForSelection: handleCreateMarkdownNoteForSelection,
261012
+ composerOpen: markdownComposerOpen
261013
+ }
261014
+ ) : null,
261015
+ markdownNotesSelection && markdownOverlayWidth != null ? /* @__PURE__ */ jsx(
261016
+ MarkdownNotePanel,
261017
+ {
261018
+ selection: markdownNotesSelection,
261019
+ notes: markdownNotes,
261020
+ style: {
261021
+ position: "absolute",
261022
+ top: 16,
261023
+ left: markdownOverlayWidth + 12,
261024
+ width: 320,
261025
+ // Hug the thread height — short threads stay compact;
261026
+ // long ones cap and engage the panel's internal scroll.
261027
+ // Bound by the available canvas above the drawer so
261028
+ // dragging the drawer up doesn't push the panel under.
261029
+ maxHeight: `min(420px, calc(100% - ${drawerHeightPct}% - 32px))`,
261030
+ // Above the markdown overlay (1900) but below the
261031
+ // request modal (2000) and the LGTM stamp animation
261032
+ // (1980) so signing the trail still cleanly covers it.
261033
+ zIndex: 1955
261034
+ },
261035
+ onClose: () => setMarkdownNotesSelection(null),
261036
+ onSubmitNote: handleSubmitMarkdownNote,
261037
+ onReplyNote: handleReplyMarkdownNote,
261038
+ onDeleteNote: handleDeleteMarkdownNote
261039
+ }
261040
+ ) : null,
261041
+ showFilesPanel && trailFiles.length > 0 ? /* @__PURE__ */ jsx(
261042
+ TrailFilesPanel,
261043
+ {
261044
+ files: trailFiles,
261045
+ onClose: () => setShowFilesPanel(false)
261046
+ }
261047
+ ) : null,
261048
+ showNotesPanel && (trail2.notes ?? []).length > 0 ? /* @__PURE__ */ jsx(
261049
+ TrailNotesPanel,
261050
+ {
261051
+ notes: trail2.notes ?? [],
261052
+ markers: trail2.markers,
261053
+ onJumpToMarker: (markerId) => {
261054
+ onSelectMarker(markerId);
261055
+ setShowNotesPanel(false);
261056
+ },
261057
+ onClose: () => setShowNotesPanel(false)
261058
+ }
261059
+ ) : null,
261060
+ (selectedMarker == null ? void 0 : selectedMarker.snippet) ? /* @__PURE__ */ jsx(
261061
+ SnippetSidePane,
261062
+ {
261063
+ ref: snippetPaneCallbackRef,
261064
+ marker: selectedMarker,
261065
+ snippet: selectedMarker.snippet,
261066
+ readFile,
261067
+ onClose: () => onSelectMarker(null),
261068
+ drawerHeightPct,
261069
+ disableBottomTransition: isResizingDrawer,
261070
+ expanded: snippetExpanded,
261071
+ onToggleExpanded: () => setSnippetExpanded((v) => !v),
261072
+ markdownOverlayWidth,
261073
+ snippetNotes,
261074
+ activeSnippetThreadKey,
261075
+ snippetComposerOpen,
261076
+ onOpenSnippetThread: handleOpenSnippetThread,
261077
+ onCloseSnippetThread: handleCloseSnippetThread,
261078
+ onOpenSnippetComposer: handleOpenSnippetComposer
261079
+ }
261080
+ ) : null,
261081
+ ((_d = selectedMarker == null ? void 0 : selectedMarker.snippet) == null ? void 0 : _d.kind) === "slice" && snippetNotesSelection && snippetPaneWidth != null ? /* @__PURE__ */ jsx(
261082
+ SnippetNotePanel,
261083
+ {
261084
+ selection: snippetNotesSelection,
261085
+ notes: snippetNotes,
261086
+ style: {
261087
+ position: "absolute",
261088
+ top: 16,
261089
+ right: snippetPaneWidth + SNIPPET_PANE_RIGHT_GUTTER_PX + 12,
261090
+ width: 320,
261091
+ maxHeight: `min(420px, calc(100% - ${drawerHeightPct}% - 32px))`,
261092
+ zIndex: 1955
261093
+ },
261094
+ onClose: () => setSnippetNotesSelection(null),
261095
+ onSubmitNote: handleSubmitSnippetNote,
261096
+ onReplyNote: handleReplySnippetNote,
261097
+ onDeleteNote: handleDeleteSnippetNote,
261098
+ onRemoveComposerRange: removeSnippetComposerRange
261099
+ }
261100
+ ) : null
261101
+ ]
259565
261102
  }
259566
261103
  ),
259567
- overlayMarkdown ? /* @__PURE__ */ jsx(
259568
- TrailMarkdownOverlay,
259569
- {
259570
- eyebrow: overlayMarkdown.eyebrow,
259571
- title: overlayMarkdown.title,
259572
- subtitle: overlayMarkdown.subtitle,
259573
- markdown: overlayMarkdown.markdown,
259574
- slideIdPrefix: overlayMarkdown.slideIdPrefix,
259575
- bottomOffset: `${drawerHeightPct}%`,
259576
- disableBottomTransition: isResizingDrawer,
259577
- containerRef: setMarkdownOverlayEl,
259578
- nav: markersForThisRepo.length > 0 ? {
259579
- position: stepperIndex,
259580
- total: markersForThisRepo.length,
259581
- onStart: handleStartTrail,
259582
- onPrev: handleStepPrev,
259583
- onNext: handleStepNext
259584
- } : void 0,
259585
- files: showSequenceDrawer ? void 0 : trailFiles
259586
- }
259587
- ) : null,
259588
261104
  /* @__PURE__ */ jsx(
259589
261105
  SequenceDrawer,
259590
261106
  {
@@ -259594,29 +261110,90 @@ const FileCityTrailSequenceLayout = ({
259594
261110
  onSelectMarker,
259595
261111
  visible: showSequenceDrawer,
259596
261112
  heightPct: drawerHeightPct,
259597
- containerHeightPx: (containerSize == null ? void 0 : containerSize.height) ?? null,
261113
+ containerHeightPx: (mainAreaSize == null ? void 0 : mainAreaSize.height) ?? null,
259598
261114
  onHeightChange: setDrawerHeightOverridePct,
259599
261115
  onResizeStart: () => setIsResizingDrawer(true),
259600
261116
  onResizeEnd: () => setIsResizingDrawer(false)
259601
261117
  }
259602
- ),
259603
- (selectedMarker == null ? void 0 : selectedMarker.snippet) ? /* @__PURE__ */ jsx(
259604
- SnippetSidePane,
259605
- {
259606
- ref: snippetPaneCallbackRef,
259607
- marker: selectedMarker,
259608
- snippet: selectedMarker.snippet,
259609
- readFile,
259610
- onClose: () => onSelectMarker(null),
259611
- drawerHeightPct,
259612
- disableBottomTransition: isResizingDrawer,
259613
- expanded: snippetExpanded,
259614
- onToggleExpanded: () => setSnippetExpanded((v) => !v),
259615
- markdownOverlayWidth
259616
- }
259617
- ) : null
261118
+ )
259618
261119
  ]
259619
261120
  }
261121
+ ),
261122
+ /* @__PURE__ */ jsx(
261123
+ TrailFooter,
261124
+ {
261125
+ markerCount: markersForThisRepo.length,
261126
+ activeMarkerIndex: stepperIndex,
261127
+ onStart: handleStartTrail,
261128
+ onPrev: handleStepPrev,
261129
+ onNext: handleStepNext,
261130
+ filesAvailable: viewMode !== "doc" && trailFiles.length > 0,
261131
+ showFilesPanel,
261132
+ onToggleFilesPanel: () => setShowFilesPanel((v) => !v),
261133
+ notesAvailable: (trail2.notes ?? []).length > 0,
261134
+ showNotesPanel,
261135
+ onToggleNotesPanel: () => setShowNotesPanel((v) => !v),
261136
+ diagramAvailable: true,
261137
+ showSequenceDrawer,
261138
+ onToggleSequenceDrawer: () => {
261139
+ setDrawerHeightOverridePct(null);
261140
+ setShowSequenceDrawer((v) => !v);
261141
+ }
261142
+ }
261143
+ ),
261144
+ signOffAnimating ? /* @__PURE__ */ jsx(SignOffStampAnimation, { informative: isInformative }) : null
261145
+ ]
261146
+ }
261147
+ );
261148
+ };
261149
+ const SignOffStampAnimation = ({
261150
+ informative
261151
+ }) => {
261152
+ const { theme: theme2 } = useTheme();
261153
+ return /* @__PURE__ */ jsxs(
261154
+ "div",
261155
+ {
261156
+ "aria-hidden": true,
261157
+ style: {
261158
+ position: "absolute",
261159
+ inset: 0,
261160
+ // Above every trail overlay but below the request modal
261161
+ // (zIndex 2000) — the modal opens once the animation
261162
+ // resolves and should fully cover this graphic.
261163
+ zIndex: 1980,
261164
+ pointerEvents: "none",
261165
+ display: "flex",
261166
+ alignItems: "center",
261167
+ justifyContent: "center"
261168
+ },
261169
+ children: [
261170
+ /* @__PURE__ */ jsx("style", { children: `
261171
+ @keyframes lgtm-stamp-land {
261172
+ 0% { transform: scale(2.6) rotate(-2deg); opacity: 0; }
261173
+ 45% { transform: scale(1.05) rotate(-9deg); opacity: 1; }
261174
+ 60% { transform: scale(1.18) rotate(-8deg); }
261175
+ 75% { transform: scale(1) rotate(-8deg); }
261176
+ 100% { transform: scale(1) rotate(-8deg); opacity: 1; }
261177
+ }
261178
+ ` }),
261179
+ /* @__PURE__ */ jsx(
261180
+ "div",
261181
+ {
261182
+ style: {
261183
+ transformOrigin: "center",
261184
+ animation: "lgtm-stamp-land 1100ms cubic-bezier(.18,.89,.32,1.28) both",
261185
+ filter: `drop-shadow(0 8px 12px ${theme2.colors.background})`
261186
+ },
261187
+ children: /* @__PURE__ */ jsx(
261188
+ LgtmStamp,
261189
+ {
261190
+ theme: theme2,
261191
+ size: 220,
261192
+ rotated: false,
261193
+ text: informative ? "ACK" : "LGTM"
261194
+ }
261195
+ )
261196
+ }
259620
261197
  )
259621
261198
  ]
259622
261199
  }
@@ -259780,7 +261357,13 @@ const SnippetSidePane = React.forwardRef(function SnippetSidePane2({
259780
261357
  disableBottomTransition,
259781
261358
  expanded,
259782
261359
  onToggleExpanded,
259783
- markdownOverlayWidth
261360
+ markdownOverlayWidth,
261361
+ snippetNotes,
261362
+ activeSnippetThreadKey,
261363
+ snippetComposerOpen,
261364
+ onOpenSnippetThread,
261365
+ onCloseSnippetThread,
261366
+ onOpenSnippetComposer
259784
261367
  }, ref) {
259785
261368
  const { theme: theme2 } = useTheme();
259786
261369
  if (!marker.sourcePath) {
@@ -259831,7 +261414,13 @@ const SnippetSidePane = React.forwardRef(function SnippetSidePane2({
259831
261414
  focusLine: snippet2.focusLine,
259832
261415
  contextLines: snippet2.contextLines,
259833
261416
  readFile,
259834
- background: theme2.colors.background
261417
+ background: theme2.colors.background,
261418
+ notes: snippetNotes,
261419
+ activeThreadKey: activeSnippetThreadKey,
261420
+ composerOpen: snippetComposerOpen,
261421
+ onOpenThread: onOpenSnippetThread,
261422
+ onCloseThread: onCloseSnippetThread,
261423
+ onOpenComposer: onOpenSnippetComposer
259835
261424
  }
259836
261425
  ) : /* @__PURE__ */ jsx(
259837
261426
  TrailDiffSnippetView,
@@ -260154,408 +261743,927 @@ const TrailDocumentView = ({
260154
261743
  lineHeight: 1.6,
260155
261744
  color: theme2.colors.text,
260156
261745
  whiteSpace: "pre-wrap",
260157
- marginBottom: 32,
260158
- paddingBottom: 24,
260159
- borderBottom: `1px solid ${theme2.colors.border}`
261746
+ marginBottom: 32,
261747
+ paddingBottom: 24,
261748
+ borderBottom: `1px solid ${theme2.colors.border}`
261749
+ },
261750
+ children: summary
261751
+ }
261752
+ ) : null,
261753
+ markers.length === 0 ? /* @__PURE__ */ jsx(
261754
+ "div",
261755
+ {
261756
+ style: {
261757
+ fontFamily: theme2.fonts.body,
261758
+ fontSize: theme2.fontSizes[1],
261759
+ color: theme2.colors.textSecondary
261760
+ },
261761
+ children: "No markers in this repo."
261762
+ }
261763
+ ) : markers.map((marker, idx) => /* @__PURE__ */ jsx(
261764
+ TrailDocumentCard,
261765
+ {
261766
+ index: idx,
261767
+ total: markers.length,
261768
+ marker,
261769
+ readFile,
261770
+ isActive: marker.id === selectedMarkerId,
261771
+ onActivate: () => onSelectMarker(
261772
+ marker.id === selectedMarkerId ? null : marker.id
261773
+ )
261774
+ },
261775
+ marker.id
261776
+ ))
261777
+ ] })
261778
+ }
261779
+ );
261780
+ };
261781
+ const TrailDocumentCard = ({
261782
+ index: index2,
261783
+ total,
261784
+ marker,
261785
+ readFile,
261786
+ isActive,
261787
+ onActivate
261788
+ }) => {
261789
+ const { theme: theme2 } = useTheme();
261790
+ const fileName = marker.sourcePath ? marker.sourcePath.split("/").pop() ?? marker.sourcePath : null;
261791
+ const headingLabel = marker.label ?? `Marker ${index2 + 1}`;
261792
+ const snippet2 = marker.snippet;
261793
+ const [snippetExpanded, setSnippetExpanded] = React.useState(false);
261794
+ return /* @__PURE__ */ jsxs(
261795
+ "div",
261796
+ {
261797
+ style: {
261798
+ marginBottom: 32,
261799
+ border: `1px solid ${isActive ? theme2.colors.accent : theme2.colors.border}`,
261800
+ borderRadius: theme2.radii[3],
261801
+ background: theme2.colors.background,
261802
+ boxShadow: isActive ? `0 0 0 2px ${withAlpha(theme2.colors.accent, 0.18)}` : "none",
261803
+ overflow: "hidden"
261804
+ },
261805
+ children: [
261806
+ /* @__PURE__ */ jsxs(
261807
+ "button",
261808
+ {
261809
+ type: "button",
261810
+ onClick: onActivate,
261811
+ title: isActive ? "Clear selection" : "Select this marker on the city view",
261812
+ style: {
261813
+ display: "flex",
261814
+ flexDirection: "column",
261815
+ alignItems: "flex-start",
261816
+ gap: 4,
261817
+ width: "100%",
261818
+ textAlign: "left",
261819
+ padding: "16px 20px",
261820
+ background: "transparent",
261821
+ border: "none",
261822
+ borderBottom: `1px solid ${theme2.colors.border}`,
261823
+ cursor: "pointer",
261824
+ color: theme2.colors.text
261825
+ },
261826
+ children: [
261827
+ /* @__PURE__ */ jsxs(
261828
+ "div",
261829
+ {
261830
+ style: {
261831
+ fontFamily: theme2.fonts.body,
261832
+ fontSize: theme2.fontSizes[0],
261833
+ color: theme2.colors.textSecondary,
261834
+ letterSpacing: "0.04em",
261835
+ textTransform: "uppercase"
261836
+ },
261837
+ children: [
261838
+ index2 + 1,
261839
+ " / ",
261840
+ total
261841
+ ]
261842
+ }
261843
+ ),
261844
+ /* @__PURE__ */ jsx(
261845
+ "div",
261846
+ {
261847
+ style: {
261848
+ fontFamily: theme2.fonts.heading,
261849
+ fontSize: theme2.fontSizes[2],
261850
+ fontWeight: theme2.fontWeights.semibold,
261851
+ color: theme2.colors.text
261852
+ },
261853
+ children: headingLabel
261854
+ }
261855
+ ),
261856
+ marker.sourcePath ? /* @__PURE__ */ jsx(
261857
+ "div",
261858
+ {
261859
+ style: {
261860
+ fontFamily: theme2.fonts.monospace,
261861
+ fontSize: theme2.fontSizes[0],
261862
+ color: theme2.colors.textSecondary
261863
+ },
261864
+ children: marker.sourcePath
261865
+ }
261866
+ ) : null
261867
+ ]
261868
+ }
261869
+ ),
261870
+ marker.description ? /* @__PURE__ */ jsx(
261871
+ "div",
261872
+ {
261873
+ style: {
261874
+ fontFamily: theme2.fonts.body,
261875
+ fontSize: theme2.fontSizes[1],
261876
+ lineHeight: 1.6,
261877
+ color: theme2.colors.text,
261878
+ whiteSpace: "pre-wrap",
261879
+ padding: "16px 20px",
261880
+ borderBottom: snippet2 ? `1px solid ${theme2.colors.border}` : "none"
260160
261881
  },
260161
- children: summary
261882
+ children: marker.description
260162
261883
  }
260163
261884
  ) : null,
260164
- markers.length === 0 ? /* @__PURE__ */ jsx(
261885
+ snippet2 && marker.sourcePath ? /* @__PURE__ */ jsxs("div", { style: { background: theme2.colors.background }, children: [
261886
+ /* @__PURE__ */ jsxs(
261887
+ "button",
261888
+ {
261889
+ type: "button",
261890
+ onClick: () => setSnippetExpanded((v) => !v),
261891
+ "aria-expanded": snippetExpanded,
261892
+ title: snippetExpanded ? "Hide snippet" : "Show snippet",
261893
+ style: {
261894
+ display: "flex",
261895
+ alignItems: "center",
261896
+ gap: 8,
261897
+ width: "100%",
261898
+ textAlign: "left",
261899
+ padding: "10px 20px",
261900
+ background: "transparent",
261901
+ border: "none",
261902
+ borderBottom: snippetExpanded ? `1px solid ${theme2.colors.border}` : "none",
261903
+ cursor: "pointer",
261904
+ fontFamily: theme2.fonts.body,
261905
+ fontSize: theme2.fontSizes[0],
261906
+ fontWeight: theme2.fontWeights.medium,
261907
+ color: theme2.colors.textSecondary,
261908
+ letterSpacing: "0.04em",
261909
+ textTransform: "uppercase"
261910
+ },
261911
+ children: [
261912
+ /* @__PURE__ */ jsx(
261913
+ "span",
261914
+ {
261915
+ "aria-hidden": true,
261916
+ style: {
261917
+ display: "inline-block",
261918
+ width: 10,
261919
+ transform: snippetExpanded ? "rotate(90deg)" : "rotate(0deg)",
261920
+ transition: "transform 120ms ease"
261921
+ },
261922
+ children: "▶"
261923
+ }
261924
+ ),
261925
+ snippetExpanded ? "Hide snippet" : "Show snippet"
261926
+ ]
261927
+ }
261928
+ ),
261929
+ snippetExpanded ? /* @__PURE__ */ jsx("div", { style: { padding: 12 }, children: snippet2.kind === "slice" ? /* @__PURE__ */ jsx(
261930
+ TrailSnippetView,
261931
+ {
261932
+ filePath: marker.sourcePath,
261933
+ fileName: fileName ?? marker.sourcePath,
261934
+ startLine: snippet2.startLine,
261935
+ endLine: snippet2.endLine,
261936
+ focusLine: snippet2.focusLine,
261937
+ contextLines: snippet2.contextLines,
261938
+ readFile,
261939
+ background: theme2.colors.background
261940
+ }
261941
+ ) : /* @__PURE__ */ jsx(
261942
+ TrailDiffSnippetView,
261943
+ {
261944
+ filePath: marker.sourcePath,
261945
+ fileName: fileName ?? marker.sourcePath,
261946
+ oldContents: snippet2.oldContents,
261947
+ newContents: snippet2.newContents,
261948
+ readFile,
261949
+ startLine: snippet2.startLine,
261950
+ endLine: snippet2.endLine,
261951
+ focusLine: snippet2.focusLine,
261952
+ contextLines: snippet2.contextLines,
261953
+ diffStyle: snippet2.diffStyle,
261954
+ background: theme2.colors.background
261955
+ }
261956
+ ) }) : null
261957
+ ] }) : null
261958
+ ]
261959
+ }
261960
+ );
261961
+ };
261962
+ const TrailHeader = ({
261963
+ trail: trail2,
261964
+ markerCount,
261965
+ activeMarkerIndex,
261966
+ show3D,
261967
+ onToggle3D,
261968
+ hideToggle = false,
261969
+ canExit,
261970
+ onExitTrail,
261971
+ viewMode,
261972
+ onSignOff,
261973
+ signedOff,
261974
+ informative
261975
+ }) => {
261976
+ const { theme: theme2 } = useTheme();
261977
+ const toggleButtonStyle = (active) => ({
261978
+ flexShrink: 0,
261979
+ fontFamily: theme2.fonts.body,
261980
+ fontSize: theme2.fontSizes[0],
261981
+ fontWeight: theme2.fontWeights.medium,
261982
+ color: active ? theme2.colors.background : theme2.colors.text,
261983
+ background: active ? theme2.colors.accent : "transparent",
261984
+ border: `1px solid ${active ? theme2.colors.accent : theme2.colors.muted}`,
261985
+ borderRadius: theme2.radii[3],
261986
+ padding: "4px 10px",
261987
+ cursor: "pointer",
261988
+ lineHeight: 1.2
261989
+ });
261990
+ const isDoc = viewMode === "doc";
261991
+ return /* @__PURE__ */ jsxs(
261992
+ "div",
261993
+ {
261994
+ style: {
261995
+ padding: "8px 16px",
261996
+ borderBottom: `1px solid ${theme2.colors.border}`,
261997
+ display: "flex",
261998
+ flexDirection: "row",
261999
+ alignItems: "center",
262000
+ justifyContent: "space-between",
262001
+ gap: 12,
262002
+ flexShrink: 0
262003
+ },
262004
+ children: [
262005
+ /* @__PURE__ */ jsxs(
260165
262006
  "div",
260166
262007
  {
260167
262008
  style: {
260168
- fontFamily: theme2.fonts.body,
260169
- fontSize: theme2.fontSizes[1],
260170
- color: theme2.colors.textSecondary
262009
+ display: "flex",
262010
+ flexDirection: "column",
262011
+ justifyContent: "center",
262012
+ gap: 2,
262013
+ minWidth: 0
260171
262014
  },
260172
- children: "No markers in this repo."
262015
+ children: [
262016
+ /* @__PURE__ */ jsx(
262017
+ "div",
262018
+ {
262019
+ style: {
262020
+ fontFamily: theme2.fonts.heading,
262021
+ fontSize: theme2.fontSizes[1],
262022
+ fontWeight: theme2.fontWeights.semibold,
262023
+ color: theme2.colors.text
262024
+ },
262025
+ children: trail2.title
262026
+ }
262027
+ ),
262028
+ /* @__PURE__ */ jsx(
262029
+ "div",
262030
+ {
262031
+ "aria-label": `${markerCount} marker${markerCount === 1 ? "" : "s"} in this repo`,
262032
+ style: {
262033
+ display: "flex",
262034
+ alignItems: "center",
262035
+ height: theme2.fontSizes[0]
262036
+ },
262037
+ children: Array.from({ length: markerCount }).map((_, i) => {
262038
+ const isActive = i === activeMarkerIndex;
262039
+ return /* @__PURE__ */ jsx(
262040
+ "span",
262041
+ {
262042
+ style: {
262043
+ width: 6,
262044
+ height: 6,
262045
+ borderRadius: "50%",
262046
+ background: isActive ? theme2.colors.accent : theme2.colors.textSecondary,
262047
+ // Group dots in fives for at-a-glance countability — a
262048
+ // wider gap before every 5th index (after the first)
262049
+ // creates visual columns of 5 like tally marks.
262050
+ marginLeft: i === 0 ? 0 : i % 5 === 0 ? 11 : 5
262051
+ }
262052
+ },
262053
+ i
262054
+ );
262055
+ })
262056
+ }
262057
+ )
262058
+ ]
260173
262059
  }
260174
- ) : markers.map((marker, idx) => /* @__PURE__ */ jsx(
260175
- TrailDocumentCard,
260176
- {
260177
- index: idx,
260178
- total: markers.length,
260179
- marker,
260180
- readFile,
260181
- isActive: marker.id === selectedMarkerId,
260182
- onActivate: () => onSelectMarker(
260183
- marker.id === selectedMarkerId ? null : marker.id
260184
- )
260185
- },
260186
- marker.id
260187
- ))
260188
- ] })
262060
+ ),
262061
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
262062
+ hideToggle || isDoc ? null : /* @__PURE__ */ jsx(
262063
+ "button",
262064
+ {
262065
+ type: "button",
262066
+ onClick: onToggle3D,
262067
+ "aria-pressed": show3D,
262068
+ title: show3D ? "Switch to flat (2D) view" : "Switch to 3D view",
262069
+ style: toggleButtonStyle(show3D),
262070
+ children: "3D"
262071
+ }
262072
+ ),
262073
+ signedOff ? null : /* @__PURE__ */ jsx(
262074
+ "button",
262075
+ {
262076
+ type: "button",
262077
+ onClick: onSignOff,
262078
+ title: informative ? "ACK — acknowledge that this trail makes sense" : "LGTM — sign off on this trail",
262079
+ style: {
262080
+ flexShrink: 0,
262081
+ fontFamily: theme2.fonts.body,
262082
+ fontSize: theme2.fontSizes[0],
262083
+ fontWeight: theme2.fontWeights.semibold,
262084
+ color: theme2.colors.background,
262085
+ background: theme2.colors.accent,
262086
+ border: `1px solid ${theme2.colors.accent}`,
262087
+ borderRadius: theme2.radii[3],
262088
+ padding: "4px 12px",
262089
+ cursor: "pointer",
262090
+ lineHeight: 1.2
262091
+ },
262092
+ children: informative ? "ACK" : "LGTM"
262093
+ }
262094
+ ),
262095
+ canExit ? /* @__PURE__ */ jsx(
262096
+ "button",
262097
+ {
262098
+ type: "button",
262099
+ onClick: onExitTrail,
262100
+ title: "Return to the trail summary",
262101
+ style: {
262102
+ flexShrink: 0,
262103
+ fontFamily: theme2.fonts.body,
262104
+ fontSize: theme2.fontSizes[0],
262105
+ fontWeight: theme2.fontWeights.medium,
262106
+ color: theme2.colors.text,
262107
+ background: "transparent",
262108
+ border: `1px solid ${theme2.colors.muted}`,
262109
+ borderRadius: theme2.radii[3],
262110
+ padding: "4px 10px",
262111
+ cursor: "pointer",
262112
+ lineHeight: 1.2
262113
+ },
262114
+ children: "Exit"
262115
+ }
262116
+ ) : null
262117
+ ] })
262118
+ ]
260189
262119
  }
260190
262120
  );
260191
262121
  };
260192
- const TrailDocumentCard = ({
260193
- index: index2,
260194
- total,
260195
- marker,
260196
- readFile,
260197
- isActive,
260198
- onActivate
262122
+ const panelToggleButtonStyle = (theme2, active) => ({
262123
+ flexShrink: 0,
262124
+ fontFamily: theme2.fonts.body,
262125
+ fontSize: theme2.fontSizes[0],
262126
+ fontWeight: theme2.fontWeights.medium,
262127
+ color: active ? theme2.colors.background : theme2.colors.text,
262128
+ background: active ? theme2.colors.accent : "transparent",
262129
+ border: `1px solid ${active ? theme2.colors.accent : theme2.colors.muted}`,
262130
+ borderRadius: theme2.radii[3],
262131
+ padding: "4px 10px",
262132
+ cursor: "pointer",
262133
+ lineHeight: 1.2
262134
+ });
262135
+ const notesSortButtonStyle = (theme2, active, side) => ({
262136
+ flexShrink: 0,
262137
+ fontFamily: theme2.fonts.body,
262138
+ fontSize: theme2.fontSizes[0],
262139
+ fontWeight: theme2.fontWeights.medium,
262140
+ color: active ? theme2.colors.background : theme2.colors.textSecondary,
262141
+ background: active ? theme2.colors.accent : "transparent",
262142
+ border: `1px solid ${active ? theme2.colors.accent : theme2.colors.muted}`,
262143
+ borderTopLeftRadius: side === "left" ? theme2.radii[2] : 0,
262144
+ borderBottomLeftRadius: side === "left" ? theme2.radii[2] : 0,
262145
+ borderTopRightRadius: side === "right" ? theme2.radii[2] : 0,
262146
+ borderBottomRightRadius: side === "right" ? theme2.radii[2] : 0,
262147
+ marginLeft: side === "right" ? -1 : 0,
262148
+ padding: "2px 8px",
262149
+ cursor: "pointer",
262150
+ lineHeight: 1.2
262151
+ });
262152
+ const TrailFooter = ({
262153
+ markerCount,
262154
+ activeMarkerIndex,
262155
+ onStart,
262156
+ onPrev,
262157
+ onNext,
262158
+ diagramAvailable,
262159
+ showSequenceDrawer,
262160
+ onToggleSequenceDrawer,
262161
+ filesAvailable,
262162
+ showFilesPanel,
262163
+ onToggleFilesPanel,
262164
+ notesAvailable,
262165
+ showNotesPanel,
262166
+ onToggleNotesPanel
260199
262167
  }) => {
260200
262168
  const { theme: theme2 } = useTheme();
260201
- const fileName = marker.sourcePath ? marker.sourcePath.split("/").pop() ?? marker.sourcePath : null;
260202
- const headingLabel = marker.label ?? `Marker ${index2 + 1}`;
260203
- const snippet2 = marker.snippet;
260204
- const [snippetExpanded, setSnippetExpanded] = React.useState(false);
262169
+ const idle = activeMarkerIndex < 0;
262170
+ const canPrev = !idle;
262171
+ const canNext = !idle && activeMarkerIndex < markerCount - 1;
262172
+ const startEnabled = markerCount > 0;
262173
+ const stepButtonStyle = (enabled) => ({
262174
+ background: "transparent",
262175
+ border: `1px solid ${theme2.colors.muted}`,
262176
+ borderRadius: theme2.radii[2],
262177
+ color: enabled ? theme2.colors.text : theme2.colors.textTertiary,
262178
+ cursor: enabled ? "pointer" : "not-allowed",
262179
+ padding: "4px 12px",
262180
+ fontFamily: theme2.fonts.body,
262181
+ fontSize: theme2.fontSizes[0],
262182
+ lineHeight: 1.2,
262183
+ opacity: enabled ? 1 : 0.5
262184
+ });
262185
+ const diagramButtonStyle = panelToggleButtonStyle(theme2, showSequenceDrawer);
260205
262186
  return /* @__PURE__ */ jsxs(
260206
262187
  "div",
260207
262188
  {
260208
262189
  style: {
260209
- marginBottom: 32,
260210
- border: `1px solid ${isActive ? theme2.colors.accent : theme2.colors.border}`,
260211
- borderRadius: theme2.radii[3],
260212
- background: theme2.colors.background,
260213
- boxShadow: isActive ? `0 0 0 2px ${withAlpha(theme2.colors.accent, 0.18)}` : "none",
260214
- overflow: "hidden"
262190
+ padding: "8px 16px",
262191
+ borderTop: `1px solid ${theme2.colors.border}`,
262192
+ display: "grid",
262193
+ // Three columns so the nav cluster sits in true center of the
262194
+ // bar regardless of whether the right-hand Diagram toggle is
262195
+ // rendered. Left spacer mirrors the right column so the middle
262196
+ // stays centered between the bar's outer edges.
262197
+ gridTemplateColumns: "1fr auto 1fr",
262198
+ alignItems: "center",
262199
+ gap: 12,
262200
+ flexShrink: 0,
262201
+ background: theme2.colors.background
260215
262202
  },
260216
262203
  children: [
260217
262204
  /* @__PURE__ */ jsxs(
260218
- "button",
262205
+ "div",
260219
262206
  {
260220
- type: "button",
260221
- onClick: onActivate,
260222
- title: isActive ? "Clear selection" : "Select this marker on the city view",
260223
262207
  style: {
262208
+ justifySelf: "start",
260224
262209
  display: "flex",
260225
- flexDirection: "column",
260226
- alignItems: "flex-start",
260227
- gap: 4,
260228
- width: "100%",
260229
- textAlign: "left",
260230
- padding: "16px 20px",
260231
- background: "transparent",
260232
- border: "none",
260233
- borderBottom: `1px solid ${theme2.colors.border}`,
260234
- cursor: "pointer",
260235
- color: theme2.colors.text
262210
+ alignItems: "center",
262211
+ gap: 6
260236
262212
  },
260237
262213
  children: [
262214
+ filesAvailable ? /* @__PURE__ */ jsx(
262215
+ "button",
262216
+ {
262217
+ type: "button",
262218
+ onClick: onToggleFilesPanel,
262219
+ "aria-pressed": showFilesPanel,
262220
+ title: showFilesPanel ? "Hide files list" : "Show files list",
262221
+ style: panelToggleButtonStyle(theme2, showFilesPanel),
262222
+ children: "Files"
262223
+ }
262224
+ ) : null,
262225
+ notesAvailable ? /* @__PURE__ */ jsx(
262226
+ "button",
262227
+ {
262228
+ type: "button",
262229
+ onClick: onToggleNotesPanel,
262230
+ "aria-pressed": showNotesPanel,
262231
+ title: showNotesPanel ? "Hide notes" : "Show all notes",
262232
+ style: panelToggleButtonStyle(theme2, showNotesPanel),
262233
+ children: "Notes"
262234
+ }
262235
+ ) : null
262236
+ ]
262237
+ }
262238
+ ),
262239
+ /* @__PURE__ */ jsx(
262240
+ "div",
262241
+ {
262242
+ style: {
262243
+ display: "flex",
262244
+ alignItems: "center",
262245
+ gap: 8,
262246
+ minWidth: 0,
262247
+ justifySelf: "center"
262248
+ },
262249
+ children: idle ? /* @__PURE__ */ jsx(
262250
+ "button",
262251
+ {
262252
+ type: "button",
262253
+ onClick: onStart,
262254
+ disabled: !startEnabled,
262255
+ title: startEnabled ? "Jump to the first step" : "No steps in this repo",
262256
+ style: {
262257
+ background: theme2.colors.accent,
262258
+ color: theme2.colors.background,
262259
+ border: `1px solid ${theme2.colors.accent}`,
262260
+ borderRadius: theme2.radii[3],
262261
+ padding: "6px 16px",
262262
+ fontFamily: theme2.fonts.body,
262263
+ fontSize: theme2.fontSizes[1],
262264
+ fontWeight: theme2.fontWeights.semibold,
262265
+ cursor: startEnabled ? "pointer" : "not-allowed",
262266
+ opacity: startEnabled ? 1 : 0.5,
262267
+ lineHeight: 1.2
262268
+ },
262269
+ children: "Start trail"
262270
+ }
262271
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
262272
+ /* @__PURE__ */ jsx(
262273
+ "button",
262274
+ {
262275
+ type: "button",
262276
+ onClick: onPrev,
262277
+ disabled: !canPrev,
262278
+ "aria-label": "Previous step",
262279
+ title: "Previous step",
262280
+ style: stepButtonStyle(canPrev),
262281
+ children: "◀ Prev"
262282
+ }
262283
+ ),
260238
262284
  /* @__PURE__ */ jsxs(
260239
- "div",
262285
+ "span",
260240
262286
  {
260241
262287
  style: {
260242
262288
  fontFamily: theme2.fonts.body,
260243
262289
  fontSize: theme2.fontSizes[0],
260244
262290
  color: theme2.colors.textSecondary,
260245
- letterSpacing: "0.04em",
260246
- textTransform: "uppercase"
262291
+ fontVariantNumeric: "tabular-nums",
262292
+ minWidth: 48,
262293
+ textAlign: "center"
260247
262294
  },
260248
262295
  children: [
260249
- index2 + 1,
262296
+ activeMarkerIndex + 1,
260250
262297
  " / ",
260251
- total
262298
+ markerCount
260252
262299
  ]
260253
262300
  }
260254
262301
  ),
260255
262302
  /* @__PURE__ */ jsx(
260256
- "div",
260257
- {
260258
- style: {
260259
- fontFamily: theme2.fonts.heading,
260260
- fontSize: theme2.fontSizes[2],
260261
- fontWeight: theme2.fontWeights.semibold,
260262
- color: theme2.colors.text
260263
- },
260264
- children: headingLabel
260265
- }
260266
- ),
260267
- marker.sourcePath ? /* @__PURE__ */ jsx(
260268
- "div",
262303
+ "button",
260269
262304
  {
260270
- style: {
260271
- fontFamily: theme2.fonts.monospace,
260272
- fontSize: theme2.fontSizes[0],
260273
- color: theme2.colors.textSecondary
260274
- },
260275
- children: marker.sourcePath
262305
+ type: "button",
262306
+ onClick: onNext,
262307
+ disabled: !canNext,
262308
+ "aria-label": "Next step",
262309
+ title: "Next step",
262310
+ style: stepButtonStyle(canNext),
262311
+ children: "Next ▶"
260276
262312
  }
260277
- ) : null
260278
- ]
262313
+ )
262314
+ ] })
260279
262315
  }
260280
262316
  ),
260281
- marker.description ? /* @__PURE__ */ jsx(
260282
- "div",
262317
+ /* @__PURE__ */ jsx("div", { style: { justifySelf: "end" }, children: diagramAvailable ? /* @__PURE__ */ jsx(
262318
+ "button",
260283
262319
  {
260284
- style: {
260285
- fontFamily: theme2.fonts.body,
260286
- fontSize: theme2.fontSizes[1],
260287
- lineHeight: 1.6,
260288
- color: theme2.colors.text,
260289
- whiteSpace: "pre-wrap",
260290
- padding: "16px 20px",
260291
- borderBottom: snippet2 ? `1px solid ${theme2.colors.border}` : "none"
260292
- },
260293
- children: marker.description
262320
+ type: "button",
262321
+ onClick: onToggleSequenceDrawer,
262322
+ "aria-pressed": showSequenceDrawer,
262323
+ title: showSequenceDrawer ? "Hide sequence diagram" : "Show sequence diagram",
262324
+ style: diagramButtonStyle,
262325
+ children: "Diagram"
260294
262326
  }
260295
- ) : null,
260296
- snippet2 && marker.sourcePath ? /* @__PURE__ */ jsxs("div", { style: { background: theme2.colors.background }, children: [
260297
- /* @__PURE__ */ jsxs(
260298
- "button",
260299
- {
260300
- type: "button",
260301
- onClick: () => setSnippetExpanded((v) => !v),
260302
- "aria-expanded": snippetExpanded,
260303
- title: snippetExpanded ? "Hide snippet" : "Show snippet",
260304
- style: {
260305
- display: "flex",
260306
- alignItems: "center",
260307
- gap: 8,
260308
- width: "100%",
260309
- textAlign: "left",
260310
- padding: "10px 20px",
260311
- background: "transparent",
260312
- border: "none",
260313
- borderBottom: snippetExpanded ? `1px solid ${theme2.colors.border}` : "none",
260314
- cursor: "pointer",
260315
- fontFamily: theme2.fonts.body,
260316
- fontSize: theme2.fontSizes[0],
260317
- fontWeight: theme2.fontWeights.medium,
260318
- color: theme2.colors.textSecondary,
260319
- letterSpacing: "0.04em",
260320
- textTransform: "uppercase"
260321
- },
260322
- children: [
260323
- /* @__PURE__ */ jsx(
260324
- "span",
260325
- {
260326
- "aria-hidden": true,
260327
- style: {
260328
- display: "inline-block",
260329
- width: 10,
260330
- transform: snippetExpanded ? "rotate(90deg)" : "rotate(0deg)",
260331
- transition: "transform 120ms ease"
260332
- },
260333
- children: "▶"
260334
- }
260335
- ),
260336
- snippetExpanded ? "Hide snippet" : "Show snippet"
260337
- ]
260338
- }
260339
- ),
260340
- snippetExpanded ? /* @__PURE__ */ jsx("div", { style: { padding: 12 }, children: snippet2.kind === "slice" ? /* @__PURE__ */ jsx(
260341
- TrailSnippetView,
260342
- {
260343
- filePath: marker.sourcePath,
260344
- fileName: fileName ?? marker.sourcePath,
260345
- startLine: snippet2.startLine,
260346
- endLine: snippet2.endLine,
260347
- focusLine: snippet2.focusLine,
260348
- contextLines: snippet2.contextLines,
260349
- readFile,
260350
- background: theme2.colors.background
260351
- }
260352
- ) : /* @__PURE__ */ jsx(
260353
- TrailDiffSnippetView,
260354
- {
260355
- filePath: marker.sourcePath,
260356
- fileName: fileName ?? marker.sourcePath,
260357
- oldContents: snippet2.oldContents,
260358
- newContents: snippet2.newContents,
260359
- readFile,
260360
- startLine: snippet2.startLine,
260361
- endLine: snippet2.endLine,
260362
- focusLine: snippet2.focusLine,
260363
- contextLines: snippet2.contextLines,
260364
- diffStyle: snippet2.diffStyle,
260365
- background: theme2.colors.background
260366
- }
260367
- ) }) : null
260368
- ] }) : null
262327
+ ) : null })
260369
262328
  ]
260370
262329
  }
260371
262330
  );
260372
262331
  };
260373
- const TrailHeader = ({
260374
- trail: trail2,
260375
- markerCount,
260376
- activeMarkerIndex,
260377
- show3D,
260378
- onToggle3D,
260379
- hideToggle = false,
260380
- showSequenceDrawer,
260381
- onToggleSequenceDrawer,
260382
- canExit,
260383
- onExitTrail,
260384
- viewMode,
260385
- onSetViewMode
262332
+ const TrailFilesPanel = ({
262333
+ files,
262334
+ onClose
262335
+ }) => {
262336
+ const { theme: theme2 } = useTheme();
262337
+ return /* @__PURE__ */ jsxs(
262338
+ "div",
262339
+ {
262340
+ style: {
262341
+ position: "absolute",
262342
+ left: 0,
262343
+ right: 0,
262344
+ bottom: 0,
262345
+ // Sized to feel like a peek, not a full takeover. Internal
262346
+ // scroll handles long file lists.
262347
+ maxHeight: "45%",
262348
+ display: "flex",
262349
+ flexDirection: "column",
262350
+ background: theme2.colors.backgroundSecondary,
262351
+ borderTop: `1px solid ${theme2.colors.border}`,
262352
+ boxShadow: theme2.shadows[3],
262353
+ // Above every trail overlay (markdown overlay 1900, leader
262354
+ // line 1901, snippet pane 1900) but below the request modal
262355
+ // (2000) so the modal can still cover everything when reopened.
262356
+ zIndex: 1950,
262357
+ animation: "trail-files-slide-up 180ms ease-out"
262358
+ },
262359
+ children: [
262360
+ /* @__PURE__ */ jsx("style", { children: `
262361
+ @keyframes trail-files-slide-up {
262362
+ from { transform: translateY(100%); }
262363
+ to { transform: translateY(0); }
262364
+ }
262365
+ ` }),
262366
+ /* @__PURE__ */ jsx(
262367
+ "button",
262368
+ {
262369
+ type: "button",
262370
+ onClick: onClose,
262371
+ "aria-label": "Hide files list",
262372
+ title: "Hide files list",
262373
+ style: {
262374
+ position: "absolute",
262375
+ top: 6,
262376
+ right: 10,
262377
+ background: "transparent",
262378
+ border: "none",
262379
+ color: theme2.colors.textTertiary,
262380
+ fontSize: theme2.fontSizes[2],
262381
+ cursor: "pointer",
262382
+ lineHeight: 1,
262383
+ padding: 0,
262384
+ zIndex: 1
262385
+ },
262386
+ children: "×"
262387
+ }
262388
+ ),
262389
+ /* @__PURE__ */ jsx("div", { style: { overflowY: "auto", minHeight: 0 }, children: /* @__PURE__ */ jsx(TrailFilesList, { files }) })
262390
+ ]
262391
+ }
262392
+ );
262393
+ };
262394
+ const TrailNotesPanel = ({
262395
+ notes,
262396
+ markers,
262397
+ onJumpToMarker,
262398
+ onClose
260386
262399
  }) => {
260387
262400
  const { theme: theme2 } = useTheme();
260388
- const toggleButtonStyle = (active) => ({
260389
- flexShrink: 0,
260390
- fontFamily: theme2.fonts.body,
260391
- fontSize: theme2.fontSizes[0],
260392
- fontWeight: theme2.fontWeights.medium,
260393
- color: active ? theme2.colors.background : theme2.colors.text,
260394
- background: active ? theme2.colors.accent : "transparent",
260395
- border: `1px solid ${active ? theme2.colors.accent : theme2.colors.muted}`,
260396
- borderRadius: theme2.radii[3],
260397
- padding: "4px 10px",
260398
- cursor: "pointer",
260399
- lineHeight: 1.2
260400
- });
260401
- const segmentStyle = (active, side) => ({
260402
- flexShrink: 0,
260403
- fontFamily: theme2.fonts.body,
260404
- fontSize: theme2.fontSizes[0],
260405
- fontWeight: theme2.fontWeights.medium,
260406
- color: active ? theme2.colors.background : theme2.colors.text,
260407
- background: active ? theme2.colors.accent : "transparent",
260408
- border: `1px solid ${active ? theme2.colors.accent : theme2.colors.muted}`,
260409
- borderTopLeftRadius: side === "left" ? theme2.radii[3] : 0,
260410
- borderBottomLeftRadius: side === "left" ? theme2.radii[3] : 0,
260411
- borderTopRightRadius: side === "right" ? theme2.radii[3] : 0,
260412
- borderBottomRightRadius: side === "right" ? theme2.radii[3] : 0,
260413
- marginLeft: side === "right" ? -1 : 0,
260414
- padding: "4px 10px",
260415
- cursor: "pointer",
260416
- lineHeight: 1.2
260417
- });
260418
- const isDoc = viewMode === "doc";
262401
+ const [sortMode, setSortMode] = React.useState("recent");
262402
+ const markerLabelById = React.useMemo(() => {
262403
+ const map2 = /* @__PURE__ */ new Map();
262404
+ for (const m of markers) {
262405
+ const label = m.label && m.label.trim().length > 0 ? m.label : m.id;
262406
+ map2.set(m.id, label);
262407
+ }
262408
+ return map2;
262409
+ }, [markers]);
262410
+ const markerIndexById = React.useMemo(() => {
262411
+ const map2 = /* @__PURE__ */ new Map();
262412
+ markers.forEach((m, i) => map2.set(m.id, i));
262413
+ return map2;
262414
+ }, [markers]);
262415
+ const formatRelative2 = (iso) => {
262416
+ const ms = Date.now() - new Date(iso).getTime();
262417
+ const min = Math.round(ms / 6e4);
262418
+ if (min < 1) return "just now";
262419
+ if (min < 60) return `${min}m ago`;
262420
+ const hr = Math.round(min / 60);
262421
+ if (hr < 24) return `${hr}h ago`;
262422
+ const day = Math.round(hr / 24);
262423
+ if (day < 7) return `${day}d ago`;
262424
+ const wk = Math.round(day / 7);
262425
+ if (wk < 5) return `${wk}w ago`;
262426
+ const mo = Math.round(day / 30);
262427
+ if (mo < 12) return `${mo}mo ago`;
262428
+ return `${Math.round(day / 365)}y ago`;
262429
+ };
262430
+ const ordered = React.useMemo(() => {
262431
+ const arr = [...notes];
262432
+ if (sortMode === "recent") {
262433
+ arr.sort(
262434
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
262435
+ );
262436
+ return arr;
262437
+ }
262438
+ const indexFor = (n) => {
262439
+ if (n.kind === "markdown" && n.scope.kind === "summary") return -1;
262440
+ const markerId = n.kind === "snippet" ? n.scope.markerId : n.kind === "markdown" && n.scope.kind === "description" ? n.scope.markerId : null;
262441
+ if (!markerId) return Number.MAX_SAFE_INTEGER;
262442
+ return markerIndexById.get(markerId) ?? Number.MAX_SAFE_INTEGER;
262443
+ };
262444
+ const subRank = (n) => {
262445
+ if (n.kind === "snippet") {
262446
+ const first = n.anchor.kind === "slice" ? n.anchor.ranges[0] : null;
262447
+ return 1e6 + ((first == null ? void 0 : first.startLine) ?? 0);
262448
+ }
262449
+ return 0;
262450
+ };
262451
+ arr.sort((a, b) => {
262452
+ const ai = indexFor(a);
262453
+ const bi = indexFor(b);
262454
+ if (ai !== bi) return ai - bi;
262455
+ const ar = subRank(a);
262456
+ const br2 = subRank(b);
262457
+ if (ar !== br2) return ar - br2;
262458
+ return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
262459
+ });
262460
+ return arr;
262461
+ }, [notes, sortMode, markerIndexById]);
260419
262462
  return /* @__PURE__ */ jsxs(
260420
262463
  "div",
260421
262464
  {
260422
262465
  style: {
260423
- padding: "8px 16px",
260424
- borderBottom: `1px solid ${theme2.colors.border}`,
262466
+ position: "absolute",
262467
+ left: 0,
262468
+ right: 0,
262469
+ bottom: 0,
262470
+ maxHeight: "55%",
260425
262471
  display: "flex",
260426
- flexDirection: "row",
260427
- alignItems: "center",
260428
- justifyContent: "space-between",
260429
- gap: 12,
260430
- flexShrink: 0
262472
+ flexDirection: "column",
262473
+ background: theme2.colors.backgroundSecondary,
262474
+ borderTop: `1px solid ${theme2.colors.border}`,
262475
+ boxShadow: theme2.shadows[3],
262476
+ zIndex: 1950,
262477
+ animation: "trail-notes-slide-up 180ms ease-out"
260431
262478
  },
260432
262479
  children: [
262480
+ /* @__PURE__ */ jsx("style", { children: `
262481
+ @keyframes trail-notes-slide-up {
262482
+ from { transform: translateY(100%); }
262483
+ to { transform: translateY(0); }
262484
+ }
262485
+ ` }),
260433
262486
  /* @__PURE__ */ jsxs(
260434
262487
  "div",
260435
262488
  {
260436
262489
  style: {
262490
+ padding: "8px 14px 4px",
262491
+ fontFamily: theme2.fonts.body,
262492
+ fontSize: theme2.fontSizes[0],
262493
+ color: theme2.colors.textSecondary,
262494
+ letterSpacing: 0.4,
262495
+ textTransform: "uppercase",
262496
+ flexShrink: 0,
260437
262497
  display: "flex",
260438
- flexDirection: "column",
260439
- justifyContent: "center",
260440
- gap: 2,
260441
- minWidth: 0
262498
+ alignItems: "center",
262499
+ gap: 8
260442
262500
  },
260443
262501
  children: [
260444
- /* @__PURE__ */ jsx(
262502
+ /* @__PURE__ */ jsx("span", { children: "Notes in this trail" }),
262503
+ /* @__PURE__ */ jsxs(
260445
262504
  "div",
260446
262505
  {
260447
262506
  style: {
260448
- fontFamily: theme2.fonts.heading,
260449
- fontSize: theme2.fontSizes[1],
260450
- fontWeight: theme2.fontWeights.semibold,
260451
- color: theme2.colors.text
262507
+ display: "flex",
262508
+ alignItems: "center",
262509
+ marginLeft: 12,
262510
+ textTransform: "none",
262511
+ letterSpacing: 0
260452
262512
  },
260453
- children: trail2.title
262513
+ children: [
262514
+ /* @__PURE__ */ jsx(
262515
+ "button",
262516
+ {
262517
+ type: "button",
262518
+ onClick: () => setSortMode("recent"),
262519
+ "aria-pressed": sortMode === "recent",
262520
+ title: "Sort by most recent",
262521
+ style: notesSortButtonStyle(theme2, sortMode === "recent", "left"),
262522
+ children: "Recent"
262523
+ }
262524
+ ),
262525
+ /* @__PURE__ */ jsx(
262526
+ "button",
262527
+ {
262528
+ type: "button",
262529
+ onClick: () => setSortMode("trail"),
262530
+ "aria-pressed": sortMode === "trail",
262531
+ title: "Sort by trail order (summary, then markers)",
262532
+ style: notesSortButtonStyle(theme2, sortMode === "trail", "right"),
262533
+ children: "Trail order"
262534
+ }
262535
+ )
262536
+ ]
260454
262537
  }
260455
262538
  ),
260456
262539
  /* @__PURE__ */ jsx(
260457
- "div",
262540
+ "button",
260458
262541
  {
260459
- "aria-label": `${markerCount} marker${markerCount === 1 ? "" : "s"} in this repo`,
262542
+ type: "button",
262543
+ onClick: onClose,
262544
+ "aria-label": "Hide notes",
262545
+ title: "Hide notes",
260460
262546
  style: {
260461
- display: "flex",
260462
- alignItems: "center",
260463
- height: theme2.fontSizes[0]
262547
+ marginLeft: "auto",
262548
+ background: "transparent",
262549
+ border: "none",
262550
+ color: theme2.colors.textTertiary,
262551
+ fontSize: theme2.fontSizes[2],
262552
+ cursor: "pointer",
262553
+ lineHeight: 1,
262554
+ padding: 0
260464
262555
  },
260465
- children: Array.from({ length: markerCount }).map((_, i) => {
260466
- const isActive = i === activeMarkerIndex;
260467
- return /* @__PURE__ */ jsx(
260468
- "span",
260469
- {
260470
- style: {
260471
- width: 6,
260472
- height: 6,
260473
- borderRadius: "50%",
260474
- background: isActive ? theme2.colors.accent : theme2.colors.textSecondary,
260475
- // Group dots in fives for at-a-glance countability — a
260476
- // wider gap before every 5th index (after the first)
260477
- // creates visual columns of 5 like tally marks.
260478
- marginLeft: i === 0 ? 0 : i % 5 === 0 ? 11 : 5
260479
- }
260480
- },
260481
- i
260482
- );
260483
- })
262556
+ children: "×"
260484
262557
  }
260485
262558
  )
260486
262559
  ]
260487
262560
  }
260488
262561
  ),
260489
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
260490
- canExit ? /* @__PURE__ */ jsx(
260491
- "button",
260492
- {
260493
- type: "button",
260494
- onClick: onExitTrail,
260495
- title: "Return to the trail summary",
260496
- style: {
260497
- flexShrink: 0,
260498
- fontFamily: theme2.fonts.body,
260499
- fontSize: theme2.fontSizes[0],
260500
- fontWeight: theme2.fontWeights.medium,
260501
- color: theme2.colors.text,
260502
- background: "transparent",
260503
- border: `1px solid ${theme2.colors.muted}`,
260504
- borderRadius: theme2.radii[3],
260505
- padding: "4px 10px",
260506
- cursor: "pointer",
260507
- lineHeight: 1.2
260508
- },
260509
- children: "Exit"
260510
- }
260511
- ) : null,
260512
- isDoc ? null : /* @__PURE__ */ jsx(
260513
- "button",
260514
- {
260515
- type: "button",
260516
- onClick: onToggleSequenceDrawer,
260517
- "aria-pressed": showSequenceDrawer,
260518
- title: showSequenceDrawer ? "Hide sequence diagram" : "Show sequence diagram",
260519
- style: toggleButtonStyle(showSequenceDrawer),
260520
- children: "Diagram"
260521
- }
260522
- ),
260523
- hideToggle || isDoc ? null : /* @__PURE__ */ jsx(
260524
- "button",
260525
- {
260526
- type: "button",
260527
- onClick: onToggle3D,
260528
- "aria-pressed": show3D,
260529
- title: show3D ? "Switch to flat (2D) view" : "Switch to 3D view",
260530
- style: toggleButtonStyle(show3D),
260531
- children: "3D"
260532
- }
260533
- ),
260534
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", marginLeft: 6 }, children: [
260535
- /* @__PURE__ */ jsx(
260536
- "button",
260537
- {
260538
- type: "button",
260539
- onClick: () => onSetViewMode("city"),
260540
- "aria-pressed": !isDoc,
260541
- title: "City view — 3D buildings + sequence drawer",
260542
- style: segmentStyle(!isDoc, "left"),
260543
- children: "City"
260544
- }
260545
- ),
260546
- /* @__PURE__ */ jsx(
260547
- "button",
260548
- {
260549
- type: "button",
260550
- onClick: () => onSetViewMode("doc"),
260551
- "aria-pressed": isDoc,
260552
- title: "Document view — scroll all markers as one page",
260553
- style: segmentStyle(isDoc, "right"),
260554
- children: "Doc"
260555
- }
260556
- )
260557
- ] })
260558
- ] })
262562
+ /* @__PURE__ */ jsx(
262563
+ "div",
262564
+ {
262565
+ style: {
262566
+ overflowY: "auto",
262567
+ minHeight: 0,
262568
+ padding: "4px 6px 8px"
262569
+ },
262570
+ children: ordered.map((n) => {
262571
+ const scopeLabel = n.kind === "snippet" ? `${markerLabelById.get(n.scope.markerId) ?? n.scope.markerId} · snippet` : n.scope.kind === "description" ? markerLabelById.get(n.scope.markerId) ?? n.scope.markerId : "Summary";
262572
+ const targetMarkerId = n.kind === "snippet" ? n.scope.markerId : n.scope.kind === "description" ? n.scope.markerId : null;
262573
+ const clickable = !!targetMarkerId;
262574
+ return /* @__PURE__ */ jsxs(
262575
+ "button",
262576
+ {
262577
+ type: "button",
262578
+ onClick: () => {
262579
+ if (targetMarkerId) onJumpToMarker(targetMarkerId);
262580
+ },
262581
+ disabled: !clickable,
262582
+ style: {
262583
+ all: "unset",
262584
+ boxSizing: "border-box",
262585
+ display: "flex",
262586
+ flexDirection: "column",
262587
+ gap: 4,
262588
+ width: "100%",
262589
+ padding: "8px 10px",
262590
+ borderRadius: theme2.radii[2],
262591
+ cursor: clickable ? "pointer" : "default",
262592
+ background: "transparent",
262593
+ color: theme2.colors.text,
262594
+ fontFamily: theme2.fonts.body,
262595
+ fontSize: theme2.fontSizes[1],
262596
+ transition: "background 100ms ease"
262597
+ },
262598
+ onMouseEnter: (e) => {
262599
+ if (clickable) {
262600
+ e.currentTarget.style.background = `color-mix(in srgb, ${theme2.colors.text} 6%, transparent)`;
262601
+ }
262602
+ },
262603
+ onMouseLeave: (e) => {
262604
+ e.currentTarget.style.background = "transparent";
262605
+ },
262606
+ title: clickable ? `Jump to ${scopeLabel}` : "Summary-scope note (no jump target)",
262607
+ children: [
262608
+ /* @__PURE__ */ jsxs(
262609
+ "div",
262610
+ {
262611
+ style: {
262612
+ display: "flex",
262613
+ alignItems: "baseline",
262614
+ gap: 8,
262615
+ fontSize: theme2.fontSizes[0]
262616
+ },
262617
+ children: [
262618
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: n.author }),
262619
+ /* @__PURE__ */ jsx(
262620
+ "span",
262621
+ {
262622
+ style: {
262623
+ color: theme2.colors.textTertiary,
262624
+ fontFamily: theme2.fonts.monospace
262625
+ },
262626
+ children: scopeLabel
262627
+ }
262628
+ ),
262629
+ /* @__PURE__ */ jsx(
262630
+ "span",
262631
+ {
262632
+ style: {
262633
+ marginLeft: "auto",
262634
+ color: theme2.colors.textTertiary,
262635
+ fontVariantNumeric: "tabular-nums"
262636
+ },
262637
+ children: formatRelative2(n.createdAt)
262638
+ }
262639
+ )
262640
+ ]
262641
+ }
262642
+ ),
262643
+ /* @__PURE__ */ jsx(
262644
+ "div",
262645
+ {
262646
+ style: {
262647
+ whiteSpace: "pre-wrap",
262648
+ wordBreak: "break-word",
262649
+ color: theme2.colors.text,
262650
+ // Cap each note body at three lines in the panel so
262651
+ // a long thread doesn't push other notes off-screen.
262652
+ display: "-webkit-box",
262653
+ WebkitLineClamp: 3,
262654
+ WebkitBoxOrient: "vertical",
262655
+ overflow: "hidden"
262656
+ },
262657
+ children: n.body
262658
+ }
262659
+ )
262660
+ ]
262661
+ },
262662
+ n.id
262663
+ );
262664
+ })
262665
+ }
262666
+ )
260559
262667
  ]
260560
262668
  }
260561
262669
  );