@industry-theme/file-city-panel 0.5.61 → 0.5.63

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.
@@ -255103,7 +255103,7 @@ const toUiNote = (n) => {
255103
255103
  };
255104
255104
  const PANEL_WIDTH_PCT$3 = 38;
255105
255105
  const FLOAT_INSET$3 = 16;
255106
- const TOP_INSET$3 = 72;
255106
+ const TOP_INSET$2 = 72;
255107
255107
  const MIN_WIDTH_PX$3 = 360;
255108
255108
  const MIN_LEFT_GAP_PX$1 = 80;
255109
255109
  const RESIZE_HANDLE_WIDTH$3 = 6;
@@ -255259,7 +255259,7 @@ const SequenceEventDetailOverlay = React.forwardRef(function SequenceEventDetail
255259
255259
  const NOTES_PANEL_GAP = 12;
255260
255260
  const notesPanelStyle = {
255261
255261
  position: "absolute",
255262
- top: TOP_INSET$3,
255262
+ top: TOP_INSET$2,
255263
255263
  bottom: `calc(${bottomOffsetCss} + ${FLOAT_INSET$3}px)`,
255264
255264
  width: NOTES_PANEL_WIDTH,
255265
255265
  right: `calc(${detailWidthCss} + ${FLOAT_INSET$3}px + ${NOTES_PANEL_GAP}px)`,
@@ -255310,7 +255310,7 @@ const SequenceEventDetailOverlay = React.forwardRef(function SequenceEventDetail
255310
255310
  onAnimationEnd: () => setHasEntered(true),
255311
255311
  style: {
255312
255312
  position: "absolute",
255313
- top: TOP_INSET$3,
255313
+ top: TOP_INSET$2,
255314
255314
  right: FLOAT_INSET$3,
255315
255315
  // Anchor the bottom edge (mirroring the markdown overlay) so the
255316
255316
  // panel has a *definite* height — otherwise transient placeholder
@@ -255574,7 +255574,7 @@ const iconButtonStyle = (color2, disabled = false) => ({
255574
255574
  });
255575
255575
  const PANEL_WIDTH_PCT$2 = 38;
255576
255576
  const FLOAT_INSET$2 = 16;
255577
- const TOP_INSET$2 = 72;
255577
+ const TOP_INSET$1 = 72;
255578
255578
  const MIN_WIDTH_PX$2 = 360;
255579
255579
  const MIN_LEFT_GAP_PX = 80;
255580
255580
  const RESIZE_HANDLE_WIDTH$2 = 6;
@@ -255631,7 +255631,7 @@ const SequenceFilesOverlay = ({
255631
255631
  onAnimationEnd: () => setHasEntered(true),
255632
255632
  style: {
255633
255633
  position: "absolute",
255634
- top: TOP_INSET$2,
255634
+ top: TOP_INSET$1,
255635
255635
  right: FLOAT_INSET$2,
255636
255636
  bottom: `calc(${bottomOffsetCss} + ${FLOAT_INSET$2}px)`,
255637
255637
  width: widthPx != null ? `${widthPx}px` : `calc(${PANEL_WIDTH_PCT$2}% - ${FLOAT_INSET$2}px)`,
@@ -256360,7 +256360,7 @@ const primaryButton = (theme2) => ({
256360
256360
  });
256361
256361
  const PANEL_WIDTH_PCT$1 = 38;
256362
256362
  const FLOAT_INSET$1 = 16;
256363
- const TOP_INSET$1 = 72;
256363
+ const TOP_INSET = 72;
256364
256364
  const MIN_WIDTH_PX$1 = 360;
256365
256365
  const RESIZE_HANDLE_WIDTH$1 = 6;
256366
256366
  const SequenceMarkdownOverlay = ({
@@ -256475,7 +256475,7 @@ const SequenceMarkdownOverlay = ({
256475
256475
  onAnimationEnd: () => setHasEntered(true),
256476
256476
  style: {
256477
256477
  position: "absolute",
256478
- top: TOP_INSET$1,
256478
+ top: TOP_INSET,
256479
256479
  left: FLOAT_INSET$1,
256480
256480
  // Anchor the bottom edge too so the column has a *definite* height —
256481
256481
  // `IndustryMarkdownSlide` renders with `height: 100%` and only
@@ -257792,7 +257792,6 @@ const TrailLeaderLine = React.forwardRef(function TrailLeaderLine2({ containerRe
257792
257792
  });
257793
257793
  const PANEL_WIDTH_PCT = 38;
257794
257794
  const FLOAT_INSET = 16;
257795
- const TOP_INSET = 16;
257796
257795
  const MIN_WIDTH_PX = 360;
257797
257796
  const RESIZE_HANDLE_WIDTH = 6;
257798
257797
  const TrailMarkdownOverlay = ({
@@ -257801,7 +257800,10 @@ const TrailMarkdownOverlay = ({
257801
257800
  markdown: markdown2,
257802
257801
  slideIdPrefix,
257803
257802
  bottomOffset,
257804
- containerRef: externalContainerRef
257803
+ containerRef: externalContainerRef,
257804
+ disableBottomTransition,
257805
+ nav,
257806
+ files
257805
257807
  }) => {
257806
257808
  const { theme: theme2 } = useTheme();
257807
257809
  const body = markdown2.trim();
@@ -257844,7 +257846,7 @@ const TrailMarkdownOverlay = ({
257844
257846
  if (!drag2) return;
257845
257847
  const parent = (_a = containerRef.current) == null ? void 0 : _a.parentElement;
257846
257848
  const parentWidth = (parent == null ? void 0 : parent.clientWidth) ?? window.innerWidth;
257847
- const maxWidth = Math.max(MIN_WIDTH_PX, parentWidth - FLOAT_INSET * 2);
257849
+ const maxWidth = Math.max(MIN_WIDTH_PX, parentWidth - FLOAT_INSET);
257848
257850
  const dx = e.clientX - drag2.startX;
257849
257851
  const next2 = Math.min(maxWidth, Math.max(MIN_WIDTH_PX, drag2.startWidth + dx));
257850
257852
  setWidthPx(next2);
@@ -257870,16 +257872,25 @@ const TrailMarkdownOverlay = ({
257870
257872
  onAnimationEnd: () => setHasEntered(true),
257871
257873
  style: {
257872
257874
  position: "absolute",
257873
- top: TOP_INSET,
257874
- left: FLOAT_INSET,
257875
- bottom: `calc(${bottomOffsetCss} + ${FLOAT_INSET}px)`,
257876
- width: widthPx != null ? widthPx : `calc(${PANEL_WIDTH_PCT}% - ${FLOAT_INSET}px)`,
257875
+ top: 0,
257876
+ left: 0,
257877
+ // 1px gap above the drawer's top edge so the drawer reads
257878
+ // cleanly through any subpixel rounding / transition flicker.
257879
+ bottom: `calc(${bottomOffsetCss} + 1px)`,
257880
+ // Match the sequence drawer's slide animation so the
257881
+ // overlay's bottom edge tracks the drawer's height instead
257882
+ // of snapping when the host toggles `bottomOffset`. Skipped
257883
+ // while the drawer is being live-resized so the bottom edge
257884
+ // tracks the cursor without lag.
257885
+ transition: disableBottomTransition ? void 0 : "bottom 280ms ease",
257886
+ width: widthPx != null ? widthPx : `${PANEL_WIDTH_PCT}%`,
257877
257887
  minWidth: MIN_WIDTH_PX,
257878
257888
  backgroundColor: theme2.colors.background,
257879
- border: `1px solid ${theme2.colors.border}`,
257880
- borderRadius: 12,
257889
+ // Flush against the city container's left/top/bottom edges;
257890
+ // only the right edge separates from the canvas, so we draw
257891
+ // the border there only.
257892
+ borderRight: `1px solid ${theme2.colors.border}`,
257881
257893
  overflow: "hidden",
257882
- boxShadow: "0 12px 32px rgba(0, 0, 0, 0.28)",
257883
257894
  display: "flex",
257884
257895
  flexDirection: "column",
257885
257896
  zIndex: 1900,
@@ -257893,7 +257904,7 @@ const TrailMarkdownOverlay = ({
257893
257904
  to { transform: translateX(0); }
257894
257905
  }
257895
257906
  ` }),
257896
- /* @__PURE__ */ jsxs(
257907
+ eyebrow || title ? /* @__PURE__ */ jsxs(
257897
257908
  "div",
257898
257909
  {
257899
257910
  style: {
@@ -257907,7 +257918,7 @@ const TrailMarkdownOverlay = ({
257907
257918
  minWidth: 0
257908
257919
  },
257909
257920
  children: [
257910
- /* @__PURE__ */ jsx(
257921
+ eyebrow ? /* @__PURE__ */ jsx(
257911
257922
  "span",
257912
257923
  {
257913
257924
  style: {
@@ -257918,8 +257929,8 @@ const TrailMarkdownOverlay = ({
257918
257929
  },
257919
257930
  children: eyebrow
257920
257931
  }
257921
- ),
257922
- /* @__PURE__ */ jsx(
257932
+ ) : null,
257933
+ title ? /* @__PURE__ */ jsx(
257923
257934
  "span",
257924
257935
  {
257925
257936
  style: {
@@ -257932,22 +257943,35 @@ const TrailMarkdownOverlay = ({
257932
257943
  title,
257933
257944
  children: title
257934
257945
  }
257935
- )
257946
+ ) : null
257936
257947
  ]
257937
257948
  }
257938
- ),
257939
- /* @__PURE__ */ jsx("div", { style: { flex: 1, minHeight: 0, position: "relative" }, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ jsx(
257940
- IndustryMarkdownSlide,
257949
+ ) : null,
257950
+ /* @__PURE__ */ jsx(
257951
+ "div",
257941
257952
  {
257942
- content: body,
257943
- slideIdPrefix,
257944
- slideIndex: 0,
257945
- isVisible: true,
257946
- theme: theme2,
257947
- transparentBackground: true,
257948
- enableKeyboardScrolling: false
257953
+ style: {
257954
+ flex: 1,
257955
+ minHeight: 0,
257956
+ maxHeight: files && files.length > 0 ? "30%" : void 0,
257957
+ position: "relative"
257958
+ },
257959
+ children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ jsx(
257960
+ IndustryMarkdownSlide,
257961
+ {
257962
+ content: body,
257963
+ slideIdPrefix,
257964
+ slideIndex: 0,
257965
+ isVisible: true,
257966
+ theme: theme2,
257967
+ transparentBackground: true,
257968
+ enableKeyboardScrolling: false
257969
+ }
257970
+ ) })
257949
257971
  }
257950
- ) }) }),
257972
+ ),
257973
+ files && files.length > 0 ? /* @__PURE__ */ jsx(TrailFilesList, { files }) : null,
257974
+ nav ? /* @__PURE__ */ jsx(TrailMarkdownOverlayFooter, { nav }) : null,
257951
257975
  /* @__PURE__ */ jsx(
257952
257976
  "div",
257953
257977
  {
@@ -257983,6 +258007,260 @@ const TrailMarkdownOverlay = ({
257983
258007
  }
257984
258008
  );
257985
258009
  };
258010
+ const TrailFilesList = ({ files }) => {
258011
+ const { theme: theme2 } = useTheme();
258012
+ return /* @__PURE__ */ jsxs(
258013
+ "div",
258014
+ {
258015
+ style: {
258016
+ borderTop: `1px solid ${theme2.colors.border}`,
258017
+ // Markdown body is capped at 50% above; files take the rest
258018
+ // of the remaining column. Grows past 50% if the summary is
258019
+ // shorter than its cap (the markdown body shrinks below 50%
258020
+ // to accommodate, since `max-height` doesn't force it).
258021
+ flex: 1,
258022
+ display: "flex",
258023
+ flexDirection: "column",
258024
+ minHeight: 0
258025
+ },
258026
+ children: [
258027
+ /* @__PURE__ */ jsx(
258028
+ "div",
258029
+ {
258030
+ style: {
258031
+ padding: "8px 14px 4px",
258032
+ fontFamily: theme2.fonts.body,
258033
+ fontSize: theme2.fontSizes[0],
258034
+ color: theme2.colors.textSecondary,
258035
+ letterSpacing: 0.4,
258036
+ textTransform: "uppercase",
258037
+ flexShrink: 0
258038
+ },
258039
+ children: "Files in this trail"
258040
+ }
258041
+ ),
258042
+ /* @__PURE__ */ jsx("div", { style: { overflowY: "auto", minHeight: 0, padding: "0 6px 8px" }, children: files.map((file) => {
258043
+ const fileName = file.sourcePath.split("/").pop() ?? file.sourcePath;
258044
+ const dir = file.sourcePath.slice(
258045
+ 0,
258046
+ Math.max(0, file.sourcePath.length - fileName.length - 1)
258047
+ );
258048
+ return /* @__PURE__ */ jsxs(
258049
+ "button",
258050
+ {
258051
+ type: "button",
258052
+ onClick: file.onClick,
258053
+ title: file.sourcePath,
258054
+ style: {
258055
+ display: "flex",
258056
+ alignItems: "center",
258057
+ justifyContent: "space-between",
258058
+ gap: 8,
258059
+ width: "100%",
258060
+ background: file.isActive ? `color-mix(in oklab, ${theme2.colors.accent} 20%, transparent)` : "transparent",
258061
+ border: "none",
258062
+ borderRadius: theme2.radii[2],
258063
+ padding: "6px 8px",
258064
+ cursor: "pointer",
258065
+ color: theme2.colors.text,
258066
+ fontFamily: theme2.fonts.monospace,
258067
+ fontSize: theme2.fontSizes[1],
258068
+ textAlign: "left"
258069
+ },
258070
+ onMouseEnter: (e) => {
258071
+ var _a;
258072
+ (_a = file.onHoverChange) == null ? void 0 : _a.call(file, file.sourcePath);
258073
+ if (file.isActive) return;
258074
+ e.currentTarget.style.background = `color-mix(in oklab, ${theme2.colors.text} 6%, transparent)`;
258075
+ },
258076
+ onMouseLeave: (e) => {
258077
+ var _a;
258078
+ (_a = file.onHoverChange) == null ? void 0 : _a.call(file, null);
258079
+ if (file.isActive) return;
258080
+ e.currentTarget.style.background = "transparent";
258081
+ },
258082
+ children: [
258083
+ /* @__PURE__ */ jsxs(
258084
+ "span",
258085
+ {
258086
+ style: {
258087
+ display: "flex",
258088
+ flexDirection: "column",
258089
+ alignItems: "flex-start",
258090
+ gap: 1,
258091
+ minWidth: 0,
258092
+ flex: 1,
258093
+ overflow: "hidden"
258094
+ },
258095
+ children: [
258096
+ /* @__PURE__ */ jsx(
258097
+ "span",
258098
+ {
258099
+ style: {
258100
+ fontWeight: file.isActive ? theme2.fontWeights.semibold : theme2.fontWeights.body,
258101
+ overflow: "hidden",
258102
+ textOverflow: "ellipsis",
258103
+ whiteSpace: "nowrap",
258104
+ maxWidth: "100%"
258105
+ },
258106
+ children: fileName
258107
+ }
258108
+ ),
258109
+ dir ? /* @__PURE__ */ jsx(
258110
+ "span",
258111
+ {
258112
+ style: {
258113
+ fontSize: theme2.fontSizes[0],
258114
+ color: theme2.colors.textTertiary,
258115
+ overflow: "hidden",
258116
+ textOverflow: "ellipsis",
258117
+ whiteSpace: "nowrap",
258118
+ direction: "rtl",
258119
+ textAlign: "left",
258120
+ maxWidth: "100%"
258121
+ },
258122
+ children: dir
258123
+ }
258124
+ ) : null
258125
+ ]
258126
+ }
258127
+ ),
258128
+ /* @__PURE__ */ jsx(
258129
+ "span",
258130
+ {
258131
+ style: {
258132
+ flexShrink: 0,
258133
+ color: theme2.colors.textSecondary,
258134
+ fontFamily: theme2.fonts.body,
258135
+ fontVariantNumeric: "tabular-nums"
258136
+ },
258137
+ children: file.markerCount
258138
+ }
258139
+ )
258140
+ ]
258141
+ },
258142
+ file.sourcePath
258143
+ );
258144
+ }) })
258145
+ ]
258146
+ }
258147
+ );
258148
+ };
258149
+ const TrailMarkdownOverlayFooter = ({
258150
+ nav
258151
+ }) => {
258152
+ const { theme: theme2 } = useTheme();
258153
+ const idle = nav.position < 0;
258154
+ const canPrev = !idle;
258155
+ const canNext = !idle && nav.position < nav.total - 1;
258156
+ const startEnabled = nav.total > 0;
258157
+ const [startHover, setStartHover] = React.useState(false);
258158
+ const [startActive, setStartActive] = React.useState(false);
258159
+ const startBackground = !startEnabled ? theme2.colors.accent : startActive ? `color-mix(in oklab, ${theme2.colors.accent} 82%, black)` : startHover ? `color-mix(in oklab, ${theme2.colors.accent} 88%, black)` : theme2.colors.accent;
258160
+ const stepButtonStyle = (enabled) => ({
258161
+ background: "transparent",
258162
+ border: `1px solid ${theme2.colors.muted}`,
258163
+ borderRadius: theme2.radii[2],
258164
+ color: enabled ? theme2.colors.text : theme2.colors.textTertiary,
258165
+ cursor: enabled ? "pointer" : "not-allowed",
258166
+ padding: "4px 10px",
258167
+ fontFamily: theme2.fonts.body,
258168
+ fontSize: theme2.fontSizes[0],
258169
+ lineHeight: 1.2,
258170
+ opacity: enabled ? 1 : 0.5
258171
+ });
258172
+ return /* @__PURE__ */ jsx(
258173
+ "div",
258174
+ {
258175
+ style: {
258176
+ // In idle state the Start button fills the entire footer
258177
+ // (no padding around it); in active state we keep the inset
258178
+ // chrome so the prev/counter/next row reads as a row.
258179
+ padding: idle ? 0 : "8px 14px",
258180
+ borderTop: `1px solid ${theme2.colors.border}`,
258181
+ display: "flex",
258182
+ alignItems: "stretch",
258183
+ justifyContent: idle ? "stretch" : "space-between",
258184
+ gap: 8,
258185
+ flexShrink: 0
258186
+ },
258187
+ children: idle ? /* @__PURE__ */ jsx(
258188
+ "button",
258189
+ {
258190
+ type: "button",
258191
+ onClick: nav.onStart,
258192
+ disabled: !startEnabled,
258193
+ title: startEnabled ? "Jump to the first marker" : "No markers in this repo",
258194
+ onMouseEnter: () => setStartHover(true),
258195
+ onMouseLeave: () => {
258196
+ setStartHover(false);
258197
+ setStartActive(false);
258198
+ },
258199
+ onPointerDown: () => setStartActive(true),
258200
+ onPointerUp: () => setStartActive(false),
258201
+ onPointerCancel: () => setStartActive(false),
258202
+ style: {
258203
+ flex: 1,
258204
+ background: startBackground,
258205
+ color: theme2.colors.background,
258206
+ border: "none",
258207
+ borderRadius: 0,
258208
+ padding: "12px 14px",
258209
+ fontFamily: theme2.fonts.body,
258210
+ fontSize: theme2.fontSizes[1],
258211
+ fontWeight: theme2.fontWeights.semibold,
258212
+ cursor: startEnabled ? "pointer" : "not-allowed",
258213
+ opacity: startEnabled ? 1 : 0.5,
258214
+ lineHeight: 1.2,
258215
+ transition: "background 120ms ease"
258216
+ },
258217
+ children: "Start trail"
258218
+ }
258219
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
258220
+ /* @__PURE__ */ jsx(
258221
+ "button",
258222
+ {
258223
+ type: "button",
258224
+ onClick: nav.onPrev,
258225
+ disabled: !canPrev,
258226
+ "aria-label": "Previous marker",
258227
+ title: "Previous marker",
258228
+ style: stepButtonStyle(canPrev),
258229
+ children: "◀ Prev"
258230
+ }
258231
+ ),
258232
+ /* @__PURE__ */ jsxs(
258233
+ "span",
258234
+ {
258235
+ style: {
258236
+ fontFamily: theme2.fonts.body,
258237
+ fontSize: theme2.fontSizes[0],
258238
+ color: theme2.colors.textSecondary,
258239
+ fontVariantNumeric: "tabular-nums"
258240
+ },
258241
+ children: [
258242
+ nav.position + 1,
258243
+ " / ",
258244
+ nav.total
258245
+ ]
258246
+ }
258247
+ ),
258248
+ /* @__PURE__ */ jsx(
258249
+ "button",
258250
+ {
258251
+ type: "button",
258252
+ onClick: nav.onNext,
258253
+ disabled: !canNext,
258254
+ "aria-label": "Next marker",
258255
+ title: "Next marker",
258256
+ style: stepButtonStyle(canNext),
258257
+ children: "Next ▶"
258258
+ }
258259
+ )
258260
+ ] })
258261
+ }
258262
+ );
258263
+ };
257986
258264
  const TrailSnippetView = ({
257987
258265
  filePath,
257988
258266
  fileName,
@@ -258255,10 +258533,11 @@ const pierreOptionsBase = {
258255
258533
  const pierreStyle = {
258256
258534
  display: "block"
258257
258535
  };
258258
- const SEQUENCE_DRAWER_HEIGHT_PCT = 38;
258536
+ const SEQUENCE_DRAWER_HEIGHT_PCT = 35;
258259
258537
  const SNIPPET_PANE_WIDTH_PX = 460;
258260
258538
  const HEADER_HEIGHT_PX = 48;
258261
- const SNIPPET_PANE_BG = "#0b0f14";
258539
+ const PANEL_TRANSITION_MS = 280;
258540
+ const THREE_D_TOGGLE_DISABLED = true;
258262
258541
  const FileCityTrailExplorerPanel = ({ context, actions }) => {
258263
258542
  var _a;
258264
258543
  const tree = context.fileTree.data;
@@ -258314,6 +258593,7 @@ const FileCityTrailExplorerPanel = ({ context, actions }) => {
258314
258593
  selectedMarkerId,
258315
258594
  onSelectMarker: setSelectedMarkerId,
258316
258595
  cityData,
258596
+ hasLineCounts: lineCounts != null && Object.keys(lineCounts).length > 0,
258317
258597
  readFile: actions.readFile
258318
258598
  }
258319
258599
  );
@@ -258325,9 +258605,22 @@ const FileCityTrailSequenceLayout = ({
258325
258605
  selectedMarkerId,
258326
258606
  onSelectMarker,
258327
258607
  cityData,
258608
+ hasLineCounts,
258328
258609
  readFile
258329
258610
  }) => {
258330
258611
  var _a, _b;
258612
+ const { theme: theme2 } = useTheme();
258613
+ const [show3D, setShow3D] = React.useState(
258614
+ false
258615
+ );
258616
+ React.useEffect(() => {
258617
+ return;
258618
+ }, [hasLineCounts]);
258619
+ const effectiveShow3D = false;
258620
+ const [showSequenceDrawer, setShowSequenceDrawer] = React.useState(false);
258621
+ const [drawerHeightOverridePct, setDrawerHeightOverridePct] = React.useState(null);
258622
+ const [isResizingDrawer, setIsResizingDrawer] = React.useState(false);
258623
+ const drawerHeightPct = !showSequenceDrawer ? 0 : drawerHeightOverridePct ?? SEQUENCE_DRAWER_HEIGHT_PCT;
258331
258624
  const selectedMarker = React.useMemo(() => {
258332
258625
  if (!selectedMarkerId) return null;
258333
258626
  return trail2.markers.find((m) => m.id === selectedMarkerId) ?? null;
@@ -258347,10 +258640,132 @@ const FileCityTrailSequenceLayout = ({
258347
258640
  },
258348
258641
  [markerBySourcePathForThisRepo, selectedMarkerId, onSelectMarker]
258349
258642
  );
258643
+ const stepperIndex = React.useMemo(() => {
258644
+ if (!selectedMarkerId) return -1;
258645
+ return markersForThisRepo.findIndex((m) => m.id === selectedMarkerId);
258646
+ }, [markersForThisRepo, selectedMarkerId]);
258647
+ const handleStartTrail = React.useCallback(() => {
258648
+ const first = markersForThisRepo[0];
258649
+ if (first) onSelectMarker(first.id);
258650
+ }, [markersForThisRepo, onSelectMarker]);
258651
+ const handleStepPrev = React.useCallback(() => {
258652
+ if (stepperIndex < 0) return;
258653
+ if (stepperIndex === 0) {
258654
+ onSelectMarker(null);
258655
+ return;
258656
+ }
258657
+ const target = markersForThisRepo[stepperIndex - 1];
258658
+ if (target) onSelectMarker(target.id);
258659
+ }, [markersForThisRepo, stepperIndex, onSelectMarker]);
258660
+ const handleStepNext = React.useCallback(() => {
258661
+ if (stepperIndex < 0 || stepperIndex >= markersForThisRepo.length - 1) return;
258662
+ const target = markersForThisRepo[stepperIndex + 1];
258663
+ if (target) onSelectMarker(target.id);
258664
+ }, [markersForThisRepo, stepperIndex, onSelectMarker]);
258665
+ const [hoveredFilePath, setHoveredFilePath] = React.useState(
258666
+ null
258667
+ );
258668
+ const trailFiles = React.useMemo(() => {
258669
+ const counts = /* @__PURE__ */ new Map();
258670
+ const firstMarkerByPath = /* @__PURE__ */ new Map();
258671
+ const order2 = [];
258672
+ for (const m of markersForThisRepo) {
258673
+ if (!m.sourcePath) continue;
258674
+ const existing = counts.get(m.sourcePath) ?? 0;
258675
+ counts.set(m.sourcePath, existing + 1);
258676
+ if (!firstMarkerByPath.has(m.sourcePath)) {
258677
+ firstMarkerByPath.set(m.sourcePath, m.id);
258678
+ order2.push(m.sourcePath);
258679
+ }
258680
+ }
258681
+ return order2.map((sourcePath) => ({
258682
+ sourcePath,
258683
+ markerCount: counts.get(sourcePath) ?? 0,
258684
+ isActive: (selectedMarker == null ? void 0 : selectedMarker.sourcePath) === sourcePath,
258685
+ onClick: () => {
258686
+ const markerId = firstMarkerByPath.get(sourcePath);
258687
+ if (markerId) onSelectMarker(markerId);
258688
+ },
258689
+ onHoverChange: setHoveredFilePath
258690
+ }));
258691
+ }, [markersForThisRepo, selectedMarker, onSelectMarker]);
258692
+ const trailFilesHighlightLayers = React.useMemo(() => {
258693
+ if (!cityData) return null;
258694
+ const sourcePaths = markersForThisRepo.map((m) => m.sourcePath).filter((p2) => typeof p2 === "string" && p2.length > 0);
258695
+ if (sourcePaths.length === 0) return null;
258696
+ const buildingByPath = new Map(cityData.buildings.map((b) => [b.path, b]));
258697
+ const byColor = /* @__PURE__ */ new Map();
258698
+ const seen = /* @__PURE__ */ new Set();
258699
+ for (const sourcePath of sourcePaths) {
258700
+ if (seen.has(sourcePath)) continue;
258701
+ const building = buildingByPath.get(sourcePath) ?? cityData.buildings.find((b) => b.path.endsWith(`/${sourcePath}`));
258702
+ if (!building) continue;
258703
+ seen.add(sourcePath);
258704
+ const color2 = building.color ?? getFileColor(building.path);
258705
+ if (!color2) continue;
258706
+ const list2 = byColor.get(color2) ?? [];
258707
+ list2.push(building.path);
258708
+ byColor.set(color2, list2);
258709
+ }
258710
+ if (byColor.size === 0) return null;
258711
+ let i = 0;
258712
+ return Array.from(byColor.entries()).map(([color2, paths]) => ({
258713
+ id: `trail-files-${i++}`,
258714
+ name: "Trail files",
258715
+ enabled: true,
258716
+ color: color2,
258717
+ opacity: 1,
258718
+ priority: 50,
258719
+ items: paths.map((path2) => ({
258720
+ path: path2,
258721
+ type: "file",
258722
+ renderStrategy: "fill"
258723
+ }))
258724
+ }));
258725
+ }, [markersForThisRepo, cityData]);
258726
+ const trailFilesActive = trailFilesHighlightLayers !== null && trailFilesHighlightLayers.length > 0;
258727
+ const hoveredFileHighlightLayer = React.useMemo(() => {
258728
+ if (!hoveredFilePath || !cityData) return null;
258729
+ const building = cityData.buildings.find((b) => b.path === hoveredFilePath) ?? cityData.buildings.find(
258730
+ (b) => b.path.endsWith(`/${hoveredFilePath}`)
258731
+ );
258732
+ if (!building) return null;
258733
+ return {
258734
+ id: "trail-files-hover",
258735
+ name: "Trail file hover",
258736
+ enabled: true,
258737
+ color: theme2.colors.accent,
258738
+ opacity: 0.9,
258739
+ borderWidth: 4,
258740
+ priority: 100,
258741
+ items: [
258742
+ {
258743
+ path: building.path,
258744
+ type: "file",
258745
+ renderStrategy: "fill"
258746
+ }
258747
+ ]
258748
+ };
258749
+ }, [hoveredFilePath, cityData, theme2.colors.accent]);
258750
+ const cityHighlightLayers = React.useMemo(() => {
258751
+ const layers = [];
258752
+ if (trailFilesHighlightLayers) layers.push(...trailFilesHighlightLayers);
258753
+ if (hoveredFileHighlightLayer) layers.push(hoveredFileHighlightLayer);
258754
+ return layers.length > 0 ? layers : void 0;
258755
+ }, [trailFilesHighlightLayers, hoveredFileHighlightLayer]);
258350
258756
  const containerRef = React.useRef(null);
258351
258757
  const snippetPaneRef = React.useRef(null);
258352
258758
  const leaderLineRef = React.useRef(null);
258353
258759
  const [markdownOverlayWidth, setMarkdownOverlayWidth] = React.useState(null);
258760
+ const [snippetPaneEl, setSnippetPaneEl] = React.useState(null);
258761
+ const snippetPaneCallbackRef = React.useCallback(
258762
+ (node2) => {
258763
+ snippetPaneRef.current = node2;
258764
+ setSnippetPaneEl(node2);
258765
+ },
258766
+ []
258767
+ );
258768
+ const [snippetPaneWidth, setSnippetPaneWidth] = React.useState(null);
258354
258769
  const [containerSize, setContainerSize] = React.useState(null);
258355
258770
  const selectedBuilding = React.useMemo(() => {
258356
258771
  if (!cityData || !(selectedMarker == null ? void 0 : selectedMarker.sourcePath)) return null;
@@ -258395,14 +258810,25 @@ const FileCityTrailSequenceLayout = ({
258395
258810
  observer.observe(markdownOverlayEl);
258396
258811
  return () => observer.disconnect();
258397
258812
  }, [markdownOverlayEl]);
258813
+ React.useEffect(() => {
258814
+ if (!snippetPaneEl) {
258815
+ setSnippetPaneWidth(null);
258816
+ return;
258817
+ }
258818
+ const update = () => setSnippetPaneWidth(snippetPaneEl.getBoundingClientRect().width);
258819
+ update();
258820
+ const observer = new ResizeObserver(update);
258821
+ observer.observe(snippetPaneEl);
258822
+ return () => observer.disconnect();
258823
+ }, [snippetPaneEl]);
258398
258824
  const overlayMarkdown = ((_a = selectedMarker == null ? void 0 : selectedMarker.description) == null ? void 0 : _a.trim()) ? {
258399
258825
  eyebrow: "Marker",
258400
258826
  title: selectedMarker.label ?? selectedMarker.id,
258401
258827
  markdown: selectedMarker.description,
258402
258828
  slideIdPrefix: `trail-${trail2.id}-marker-${selectedMarker.id}`
258403
258829
  } : ((_b = trail2.summary) == null ? void 0 : _b.trim()) ? {
258404
- eyebrow: "Trail",
258405
- title: trail2.title,
258830
+ eyebrow: null,
258831
+ title: null,
258406
258832
  markdown: trail2.summary,
258407
258833
  slideIdPrefix: `trail-${trail2.id}-summary`
258408
258834
  } : null;
@@ -258415,15 +258841,13 @@ const FileCityTrailSequenceLayout = ({
258415
258841
  const apply = () => {
258416
258842
  if (cancelled) return;
258417
258843
  if (hasOverlayMarkdown && markdownOverlayWidth == null) return;
258844
+ if (showSnippetPane && snippetPaneWidth == null) return;
258418
258845
  const FLOAT_INSET2 = 16;
258419
- const leftInset = hasOverlayMarkdown ? FLOAT_INSET2 + (markdownOverlayWidth ?? 0) : 0;
258420
- const snippetW = Math.min(
258421
- SNIPPET_PANE_WIDTH_PX,
258422
- containerSize.width / 2
258423
- );
258846
+ const leftInset = hasOverlayMarkdown ? markdownOverlayWidth ?? 0 : 0;
258847
+ const snippetW = snippetPaneWidth ?? SNIPPET_PANE_WIDTH_PX;
258424
258848
  const rightInset = showSnippetPane ? snippetW + FLOAT_INSET2 : 0;
258425
258849
  const topInset = FLOAT_INSET2;
258426
- const bottomInset = SEQUENCE_DRAWER_HEIGHT_PCT / 100 * containerSize.height;
258850
+ const bottomInset = drawerHeightPct / 100 * containerSize.height;
258427
258851
  const result = computeCameraFraming({
258428
258852
  canvasW: containerSize.width,
258429
258853
  canvasH: containerSize.height,
@@ -258466,8 +258890,10 @@ const FileCityTrailSequenceLayout = ({
258466
258890
  cityData,
258467
258891
  containerSize,
258468
258892
  showSnippetPane,
258893
+ snippetPaneWidth,
258469
258894
  hasOverlayMarkdown,
258470
- markdownOverlayWidth
258895
+ markdownOverlayWidth,
258896
+ drawerHeightPct
258471
258897
  ]);
258472
258898
  return /* @__PURE__ */ jsxs(
258473
258899
  "div",
@@ -258478,11 +258904,25 @@ const FileCityTrailSequenceLayout = ({
258478
258904
  height: "100%",
258479
258905
  display: "flex",
258480
258906
  flexDirection: "column",
258481
- backgroundColor: "#0b0f14",
258482
- color: "#e5e7eb"
258907
+ backgroundColor: theme2.colors.background,
258908
+ color: theme2.colors.text
258483
258909
  },
258484
258910
  children: [
258485
- /* @__PURE__ */ jsx(TrailHeader, { trail: trail2, markerCount: markersForThisRepo.length }),
258911
+ /* @__PURE__ */ jsx(
258912
+ TrailHeader,
258913
+ {
258914
+ trail: trail2,
258915
+ markerCount: markersForThisRepo.length,
258916
+ show3D: effectiveShow3D,
258917
+ onToggle3D: () => setShow3D((v) => !v),
258918
+ hideToggle: THREE_D_TOGGLE_DISABLED,
258919
+ showSequenceDrawer,
258920
+ onToggleSequenceDrawer: () => {
258921
+ setDrawerHeightOverridePct(null);
258922
+ setShowSequenceDrawer((v) => !v);
258923
+ }
258924
+ }
258925
+ ),
258486
258926
  /* @__PURE__ */ jsxs(
258487
258927
  "div",
258488
258928
  {
@@ -258504,7 +258944,11 @@ const FileCityTrailSequenceLayout = ({
258504
258944
  onBuildingClick: handleBuildingClick,
258505
258945
  onCameraFrame,
258506
258946
  showControls: false,
258507
- backgroundColor: "#0b0f14"
258947
+ backgroundColor: theme2.colors.background,
258948
+ textColor: theme2.colors.textMuted,
258949
+ isGrown: effectiveShow3D,
258950
+ highlightLayers: cityHighlightLayers,
258951
+ defaultBuildingColor: trailFilesActive ? theme2.colors.textTertiary : void 0
258508
258952
  }
258509
258953
  ) : /* @__PURE__ */ jsx(CityLoadingPlaceholder, {}),
258510
258954
  /* @__PURE__ */ jsx(
@@ -258524,8 +258968,17 @@ const FileCityTrailSequenceLayout = ({
258524
258968
  title: overlayMarkdown.title,
258525
258969
  markdown: overlayMarkdown.markdown,
258526
258970
  slideIdPrefix: overlayMarkdown.slideIdPrefix,
258527
- bottomOffset: `${SEQUENCE_DRAWER_HEIGHT_PCT}%`,
258528
- containerRef: setMarkdownOverlayEl
258971
+ bottomOffset: `${drawerHeightPct}%`,
258972
+ disableBottomTransition: isResizingDrawer,
258973
+ containerRef: setMarkdownOverlayEl,
258974
+ nav: markersForThisRepo.length > 0 ? {
258975
+ position: stepperIndex,
258976
+ total: markersForThisRepo.length,
258977
+ onStart: handleStartTrail,
258978
+ onPrev: handleStepPrev,
258979
+ onNext: handleStepNext
258980
+ } : void 0,
258981
+ files: trailFiles
258529
258982
  }
258530
258983
  ) : null,
258531
258984
  /* @__PURE__ */ jsx(
@@ -258534,17 +258987,25 @@ const FileCityTrailSequenceLayout = ({
258534
258987
  trail: trail2,
258535
258988
  view,
258536
258989
  selectedMarkerId,
258537
- onSelectMarker
258990
+ onSelectMarker,
258991
+ visible: showSequenceDrawer,
258992
+ heightPct: drawerHeightPct,
258993
+ containerHeightPx: (containerSize == null ? void 0 : containerSize.height) ?? null,
258994
+ onHeightChange: setDrawerHeightOverridePct,
258995
+ onResizeStart: () => setIsResizingDrawer(true),
258996
+ onResizeEnd: () => setIsResizingDrawer(false)
258538
258997
  }
258539
258998
  ),
258540
258999
  (selectedMarker == null ? void 0 : selectedMarker.snippet) ? /* @__PURE__ */ jsx(
258541
259000
  SnippetSidePane,
258542
259001
  {
258543
- ref: snippetPaneRef,
259002
+ ref: snippetPaneCallbackRef,
258544
259003
  marker: selectedMarker,
258545
259004
  snippet: selectedMarker.snippet,
258546
259005
  readFile,
258547
- onClose: () => onSelectMarker(null)
259006
+ onClose: () => onSelectMarker(null),
259007
+ drawerHeightPct,
259008
+ disableBottomTransition: isResizingDrawer
258548
259009
  }
258549
259010
  ) : null
258550
259011
  ]
@@ -258554,12 +259015,22 @@ const FileCityTrailSequenceLayout = ({
258554
259015
  }
258555
259016
  );
258556
259017
  };
259018
+ const DRAWER_RESIZE_HANDLE_HEIGHT = 6;
259019
+ const DRAWER_MIN_HEIGHT_PCT = 12;
259020
+ const DRAWER_MAX_HEIGHT_PCT = 80;
258557
259021
  const SequenceDrawer = ({
258558
259022
  trail: trail2,
258559
259023
  view,
258560
259024
  selectedMarkerId,
258561
- onSelectMarker
259025
+ onSelectMarker,
259026
+ visible,
259027
+ heightPct,
259028
+ containerHeightPx,
259029
+ onHeightChange,
259030
+ onResizeStart,
259031
+ onResizeEnd
258562
259032
  }) => {
259033
+ const { theme: theme2 } = useTheme();
258563
259034
  const inputs = React.useMemo(
258564
259035
  () => buildSequenceViewInputs(trail2, view),
258565
259036
  [trail2, view]
@@ -258571,94 +259042,322 @@ const SequenceDrawer = ({
258571
259042
  },
258572
259043
  [onSelectMarker, selectedMarkerId]
258573
259044
  );
258574
- return /* @__PURE__ */ jsx(
259045
+ const [isResizing, setIsResizing] = React.useState(false);
259046
+ const dragStateRef = React.useRef(null);
259047
+ const handleResizePointerDown = React.useCallback(
259048
+ (e) => {
259049
+ if (e.button !== 0) return;
259050
+ if (containerHeightPx == null || containerHeightPx <= 0) return;
259051
+ e.preventDefault();
259052
+ dragStateRef.current = {
259053
+ startY: e.clientY,
259054
+ startHeightPct: heightPct,
259055
+ containerHeightPx
259056
+ };
259057
+ setIsResizing(true);
259058
+ onResizeStart();
259059
+ e.currentTarget.setPointerCapture(e.pointerId);
259060
+ },
259061
+ [heightPct, containerHeightPx, onResizeStart]
259062
+ );
259063
+ const handleResizePointerMove = React.useCallback(
259064
+ (e) => {
259065
+ const drag2 = dragStateRef.current;
259066
+ if (!drag2) return;
259067
+ const dy = e.clientY - drag2.startY;
259068
+ const deltaPct = dy / drag2.containerHeightPx * 100;
259069
+ const next2 = Math.min(
259070
+ DRAWER_MAX_HEIGHT_PCT,
259071
+ Math.max(DRAWER_MIN_HEIGHT_PCT, drag2.startHeightPct - deltaPct)
259072
+ );
259073
+ onHeightChange(next2);
259074
+ },
259075
+ [onHeightChange]
259076
+ );
259077
+ const handleResizePointerUp = React.useCallback(
259078
+ (e) => {
259079
+ if (!dragStateRef.current) return;
259080
+ dragStateRef.current = null;
259081
+ setIsResizing(false);
259082
+ onResizeEnd();
259083
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) {
259084
+ e.currentTarget.releasePointerCapture(e.pointerId);
259085
+ }
259086
+ },
259087
+ [onResizeEnd]
259088
+ );
259089
+ const handleResizeDoubleClick = React.useCallback(() => {
259090
+ onHeightChange(null);
259091
+ }, [onHeightChange]);
259092
+ return /* @__PURE__ */ jsxs(
258575
259093
  "div",
258576
259094
  {
259095
+ "aria-hidden": !visible,
258577
259096
  style: {
258578
259097
  position: "absolute",
258579
259098
  left: 0,
258580
259099
  right: 0,
258581
259100
  bottom: 0,
258582
- height: `${SEQUENCE_DRAWER_HEIGHT_PCT}%`,
258583
- backgroundColor: "rgba(11, 15, 20, 0.92)",
258584
- borderTop: "1px solid #1f2937",
258585
- zIndex: 10
259101
+ height: visible ? `${heightPct}%` : 0,
259102
+ opacity: visible ? 1 : 0,
259103
+ pointerEvents: visible ? "auto" : "none",
259104
+ backgroundColor: withAlpha(theme2.colors.background, 92),
259105
+ borderTop: `1px solid ${theme2.colors.border}`,
259106
+ zIndex: 10,
259107
+ overflow: "hidden",
259108
+ transition: isResizing ? `opacity ${PANEL_TRANSITION_MS}ms ease` : `height ${PANEL_TRANSITION_MS}ms ease, opacity ${PANEL_TRANSITION_MS}ms ease`
258586
259109
  },
258587
- children: /* @__PURE__ */ jsx(
258588
- SequenceDiagramRenderer,
258589
- {
258590
- events: inputs.events,
258591
- edges: inputs.edges,
258592
- layoutOptions: inputs.layoutOptions,
258593
- width: "100%",
258594
- height: "100%",
258595
- showControls: false,
258596
- showBackground: false,
258597
- stickyHeaders: true,
258598
- selectedNodeId: selectedMarkerId ?? void 0,
258599
- onNodeClick: handleNodeClick2
258600
- }
258601
- )
259110
+ children: [
259111
+ visible && containerHeightPx != null ? /* @__PURE__ */ jsx(
259112
+ "div",
259113
+ {
259114
+ role: "separator",
259115
+ "aria-orientation": "horizontal",
259116
+ "aria-label": "Resize sequence drawer",
259117
+ onPointerDown: handleResizePointerDown,
259118
+ onPointerMove: handleResizePointerMove,
259119
+ onPointerUp: handleResizePointerUp,
259120
+ onPointerCancel: handleResizePointerUp,
259121
+ onDoubleClick: handleResizeDoubleClick,
259122
+ title: "Drag to resize · double-click to reset",
259123
+ style: {
259124
+ position: "absolute",
259125
+ top: 0,
259126
+ left: 0,
259127
+ right: 0,
259128
+ height: DRAWER_RESIZE_HANDLE_HEIGHT,
259129
+ cursor: "ns-resize",
259130
+ backgroundColor: isResizing ? theme2.colors.accent : "transparent",
259131
+ transition: isResizing ? void 0 : "background-color 120ms ease",
259132
+ touchAction: "none",
259133
+ zIndex: 1
259134
+ },
259135
+ onMouseEnter: (e) => {
259136
+ if (isResizing) return;
259137
+ e.currentTarget.style.backgroundColor = theme2.colors.border;
259138
+ },
259139
+ onMouseLeave: (e) => {
259140
+ if (isResizing) return;
259141
+ e.currentTarget.style.backgroundColor = "transparent";
259142
+ }
259143
+ }
259144
+ ) : null,
259145
+ /* @__PURE__ */ jsx(
259146
+ SequenceDiagramRenderer,
259147
+ {
259148
+ events: inputs.events,
259149
+ edges: inputs.edges,
259150
+ layoutOptions: inputs.layoutOptions,
259151
+ width: "100%",
259152
+ height: "100%",
259153
+ showControls: false,
259154
+ showBackground: false,
259155
+ stickyHeaders: true,
259156
+ selectedNodeId: selectedMarkerId ?? void 0,
259157
+ onNodeClick: handleNodeClick2
259158
+ }
259159
+ )
259160
+ ]
258602
259161
  }
258603
259162
  );
258604
259163
  };
258605
- const SnippetSidePane = React.forwardRef(function SnippetSidePane2({ marker, snippet: snippet2, readFile, onClose }, ref) {
259164
+ const SnippetSidePane = React.forwardRef(function SnippetSidePane2({
259165
+ marker,
259166
+ snippet: snippet2,
259167
+ readFile,
259168
+ onClose,
259169
+ drawerHeightPct,
259170
+ disableBottomTransition
259171
+ }, ref) {
259172
+ const { theme: theme2 } = useTheme();
258606
259173
  if (!marker.sourcePath) {
258607
- return /* @__PURE__ */ jsx(SidePaneFrame, { ref, marker, onClose, children: /* @__PURE__ */ jsx("div", { style: { fontSize: 11, opacity: 0.6, padding: 8 }, children: "Marker has no sourcePath; snippet cannot be rendered." }) });
259174
+ return /* @__PURE__ */ jsx(
259175
+ SidePaneFrame,
259176
+ {
259177
+ ref,
259178
+ marker,
259179
+ onClose,
259180
+ drawerHeightPct,
259181
+ disableBottomTransition,
259182
+ children: /* @__PURE__ */ jsx(
259183
+ "div",
259184
+ {
259185
+ style: {
259186
+ fontSize: theme2.fontSizes[0],
259187
+ color: theme2.colors.textSecondary,
259188
+ padding: 8
259189
+ },
259190
+ children: "Marker has no sourcePath; snippet cannot be rendered."
259191
+ }
259192
+ )
259193
+ }
259194
+ );
258608
259195
  }
258609
259196
  const fileName = marker.sourcePath.split("/").pop() ?? marker.sourcePath;
258610
- return /* @__PURE__ */ jsx(SidePaneFrame, { ref, marker, onClose, children: snippet2.kind === "slice" ? /* @__PURE__ */ jsx(
258611
- TrailSnippetView,
258612
- {
258613
- filePath: marker.sourcePath,
258614
- fileName,
258615
- startLine: snippet2.startLine,
258616
- endLine: snippet2.endLine,
258617
- focusLine: snippet2.focusLine,
258618
- contextLines: snippet2.contextLines,
258619
- readFile,
258620
- background: SNIPPET_PANE_BG
258621
- }
258622
- ) : /* @__PURE__ */ jsx(
258623
- TrailDiffSnippetView,
259197
+ return /* @__PURE__ */ jsx(
259198
+ SidePaneFrame,
258624
259199
  {
258625
- filePath: marker.sourcePath,
258626
- fileName,
258627
- oldContents: snippet2.oldContents,
258628
- newContents: snippet2.newContents,
258629
- readFile,
258630
- startLine: snippet2.startLine,
258631
- endLine: snippet2.endLine,
258632
- focusLine: snippet2.focusLine,
258633
- contextLines: snippet2.contextLines,
258634
- diffStyle: snippet2.diffStyle,
258635
- background: SNIPPET_PANE_BG
259200
+ ref,
259201
+ marker,
259202
+ onClose,
259203
+ drawerHeightPct,
259204
+ disableBottomTransition,
259205
+ children: snippet2.kind === "slice" ? /* @__PURE__ */ jsx(
259206
+ TrailSnippetView,
259207
+ {
259208
+ filePath: marker.sourcePath,
259209
+ fileName,
259210
+ startLine: snippet2.startLine,
259211
+ endLine: snippet2.endLine,
259212
+ focusLine: snippet2.focusLine,
259213
+ contextLines: snippet2.contextLines,
259214
+ readFile,
259215
+ background: theme2.colors.background
259216
+ }
259217
+ ) : /* @__PURE__ */ jsx(
259218
+ TrailDiffSnippetView,
259219
+ {
259220
+ filePath: marker.sourcePath,
259221
+ fileName,
259222
+ oldContents: snippet2.oldContents,
259223
+ newContents: snippet2.newContents,
259224
+ readFile,
259225
+ startLine: snippet2.startLine,
259226
+ endLine: snippet2.endLine,
259227
+ focusLine: snippet2.focusLine,
259228
+ contextLines: snippet2.contextLines,
259229
+ diffStyle: snippet2.diffStyle,
259230
+ background: theme2.colors.background
259231
+ }
259232
+ )
258636
259233
  }
258637
- ) });
259234
+ );
258638
259235
  });
259236
+ const SNIPPET_PANE_MIN_WIDTH_PX = 320;
259237
+ const SNIPPET_PANE_RESIZE_HANDLE_WIDTH = 6;
258639
259238
  const SidePaneFrame = React.forwardRef(
258640
- function SidePaneFrame2({ marker, onClose, children: children2 }, ref) {
259239
+ function SidePaneFrame2({ marker, onClose, children: children2, drawerHeightPct, disableBottomTransition }, ref) {
259240
+ const { theme: theme2 } = useTheme();
259241
+ const [widthPx, setWidthPx] = React.useState(null);
259242
+ const [isResizing, setIsResizing] = React.useState(false);
259243
+ const innerRef = React.useRef(null);
259244
+ const setInnerRef = React.useCallback(
259245
+ (node2) => {
259246
+ innerRef.current = node2;
259247
+ if (typeof ref === "function") {
259248
+ ref(node2);
259249
+ } else if (ref) {
259250
+ ref.current = node2;
259251
+ }
259252
+ },
259253
+ [ref]
259254
+ );
259255
+ const dragStateRef = React.useRef(null);
259256
+ const handleResizePointerDown = React.useCallback(
259257
+ (e) => {
259258
+ if (e.button !== 0) return;
259259
+ const el = innerRef.current;
259260
+ if (!el) return;
259261
+ e.preventDefault();
259262
+ dragStateRef.current = {
259263
+ startX: e.clientX,
259264
+ startWidth: el.getBoundingClientRect().width
259265
+ };
259266
+ setIsResizing(true);
259267
+ e.currentTarget.setPointerCapture(e.pointerId);
259268
+ },
259269
+ []
259270
+ );
259271
+ const handleResizePointerMove = React.useCallback(
259272
+ (e) => {
259273
+ var _a;
259274
+ const drag2 = dragStateRef.current;
259275
+ if (!drag2) return;
259276
+ const parent = (_a = innerRef.current) == null ? void 0 : _a.parentElement;
259277
+ const parentWidth = (parent == null ? void 0 : parent.clientWidth) ?? window.innerWidth;
259278
+ const maxWidth = Math.max(
259279
+ SNIPPET_PANE_MIN_WIDTH_PX,
259280
+ parentWidth - 32
259281
+ );
259282
+ const dx = e.clientX - drag2.startX;
259283
+ const next2 = Math.min(
259284
+ maxWidth,
259285
+ Math.max(SNIPPET_PANE_MIN_WIDTH_PX, drag2.startWidth - dx)
259286
+ );
259287
+ setWidthPx(next2);
259288
+ },
259289
+ []
259290
+ );
259291
+ const handleResizePointerUp = React.useCallback(
259292
+ (e) => {
259293
+ if (!dragStateRef.current) return;
259294
+ dragStateRef.current = null;
259295
+ setIsResizing(false);
259296
+ if (e.currentTarget.hasPointerCapture(e.pointerId)) {
259297
+ e.currentTarget.releasePointerCapture(e.pointerId);
259298
+ }
259299
+ },
259300
+ []
259301
+ );
258641
259302
  return /* @__PURE__ */ jsxs(
258642
259303
  "div",
258643
259304
  {
258644
- ref,
259305
+ ref: setInnerRef,
258645
259306
  style: {
258646
259307
  position: "absolute",
258647
259308
  top: 16,
258648
259309
  right: 16,
258649
- bottom: `calc(${SEQUENCE_DRAWER_HEIGHT_PCT}% + 16px)`,
258650
- width: SNIPPET_PANE_WIDTH_PX,
258651
- maxWidth: "50%",
258652
- backgroundColor: SNIPPET_PANE_BG,
258653
- border: "1px solid #1f2937",
258654
- borderRadius: 12,
258655
- boxShadow: "-2px 0 12px rgba(0,0,0,0.35)",
259310
+ bottom: `calc(${drawerHeightPct}% + 16px)`,
259311
+ width: widthPx ?? SNIPPET_PANE_WIDTH_PX,
259312
+ minWidth: SNIPPET_PANE_MIN_WIDTH_PX,
259313
+ backgroundColor: theme2.colors.background,
259314
+ border: `1px solid ${theme2.colors.border}`,
259315
+ borderRadius: theme2.radii[5],
259316
+ boxShadow: theme2.shadows[3],
258656
259317
  display: "flex",
258657
259318
  flexDirection: "column",
258658
259319
  overflow: "hidden",
258659
- zIndex: 1900
259320
+ zIndex: 1900,
259321
+ // Match the sequence drawer's slide animation so the pane
259322
+ // grows/shrinks with the drawer instead of snapping. Skipped
259323
+ // while the drawer is being live-resized so the bottom edge
259324
+ // tracks the cursor without lag.
259325
+ transition: disableBottomTransition ? void 0 : `bottom ${PANEL_TRANSITION_MS}ms ease`,
259326
+ userSelect: isResizing ? "none" : void 0
258660
259327
  },
258661
259328
  children: [
259329
+ /* @__PURE__ */ jsx(
259330
+ "div",
259331
+ {
259332
+ role: "separator",
259333
+ "aria-orientation": "vertical",
259334
+ "aria-label": "Resize snippet panel",
259335
+ onPointerDown: handleResizePointerDown,
259336
+ onPointerMove: handleResizePointerMove,
259337
+ onPointerUp: handleResizePointerUp,
259338
+ onPointerCancel: handleResizePointerUp,
259339
+ style: {
259340
+ position: "absolute",
259341
+ top: 0,
259342
+ left: 0,
259343
+ bottom: 0,
259344
+ width: SNIPPET_PANE_RESIZE_HANDLE_WIDTH,
259345
+ cursor: "ew-resize",
259346
+ backgroundColor: isResizing ? theme2.colors.accent : "transparent",
259347
+ transition: isResizing ? void 0 : "background-color 120ms ease",
259348
+ touchAction: "none",
259349
+ zIndex: 1
259350
+ },
259351
+ onMouseEnter: (e) => {
259352
+ if (isResizing) return;
259353
+ e.currentTarget.style.backgroundColor = theme2.colors.border;
259354
+ },
259355
+ onMouseLeave: (e) => {
259356
+ if (isResizing) return;
259357
+ e.currentTarget.style.backgroundColor = "transparent";
259358
+ }
259359
+ }
259360
+ ),
258662
259361
  /* @__PURE__ */ jsxs(
258663
259362
  "div",
258664
259363
  {
@@ -258668,19 +259367,30 @@ const SidePaneFrame = React.forwardRef(
258668
259367
  justifyContent: "space-between",
258669
259368
  gap: 8,
258670
259369
  padding: "10px 14px",
258671
- borderBottom: "1px solid #1f2937",
259370
+ borderBottom: `1px solid ${theme2.colors.border}`,
258672
259371
  flexShrink: 0
258673
259372
  },
258674
259373
  children: [
258675
259374
  /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
258676
- /* @__PURE__ */ jsx("div", { style: { fontSize: 12, fontWeight: 600 }, children: marker.label ?? marker.id }),
259375
+ /* @__PURE__ */ jsx(
259376
+ "div",
259377
+ {
259378
+ style: {
259379
+ fontFamily: theme2.fonts.heading,
259380
+ fontSize: theme2.fontSizes[0],
259381
+ fontWeight: theme2.fontWeights.semibold,
259382
+ color: theme2.colors.text
259383
+ },
259384
+ children: marker.label ?? marker.id
259385
+ }
259386
+ ),
258677
259387
  marker.sourcePath ? /* @__PURE__ */ jsx(
258678
259388
  "div",
258679
259389
  {
258680
259390
  style: {
258681
- fontSize: 11,
258682
- opacity: 0.6,
258683
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
259391
+ fontSize: theme2.fontSizes[0],
259392
+ color: theme2.colors.textSecondary,
259393
+ fontFamily: theme2.fonts.monospace,
258684
259394
  marginTop: 2,
258685
259395
  wordBreak: "break-all"
258686
259396
  },
@@ -258694,12 +259404,13 @@ const SidePaneFrame = React.forwardRef(
258694
259404
  onClick: onClose,
258695
259405
  style: {
258696
259406
  background: "transparent",
258697
- color: "#e5e7eb",
258698
- border: "1px solid #374151",
258699
- borderRadius: 4,
259407
+ color: theme2.colors.text,
259408
+ border: `1px solid ${theme2.colors.muted}`,
259409
+ borderRadius: theme2.radii[2],
258700
259410
  padding: "2px 8px",
258701
259411
  cursor: "pointer",
258702
- fontSize: 11,
259412
+ fontSize: theme2.fontSizes[0],
259413
+ fontFamily: theme2.fonts.body,
258703
259414
  flexShrink: 0
258704
259415
  },
258705
259416
  children: "Close"
@@ -258714,27 +259425,109 @@ const SidePaneFrame = React.forwardRef(
258714
259425
  );
258715
259426
  }
258716
259427
  );
258717
- const TrailHeader = ({ trail: trail2, markerCount }) => {
259428
+ const TrailHeader = ({
259429
+ trail: trail2,
259430
+ markerCount,
259431
+ show3D,
259432
+ onToggle3D,
259433
+ hideToggle = false,
259434
+ showSequenceDrawer,
259435
+ onToggleSequenceDrawer
259436
+ }) => {
259437
+ const { theme: theme2 } = useTheme();
259438
+ const toggleButtonStyle = (active) => ({
259439
+ flexShrink: 0,
259440
+ fontFamily: theme2.fonts.body,
259441
+ fontSize: theme2.fontSizes[0],
259442
+ fontWeight: theme2.fontWeights.medium,
259443
+ color: active ? theme2.colors.background : theme2.colors.text,
259444
+ background: active ? theme2.colors.accent : "transparent",
259445
+ border: `1px solid ${active ? theme2.colors.accent : theme2.colors.muted}`,
259446
+ borderRadius: theme2.radii[3],
259447
+ padding: "4px 10px",
259448
+ cursor: "pointer",
259449
+ lineHeight: 1.2
259450
+ });
258718
259451
  return /* @__PURE__ */ jsxs(
258719
259452
  "div",
258720
259453
  {
258721
259454
  style: {
258722
259455
  height: HEADER_HEIGHT_PX,
258723
259456
  padding: "8px 16px",
258724
- borderBottom: "1px solid #1f2937",
259457
+ borderBottom: `1px solid ${theme2.colors.border}`,
258725
259458
  display: "flex",
258726
- flexDirection: "column",
258727
- justifyContent: "center",
258728
- gap: 2,
259459
+ flexDirection: "row",
259460
+ alignItems: "center",
259461
+ justifyContent: "space-between",
259462
+ gap: 12,
258729
259463
  flexShrink: 0
258730
259464
  },
258731
259465
  children: [
258732
- /* @__PURE__ */ jsx("div", { style: { fontSize: 14, fontWeight: 600 }, children: trail2.title }),
258733
- /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, opacity: 0.6 }, children: [
258734
- markerCount,
258735
- " marker",
258736
- markerCount === 1 ? "" : "s",
258737
- " in this repo"
259466
+ /* @__PURE__ */ jsxs(
259467
+ "div",
259468
+ {
259469
+ style: {
259470
+ display: "flex",
259471
+ flexDirection: "column",
259472
+ justifyContent: "center",
259473
+ gap: 2,
259474
+ minWidth: 0
259475
+ },
259476
+ children: [
259477
+ /* @__PURE__ */ jsx(
259478
+ "div",
259479
+ {
259480
+ style: {
259481
+ fontFamily: theme2.fonts.heading,
259482
+ fontSize: theme2.fontSizes[1],
259483
+ fontWeight: theme2.fontWeights.semibold,
259484
+ color: theme2.colors.text
259485
+ },
259486
+ children: trail2.title
259487
+ }
259488
+ ),
259489
+ /* @__PURE__ */ jsxs(
259490
+ "div",
259491
+ {
259492
+ style: {
259493
+ fontFamily: theme2.fonts.body,
259494
+ fontSize: theme2.fontSizes[0],
259495
+ color: theme2.colors.textSecondary
259496
+ },
259497
+ children: [
259498
+ markerCount,
259499
+ " marker",
259500
+ markerCount === 1 ? "" : "s",
259501
+ " in this repo"
259502
+ ]
259503
+ }
259504
+ )
259505
+ ]
259506
+ }
259507
+ ),
259508
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
259509
+ /* @__PURE__ */ jsx(
259510
+ "button",
259511
+ {
259512
+ type: "button",
259513
+ onClick: onToggleSequenceDrawer,
259514
+ "aria-pressed": showSequenceDrawer,
259515
+ title: showSequenceDrawer ? "Hide sequence diagram" : "Show sequence diagram",
259516
+ style: toggleButtonStyle(showSequenceDrawer),
259517
+ children: "Diagram"
259518
+ }
259519
+ ),
259520
+ hideToggle ? null : /* @__PURE__ */ jsx(
259521
+ "button",
259522
+ {
259523
+ type: "button",
259524
+ onClick: onToggle3D,
259525
+ "aria-pressed": show3D,
259526
+ title: show3D ? "Switch to flat (2D) view" : "Switch to 3D view",
259527
+ style: toggleButtonStyle(show3D),
259528
+ children: "3D"
259529
+ }
259530
+ )
258738
259531
  ] })
258739
259532
  ]
258740
259533
  }
@@ -258744,6 +259537,7 @@ const FileCityTrailExplorerEmptyState = ({
258744
259537
  message,
258745
259538
  cityData
258746
259539
  }) => {
259540
+ const { theme: theme2 } = useTheme();
258747
259541
  return /* @__PURE__ */ jsxs(
258748
259542
  "div",
258749
259543
  {
@@ -258751,8 +259545,8 @@ const FileCityTrailExplorerEmptyState = ({
258751
259545
  position: "relative",
258752
259546
  width: "100%",
258753
259547
  height: "100%",
258754
- backgroundColor: "#0b0f14",
258755
- color: "#e5e7eb"
259548
+ backgroundColor: theme2.colors.background,
259549
+ color: theme2.colors.text
258756
259550
  },
258757
259551
  children: [
258758
259552
  cityData ? /* @__PURE__ */ jsx(
@@ -258762,7 +259556,8 @@ const FileCityTrailExplorerEmptyState = ({
258762
259556
  width: "100%",
258763
259557
  height: "100%",
258764
259558
  showControls: false,
258765
- backgroundColor: "#0b0f14"
259559
+ backgroundColor: theme2.colors.background,
259560
+ textColor: theme2.colors.textMuted
258766
259561
  }
258767
259562
  ) : null,
258768
259563
  /* @__PURE__ */ jsx(
@@ -258775,9 +259570,10 @@ const FileCityTrailExplorerEmptyState = ({
258775
259570
  alignItems: "center",
258776
259571
  justifyContent: "center",
258777
259572
  padding: 16,
258778
- backgroundColor: cityData ? "rgba(11, 15, 20, 0.6)" : void 0,
258779
- opacity: 0.85,
258780
- fontSize: 13
259573
+ backgroundColor: cityData ? withAlpha(theme2.colors.background, 60) : void 0,
259574
+ color: theme2.colors.textSecondary,
259575
+ fontFamily: theme2.fonts.body,
259576
+ fontSize: theme2.fontSizes[1]
258781
259577
  },
258782
259578
  children: message
258783
259579
  }
@@ -258786,21 +259582,25 @@ const FileCityTrailExplorerEmptyState = ({
258786
259582
  }
258787
259583
  );
258788
259584
  };
258789
- const CityLoadingPlaceholder = () => /* @__PURE__ */ jsx(
258790
- "div",
258791
- {
258792
- style: {
258793
- width: "100%",
258794
- height: "100%",
258795
- display: "flex",
258796
- alignItems: "center",
258797
- justifyContent: "center",
258798
- opacity: 0.5,
258799
- fontSize: 12
258800
- },
258801
- children: "Building the city…"
258802
- }
258803
- );
259585
+ const CityLoadingPlaceholder = () => {
259586
+ const { theme: theme2 } = useTheme();
259587
+ return /* @__PURE__ */ jsx(
259588
+ "div",
259589
+ {
259590
+ style: {
259591
+ width: "100%",
259592
+ height: "100%",
259593
+ display: "flex",
259594
+ alignItems: "center",
259595
+ justifyContent: "center",
259596
+ color: theme2.colors.textMuted,
259597
+ fontFamily: theme2.fonts.body,
259598
+ fontSize: theme2.fontSizes[0]
259599
+ },
259600
+ children: "Building the city…"
259601
+ }
259602
+ );
259603
+ };
258804
259604
  function filterMarkersForRepo(trail2, repository) {
258805
259605
  const repos = trail2.repos ?? [];
258806
259606
  if (repos.length <= 1 || !repository) {