feat(dashboard): Live-3D-Linie (Anlagen-Synoptik 3D, Platzhalter-zuerst) - #54
Conversation
Neue Sektion-A-Sub-Ansicht /synoptik: Three.js-Liniensicht der Montagelinie 1, 12 Maschinen in kanonischer Sequenz (Fördern→Pressen→Handling→Bestücken→ Endkontrolle), Live-Status aus /overview über das WS-Thema "overview", Klick/ Hover → kanonische Maschinenkarte (machineHref, loser Vertrag machine_id→Karte). - Reine, render-agnostische Logik (lib/synoptic3d): Layout, Swap-Manifest, Status-Farbe, Platzhalter-Proportionen — voll Vitest-getestet. - Three.js-Renderer (raw, kein R3F): EINE geteilte neutrale Beleuchtung + IBL + ACESFilmic (Render-Kohärenz-Hebel), OrbitControls, proportionale Blockouts je Klasse, Live-Recolor in-place, ehrliche WebGL-Degradation. - GLB-Swap-Naht verdrahtet (GLTFLoader + Draco/meshopt + KTX2), heute ruhend (Manifest = nur Platzhalter); GLB-Vertrag dokumentiert (Meter/Y-up/Boden-Pivot). - Einstieg über Ansichts-Umschalter im Cockpit (Heatmap/3D-Linie); kein achter Nav-Eintrag (Designstudie §3.3, ≤7). Sim-Vorbehalt sichtbar; Ansicht navigiert nur. - Barrierefreie Maschinen-Leiste (Tastatur + Fallback ohne WebGL). three@^0.171.0 nur auf der /synoptik-Route gebündelt. Gates grün: tsc 0, eslint 0, vitest 708, tokens synchron, next build ok. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughDie PR extrahiert den gemeinsamen Overview-Snapshot-Fetch, ergänzt eine neue Synoptik-Route und baut dafür Layout-, Scene-, View- und Navigationsbausteine für die 3D-Anzeige sowie begleitende Tests und ChangesSynoptik 3D-Grundlagen
Sequence Diagram(s)sequenceDiagram
participant SynoptikPage
participant fetchOverviewSnapshot
participant SynoptikView
SynoptikPage->>fetchOverviewSnapshot: fetchOverviewSnapshot()
fetchOverviewSnapshot-->>SynoptikPage: FleetOverviewOut | undefined
SynoptikPage->>SynoptikView: user + initialData
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
frontend/lib/synoptic3d/placeholder-proportions.test.ts (1)
12-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
mixing_unitfehlt in der Testabdeckung der bekannten Klassen.In
frontend/lib/synoptic3d/placeholder-proportions.tsistmixing_unitebenfalls Teil der bekannten Tabelle, aber hier wird weder seine Shape-Zuordnung noch sein Dimensionsvertrag geprüft. Ein Regression dort würde aktuell grün bleiben.Vorgeschlagene Ergänzung
it("gibt jeder bekannten Klasse ihre eigene Silhouette", () => { expect(proportionsFor("feeder").shape).toBe("conveyor"); expect(proportionsFor("servo_press").shape).toBe("press"); expect(proportionsFor("servo_axis").shape).toBe("axis"); expect(proportionsFor("robot").shape).toBe("robot"); expect(proportionsFor("vision").shape).toBe("vision"); + expect(proportionsFor("mixing_unit").shape).toBe("generic"); }); @@ - for (const c of ["feeder", "servo_press", "servo_axis", "robot", "vision", null]) { + for (const c of ["feeder", "servo_press", "servo_axis", "robot", "vision", "mixing_unit", null]) { const p = proportionsFor(c); expect(p.width).toBeGreaterThan(0); expect(p.height).toBeGreaterThan(0); expect(p.depth).toBeGreaterThan(0); }Also applies to: 37-44
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/lib/synoptic3d/placeholder-proportions.test.ts` around lines 12 - 18, The known-classes coverage in the `proportionsFor` test is missing `mixing_unit`, so add assertions for that entry alongside the existing shape checks. Extend the `placeholder-proportions.test.ts` suite to verify `proportionsFor("mixing_unit")` returns the expected `shape` and also covers its dimension contract, matching the other known class cases.frontend/components/synoptik/synoptik-view.tsx (1)
46-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptional: SSR-Snapshot-Überbrückung als Helper extrahieren.
Die Logik
live.kind === "loading" && initialData ? { kind: "cached", data } : liveist identisch zuCockpitView(cockpit-view.tsx, Zeile 60-61). Ein gemeinsamer Helper (z. B.bridgeWithSnapshot(live, initialData)) hält das Verhalten zwischen beiden Ansichten konsistent, falls sich die Übergangslogik später ändert.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/components/synoptik/synoptik-view.tsx` around lines 46 - 48, The SSR snapshot bridging logic in synoptik-view should be extracted into a shared helper to match CockpitView and keep both views consistent. Move the `live.kind === "loading" && initialData ? { kind: "cached", data: initialData } : live` pattern into a reusable function such as `bridgeWithSnapshot(live, initialData)`, then call that helper from `SynoptikView` and `CockpitView` so any future changes to the transition behavior happen in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/components/synoptik/scene/lighting.ts`:
- Around line 31-42: The PMREM cleanup in lighting.ts is only disposing the
texture, but PMREMGenerator.fromScene returns a WebGLRenderTarget that must be
released separately. Update the code around the PMREMGenerator/fromScene setup
to store the returned target (for example alongside envTexture), and in the
returned dispose() implementation free that target explicitly before disposing
the PMREMGenerator and room resources.
In `@frontend/components/synoptik/scene/placeholders.ts`:
- Around line 99-105: The status ring is rendered in the placeholder mesh setup
but never registered as an interaction target, so hover/click never reaches it.
Update the placeholder creation logic in the ring mesh path (the ring generation
in the scene placeholders helper) to expose the ring as a pickable target, and
then add that ring mesh to the interaction target list consumed by
synoptik-scene’s handle.pickTargets so the existing picking logic can detect it.
In `@frontend/components/synoptik/synoptik-scene.tsx`:
- Around line 202-210: The handleClick logic in synoptik-scene.tsx is treating
every mouse up as a selection, which can trigger onSelectMachine after an
OrbitControls drag. Update handleClick to distinguish a real click from a camera
drag by tracking whether the pointer interaction moved/dragged (likely via
updatePointer and related pointer state in SynoptikScene), and only call
pickMachineId/onSelectMachine when the interaction was not a drag.
In `@frontend/lib/api/overview-snapshot.ts`:
- Around line 19-30: The SSR fetch in the overview snapshot helper can hang
indefinitely because it has no timeout. Update the fetch call inside the
overview snapshot function to use AbortSignal.timeout(...) with a reasonable
limit (for example 5_000 ms), and keep the existing error handling so timeouts
still return undefined. Use the overview snapshot fetch logic in
overview-snapshot.ts and the backendUrl() / fetch() call site to locate the
change.
In `@frontend/lib/synoptic3d/testing/fixtures.ts`:
- Around line 35-48: Align the PARK fixture in fixtures.ts with the scenario
described by the comment: currently only the PARK entries with
machineClass/status combinations in the PARK array are inconsistent with the
stated drift set. Update either the comment or the ParkSpec statuses so that
only the intended machines are marked drift_active and PR-02/VS-01 remain
open_warning; use the PARK constant and its ParkSpec entries to verify the
scenario stays consistent.
---
Nitpick comments:
In `@frontend/components/synoptik/synoptik-view.tsx`:
- Around line 46-48: The SSR snapshot bridging logic in synoptik-view should be
extracted into a shared helper to match CockpitView and keep both views
consistent. Move the `live.kind === "loading" && initialData ? { kind: "cached",
data: initialData } : live` pattern into a reusable function such as
`bridgeWithSnapshot(live, initialData)`, then call that helper from
`SynoptikView` and `CockpitView` so any future changes to the transition
behavior happen in one place.
In `@frontend/lib/synoptic3d/placeholder-proportions.test.ts`:
- Around line 12-18: The known-classes coverage in the `proportionsFor` test is
missing `mixing_unit`, so add assertions for that entry alongside the existing
shape checks. Extend the `placeholder-proportions.test.ts` suite to verify
`proportionsFor("mixing_unit")` returns the expected `shape` and also covers its
dimension contract, matching the other known class cases.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ff36f704-8a7b-4af8-aa98-533722d001c4
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (31)
frontend/app/(app)/overview/page.tsxfrontend/app/(app)/synoptik/page.tsxfrontend/components/cockpit/cockpit-view-switch.test.tsxfrontend/components/cockpit/cockpit-view-switch.tsxfrontend/components/cockpit/cockpit-view.tsxfrontend/components/synoptik/scene/colors.tsfrontend/components/synoptik/scene/floor.tsfrontend/components/synoptik/scene/lighting.tsfrontend/components/synoptik/scene/loaders.tsfrontend/components/synoptik/scene/placeholders.tsfrontend/components/synoptik/synoptik-legend.test.tsxfrontend/components/synoptik/synoptik-legend.tsxfrontend/components/synoptik/synoptik-machine-list.test.tsxfrontend/components/synoptik/synoptik-machine-list.tsxfrontend/components/synoptik/synoptik-scene.test.tsxfrontend/components/synoptik/synoptik-scene.tsxfrontend/components/synoptik/synoptik-view.test.tsxfrontend/components/synoptik/synoptik-view.tsxfrontend/lib/api/overview-snapshot.tsfrontend/lib/machine/grouping.tsfrontend/lib/synoptic3d/layout.test.tsfrontend/lib/synoptic3d/layout.tsfrontend/lib/synoptic3d/manifest.test.tsfrontend/lib/synoptic3d/manifest.tsfrontend/lib/synoptic3d/placeholder-proportions.test.tsfrontend/lib/synoptic3d/placeholder-proportions.tsfrontend/lib/synoptic3d/status-color.test.tsfrontend/lib/synoptic3d/status-color.tsfrontend/lib/synoptic3d/testing/fixtures.tsfrontend/lib/synoptic3d/types.tsfrontend/package.json
…Pick, Timeout) - lighting.ts: PMREM-Render-Target (nicht nur .texture) freigeben — sonst bleiben Framebuffer/Renderbuffer des WebGLRenderTarget liegen. - synoptik-scene.tsx: Klick von Kamera-Drag trennen (Distanz-Schwelle zwischen pointerdown und click) — eine OrbitControls-Rotation löst kein onSelectMachine mehr aus. - placeholders.ts: den sichtbaren Status-Ring als Pick-Ziel registrieren, damit Klick/Hover genau auf der Statusfläche greifen. - overview-snapshot.ts: SSR-Fetch mit AbortSignal.timeout(5s) absichern — ein hängendes Backend blockiert das Rendering nicht mehr (Node ≥ 20). - fixtures.ts: Kommentar an die tatsächlichen Statuswerte angeglichen (drift_active vs. open_warning). Gates grün: tsc 0, eslint 0, vitest 708, tokens synchron, next build ok. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CodeRabbit-Findings abgearbeitet (Commit
|
| Finding | Datei | Fix |
|---|---|---|
| 🟠 PMREM-Render-Target-Leck | scene/lighting.ts |
fromScene()-Target gehalten und envTarget.dispose() statt nur .texture — gibt Framebuffer/Renderbuffer mit frei |
| 🟠 Klick vs. Kamera-Drag | synoptik-scene.tsx |
Distanz-Schwelle (6 px) zwischen pointerdown und click → eine OrbitControls-Rotation löst kein onSelectMachine mehr aus |
| 🟡 Status-Ring nicht pickbar | scene/placeholders.ts |
Ring in pickTargets registriert (klick-/hoverbar) |
| 🟠 SSR-Fetch ohne Timeout | lib/api/overview-snapshot.ts |
AbortSignal.timeout(5_000) — hängendes Backend blockiert das Rendering nicht mehr |
| 🟡 Kommentar/Daten-Mismatch | lib/synoptic3d/testing/fixtures.ts |
Kommentar an die tatsächlichen Statuswerte (drift_active vs. open_warning) angeglichen |
Gates grün: tsc 0 · eslint 0 · vitest 708 · next build ok.
…änzt three (0 Dependencies) + @types/three samt Leaf-Deps in den unveränderten, CI-grünen main-Lock eingefügt; der @emnapi/wasm-Optional-Baum bleibt byte-identisch zu main. Vermeidet die npm-Versions-/Plattform-Diskrepanz, die beim vollständigen Neu-Auflösen auf Windows @emnapi/wasi-threads bumpte (npm ci out of sync auf Linux). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
0fa961e to
ee90192
Compare
Was
Neue datengetriebene FE-Sektion Anlagen-Synoptik 3D (
/synoptik): eine interaktive Three.js-Liniensicht der Montagelinie 1 — 12 Maschinen in kanonischer Sequenz (Fördern → Pressen → Handling → Bestücken → Endkontrolle), Live-Status, klickbar → kanonische Maschinenkarte.Gebaut gegen saubere Platzhalter-Geometrie (wie beauftragt). Der Realismus kommt später aus den GLB-Modellen — die datengetriebene Mechanik steht jetzt und ist getestet. GLB-Tausch = Asset-Swap über ein Manifest, kein Renderer-Umbau.
Verortung (Entscheidung mit Patric)
/overviewist exakt fürmanager/shift_leadautorisiert, deckungsgleich mit Sektion A.overview-WS-Topics (gleiche Quelle wie Cockpit/Stream-Kachel).Architektur
lib/synoptic3d/, voll Vitest-getestet):layout.ts(Sequenz aus geteiltemSTAGE_ORDER),manifest.ts(Swap-Naht),status-color.ts(Status→FCSM-Token, eine Quelle wie Cockpit),placeholder-proportions.ts(Silhouetten je Klasse)./overview).Abnahmekriterien
/overviewmachineHref, loser Vertrag)Gates grün:
tsc --noEmit0 ·eslint0 ·vitest708 (+29) ·tokens:checksynchron ·next buildok (three nur auf der/synoptik-Route gebündelt: 196 kB).Adversarialer Review (Multi-Agent)
8 Findings bestätigt, 6 verworfen. 5 jetzt gefixt: Touch-Mindesthöhe der Maschinenliste (§5.4),
RoomEnvironment-Dispose-Leck, Fremdklassen-Verschmelzung im Layout, Fokusring-Token, internes Akronym „HITL" aus dem sichtbaren UI.3 bewusst in die GLB-Swap-Phase verschoben (betreffen nur den ruhenden GLB-Pfad — heute kein Impact, vom Verifier bestätigt; als TODO am Seam + im GLB-Vertrag dokumentiert): GLB-Dispose/Unmount-Race, GLB-Raycast-Registrierung (Klick-Vertrag), Beacon/Ring-Reposition auf echte GLB-Höhe.
Nächste Phase
Die optimierten GLBs liegen bereits unter
docs/FOREMAN-3D/models(feeder/servo_press/servo_axis/robot/vision, inkl. KTX2-Varianten). Der Swap (Assets nachpublic/, Manifest-Einträge + Transforms, die 3 offenen Renderer-Punkte, visuelle Maßstab-/Pivot-Kalibrierung) folgt als eigener, visuell verifizierter PR.🤖 Generated with Claude Code
Summary by CodeRabbit