Skip to content

Latest commit

 

History

History
14 lines (12 loc) · 5.3 KB

File metadata and controls

14 lines (12 loc) · 5.3 KB

Agent notes: apps/viewer

Supplements the root AGENTS.md; the shared contract (canonical load path, exports, tours, IFC EXPRESS naming) still applies here. Viewer-specific footguns:

  • Selection has two channels. Only the global-id set selectedEntityIds (and scalar selectedEntityId) drives renderer highlight and frameSelection; selectedEntity / selectedEntitiesSet hold EntityRef (expressId) for property lookup only. The multi-model actions (setSelectedEntity / addEntityToSelection / toggleEntitySelection) deliberately do not write selectedEntityId, so also call setSelectedEntityId(globalId) or the element won't highlight (src/store/slices/selectionSlice.ts:171).
  • main.tsx import order is load-bearing. ./disable-react-dev-perf-track must be the first import and ./harden-dom-mutations second, both before react-dom. The first nulls performance.measure so React 19.2's dev render tracker can't recurse big geometry/dataStore props into a 4GB OOM; the second no-ops removeChild / insertBefore so a translate/password browser extension mutating the DOM can't crash the reconciler. Do not reorder or drop them (#1229/#1230/#1232).
  • PostHog is scrub-gated. Every event passes scrubEvent (before_send) in lib/analytics-scrub.ts, which deletes any property keyed name/filename/model/title/label/path/url/email/comment/query/psetname and redacts path-ish string values. Never put a file name, model name, BCF title, pset/property name, or free text in a capture() call; it is silently stripped by design (the confidential-model privacy contract), not a bug.
  • Storey isolation is one channel. Solo and "isolate this storey" both ride selectedStoreys, and applyLevelDisplayMode() (store/levelDisplay.ts) is the single transition every entry point (storey tab, command palette, hierarchy click, viewport chip) must call. Stacked/Exploded clears selectedStoreys. Do not add a second isolatedEntities channel (it left models stuck isolated before).
  • The IfcAnnotation/IfcGrid 3D overlay is a global toggle, never section-filtered. It renders on the typeVisibility.ifcAnnotations / typeVisibility.ifcGrid booleans regardless of any active section cut; do not tie it to sectionPlane. Grids get their own 1.5m gridSectionClip band, but annotation curves are never section-filtered (components/viewer/Viewport.tsx:943).
  • CSV/list exports neutralize formula injection in two places. lib/lists/export/csv.ts esc() (also strips a leading BOM first) and lib/search/result-export.ts escapeCsvCell() both prefix a leading = + - @ TAB CR with an apostrophe (CWE-1236). Any new CSV/list export of attacker-controllable IFC values must reuse one of these, not add a third.
  • Save files through lib/export/download.ts (downloadBlob / downloadFile / downloadDataUrl); never hand-roll an <a download> blob dance. Pass the stem through sanitizeFilename (preserves case and dots so DRAWINGS and 000.000 survive, #1299), then append the extension yourself. Every download fires emitFileDownloaded(filename), which task-gated tours observe; keep that seam.
  • New top-right viewport overlays anchor at top-32 right-4 or lower. The ViewCube sits at top-6 right-6 (components/viewer/ViewportOverlays.tsx); the existing top-right panels (SunSky, level-display selector) clear it deliberately. Never cover the ViewCube corner.
  • Materials built from IFC geometry need double-sided rendering. Anything meshing an actual IFC file must set side: THREE.DoubleSide / doubleSided: true on every material: the MCP playground's per-entity materials (playground-scene-registry.ts) and the Cesium GLB export. IFC winding is not reliably outward (MeshData.indices says so in as many words), so back-face culling shows see-through walls. The /mcp hero is a deliberate exception, not an oversight: hero-scene-building.ts meshes authored BoxGeometry / PlaneGeometry and a hand-wound hip roof, all outward-facing by construction, so it renders front-side (its one DoubleSide material is the section-plane rectangle, which is genuinely viewed from both sides). Don't "fix" it to match the rule — the rule is about IFC winding, and there is no IFC in the hero. This is all separate from the main WebGPU renderer (which lives in packages/*, not here).
  • Never construct a GPU context straight from a mount effect. new THREE.WebGLRenderer and new maplibregl.Map both throw when the device refuses a context, and a throw inside useEffect unwinds to the nearest error boundary — which on /mcp is the route-level ChunkErrorBoundary, so one dead canvas replaced a whole page of GPU-free content with an unrecoverable "Reload" card (#1914, #2401). Mount three.js scenes through components/mcp/useThreeScene.ts and maps through LocationMap.tsx's degradeMap; both ride the one session-latched gate in lib/webgl-capability.ts (probe once, latch the verdict, report ONE handled exception per session). Don't add a third probe or a second latch — a device that refuses a context refuses it for the whole session, and re-probing burns one of the ~16 context slots the page gets. A guard must still rethrow anything that is not the library's own context-refusal message, or a real bug in scene code gets relabelled "your device cannot do 3D" and disappears from error tracking.