@principal-ai/file-city-react 0.5.39 → 0.5.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/components/FileCity3D/FileCity3D.d.ts +8 -2
  2. package/dist/components/FileCity3D/FileCity3D.d.ts.map +1 -1
  3. package/dist/components/FileCity3D/FileCity3D.js +129 -40
  4. package/dist/components/FileCityExplorer/AddToAreaModal.d.ts +14 -0
  5. package/dist/components/FileCityExplorer/AddToAreaModal.d.ts.map +1 -0
  6. package/dist/components/FileCityExplorer/AddToAreaModal.js +140 -0
  7. package/dist/components/FileCityExplorer/AddToScopeModal.d.ts +14 -0
  8. package/dist/components/FileCityExplorer/AddToScopeModal.d.ts.map +1 -0
  9. package/dist/components/FileCityExplorer/AddToScopeModal.js +176 -0
  10. package/dist/components/FileCityExplorer/FileCityExplorer.d.ts +30 -0
  11. package/dist/components/FileCityExplorer/FileCityExplorer.d.ts.map +1 -0
  12. package/dist/components/FileCityExplorer/FileCityExplorer.js +1045 -0
  13. package/dist/components/FileCityExplorer/ScopeInfoOverlay.d.ts +10 -0
  14. package/dist/components/FileCityExplorer/ScopeInfoOverlay.d.ts.map +1 -0
  15. package/dist/components/FileCityExplorer/ScopeInfoOverlay.js +73 -0
  16. package/dist/components/FileCityExplorer/index.d.ts +3 -0
  17. package/dist/components/FileCityExplorer/index.d.ts.map +1 -0
  18. package/dist/components/FileCityExplorer/index.js +1 -0
  19. package/dist/components/FileCityExplorer/layers.d.ts +16 -0
  20. package/dist/components/FileCityExplorer/layers.d.ts.map +1 -0
  21. package/dist/components/FileCityExplorer/layers.js +61 -0
  22. package/dist/components/FileCityExplorer/model.d.ts +32 -0
  23. package/dist/components/FileCityExplorer/model.d.ts.map +1 -0
  24. package/dist/components/FileCityExplorer/model.js +14 -0
  25. package/dist/components/FileCityExplorer/pathConversion.d.ts +19 -0
  26. package/dist/components/FileCityExplorer/pathConversion.d.ts.map +1 -0
  27. package/dist/components/FileCityExplorer/pathConversion.js +26 -0
  28. package/dist/components/FileCityExplorer/scopeTreePaths.d.ts +21 -0
  29. package/dist/components/FileCityExplorer/scopeTreePaths.d.ts.map +1 -0
  30. package/dist/components/FileCityExplorer/scopeTreePaths.js +42 -0
  31. package/dist/components/FileCityExplorer/styles.d.ts +9 -0
  32. package/dist/components/FileCityExplorer/styles.d.ts.map +1 -0
  33. package/dist/components/FileCityExplorer/styles.js +28 -0
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +2 -0
  37. package/dist/utils/folderElevatedPanels.d.ts +62 -0
  38. package/dist/utils/folderElevatedPanels.d.ts.map +1 -0
  39. package/dist/utils/folderElevatedPanels.js +130 -0
  40. package/package.json +2 -1
  41. package/src/components/FileCity3D/FileCity3D.tsx +200 -52
  42. package/src/components/FileCityExplorer/AddToAreaModal.tsx +273 -0
  43. package/src/components/FileCityExplorer/AddToScopeModal.tsx +320 -0
  44. package/src/components/FileCityExplorer/FileCityExplorer.tsx +1457 -0
  45. package/src/components/FileCityExplorer/ScopeInfoOverlay.tsx +229 -0
  46. package/src/components/FileCityExplorer/index.ts +2 -0
  47. package/src/components/FileCityExplorer/layers.ts +72 -0
  48. package/src/components/FileCityExplorer/model.ts +35 -0
  49. package/src/components/FileCityExplorer/pathConversion.ts +32 -0
  50. package/src/components/FileCityExplorer/scopeTreePaths.ts +52 -0
  51. package/src/components/FileCityExplorer/styles.ts +34 -0
  52. package/src/index.ts +8 -0
  53. package/src/stories/2D3DComparison.stories.tsx +13 -2
  54. package/src/stories/ElevatedScopePanels.stories.tsx +295 -0
  55. package/src/stories/FileCity3D.stories.tsx +24 -3
  56. package/src/stories/FileCityExplorer.stories.tsx +2474 -0
  57. package/src/stories/FileCityExplorerComponent.stories.tsx +59 -0
  58. package/src/utils/folderElevatedPanels.ts +176 -0
  59. package/src/stories/ScopeOverlay.stories.tsx +0 -1687
@@ -0,0 +1,295 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import {
4
+ FileCity3D,
5
+ type CityData,
6
+ type ElevatedScopePanel,
7
+ } from '../components/FileCity3D';
8
+ import {
9
+ buildFolderElevatedPanels,
10
+ buildFolderIndex,
11
+ } from '../utils/folderElevatedPanels';
12
+
13
+ import electronAppCityData from '../../../../assets/electron-app-city-data.json';
14
+
15
+ /**
16
+ * Use cases for `ElevatedScopePanel` — the colored slabs the file explorer
17
+ * renders over collapsed directories. These stories drive `<FileCity3D>`
18
+ * directly with hand-crafted panel arrays, so each story is a frozen
19
+ * snapshot you can copy as a template when adding new panel features.
20
+ *
21
+ * Panels render only while the city is in flat (2D) mode; every story locks
22
+ * the city flat with `isGrown={false}`.
23
+ */
24
+
25
+ const cityData = electronAppCityData as CityData;
26
+ const folderIndex = buildFolderIndex(cityData);
27
+ const districtsByPath = new Map(cityData.districts.map(d => [d.path, d]));
28
+
29
+ function panelsFor(expanded: ReadonlySet<string>): ElevatedScopePanel[] {
30
+ return buildFolderElevatedPanels({
31
+ cityData,
32
+ expandedFolders: expanded,
33
+ index: folderIndex,
34
+ });
35
+ }
36
+
37
+ const ROOT_ONLY = new Set<string>();
38
+ const TOP_LEVEL = new Set<string>(['electron-app']);
39
+ const PARTIAL = new Set<string>(['electron-app', 'electron-app/src']);
40
+ const DEEP = new Set<string>([
41
+ 'electron-app',
42
+ 'electron-app/src',
43
+ 'electron-app/src/main',
44
+ ]);
45
+
46
+ const meta: Meta<typeof FileCity3D> = {
47
+ title: 'Experiments/Elevated Scope Panels',
48
+ component: FileCity3D,
49
+ parameters: {
50
+ layout: 'fullscreen',
51
+ docs: {
52
+ description: {
53
+ component:
54
+ 'Panel-interface use cases extracted from FileCityExplorer. Each ' +
55
+ 'story is static (no expansion/selection state) so the snapshots ' +
56
+ 'stay readable as documentation and copy-paste templates.',
57
+ },
58
+ },
59
+ },
60
+ };
61
+ export default meta;
62
+
63
+ type Story = StoryObj<typeof FileCity3D>;
64
+
65
+ const baseArgs = {
66
+ cityData,
67
+ isGrown: false,
68
+ height: '100vh',
69
+ } as const;
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Expansion-state snapshots (helper-driven panels)
73
+ // ---------------------------------------------------------------------------
74
+
75
+ export const RootUmbrella: Story = {
76
+ args: { ...baseArgs, elevatedScopePanels: panelsFor(ROOT_ONLY) },
77
+ parameters: {
78
+ docs: {
79
+ description: {
80
+ story:
81
+ 'Empty `expandedFolders` — one umbrella covers the whole project.',
82
+ },
83
+ },
84
+ },
85
+ };
86
+
87
+ export const TopLevelExpanded: Story = {
88
+ args: { ...baseArgs, elevatedScopePanels: panelsFor(TOP_LEVEL) },
89
+ parameters: {
90
+ docs: {
91
+ description: {
92
+ story:
93
+ 'Project root expanded — one umbrella per top-level directory ' +
94
+ '(`src`, `docs`, `scripts`, …).',
95
+ },
96
+ },
97
+ },
98
+ };
99
+
100
+ export const PartiallyExpanded: Story = {
101
+ args: { ...baseArgs, elevatedScopePanels: panelsFor(PARTIAL) },
102
+ parameters: {
103
+ docs: {
104
+ description: {
105
+ story:
106
+ 'Root + `src` expanded. `src/*` directories surface as their own ' +
107
+ 'tiles while sibling top-level directories stay umbrella-ed.',
108
+ },
109
+ },
110
+ },
111
+ };
112
+
113
+ export const DeeplyExpanded: Story = {
114
+ args: { ...baseArgs, elevatedScopePanels: panelsFor(DEEP) },
115
+ parameters: {
116
+ docs: {
117
+ description: {
118
+ story:
119
+ 'Three levels expanded — `src/main/*` directories are now visible ' +
120
+ 'as individual tiles. Demonstrates how panel granularity follows ' +
121
+ 'expansion depth.',
122
+ },
123
+ },
124
+ },
125
+ };
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Decorating helper output
129
+ // ---------------------------------------------------------------------------
130
+
131
+ const HUMAN_AREA_LABELS: Record<string, string> = {
132
+ 'electron-app/docs': 'Documentation',
133
+ 'electron-app/scripts': 'Build & Tooling',
134
+ 'electron-app/.github': 'CI / CD',
135
+ 'electron-app/src': 'Application Code',
136
+ };
137
+
138
+ export const WithDisplayLabels: Story = {
139
+ args: {
140
+ ...baseArgs,
141
+ elevatedScopePanels: panelsFor(TOP_LEVEL).map(panel => {
142
+ const folderPath = panel.id.startsWith('folder::')
143
+ ? panel.id.slice('folder::'.length)
144
+ : null;
145
+ const displayLabel = folderPath ? HUMAN_AREA_LABELS[folderPath] : undefined;
146
+ return displayLabel ? { ...panel, displayLabel } : panel;
147
+ }),
148
+ },
149
+ parameters: {
150
+ docs: {
151
+ description: {
152
+ story:
153
+ 'Post-process the helper\'s output to attach a `displayLabel` ' +
154
+ '(rendered above the technical folder name in a smaller font). ' +
155
+ 'This is the pattern FileCityExplorer uses to surface ' +
156
+ 'human-readable area names on top of folder umbrellas.',
157
+ },
158
+ },
159
+ },
160
+ };
161
+
162
+ const RECOLORED_BY_PATH: Record<string, string> = {
163
+ 'electron-app/src': '#22c55e',
164
+ 'electron-app/docs': '#3b82f6',
165
+ 'electron-app/scripts': '#f59e0b',
166
+ };
167
+
168
+ export const RecoloredAndTranslucent: Story = {
169
+ args: {
170
+ ...baseArgs,
171
+ elevatedScopePanels: panelsFor(TOP_LEVEL).map(panel => {
172
+ const folderPath = panel.id.startsWith('folder::')
173
+ ? panel.id.slice('folder::'.length)
174
+ : null;
175
+ const color = folderPath ? RECOLORED_BY_PATH[folderPath] : undefined;
176
+ return color ? { ...panel, color, opacity: 0.55 } : { ...panel, opacity: 0.35 };
177
+ }),
178
+ },
179
+ parameters: {
180
+ docs: {
181
+ description: {
182
+ story:
183
+ 'Override `color` and `opacity` on helper output. Translucent ' +
184
+ 'panels let buildings beneath show through — useful when the ' +
185
+ 'panel is a hint, not an occluder.',
186
+ },
187
+ },
188
+ },
189
+ };
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Selection ring
193
+ // ---------------------------------------------------------------------------
194
+
195
+ const SELECTED_FOLDER = 'electron-app/src';
196
+ const SELECTION_RING_COLOR = '#fbbf24';
197
+
198
+ export const WithSelectionRing: Story = {
199
+ args: {
200
+ ...baseArgs,
201
+ elevatedScopePanels: (() => {
202
+ const panels = panelsFor(TOP_LEVEL);
203
+ const idx = panels.findIndex(p => p.id === `folder::${SELECTED_FOLDER}`);
204
+ if (idx < 0) return panels;
205
+ const target = panels[idx];
206
+ const inflate = 4;
207
+ const ring: ElevatedScopePanel = {
208
+ id: `folder-border::${SELECTED_FOLDER}`,
209
+ color: SELECTION_RING_COLOR,
210
+ height: (target.height ?? 4) - 2,
211
+ thickness: 1,
212
+ bounds: {
213
+ minX: target.bounds.minX - inflate,
214
+ maxX: target.bounds.maxX + inflate,
215
+ minZ: target.bounds.minZ - inflate,
216
+ maxZ: target.bounds.maxZ + inflate,
217
+ },
218
+ };
219
+ const next = [...panels];
220
+ next.splice(idx, 0, ring);
221
+ return next;
222
+ })(),
223
+ },
224
+ parameters: {
225
+ docs: {
226
+ description: {
227
+ story:
228
+ 'Insert an inflated, lower-height panel just *before* the target ' +
229
+ 'umbrella so only its rim peeks out — the selection-ring pattern ' +
230
+ 'used by FileCityExplorer. Order matters: the ring must come ' +
231
+ 'first so the umbrella draws on top of its centre.',
232
+ },
233
+ },
234
+ },
235
+ };
236
+
237
+ // ---------------------------------------------------------------------------
238
+ // Hand-built panels (no helper)
239
+ // ---------------------------------------------------------------------------
240
+
241
+ function panelFromDistrict(
242
+ path: string,
243
+ overrides: Partial<ElevatedScopePanel> = {},
244
+ ): ElevatedScopePanel | null {
245
+ const d = districtsByPath.get(path);
246
+ if (!d) return null;
247
+ return {
248
+ id: `manual::${path}`,
249
+ color: '#64748b',
250
+ height: 4,
251
+ thickness: 2,
252
+ bounds: d.worldBounds,
253
+ label: path.split('/').pop() ?? path,
254
+ ...overrides,
255
+ };
256
+ }
257
+
258
+ export const HandBuiltPanels: Story = {
259
+ args: {
260
+ ...baseArgs,
261
+ elevatedScopePanels: [
262
+ panelFromDistrict('electron-app/src', {
263
+ color: '#22c55e',
264
+ label: 'src',
265
+ displayLabel: 'Application Code',
266
+ labelColor: '#0f172a',
267
+ labelSize: 80,
268
+ }),
269
+ panelFromDistrict('electron-app/docs', {
270
+ color: '#3b82f6',
271
+ opacity: 0.6,
272
+ label: 'docs',
273
+ }),
274
+ panelFromDistrict('electron-app/scripts', {
275
+ color: '#f59e0b',
276
+ thickness: 6,
277
+ label: 'scripts',
278
+ displayLabel: 'Tooling',
279
+ displayLabelColor: '#78350f',
280
+ }),
281
+ ].filter((p): p is ElevatedScopePanel => p !== null),
282
+ },
283
+ parameters: {
284
+ docs: {
285
+ description: {
286
+ story:
287
+ 'Fully manual panel array — no helper. Bounds come straight from ' +
288
+ '`cityData.districts[i].worldBounds`. Demonstrates `color`, ' +
289
+ '`opacity`, `label`, `labelColor`, `labelSize`, `displayLabel`, ' +
290
+ '`displayLabelColor`, and `thickness`. Use this shape when adding ' +
291
+ 'fields to `ElevatedScopePanel`.',
292
+ },
293
+ },
294
+ },
295
+ };
@@ -422,6 +422,7 @@ export const IsolationTransparent: Story = {
422
422
  id: 'focus',
423
423
  name: 'Focus Layer',
424
424
  enabled: true,
425
+ priority: 0,
425
426
  color: '#22c55e',
426
427
  items: [{ path: 'src', type: 'directory' as const, renderStrategy: 'fill' as const }],
427
428
  },
@@ -442,6 +443,7 @@ export const IsolationCollapse: Story = {
442
443
  id: 'focus',
443
444
  name: 'Focus Layer',
444
445
  enabled: true,
446
+ priority: 0,
445
447
  color: '#3b82f6',
446
448
  items: [{ path: 'src/components', type: 'directory' as const, renderStrategy: 'fill' as const }],
447
449
  },
@@ -462,6 +464,7 @@ export const IsolationHide: Story = {
462
464
  id: 'focus',
463
465
  name: 'Focus Layer',
464
466
  enabled: true,
467
+ priority: 0,
465
468
  color: '#f59e0b',
466
469
  items: [{ path: 'tests', type: 'directory' as const, renderStrategy: 'fill' as const }],
467
470
  },
@@ -483,6 +486,7 @@ export const MultipleHighlights: Story = {
483
486
  id: 'src',
484
487
  name: 'Source',
485
488
  enabled: true,
489
+ priority: 0,
486
490
  color: '#22c55e',
487
491
  items: [{ path: 'src', type: 'directory' as const, renderStrategy: 'fill' as const }],
488
492
  },
@@ -490,6 +494,7 @@ export const MultipleHighlights: Story = {
490
494
  id: 'tests',
491
495
  name: 'Tests',
492
496
  enabled: true,
497
+ priority: 0,
493
498
  color: '#ef4444',
494
499
  items: [{ path: 'tests', type: 'directory' as const, renderStrategy: 'fill' as const }],
495
500
  },
@@ -575,6 +580,7 @@ export const AnimatedWithHighlight: Story = {
575
580
  id: 'components',
576
581
  name: 'Components',
577
582
  enabled: true,
583
+ priority: 0,
578
584
  color: '#8b5cf6',
579
585
  items: [{ path: 'src/components', type: 'directory' as const, renderStrategy: 'fill' as const }],
580
586
  },
@@ -627,6 +633,7 @@ const authServerTourSteps: TourStep[] = [
627
633
  id: 'workos',
628
634
  name: 'WorkOS Auth',
629
635
  enabled: true,
636
+ priority: 0,
630
637
  color: '#22c55e',
631
638
  items: [{ path: 'auth-server/src/app/api/auth/workos', type: 'directory' as const }],
632
639
  },
@@ -642,6 +649,7 @@ const authServerTourSteps: TourStep[] = [
642
649
  id: 'browser',
643
650
  name: 'Browser Tokens',
644
651
  enabled: true,
652
+ priority: 0,
645
653
  color: '#3b82f6',
646
654
  items: [{ path: 'auth-server/src/app/api/auth/browser', type: 'directory' as const }],
647
655
  },
@@ -649,6 +657,7 @@ const authServerTourSteps: TourStep[] = [
649
657
  id: 'cli',
650
658
  name: 'CLI Tokens',
651
659
  enabled: true,
660
+ priority: 0,
652
661
  color: '#f59e0b',
653
662
  items: [{ path: 'auth-server/src/app/api/auth/cli', type: 'directory' as const }],
654
663
  },
@@ -664,6 +673,7 @@ const authServerTourSteps: TourStep[] = [
664
673
  id: 'lib',
665
674
  name: 'Libraries',
666
675
  enabled: true,
676
+ priority: 0,
667
677
  color: '#8b5cf6',
668
678
  items: [{ path: 'auth-server/src/lib', type: 'directory' as const }],
669
679
  },
@@ -679,6 +689,7 @@ const authServerTourSteps: TourStep[] = [
679
689
  id: 'bruno',
680
690
  name: 'Bruno Tests',
681
691
  enabled: true,
692
+ priority: 0,
682
693
  color: '#ef4444',
683
694
  items: [{ path: 'auth-server/bruno', type: 'directory' as const }],
684
695
  },
@@ -694,6 +705,7 @@ const authServerTourSteps: TourStep[] = [
694
705
  id: 'views',
695
706
  name: 'Principal Views',
696
707
  enabled: true,
708
+ priority: 0,
697
709
  color: '#ec4899',
698
710
  items: [{ path: 'auth-server/.principal-views', type: 'directory' as const }],
699
711
  },
@@ -1136,6 +1148,7 @@ const testScenarios: TestScenario[] = [
1136
1148
  id: 'api-layer',
1137
1149
  name: 'API Routes',
1138
1150
  enabled: true,
1151
+ priority: 0,
1139
1152
  color: '#22c55e',
1140
1153
  items: [{ path: 'auth-server/src/app/api', type: 'directory' as const }],
1141
1154
  },
@@ -1152,6 +1165,7 @@ const testScenarios: TestScenario[] = [
1152
1165
  id: 'api-layer',
1153
1166
  name: 'API Routes',
1154
1167
  enabled: true,
1168
+ priority: 0,
1155
1169
  color: '#3b82f6',
1156
1170
  items: [{ path: 'auth-server/src/app/api', type: 'directory' as const }],
1157
1171
  },
@@ -1168,6 +1182,7 @@ const testScenarios: TestScenario[] = [
1168
1182
  id: 'lib-layer',
1169
1183
  name: 'Libraries',
1170
1184
  enabled: true,
1185
+ priority: 0,
1171
1186
  color: '#8b5cf6',
1172
1187
  items: [{ path: 'auth-server/src/lib', type: 'directory' as const }],
1173
1188
  },
@@ -1184,6 +1199,7 @@ const testScenarios: TestScenario[] = [
1184
1199
  id: 'api-layer',
1185
1200
  name: 'API Routes',
1186
1201
  enabled: true,
1202
+ priority: 0,
1187
1203
  color: '#22c55e',
1188
1204
  items: [{ path: 'auth-server/src/app/api', type: 'directory' as const }],
1189
1205
  },
@@ -1191,6 +1207,7 @@ const testScenarios: TestScenario[] = [
1191
1207
  id: 'lib-layer',
1192
1208
  name: 'Libraries',
1193
1209
  enabled: true,
1210
+ priority: 0,
1194
1211
  color: '#f59e0b',
1195
1212
  items: [{ path: 'auth-server/src/lib', type: 'directory' as const }],
1196
1213
  },
@@ -1207,6 +1224,7 @@ const testScenarios: TestScenario[] = [
1207
1224
  id: 'api-layer',
1208
1225
  name: 'API Routes',
1209
1226
  enabled: true,
1227
+ priority: 0,
1210
1228
  color: '#3b82f6',
1211
1229
  items: [{ path: 'auth-server/src/app/api', type: 'directory' as const }],
1212
1230
  },
@@ -1214,6 +1232,7 @@ const testScenarios: TestScenario[] = [
1214
1232
  id: 'bruno-layer',
1215
1233
  name: 'Bruno Tests',
1216
1234
  enabled: true,
1235
+ priority: 0,
1217
1236
  color: '#ef4444',
1218
1237
  items: [{ path: 'auth-server/bruno', type: 'directory' as const }],
1219
1238
  },
@@ -1231,6 +1250,7 @@ const testScenarios: TestScenario[] = [
1231
1250
  id: 'src-layer',
1232
1251
  name: 'All Source',
1233
1252
  enabled: true,
1253
+ priority: 0,
1234
1254
  color: '#22c55e',
1235
1255
  items: [{ path: 'auth-server/src', type: 'directory' as const }],
1236
1256
  },
@@ -1238,6 +1258,7 @@ const testScenarios: TestScenario[] = [
1238
1258
  id: 'api-layer',
1239
1259
  name: 'API Only',
1240
1260
  enabled: true,
1261
+ priority: 0,
1241
1262
  color: '#ef4444',
1242
1263
  items: [{ path: 'auth-server/src/app/api', type: 'directory' as const }],
1243
1264
  },
@@ -1254,6 +1275,7 @@ const testScenarios: TestScenario[] = [
1254
1275
  id: 'api-layer',
1255
1276
  name: 'All API',
1256
1277
  enabled: true,
1278
+ priority: 0,
1257
1279
  color: '#8b5cf6',
1258
1280
  items: [{ path: 'auth-server/src/app/api', type: 'directory' as const }],
1259
1281
  },
@@ -2239,7 +2261,6 @@ const RepositoryProfileTemplate: React.FC = () => {
2239
2261
  // Create highlight layers
2240
2262
  const highlightLayers = React.useMemo(() => {
2241
2263
  const layers: HighlightLayer[] = [];
2242
- const backgroundColor = '#1e293b'; // slate-800
2243
2264
  const hasGitChanges = showGitStatus && gitStatusType !== 'clean';
2244
2265
 
2245
2266
  // 1. File suffix color layers (higher priority = rendered underneath)
@@ -2791,12 +2812,12 @@ const AsyncDataLoadingTemplate: React.FC = () => {
2791
2812
  </div>
2792
2813
  <div style={{ color: '#94a3b8', fontSize: 12, fontFamily: 'system-ui, sans-serif', lineHeight: 1.6 }}>
2793
2814
  <p style={{ margin: '0 0 8px' }}>
2794
- When FileCity3D mounts before cityData is available (or while it's null),
2815
+ When FileCity3D mounts before cityData is available (or while it&apos;s null),
2795
2816
  the camera may initialize at position (0,0,0) instead of calculating the
2796
2817
  proper viewing position from city bounds.
2797
2818
  </p>
2798
2819
  <p style={{ margin: '8px 0 0' }}>
2799
- This caused electron-app's RepositoryProfilePanel to show a black screen
2820
+ This caused electron-app&apos;s RepositoryProfilePanel to show a black screen
2800
2821
  or wrong camera position, leading to a switch to the 2D view.
2801
2822
  </p>
2802
2823
  </div>