@industry-theme/file-city-panel 0.5.62 → 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 = ({
@@ -257802,7 +257801,9 @@ const TrailMarkdownOverlay = ({
257802
257801
  slideIdPrefix,
257803
257802
  bottomOffset,
257804
257803
  containerRef: externalContainerRef,
257805
- nav
257804
+ disableBottomTransition,
257805
+ nav,
257806
+ files
257806
257807
  }) => {
257807
257808
  const { theme: theme2 } = useTheme();
257808
257809
  const body = markdown2.trim();
@@ -257845,7 +257846,7 @@ const TrailMarkdownOverlay = ({
257845
257846
  if (!drag2) return;
257846
257847
  const parent = (_a = containerRef.current) == null ? void 0 : _a.parentElement;
257847
257848
  const parentWidth = (parent == null ? void 0 : parent.clientWidth) ?? window.innerWidth;
257848
- const maxWidth = Math.max(MIN_WIDTH_PX, parentWidth - FLOAT_INSET * 2);
257849
+ const maxWidth = Math.max(MIN_WIDTH_PX, parentWidth - FLOAT_INSET);
257849
257850
  const dx = e.clientX - drag2.startX;
257850
257851
  const next2 = Math.min(maxWidth, Math.max(MIN_WIDTH_PX, drag2.startWidth + dx));
257851
257852
  setWidthPx(next2);
@@ -257871,16 +257872,25 @@ const TrailMarkdownOverlay = ({
257871
257872
  onAnimationEnd: () => setHasEntered(true),
257872
257873
  style: {
257873
257874
  position: "absolute",
257874
- top: TOP_INSET,
257875
- left: FLOAT_INSET,
257876
- bottom: `calc(${bottomOffsetCss} + ${FLOAT_INSET}px)`,
257877
- 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}%`,
257878
257887
  minWidth: MIN_WIDTH_PX,
257879
257888
  backgroundColor: theme2.colors.background,
257880
- border: `1px solid ${theme2.colors.border}`,
257881
- 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}`,
257882
257893
  overflow: "hidden",
257883
- boxShadow: "0 12px 32px rgba(0, 0, 0, 0.28)",
257884
257894
  display: "flex",
257885
257895
  flexDirection: "column",
257886
257896
  zIndex: 1900,
@@ -257894,7 +257904,7 @@ const TrailMarkdownOverlay = ({
257894
257904
  to { transform: translateX(0); }
257895
257905
  }
257896
257906
  ` }),
257897
- /* @__PURE__ */ jsxs(
257907
+ eyebrow || title ? /* @__PURE__ */ jsxs(
257898
257908
  "div",
257899
257909
  {
257900
257910
  style: {
@@ -257908,7 +257918,7 @@ const TrailMarkdownOverlay = ({
257908
257918
  minWidth: 0
257909
257919
  },
257910
257920
  children: [
257911
- /* @__PURE__ */ jsx(
257921
+ eyebrow ? /* @__PURE__ */ jsx(
257912
257922
  "span",
257913
257923
  {
257914
257924
  style: {
@@ -257919,8 +257929,8 @@ const TrailMarkdownOverlay = ({
257919
257929
  },
257920
257930
  children: eyebrow
257921
257931
  }
257922
- ),
257923
- /* @__PURE__ */ jsx(
257932
+ ) : null,
257933
+ title ? /* @__PURE__ */ jsx(
257924
257934
  "span",
257925
257935
  {
257926
257936
  style: {
@@ -257933,22 +257943,34 @@ const TrailMarkdownOverlay = ({
257933
257943
  title,
257934
257944
  children: title
257935
257945
  }
257936
- )
257946
+ ) : null
257937
257947
  ]
257938
257948
  }
257939
- ),
257940
- /* @__PURE__ */ jsx("div", { style: { flex: 1, minHeight: 0, position: "relative" }, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ jsx(
257941
- IndustryMarkdownSlide,
257949
+ ) : null,
257950
+ /* @__PURE__ */ jsx(
257951
+ "div",
257942
257952
  {
257943
- content: body,
257944
- slideIdPrefix,
257945
- slideIndex: 0,
257946
- isVisible: true,
257947
- theme: theme2,
257948
- transparentBackground: true,
257949
- 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
+ ) })
257950
257971
  }
257951
- ) }) }),
257972
+ ),
257973
+ files && files.length > 0 ? /* @__PURE__ */ jsx(TrailFilesList, { files }) : null,
257952
257974
  nav ? /* @__PURE__ */ jsx(TrailMarkdownOverlayFooter, { nav }) : null,
257953
257975
  /* @__PURE__ */ jsx(
257954
257976
  "div",
@@ -257985,13 +258007,156 @@ const TrailMarkdownOverlay = ({
257985
258007
  }
257986
258008
  );
257987
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
+ };
257988
258149
  const TrailMarkdownOverlayFooter = ({
257989
258150
  nav
257990
258151
  }) => {
257991
258152
  const { theme: theme2 } = useTheme();
257992
258153
  const idle = nav.position < 0;
257993
- const canPrev = !idle && nav.position > 0;
258154
+ const canPrev = !idle;
257994
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;
257995
258160
  const stepButtonStyle = (enabled) => ({
257996
258161
  background: "transparent",
257997
258162
  border: `1px solid ${theme2.colors.muted}`,
@@ -258008,11 +258173,14 @@ const TrailMarkdownOverlayFooter = ({
258008
258173
  "div",
258009
258174
  {
258010
258175
  style: {
258011
- padding: "8px 14px",
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",
258012
258180
  borderTop: `1px solid ${theme2.colors.border}`,
258013
258181
  display: "flex",
258014
- alignItems: "center",
258015
- justifyContent: idle ? "center" : "space-between",
258182
+ alignItems: "stretch",
258183
+ justifyContent: idle ? "stretch" : "space-between",
258016
258184
  gap: 8,
258017
258185
  flexShrink: 0
258018
258186
  },
@@ -258021,20 +258189,30 @@ const TrailMarkdownOverlayFooter = ({
258021
258189
  {
258022
258190
  type: "button",
258023
258191
  onClick: nav.onStart,
258024
- disabled: nav.total === 0,
258025
- title: nav.total === 0 ? "No markers in this repo" : "Jump to the first marker",
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),
258026
258202
  style: {
258027
- background: theme2.colors.accent,
258203
+ flex: 1,
258204
+ background: startBackground,
258028
258205
  color: theme2.colors.background,
258029
258206
  border: "none",
258030
- borderRadius: theme2.radii[3],
258031
- padding: "6px 14px",
258207
+ borderRadius: 0,
258208
+ padding: "12px 14px",
258032
258209
  fontFamily: theme2.fonts.body,
258033
- fontSize: theme2.fontSizes[0],
258210
+ fontSize: theme2.fontSizes[1],
258034
258211
  fontWeight: theme2.fontWeights.semibold,
258035
- cursor: nav.total === 0 ? "not-allowed" : "pointer",
258036
- opacity: nav.total === 0 ? 0.5 : 1,
258037
- lineHeight: 1.2
258212
+ cursor: startEnabled ? "pointer" : "not-allowed",
258213
+ opacity: startEnabled ? 1 : 0.5,
258214
+ lineHeight: 1.2,
258215
+ transition: "background 120ms ease"
258038
258216
  },
258039
258217
  children: "Start trail"
258040
258218
  }
@@ -258355,9 +258533,10 @@ const pierreOptionsBase = {
258355
258533
  const pierreStyle = {
258356
258534
  display: "block"
258357
258535
  };
258358
- const SEQUENCE_DRAWER_HEIGHT_PCT = 38;
258536
+ const SEQUENCE_DRAWER_HEIGHT_PCT = 35;
258359
258537
  const SNIPPET_PANE_WIDTH_PX = 460;
258360
258538
  const HEADER_HEIGHT_PX = 48;
258539
+ const PANEL_TRANSITION_MS = 280;
258361
258540
  const THREE_D_TOGGLE_DISABLED = true;
258362
258541
  const FileCityTrailExplorerPanel = ({ context, actions }) => {
258363
258542
  var _a;
@@ -258438,6 +258617,10 @@ const FileCityTrailSequenceLayout = ({
258438
258617
  return;
258439
258618
  }, [hasLineCounts]);
258440
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;
258441
258624
  const selectedMarker = React.useMemo(() => {
258442
258625
  if (!selectedMarkerId) return null;
258443
258626
  return trail2.markers.find((m) => m.id === selectedMarkerId) ?? null;
@@ -258466,7 +258649,11 @@ const FileCityTrailSequenceLayout = ({
258466
258649
  if (first) onSelectMarker(first.id);
258467
258650
  }, [markersForThisRepo, onSelectMarker]);
258468
258651
  const handleStepPrev = React.useCallback(() => {
258469
- if (stepperIndex <= 0) return;
258652
+ if (stepperIndex < 0) return;
258653
+ if (stepperIndex === 0) {
258654
+ onSelectMarker(null);
258655
+ return;
258656
+ }
258470
258657
  const target = markersForThisRepo[stepperIndex - 1];
258471
258658
  if (target) onSelectMarker(target.id);
258472
258659
  }, [markersForThisRepo, stepperIndex, onSelectMarker]);
@@ -258475,6 +258662,33 @@ const FileCityTrailSequenceLayout = ({
258475
258662
  const target = markersForThisRepo[stepperIndex + 1];
258476
258663
  if (target) onSelectMarker(target.id);
258477
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]);
258478
258692
  const trailFilesHighlightLayers = React.useMemo(() => {
258479
258693
  if (!cityData) return null;
258480
258694
  const sourcePaths = markersForThisRepo.map((m) => m.sourcePath).filter((p2) => typeof p2 === "string" && p2.length > 0);
@@ -258510,6 +258724,35 @@ const FileCityTrailSequenceLayout = ({
258510
258724
  }));
258511
258725
  }, [markersForThisRepo, cityData]);
258512
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]);
258513
258756
  const containerRef = React.useRef(null);
258514
258757
  const snippetPaneRef = React.useRef(null);
258515
258758
  const leaderLineRef = React.useRef(null);
@@ -258584,8 +258827,8 @@ const FileCityTrailSequenceLayout = ({
258584
258827
  markdown: selectedMarker.description,
258585
258828
  slideIdPrefix: `trail-${trail2.id}-marker-${selectedMarker.id}`
258586
258829
  } : ((_b = trail2.summary) == null ? void 0 : _b.trim()) ? {
258587
- eyebrow: "Trail",
258588
- title: trail2.title,
258830
+ eyebrow: null,
258831
+ title: null,
258589
258832
  markdown: trail2.summary,
258590
258833
  slideIdPrefix: `trail-${trail2.id}-summary`
258591
258834
  } : null;
@@ -258600,11 +258843,11 @@ const FileCityTrailSequenceLayout = ({
258600
258843
  if (hasOverlayMarkdown && markdownOverlayWidth == null) return;
258601
258844
  if (showSnippetPane && snippetPaneWidth == null) return;
258602
258845
  const FLOAT_INSET2 = 16;
258603
- const leftInset = hasOverlayMarkdown ? FLOAT_INSET2 + (markdownOverlayWidth ?? 0) : 0;
258846
+ const leftInset = hasOverlayMarkdown ? markdownOverlayWidth ?? 0 : 0;
258604
258847
  const snippetW = snippetPaneWidth ?? SNIPPET_PANE_WIDTH_PX;
258605
258848
  const rightInset = showSnippetPane ? snippetW + FLOAT_INSET2 : 0;
258606
258849
  const topInset = FLOAT_INSET2;
258607
- const bottomInset = SEQUENCE_DRAWER_HEIGHT_PCT / 100 * containerSize.height;
258850
+ const bottomInset = drawerHeightPct / 100 * containerSize.height;
258608
258851
  const result = computeCameraFraming({
258609
258852
  canvasW: containerSize.width,
258610
258853
  canvasH: containerSize.height,
@@ -258649,7 +258892,8 @@ const FileCityTrailSequenceLayout = ({
258649
258892
  showSnippetPane,
258650
258893
  snippetPaneWidth,
258651
258894
  hasOverlayMarkdown,
258652
- markdownOverlayWidth
258895
+ markdownOverlayWidth,
258896
+ drawerHeightPct
258653
258897
  ]);
258654
258898
  return /* @__PURE__ */ jsxs(
258655
258899
  "div",
@@ -258671,7 +258915,12 @@ const FileCityTrailSequenceLayout = ({
258671
258915
  markerCount: markersForThisRepo.length,
258672
258916
  show3D: effectiveShow3D,
258673
258917
  onToggle3D: () => setShow3D((v) => !v),
258674
- hideToggle: THREE_D_TOGGLE_DISABLED
258918
+ hideToggle: THREE_D_TOGGLE_DISABLED,
258919
+ showSequenceDrawer,
258920
+ onToggleSequenceDrawer: () => {
258921
+ setDrawerHeightOverridePct(null);
258922
+ setShowSequenceDrawer((v) => !v);
258923
+ }
258675
258924
  }
258676
258925
  ),
258677
258926
  /* @__PURE__ */ jsxs(
@@ -258698,7 +258947,7 @@ const FileCityTrailSequenceLayout = ({
258698
258947
  backgroundColor: theme2.colors.background,
258699
258948
  textColor: theme2.colors.textMuted,
258700
258949
  isGrown: effectiveShow3D,
258701
- highlightLayers: trailFilesHighlightLayers ?? void 0,
258950
+ highlightLayers: cityHighlightLayers,
258702
258951
  defaultBuildingColor: trailFilesActive ? theme2.colors.textTertiary : void 0
258703
258952
  }
258704
258953
  ) : /* @__PURE__ */ jsx(CityLoadingPlaceholder, {}),
@@ -258719,7 +258968,8 @@ const FileCityTrailSequenceLayout = ({
258719
258968
  title: overlayMarkdown.title,
258720
258969
  markdown: overlayMarkdown.markdown,
258721
258970
  slideIdPrefix: overlayMarkdown.slideIdPrefix,
258722
- bottomOffset: `${SEQUENCE_DRAWER_HEIGHT_PCT}%`,
258971
+ bottomOffset: `${drawerHeightPct}%`,
258972
+ disableBottomTransition: isResizingDrawer,
258723
258973
  containerRef: setMarkdownOverlayEl,
258724
258974
  nav: markersForThisRepo.length > 0 ? {
258725
258975
  position: stepperIndex,
@@ -258727,7 +258977,8 @@ const FileCityTrailSequenceLayout = ({
258727
258977
  onStart: handleStartTrail,
258728
258978
  onPrev: handleStepPrev,
258729
258979
  onNext: handleStepNext
258730
- } : void 0
258980
+ } : void 0,
258981
+ files: trailFiles
258731
258982
  }
258732
258983
  ) : null,
258733
258984
  /* @__PURE__ */ jsx(
@@ -258736,7 +258987,13 @@ const FileCityTrailSequenceLayout = ({
258736
258987
  trail: trail2,
258737
258988
  view,
258738
258989
  selectedMarkerId,
258739
- 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)
258740
258997
  }
258741
258998
  ),
258742
258999
  (selectedMarker == null ? void 0 : selectedMarker.snippet) ? /* @__PURE__ */ jsx(
@@ -258746,7 +259003,9 @@ const FileCityTrailSequenceLayout = ({
258746
259003
  marker: selectedMarker,
258747
259004
  snippet: selectedMarker.snippet,
258748
259005
  readFile,
258749
- onClose: () => onSelectMarker(null)
259006
+ onClose: () => onSelectMarker(null),
259007
+ drawerHeightPct,
259008
+ disableBottomTransition: isResizingDrawer
258750
259009
  }
258751
259010
  ) : null
258752
259011
  ]
@@ -258756,11 +259015,20 @@ const FileCityTrailSequenceLayout = ({
258756
259015
  }
258757
259016
  );
258758
259017
  };
259018
+ const DRAWER_RESIZE_HANDLE_HEIGHT = 6;
259019
+ const DRAWER_MIN_HEIGHT_PCT = 12;
259020
+ const DRAWER_MAX_HEIGHT_PCT = 80;
258759
259021
  const SequenceDrawer = ({
258760
259022
  trail: trail2,
258761
259023
  view,
258762
259024
  selectedMarkerId,
258763
- onSelectMarker
259025
+ onSelectMarker,
259026
+ visible,
259027
+ heightPct,
259028
+ containerHeightPx,
259029
+ onHeightChange,
259030
+ onResizeStart,
259031
+ onResizeEnd
258764
259032
  }) => {
258765
259033
  const { theme: theme2 } = useTheme();
258766
259034
  const inputs = React.useMemo(
@@ -258774,86 +259042,201 @@ const SequenceDrawer = ({
258774
259042
  },
258775
259043
  [onSelectMarker, selectedMarkerId]
258776
259044
  );
258777
- 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(
258778
259093
  "div",
258779
259094
  {
259095
+ "aria-hidden": !visible,
258780
259096
  style: {
258781
259097
  position: "absolute",
258782
259098
  left: 0,
258783
259099
  right: 0,
258784
259100
  bottom: 0,
258785
- height: `${SEQUENCE_DRAWER_HEIGHT_PCT}%`,
259101
+ height: visible ? `${heightPct}%` : 0,
259102
+ opacity: visible ? 1 : 0,
259103
+ pointerEvents: visible ? "auto" : "none",
258786
259104
  backgroundColor: withAlpha(theme2.colors.background, 92),
258787
259105
  borderTop: `1px solid ${theme2.colors.border}`,
258788
- zIndex: 10
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`
258789
259109
  },
258790
- children: /* @__PURE__ */ jsx(
258791
- SequenceDiagramRenderer,
258792
- {
258793
- events: inputs.events,
258794
- edges: inputs.edges,
258795
- layoutOptions: inputs.layoutOptions,
258796
- width: "100%",
258797
- height: "100%",
258798
- showControls: false,
258799
- showBackground: false,
258800
- stickyHeaders: true,
258801
- selectedNodeId: selectedMarkerId ?? void 0,
258802
- onNodeClick: handleNodeClick2
258803
- }
258804
- )
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
+ ]
258805
259161
  }
258806
259162
  );
258807
259163
  };
258808
- 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) {
258809
259172
  const { theme: theme2 } = useTheme();
258810
259173
  if (!marker.sourcePath) {
258811
- return /* @__PURE__ */ jsx(SidePaneFrame, { ref, marker, onClose, children: /* @__PURE__ */ jsx(
258812
- "div",
259174
+ return /* @__PURE__ */ jsx(
259175
+ SidePaneFrame,
258813
259176
  {
258814
- style: {
258815
- fontSize: theme2.fontSizes[0],
258816
- color: theme2.colors.textSecondary,
258817
- padding: 8
258818
- },
258819
- children: "Marker has no sourcePath; snippet cannot be rendered."
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
+ )
258820
259193
  }
258821
- ) });
259194
+ );
258822
259195
  }
258823
259196
  const fileName = marker.sourcePath.split("/").pop() ?? marker.sourcePath;
258824
- return /* @__PURE__ */ jsx(SidePaneFrame, { ref, marker, onClose, children: snippet2.kind === "slice" ? /* @__PURE__ */ jsx(
258825
- TrailSnippetView,
258826
- {
258827
- filePath: marker.sourcePath,
258828
- fileName,
258829
- startLine: snippet2.startLine,
258830
- endLine: snippet2.endLine,
258831
- focusLine: snippet2.focusLine,
258832
- contextLines: snippet2.contextLines,
258833
- readFile,
258834
- background: theme2.colors.background
258835
- }
258836
- ) : /* @__PURE__ */ jsx(
258837
- TrailDiffSnippetView,
259197
+ return /* @__PURE__ */ jsx(
259198
+ SidePaneFrame,
258838
259199
  {
258839
- filePath: marker.sourcePath,
258840
- fileName,
258841
- oldContents: snippet2.oldContents,
258842
- newContents: snippet2.newContents,
258843
- readFile,
258844
- startLine: snippet2.startLine,
258845
- endLine: snippet2.endLine,
258846
- focusLine: snippet2.focusLine,
258847
- contextLines: snippet2.contextLines,
258848
- diffStyle: snippet2.diffStyle,
258849
- background: theme2.colors.background
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
+ )
258850
259233
  }
258851
- ) });
259234
+ );
258852
259235
  });
258853
259236
  const SNIPPET_PANE_MIN_WIDTH_PX = 320;
258854
259237
  const SNIPPET_PANE_RESIZE_HANDLE_WIDTH = 6;
258855
259238
  const SidePaneFrame = React.forwardRef(
258856
- function SidePaneFrame2({ marker, onClose, children: children2 }, ref) {
259239
+ function SidePaneFrame2({ marker, onClose, children: children2, drawerHeightPct, disableBottomTransition }, ref) {
258857
259240
  const { theme: theme2 } = useTheme();
258858
259241
  const [widthPx, setWidthPx] = React.useState(null);
258859
259242
  const [isResizing, setIsResizing] = React.useState(false);
@@ -258924,7 +259307,7 @@ const SidePaneFrame = React.forwardRef(
258924
259307
  position: "absolute",
258925
259308
  top: 16,
258926
259309
  right: 16,
258927
- bottom: `calc(${SEQUENCE_DRAWER_HEIGHT_PCT}% + 16px)`,
259310
+ bottom: `calc(${drawerHeightPct}% + 16px)`,
258928
259311
  width: widthPx ?? SNIPPET_PANE_WIDTH_PX,
258929
259312
  minWidth: SNIPPET_PANE_MIN_WIDTH_PX,
258930
259313
  backgroundColor: theme2.colors.background,
@@ -258935,6 +259318,11 @@ const SidePaneFrame = React.forwardRef(
258935
259318
  flexDirection: "column",
258936
259319
  overflow: "hidden",
258937
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`,
258938
259326
  userSelect: isResizing ? "none" : void 0
258939
259327
  },
258940
259328
  children: [
@@ -259042,9 +259430,24 @@ const TrailHeader = ({
259042
259430
  markerCount,
259043
259431
  show3D,
259044
259432
  onToggle3D,
259045
- hideToggle = false
259433
+ hideToggle = false,
259434
+ showSequenceDrawer,
259435
+ onToggleSequenceDrawer
259046
259436
  }) => {
259047
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
+ });
259048
259451
  return /* @__PURE__ */ jsxs(
259049
259452
  "div",
259050
259453
  {
@@ -259102,29 +259505,30 @@ const TrailHeader = ({
259102
259505
  ]
259103
259506
  }
259104
259507
  ),
259105
- hideToggle ? null : /* @__PURE__ */ jsx(
259106
- "button",
259107
- {
259108
- type: "button",
259109
- onClick: onToggle3D,
259110
- "aria-pressed": show3D,
259111
- title: show3D ? "Switch to flat (2D) view" : "Switch to 3D view",
259112
- style: {
259113
- flexShrink: 0,
259114
- fontFamily: theme2.fonts.body,
259115
- fontSize: theme2.fontSizes[0],
259116
- fontWeight: theme2.fontWeights.medium,
259117
- color: show3D ? theme2.colors.background : theme2.colors.text,
259118
- background: show3D ? theme2.colors.accent : "transparent",
259119
- border: `1px solid ${show3D ? theme2.colors.accent : theme2.colors.muted}`,
259120
- borderRadius: theme2.radii[3],
259121
- padding: "4px 10px",
259122
- cursor: "pointer",
259123
- lineHeight: 1.2
259124
- },
259125
- children: "3D"
259126
- }
259127
- )
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
+ )
259531
+ ] })
259128
259532
  ]
259129
259533
  }
259130
259534
  );