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

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.
@@ -1 +1 @@
1
- {"version":3,"file":"TrailFilePath.d.ts","sourceRoot":"","sources":["../../../../src/panels/FileCityTrailExplorerPanel/overlays/TrailFilePath.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACrD;;;;OAIG;IACH,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;IACnC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;IACtD;;;;OAIG;IACH,UAAU,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5C;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAcD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,gGA4dxB,CAAC"}
1
+ {"version":3,"file":"TrailFilePath.d.ts","sourceRoot":"","sources":["../../../../src/panels/FileCityTrailExplorerPanel/overlays/TrailFilePath.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,mBAAmB;IAClC,qEAAqE;IACrE,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACrD;;;;OAIG;IACH,SAAS,EAAE,SAAS,YAAY,EAAE,CAAC;IACnC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC;IACtD;;;;OAIG;IACH,UAAU,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5C;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAmCD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,gGAksBxB,CAAC"}
@@ -257807,6 +257807,10 @@ const TrailLeaderLine = React.forwardRef(function TrailLeaderLine2({ containerRe
257807
257807
  ] });
257808
257808
  });
257809
257809
  const CALLOUT_FADE_IN_MS = 350;
257810
+ const TITLE_TYPEWRITER_CPS = 28;
257811
+ const TITLE_TYPEWRITER_HOLD_MS = 500;
257812
+ const TITLE_INTER_HOLD_MS = 220;
257813
+ const TITLE_CARET = "▍";
257810
257814
  const fileBasename = (path2) => {
257811
257815
  const i = path2.lastIndexOf("/");
257812
257816
  return i >= 0 ? path2.slice(i + 1) : path2;
@@ -257823,7 +257827,13 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
257823
257827
  animate: animate2 = true,
257824
257828
  currentStepIndex = null,
257825
257829
  stepMs = 1100,
257826
- dwellMs = 1400,
257830
+ // Dwell budget covers: fade-in (350ms) + initial hold (500ms) +
257831
+ // filename typewriter (~ name.length * 36ms at 28cps) + inter-hold
257832
+ // (220ms) + title typewriter (~ title.length * 36ms) + a digest
257833
+ // beat after typing finishes so the user can read the full
257834
+ // callout before the dot walks on. 3500ms leaves ~1.5s of digest
257835
+ // for typical 10-char filenames + 15-char titles.
257836
+ dwellMs = 3500,
257827
257837
  endHoldMs = 2200
257828
257838
  }, ref) {
257829
257839
  const { theme: theme2 } = useTheme();
@@ -257845,6 +257855,8 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
257845
257855
  const animStartTsRef = React.useRef(
257846
257856
  typeof performance !== "undefined" ? performance.now() : 0
257847
257857
  );
257858
+ const hoverFrozenElapsedRef = React.useRef(null);
257859
+ const periodRef = React.useRef(0);
257848
257860
  const buildingsRef = React.useRef(buildings);
257849
257861
  const markerTitlesRef = React.useRef(markerTitles);
257850
257862
  const cityCenterRef = React.useRef(cityCenter);
@@ -257874,7 +257886,7 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
257874
257886
  }, [animate2, buildings, dwellMs, stepMs, endHoldMs]);
257875
257887
  const onCameraFrame = React.useCallback(
257876
257888
  (camera, size) => {
257877
- var _a;
257889
+ var _a, _b;
257878
257890
  const targets = buildingsRef.current;
257879
257891
  const center = cityCenterRef.current;
257880
257892
  const container = containerRef.current;
@@ -257906,6 +257918,7 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
257906
257918
  const N = points.length;
257907
257919
  const slotMs = dwellMs + stepMs;
257908
257920
  const period = N >= 2 ? (N - 1) * slotMs + dwellMs + endHoldMs : 0;
257921
+ periodRef.current = period;
257909
257922
  const rawFrozen = currentStepIndexRef.current;
257910
257923
  const frozenIndex = rawFrozen != null && rawFrozen >= 0 && rawFrozen < N ? rawFrozen : null;
257911
257924
  let revealedCount = animate2 ? 0 : N;
@@ -257916,7 +257929,7 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
257916
257929
  revealedCount = frozenIndex + 1;
257917
257930
  dwellElapsed = CALLOUT_FADE_IN_MS;
257918
257931
  } else if (animate2 && period > 0) {
257919
- const elapsed = ((typeof performance !== "undefined" ? performance.now() : Date.now()) - animStartTsRef.current) % period;
257932
+ const elapsed = hoverFrozenElapsedRef.current !== null ? hoverFrozenElapsedRef.current : ((typeof performance !== "undefined" ? performance.now() : Date.now()) - animStartTsRef.current) % period;
257920
257933
  const traversalsEnd = (N - 1) * slotMs;
257921
257934
  if (elapsed >= traversalsEnd) {
257922
257935
  revealedCount = N;
@@ -258030,43 +258043,56 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
258030
258043
  }
258031
258044
  }
258032
258045
  const SVG_NS = "http://www.w3.org/2000/svg";
258033
- const paintCallout = (textEl, bgEl, point2, path2, title, opacity) => {
258046
+ const paintCallout = (textEl, bgEl, point2, path2, title, opacity, nameRevealChars2, titleRevealChars2) => {
258034
258047
  if (!textEl || !bgEl) return;
258035
258048
  if (!point2 || !point2.visible || opacity <= 1e-3 || !path2) {
258036
258049
  textEl.setAttribute("opacity", "0");
258037
258050
  bgEl.setAttribute("opacity", "0");
258051
+ bgEl.setAttribute("pointer-events", "none");
258052
+ textEl.setAttribute("pointer-events", "none");
258038
258053
  return;
258039
258054
  }
258055
+ const interactive = opacity >= 0.6;
258056
+ bgEl.setAttribute(
258057
+ "pointer-events",
258058
+ interactive ? "auto" : "none"
258059
+ );
258060
+ textEl.setAttribute(
258061
+ "pointer-events",
258062
+ interactive ? "auto" : "none"
258063
+ );
258040
258064
  while (textEl.children.length < 3) {
258041
258065
  textEl.appendChild(document.createElementNS(SVG_NS, "tspan"));
258042
258066
  }
258043
258067
  while (textEl.children.length > 3) {
258044
258068
  textEl.removeChild(textEl.lastChild);
258045
258069
  }
258046
- const titleTspan = textEl.children[0];
258070
+ const pathTspan = textEl.children[0];
258047
258071
  const nameTspan = textEl.children[1];
258048
- const pathTspan = textEl.children[2];
258072
+ const titleTspan = textEl.children[2];
258049
258073
  const name2 = fileBasename(path2);
258050
258074
  const dir = fileDirname(path2);
258051
258075
  const trimmedTitle = (title == null ? void 0 : title.trim()) ?? "";
258052
- const lineCount = (trimmedTitle ? 1 : 0) + 1 + (dir ? 1 : 0);
258076
+ const displayTitle = trimmedTitle && titleRevealChars2 !== null ? titleRevealChars2 < 0 ? "" : titleRevealChars2 >= trimmedTitle.length ? trimmedTitle : trimmedTitle.slice(0, titleRevealChars2) + TITLE_CARET : trimmedTitle;
258077
+ const displayName = nameRevealChars2 !== null ? nameRevealChars2 < 0 ? "" : nameRevealChars2 >= name2.length ? name2 : name2.slice(0, nameRevealChars2) + TITLE_CARET : name2;
258078
+ const lineCount = (dir ? 1 : 0) + 1 + (trimmedTitle ? 1 : 0);
258053
258079
  const BOTTOM_LINE_OFFSET = 24;
258054
258080
  const topLineY = point2.y - BOTTOM_LINE_OFFSET - (lineCount - 1) * calloutLineDyPx;
258055
258081
  textEl.setAttribute("text-anchor", "middle");
258056
258082
  textEl.setAttribute("font-family", fontFamily);
258057
258083
  textEl.setAttribute("opacity", String(opacity));
258058
- titleTspan.textContent = trimmedTitle;
258059
- titleTspan.setAttribute("x", String(point2.x));
258060
- titleTspan.setAttribute("y", String(topLineY));
258061
- titleTspan.setAttribute("fill", filenameColor);
258062
- titleTspan.setAttribute("font-size", calloutFontSize);
258063
- titleTspan.setAttribute("font-weight", calloutFontWeight);
258084
+ pathTspan.textContent = dir;
258085
+ pathTspan.setAttribute("text-anchor", "middle");
258086
+ pathTspan.setAttribute("x", String(point2.x));
258087
+ pathTspan.setAttribute("y", String(topLineY));
258088
+ pathTspan.removeAttribute("dy");
258089
+ pathTspan.setAttribute("fill", color2);
258090
+ pathTspan.setAttribute("font-size", calloutFontSize);
258091
+ pathTspan.setAttribute("font-weight", calloutFontWeight);
258064
258092
  nameTspan.textContent = name2;
258065
- nameTspan.setAttribute("x", String(point2.x));
258066
- nameTspan.setAttribute(
258067
- "dy",
258068
- trimmedTitle ? calloutLineDy : "0"
258069
- );
258093
+ nameTspan.setAttribute("text-anchor", "start");
258094
+ nameTspan.removeAttribute("y");
258095
+ nameTspan.setAttribute("dy", dir ? calloutLineDy : "0");
258070
258096
  nameTspan.setAttribute(
258071
258097
  "fill",
258072
258098
  trimmedTitle ? dirColor : filenameColor
@@ -258076,12 +258102,20 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
258076
258102
  "font-weight",
258077
258103
  trimmedTitle ? calloutBodyFontWeight : calloutFontWeight
258078
258104
  );
258079
- pathTspan.textContent = dir;
258080
- pathTspan.setAttribute("x", String(point2.x));
258081
- pathTspan.setAttribute("dy", dir ? calloutLineDy : "0");
258082
- pathTspan.setAttribute("fill", dirColor);
258083
- pathTspan.setAttribute("font-size", calloutFontSize);
258084
- pathTspan.setAttribute("font-weight", calloutFontWeight);
258105
+ const nameWidth = typeof nameTspan.getComputedTextLength === "function" ? nameTspan.getComputedTextLength() : 0;
258106
+ nameTspan.setAttribute("x", String(point2.x - nameWidth / 2));
258107
+ titleTspan.textContent = trimmedTitle;
258108
+ titleTspan.setAttribute("text-anchor", "start");
258109
+ titleTspan.removeAttribute("y");
258110
+ titleTspan.setAttribute(
258111
+ "dy",
258112
+ trimmedTitle ? calloutLineDy : "0"
258113
+ );
258114
+ titleTspan.setAttribute("fill", filenameColor);
258115
+ titleTspan.setAttribute("font-size", calloutFontSize);
258116
+ titleTspan.setAttribute("font-weight", calloutFontWeight);
258117
+ const titleWidth = typeof titleTspan.getComputedTextLength === "function" ? titleTspan.getComputedTextLength() : 0;
258118
+ titleTspan.setAttribute("x", String(point2.x - titleWidth / 2));
258085
258119
  const bbox = textEl.getBBox();
258086
258120
  const padX = 14;
258087
258121
  const padY = 10;
@@ -258094,6 +258128,12 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
258094
258128
  bgEl.setAttribute("stroke", color2);
258095
258129
  bgEl.setAttribute("stroke-width", "1.5");
258096
258130
  bgEl.setAttribute("opacity", String(0.94 * opacity));
258131
+ if (displayName !== name2) {
258132
+ nameTspan.textContent = displayName;
258133
+ }
258134
+ if (displayTitle !== trimmedTitle) {
258135
+ titleTspan.textContent = displayTitle;
258136
+ }
258097
258137
  };
258098
258138
  let calloutOpacity = 0;
258099
258139
  if (frozenIndex != null && lastIndex >= 0) {
@@ -258109,13 +258149,49 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
258109
258149
  } else if (!animate2 && lastIndex >= 0) {
258110
258150
  calloutOpacity = 1;
258111
258151
  }
258152
+ const activePath = lastIndex >= 0 ? targets[lastIndex].path : null;
258153
+ const activeName = activePath ? fileBasename(activePath) : "";
258154
+ const activeTitle = lastIndex >= 0 ? (((_a = markerTitlesRef.current) == null ? void 0 : _a[lastIndex]) ?? "").trim() : "";
258155
+ let nameRevealChars = null;
258156
+ let titleRevealChars = null;
258157
+ if (animate2 && frozenIndex == null && traverseFrac === null && lastIndex >= 0) {
258158
+ const charsPerMs = TITLE_TYPEWRITER_CPS / 1e3;
258159
+ const nameStart = CALLOUT_FADE_IN_MS + TITLE_TYPEWRITER_HOLD_MS;
258160
+ const nameDuration = activeName.length > 0 ? activeName.length / charsPerMs : 0;
258161
+ const nameDone = nameStart + nameDuration;
258162
+ const titleStart = nameDone + TITLE_INTER_HOLD_MS;
258163
+ if (activeName.length > 0) {
258164
+ if (dwellElapsed < nameStart) {
258165
+ nameRevealChars = 0;
258166
+ } else if (dwellElapsed < nameDone) {
258167
+ const typed = Math.floor(
258168
+ (dwellElapsed - nameStart) * charsPerMs
258169
+ );
258170
+ nameRevealChars = typed >= activeName.length ? null : Math.max(0, typed);
258171
+ } else {
258172
+ nameRevealChars = null;
258173
+ }
258174
+ }
258175
+ if (activeTitle.length > 0) {
258176
+ if (dwellElapsed < titleStart) {
258177
+ titleRevealChars = -1;
258178
+ } else {
258179
+ const typed = Math.floor(
258180
+ (dwellElapsed - titleStart) * charsPerMs
258181
+ );
258182
+ titleRevealChars = typed >= activeTitle.length ? null : Math.max(0, typed);
258183
+ }
258184
+ }
258185
+ }
258112
258186
  paintCallout(
258113
258187
  calloutTextRef.current,
258114
258188
  calloutBgRef.current,
258115
258189
  lastIndex >= 0 ? points[lastIndex] : null,
258116
- lastIndex >= 0 ? targets[lastIndex].path : null,
258117
- lastIndex >= 0 ? ((_a = markerTitlesRef.current) == null ? void 0 : _a[lastIndex]) ?? null : null,
258118
- calloutOpacity
258190
+ activePath,
258191
+ lastIndex >= 0 ? ((_b = markerTitlesRef.current) == null ? void 0 : _b[lastIndex]) ?? null : null,
258192
+ calloutOpacity,
258193
+ nameRevealChars,
258194
+ titleRevealChars
258119
258195
  );
258120
258196
  g2.setAttribute("opacity", "1");
258121
258197
  },
@@ -258139,6 +258215,19 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
258139
258215
  ]
258140
258216
  );
258141
258217
  React.useImperativeHandle(ref, () => ({ onCameraFrame }), [onCameraFrame]);
258218
+ const handleCalloutEnter = React.useCallback(() => {
258219
+ if (hoverFrozenElapsedRef.current !== null) return;
258220
+ const period = periodRef.current;
258221
+ if (period <= 0) return;
258222
+ const now2 = typeof performance !== "undefined" ? performance.now() : Date.now();
258223
+ hoverFrozenElapsedRef.current = (now2 - animStartTsRef.current) % period;
258224
+ }, []);
258225
+ const handleCalloutLeave = React.useCallback(() => {
258226
+ if (hoverFrozenElapsedRef.current === null) return;
258227
+ const now2 = typeof performance !== "undefined" ? performance.now() : Date.now();
258228
+ animStartTsRef.current = now2 - hoverFrozenElapsedRef.current;
258229
+ hoverFrozenElapsedRef.current = null;
258230
+ }, []);
258142
258231
  return /* @__PURE__ */ jsxs(
258143
258232
  "svg",
258144
258233
  {
@@ -258156,8 +258245,26 @@ const TrailFilePath = React.forwardRef(function TrailFilePath2({
258156
258245
  children: [
258157
258246
  /* @__PURE__ */ jsx("g", { ref: groupRef, opacity: 0 }),
258158
258247
  /* @__PURE__ */ jsx("circle", { ref: leadDotRef, opacity: 0, pointerEvents: "none" }),
258159
- /* @__PURE__ */ jsx("rect", { ref: calloutBgRef, opacity: 0, pointerEvents: "none" }),
258160
- /* @__PURE__ */ jsx("text", { ref: calloutTextRef, opacity: 0, pointerEvents: "none" })
258248
+ /* @__PURE__ */ jsx(
258249
+ "rect",
258250
+ {
258251
+ ref: calloutBgRef,
258252
+ opacity: 0,
258253
+ pointerEvents: "auto",
258254
+ onMouseEnter: handleCalloutEnter,
258255
+ onMouseLeave: handleCalloutLeave
258256
+ }
258257
+ ),
258258
+ /* @__PURE__ */ jsx(
258259
+ "text",
258260
+ {
258261
+ ref: calloutTextRef,
258262
+ opacity: 0,
258263
+ pointerEvents: "auto",
258264
+ onMouseEnter: handleCalloutEnter,
258265
+ onMouseLeave: handleCalloutLeave
258266
+ }
258267
+ )
258161
258268
  ]
258162
258269
  }
258163
258270
  );
@@ -258993,20 +259100,24 @@ const FileCityTrailSequenceLayout = ({
258993
259100
  if (!selectedMarkerId) return null;
258994
259101
  return trail2.markers.find((m) => m.id === selectedMarkerId) ?? null;
258995
259102
  }, [trail2.markers, selectedMarkerId]);
258996
- const markerBySourcePathForThisRepo = React.useMemo(() => {
259103
+ const markerByBuildingPath = React.useMemo(() => {
258997
259104
  const map2 = /* @__PURE__ */ new Map();
259105
+ if (!cityData) return map2;
258998
259106
  for (const m of markersForThisRepo) {
258999
- if (m.sourcePath) map2.set(m.sourcePath, m);
259107
+ const cityPath = markerCityPath(m, trail2);
259108
+ if (!cityPath) continue;
259109
+ const b = cityData.buildings.find((cb) => cb.path === cityPath) ?? cityData.buildings.find((cb) => cb.path.endsWith(`/${cityPath}`));
259110
+ if (b) map2.set(b.path, m);
259000
259111
  }
259001
259112
  return map2;
259002
- }, [markersForThisRepo]);
259113
+ }, [markersForThisRepo, cityData, trail2]);
259003
259114
  const handleBuildingClick = React.useCallback(
259004
259115
  (building) => {
259005
- const marker = markerBySourcePathForThisRepo.get(building.path);
259116
+ const marker = markerByBuildingPath.get(building.path);
259006
259117
  if (!marker) return;
259007
259118
  onSelectMarker(selectedMarkerId === marker.id ? null : marker.id);
259008
259119
  },
259009
- [markerBySourcePathForThisRepo, selectedMarkerId, onSelectMarker]
259120
+ [markerByBuildingPath, selectedMarkerId, onSelectMarker]
259010
259121
  );
259011
259122
  const stepperIndex = React.useMemo(() => {
259012
259123
  if (!selectedMarkerId) return -1;
@@ -259042,37 +259153,39 @@ const FileCityTrailSequenceLayout = ({
259042
259153
  const firstMarkerByPath = /* @__PURE__ */ new Map();
259043
259154
  const order2 = [];
259044
259155
  for (const m of markersForThisRepo) {
259045
- if (!m.sourcePath) continue;
259046
- const existing = counts.get(m.sourcePath) ?? 0;
259047
- counts.set(m.sourcePath, existing + 1);
259048
- if (!firstMarkerByPath.has(m.sourcePath)) {
259049
- firstMarkerByPath.set(m.sourcePath, m.id);
259050
- order2.push(m.sourcePath);
259051
- }
259052
- }
259053
- return order2.map((sourcePath) => ({
259054
- sourcePath,
259055
- markerCount: counts.get(sourcePath) ?? 0,
259056
- isActive: (selectedMarker == null ? void 0 : selectedMarker.sourcePath) === sourcePath,
259156
+ const cityPath = markerCityPath(m, trail2);
259157
+ if (!cityPath) continue;
259158
+ const existing = counts.get(cityPath) ?? 0;
259159
+ counts.set(cityPath, existing + 1);
259160
+ if (!firstMarkerByPath.has(cityPath)) {
259161
+ firstMarkerByPath.set(cityPath, m.id);
259162
+ order2.push(cityPath);
259163
+ }
259164
+ }
259165
+ const selectedCityPath = selectedMarker ? markerCityPath(selectedMarker, trail2) : null;
259166
+ return order2.map((cityPath) => ({
259167
+ sourcePath: cityPath,
259168
+ markerCount: counts.get(cityPath) ?? 0,
259169
+ isActive: selectedCityPath === cityPath,
259057
259170
  onClick: () => {
259058
- const markerId = firstMarkerByPath.get(sourcePath);
259171
+ const markerId = firstMarkerByPath.get(cityPath);
259059
259172
  if (markerId) onSelectMarker(markerId);
259060
259173
  },
259061
259174
  onHoverChange: setHoveredFilePath
259062
259175
  }));
259063
- }, [markersForThisRepo, selectedMarker, onSelectMarker]);
259176
+ }, [markersForThisRepo, selectedMarker, onSelectMarker, trail2]);
259064
259177
  const trailFilesHighlightLayers = React.useMemo(() => {
259065
259178
  if (!cityData) return null;
259066
- const sourcePaths = markersForThisRepo.map((m) => m.sourcePath).filter((p2) => typeof p2 === "string" && p2.length > 0);
259067
- if (sourcePaths.length === 0) return null;
259179
+ const cityPaths = markersForThisRepo.map((m) => markerCityPath(m, trail2)).filter((p2) => typeof p2 === "string" && p2.length > 0);
259180
+ if (cityPaths.length === 0) return null;
259068
259181
  const buildingByPath = new Map(cityData.buildings.map((b) => [b.path, b]));
259069
259182
  const byColor = /* @__PURE__ */ new Map();
259070
259183
  const seen = /* @__PURE__ */ new Set();
259071
- for (const sourcePath of sourcePaths) {
259072
- if (seen.has(sourcePath)) continue;
259073
- const building = buildingByPath.get(sourcePath) ?? cityData.buildings.find((b) => b.path.endsWith(`/${sourcePath}`));
259184
+ for (const cityPath of cityPaths) {
259185
+ if (seen.has(cityPath)) continue;
259186
+ const building = buildingByPath.get(cityPath) ?? cityData.buildings.find((b) => b.path.endsWith(`/${cityPath}`));
259074
259187
  if (!building) continue;
259075
- seen.add(sourcePath);
259188
+ seen.add(cityPath);
259076
259189
  const color2 = building.color ?? getFileColor(building.path);
259077
259190
  if (!color2) continue;
259078
259191
  const list2 = byColor.get(color2) ?? [];
@@ -259094,7 +259207,7 @@ const FileCityTrailSequenceLayout = ({
259094
259207
  renderStrategy: "fill"
259095
259208
  }))
259096
259209
  }));
259097
- }, [markersForThisRepo, cityData]);
259210
+ }, [markersForThisRepo, cityData, trail2]);
259098
259211
  const trailFilesActive = trailFilesHighlightLayers !== null && trailFilesHighlightLayers.length > 0;
259099
259212
  const hoveredFileHighlightLayer = React.useMemo(() => {
259100
259213
  if (!hoveredFilePath || !cityData) return null;
@@ -259119,12 +259232,49 @@ const FileCityTrailSequenceLayout = ({
259119
259232
  ]
259120
259233
  };
259121
259234
  }, [hoveredFilePath, cityData, theme2.colors.accent]);
259235
+ const districtTintLayers = React.useMemo(() => {
259236
+ if (!cityData) return null;
259237
+ const repos = trail2.repos ?? [];
259238
+ if (repos.length <= 1) return null;
259239
+ const palette = ["#3b82f6", "#a855f7", "#f97316", "#10b981", "#eab308"];
259240
+ const layers = [];
259241
+ repos.forEach((repo, i) => {
259242
+ const prefix = `${repo.name}/`;
259243
+ const paths = [];
259244
+ for (const b of cityData.buildings) {
259245
+ if (b.path === repo.name || b.path.startsWith(prefix)) {
259246
+ paths.push(b.path);
259247
+ } else if (b.path.includes(`/${prefix}`)) {
259248
+ paths.push(b.path);
259249
+ }
259250
+ }
259251
+ if (paths.length === 0) return;
259252
+ layers.push({
259253
+ // The layer-system id is internal — using the PURL here is fine
259254
+ // (and uniquely scopes by canonical identity even if two repos
259255
+ // happened to share a `name`).
259256
+ id: `district-${repo.id}`,
259257
+ name: `District: ${repo.name}`,
259258
+ enabled: true,
259259
+ color: palette[i % palette.length],
259260
+ opacity: 0.18,
259261
+ priority: 10,
259262
+ items: paths.map((path2) => ({
259263
+ path: path2,
259264
+ type: "file",
259265
+ renderStrategy: "fill"
259266
+ }))
259267
+ });
259268
+ });
259269
+ return layers.length > 0 ? layers : null;
259270
+ }, [cityData, trail2.repos]);
259122
259271
  const cityHighlightLayers = React.useMemo(() => {
259123
259272
  const layers = [];
259273
+ if (districtTintLayers) layers.push(...districtTintLayers);
259124
259274
  if (trailFilesHighlightLayers) layers.push(...trailFilesHighlightLayers);
259125
259275
  if (hoveredFileHighlightLayer) layers.push(hoveredFileHighlightLayer);
259126
259276
  return layers.length > 0 ? layers : void 0;
259127
- }, [trailFilesHighlightLayers, hoveredFileHighlightLayer]);
259277
+ }, [districtTintLayers, trailFilesHighlightLayers, hoveredFileHighlightLayer]);
259128
259278
  const containerRef = React.useRef(null);
259129
259279
  const snippetPaneRef = React.useRef(null);
259130
259280
  const leaderLineRef = React.useRef(null);
@@ -259141,10 +259291,11 @@ const FileCityTrailSequenceLayout = ({
259141
259291
  const [snippetPaneWidth, setSnippetPaneWidth] = React.useState(null);
259142
259292
  const [containerSize, setContainerSize] = React.useState(null);
259143
259293
  const selectedBuilding = React.useMemo(() => {
259144
- if (!cityData || !(selectedMarker == null ? void 0 : selectedMarker.sourcePath)) return null;
259145
- const path2 = selectedMarker.sourcePath;
259294
+ if (!cityData || !selectedMarker) return null;
259295
+ const path2 = markerCityPath(selectedMarker, trail2);
259296
+ if (!path2) return null;
259146
259297
  return cityData.buildings.find((b) => b.path === path2) ?? cityData.buildings.find((b) => b.path.endsWith(`/${path2}`)) ?? null;
259147
- }, [cityData, selectedMarker]);
259298
+ }, [cityData, selectedMarker, trail2]);
259148
259299
  const cityCenter = React.useMemo(() => {
259149
259300
  if (!cityData) return null;
259150
259301
  return {
@@ -259168,10 +259319,9 @@ const FileCityTrailSequenceLayout = ({
259168
259319
  const titles = [];
259169
259320
  const markerIds = [];
259170
259321
  for (const m of markersForThisRepo) {
259171
- if (!m.sourcePath) continue;
259172
- const b = buildingByPath.get(m.sourcePath) ?? cityData.buildings.find(
259173
- (cb) => cb.path.endsWith(`/${m.sourcePath}`)
259174
- );
259322
+ const cityPath = markerCityPath(m, trail2);
259323
+ if (!cityPath) continue;
259324
+ const b = buildingByPath.get(cityPath) ?? cityData.buildings.find((cb) => cb.path.endsWith(`/${cityPath}`));
259175
259325
  if (b) {
259176
259326
  buildings.push(b);
259177
259327
  titles.push(((_a2 = m.label) == null ? void 0 : _a2.trim()) || null);
@@ -259183,7 +259333,7 @@ const FileCityTrailSequenceLayout = ({
259183
259333
  trailPathTitles: titles,
259184
259334
  trailPathMarkerIds: markerIds
259185
259335
  };
259186
- }, [cityData, markersForThisRepo]);
259336
+ }, [cityData, markersForThisRepo, trail2]);
259187
259337
  const trailPathCurrentStepIndex = React.useMemo(() => {
259188
259338
  if (!selectedMarkerId) return null;
259189
259339
  const idx = trailPathMarkerIds.indexOf(selectedMarkerId);
@@ -259382,7 +259532,7 @@ const FileCityTrailSequenceLayout = ({
259382
259532
  cityData,
259383
259533
  width: "100%",
259384
259534
  height: "100%",
259385
- selectedPath: (selectedMarker == null ? void 0 : selectedMarker.sourcePath) ?? null,
259535
+ selectedPath: selectedMarker ? markerCityPath(selectedMarker, trail2) : null,
259386
259536
  onBuildingClick: handleBuildingClick,
259387
259537
  onCameraFrame,
259388
259538
  showControls: false,
@@ -260485,6 +260635,15 @@ function filterMarkersForRepo(trail2, repository) {
260485
260635
  }
260486
260636
  return trail2.markers.filter((m) => m.repo === repository.id);
260487
260637
  }
260638
+ function markerCityPath(marker, trail2) {
260639
+ if (!marker.sourcePath) return null;
260640
+ const repos = trail2.repos ?? [];
260641
+ if (repos.length <= 1) return marker.sourcePath;
260642
+ if (!marker.repo) return null;
260643
+ const repo = repos.find((r2) => r2.id === marker.repo);
260644
+ if (!repo) return null;
260645
+ return `${repo.name}/${marker.sourcePath}`;
260646
+ }
260488
260647
  const focusBuildingTool = {
260489
260648
  name: "focus_building",
260490
260649
  description: "Focuses the camera on a specific file (building) in the file city visualization",