Skip to content

feat(f5-fe): machine detail (Section B) with bespoke TimeSeriesChart - #26

Merged
patricznr1 merged 2 commits into
mainfrom
feat/f5-fe-machine-detail
Jun 17, 2026
Merged

feat(f5-fe): machine detail (Section B) with bespoke TimeSeriesChart#26
patricznr1 merged 2 commits into
mainfrom
feat/f5-fe-machine-detail

Conversation

@patricznr1

@patricznr1 patricznr1 commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Was

Sektion B — Maschinen-Detail ([KERN STEHT]), die zentrale Drill-down-Sicht auf dem FE1-Fundament und Ziel vieler Querlinks (C/E/A/H → Maschine). Routen /machines (Übersicht/Landing) + /machines/[id] (Detail). Designgrundlage: Studie §4B (+ §2/§3.2/§5.4/§5.5/§5.6/§5.8).

Highlights

  • TimeSeriesChart — maßgeschneidertes, token-getriebenes SVG (bewusst KEINE Charting-Lib; hält das <100-kB-Erstbild-Ziel, volle Kontrolle über Mehrkanal-Kodierung, transport-agnostisch). Historischer /trend-Pull (by NAME) + Live trend:{data_point_id} (by ID, pusht das ganze 1-h-Fenster neu) auf dem bucket-Schlüssel verschmolzen → der Live-Rand atmet ohne Achsen-/Layout-Sprung. Normalband entsättigte Fläche, Drift als Differenzfläche (diff-over/diff-under + Schraffur), Eigenprofil graceful (profile_band null → kein erfundener Strich), mehrkanalig (Linie+Fläche+Schraffur+aria). Drift ist ein Akzent, NIE Alarm-Rot.
  • Kopf (Identität + FCSM groß, live über machine:{id}), Stammdaten, Historie (chronologisch, blätterbar, PII maskiert #hex6), offene Alarme über die wiederverwendete C-AlarmRow (maschinengefiltert, kein dupliziertes Rendering).
  • Rollen (Matrix 3.1, ACCESS_MATRIX.B, ohne bedingte Hooks): Werker reduziert + Notiz / Schichtleiter voll + Vorhersage + quittieren / Techniker Diagnose-Tiefe + Offline-Cache / Manager verdichtet.
  • HITL hart: Schnellaktionen (Notiz → J, Vorhersage → E, Ereigniskette → D) sind Navigation/Anforderung, nie Anlagen-Aktorik.
  • Reine, transport-agnostische Logik in lib/machine/ (TDD), geteilte PII-Primitive lib/ui/pii.ts.

Markierte Anschlusspunkte (bewusst, nicht erfunden)

F4-Eigenprofil-Overlay (profile_band reserviert/null) graceful; tiefe Zeitreise (Scrubbing/Monat/9-Monate) = [VISION], Komponente erweiterbar entworfen; GET /machines//alarms/Trend nicht server-scope-gefiltert → Rollen-Scope = UX-Filter, echte Grenze hält das Backend auf den WS-/Trend-Themen (§20.4); kein Einzelmaschinen-HTTP-Status für Werker/Techniker → FCSM über machine:{id}-WS-Snapshot; Querlinks J/D graceful.

Getestet (lokal grün)

tsc --strict 0 · ESLint 0 · Vitest 301 gesamt (58 neu für B) · tokens:check synchron · next build ok (/machines/[id] ~121 kB First Load — bespoke SVG ohne Charting-Lib). Hidden-Term-Scan sauber.

Doku (selber Commit, DoD)

GROUND_TRUTH §21.6-Tabelle + neues §21.11 · WALKTHROUGH Sektion B.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Neue Maschinen-Detailansicht mit Sensortrend (SVG), Zeitfensterauswahl und Drift-Erkennung.
    • Maschinen-Übersicht mit rollenbasierter Filterung sowie Direktfokus auf die Maschinenliste/Details.
    • Detail-UI mit Stammdaten, Sensor-Auswahl, Offene Alarme (inkl. Shelf/Quittierung) und chronologischer Wartungs-/Notizhistorie mit „Mehr laden“.
    • Rollenabhängige Schnellaktionen (Notiz, Vorhersage, Ereigniskette) und konsistente PII-Maskierung.
  • Documentation

    • Erweiterte Spezifikation und Walkthrough für „Maschinen-Detail“ (Sektion B).

First [KERN] section on the FE1 foundation: the central drill-down view
and target of many cross-links (C/E/A/H -> machine).

- bespoke token-driven SVG TimeSeriesChart (no charting lib, holds the
  <100kB first-load goal): merges historical /trend pull with the live
  trend:{data_point_id} window on the bucket key (live edge breathes, no
  axis jump), desaturated normal band, drift as difference area
  (diff-over/under + hatch), profile_band graceful (null -> no invented
  line), multi-channel encoding + aria.
- machine header (FCSM large, live via machine:{id}), specs, history
  (PII masked to #hex6), open alarms via the REUSED C AlarmRow
  (machine-filtered, no duplicate rendering), list + detail routes,
  role split per Matrix 3.1 (no conditional hooks).
- HITL: quick actions are navigation/request (note -> J, prediction
  -> E, chain -> D), never actuation.
- pure transport-agnostic logic in lib/machine/ (TDD), shared PII
  primitive in lib/ui/pii.ts.

Gates: tsc strict 0, eslint 0, vitest 301 (58 new), tokens:check, build.
GROUND_TRUTH section 21.11 + WALKTHROUGH updated (same commit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c89511aa-0a7b-4c1f-b34e-f8195f610c36

📥 Commits

Reviewing files that changed from the base of the PR and between ee015ce and 078f88f.

📒 Files selected for processing (3)
  • frontend/components/machine/machine-alarms.tsx
  • frontend/components/machine/time-series-chart.tsx
  • frontend/lib/ui/pii.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/lib/ui/pii.ts
  • frontend/components/machine/machine-alarms.tsx
  • frontend/components/machine/time-series-chart.tsx

📝 Walkthrough

Walkthrough

Neue Frontend-Sektion B (Maschinen-Detail): API-Verträge, transport-agnostische Lib-Utilities (geometry, history, trend-series, time-window, roles, url), Hooks (useMachineTrend, useMachineHistory), neun UI-Komponenten (inkl. benutzerdefiniertem SVG-TimeSeriesChart), Next.js-Routen /machines und /machines/[id] sowie ergänzte Spezifikationsdokumentation.

Changes

Maschinen-Detail-Ansicht (Sektion B)

Layer / Datei(en) Zusammenfassung
API-Verträge und View-State-Typen
frontend/lib/api/contracts.ts, frontend/lib/machine/types.ts, frontend/lib/ui/pii.ts
Neue Exportinterfaces MachineRead, ComponentRead, DataPointRead, MaintenanceEventRead, WorkerNoteRead in contracts.ts; View-State-Typen TrendSample, TrendSeries, DriftDirection, DriftSegment in types.ts; PII-Maskierungsfunktion maskPseudonym (kürzt HMAC-Token auf #hex6) in pii.ts mit Tests.
Rollenlogik, Zeitfenster, URL-Builder und SVG-Geometrie
frontend/lib/machine/roles.ts, frontend/lib/machine/time-window.ts, frontend/lib/machine/url.ts, frontend/lib/machine/geometry.ts (inkl. Tests)
machineRoleView liefert MachineRoleView-Capabilities pro Rolle mit defensivem FALLBACK_VIEW; TIME_WINDOWS definiert Schicht/Tag/Woche mit MAX_BACKEND_HOURS=168; BFF-URL-Builder für alle Maschinen-Endpunkte; DOM-freie SVG-Utilities scaleLinear und linePath.
Trend-Merge-Logik, Drift-Segmente und PII-maskierte Historie
frontend/lib/machine/trend-series.ts, frontend/lib/machine/history.ts (inkl. Tests)
toTrendSamples/mergeTrendSeries/deriveDriftSegments verarbeiten historische und Live-Trenddaten bucket-basiert zu TrendSeries mit Drift-Segmenten; buildHistory vereint MaintenanceEventRead und WorkerNoteRead mit maskPseudonym-Maskierung zu chronologisch sortierten MachineHistoryItem-Listen.
useMachineTrend und useMachineHistory
frontend/lib/machine/use-machine-trend.ts, frontend/lib/machine/use-machine-history.ts
useMachineTrend lädt historischen Trend per fetch mit AbortController, abonniert trend:{dataPointId}-Topic live, wählt DataState zwischen live/cached/loading/error/empty; useMachineHistory lädt paginierte Events und Notizen parallel, akkumuliert sie seitenweise und stellt loadMore/hasMore bereit.
Atomare UI-Komponenten: Picker und Chart
frontend/components/machine/sensor-picker.tsx, frontend/components/machine/time-window-picker.tsx, frontend/components/machine/time-series-chart.tsx, frontend/components/machine/machine-cross-links.tsx (inkl. Tests)
SensorPicker verwaltet aria-pressed und optionales max-Limit; TimeWindowPicker rendert Zeitfenster-Buttons; TimeSeriesChart rendert token-gesteuertes SVG mit Normalband, Drift-Differenzflächen (über/unter) und Schraffur sowie ARIA-Label; MachineCrossLinks zeigt rollenabhängige Schnellaktionen.
MachineList, Specs, Alarms, History, Header und TrendPanel
frontend/components/machine/machine-list.tsx, frontend/components/machine/machine-specs.tsx, frontend/components/machine/machine-alarms.tsx, frontend/components/machine/machine-history.tsx, frontend/components/machine/machine-header.tsx, frontend/components/machine/machine-trend-panel.tsx (inkl. Tests)
MachineList rendert Übersichtsliste mit Detaillinks; MachineSpecs zeigt Stammdaten/Komponenten/Datenpunkte; MachineAlarms filtert nach machineId mit Shelf-TTL; MachineHistory zeigt PII-maskierte Historienzeilen mit loadMore; MachineHeader abonniert machine:{id}-Realtime-Topic; MachineTrendPanel verdrahtet useMachineTrend mit TimeSeriesChart.
MachineDetailView und Next.js-Routen
frontend/components/machine/machine-detail-view.tsx, frontend/app/(app)/machines/[id]/page.tsx, frontend/app/(app)/machines/page.tsx (inkl. Tests)
MachineDetailView orchestriert alle Teilkomponenten mit rollenabhängiger Sensorreduktion und Zeitfenster-State; MachineDetailPage lädt SSR-Daten via authedJson mit requireSection("B"); MachinesPage ersetzt SectionPlaceholder durch fetchMachines + inScope-Rollenfilter.
Spezifikationsdokumentation
GROUND_TRUTH.md, docs/WALKTHROUGH.md
GROUND_TRUTH.md ergänzt §21.11 mit Kern-Komponenten, Rollenvarianten, HITL-Grenzen und PII-Anforderungen; WALKTHROUGH.md dokumentiert Trend-/Drift-Logik, SVG-Rendering-Strategie und Architekturprinzipien.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant MachinesPage as MachinesPage (SSR)
  participant MachineDetailPage as MachineDetailPage (SSR)
  participant Backend as Backend API
  participant MachineDetailView as MachineDetailView (Client)
  participant useMachineTrend
  participant RealtimeWS as Realtime-WS

  Browser->>MachinesPage: GET /machines
  MachinesPage->>Backend: GET /api/v1/machines?limit=1000 (Bearer)
  Backend-->>MachinesPage: MachineRead[]
  MachinesPage->>MachinesPage: inScope(user, machine) filtern
  MachinesPage-->>Browser: MachineList rendern

  Browser->>MachineDetailPage: GET /machines/[id]
  MachineDetailPage->>MachineDetailPage: requireSection("B")
  MachineDetailPage->>Backend: authedJson machineUrl, componentsUrl, dataPointsUrl
  Backend-->>MachineDetailPage: MachineRead, ComponentRead[], DataPointRead[]
  MachineDetailPage->>MachineDetailView: Props übergeben

  MachineDetailView->>useMachineTrend: machineId, dataPointId, hours
  useMachineTrend->>Backend: GET machineTrendUrl (AbortController)
  Backend-->>useMachineTrend: MachineTrendOut (historisch)
  useMachineTrend->>RealtimeWS: trend:{dataPointId} abonnieren
  RealtimeWS-->>useMachineTrend: MachineTrendOut (live)
  useMachineTrend->>useMachineTrend: mergeTrendSeries + deriveDriftSegments
  useMachineTrend-->>MachineDetailView: TrendSeries, DriftSegments, DataState
  MachineDetailView-->>Browser: TimeSeriesChart SVG rendern
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • patricznr1/foreman#23: Führt useAlarms und buildAlarmViewModel ein, die von MachineAlarms direkt importiert und verwendet werden.

Poem

🐇 Ein Hase baut die Detail-Welt,
SVG-Linien, die Drift erhellt.
Rollen prüfen, wer was sieht —
PII maskiert, bevor es blüht.
Trend aus Hist' und Live vereint,
bis jede Kurve sauber scheint! ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/f5-fe-machine-detail

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
frontend/lib/machine/trend-series.ts (1)

18-29: 💤 Low value

Optional: Erwäge defensive Prüfung für Date.parse-Fehler.

Date.parse gibt bei ungültigen Datumswerten NaN zurück, was die Sortierung und nachgelagerte Logik stören würde. Obwohl der Backend-Vertrag ISO-8601-Buckets garantiert, könnte eine defensive Prüfung (z. B. isNaN(t) filtern oder werfen) die Robustheit erhöhen.

🤖 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/machine/trend-series.ts` around lines 18 - 29, The
toTrendSamples function uses Date.parse on p.bucket which can return NaN for
invalid date strings, potentially breaking the sort operation and downstream
logic. Add a defensive check after the map operation to either filter out
entries where the parsed time t is NaN, or throw an error to alert callers of
invalid data, improving robustness even though the backend contract should
guarantee ISO-8601 formatted bucket values.
frontend/lib/machine/use-machine-trend.ts (1)

84-88: 💤 Low value

Optional: Erwäge Runtime-Validierung der API-Antwort.

Die Antwort wird direkt als MachineTrendOut gecastet, ohne Struktur-Validierung. Wenn das Backend fehlerhafte Daten liefert (z. B. fehlende Felder), könnten nachgelagerte Fehler in mergeTrendSeries oder deriveDriftSegments auftreten. Eine Zod-Schema-Validierung oder zumindest defensive Null-Checks würden die Robustheit erhöhen.

🤖 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/machine/use-machine-trend.ts` around lines 84 - 88, The API
response is being directly cast to MachineTrendOut without any runtime
validation. If the backend returns malformed or incomplete data, downstream
functions like mergeTrendSeries or deriveDriftSegments could fail unexpectedly.
Add runtime validation to the response object before casting it to
MachineTrendOut using either a Zod schema or defensive null-checks to ensure all
required fields are present with correct types. This will catch data structure
issues at the source rather than allowing them to propagate downstream.
frontend/lib/machine/url.test.ts (1)

22-26: ⚡ Quick win

Encoding-Testfall mit reservierten Zeichen ergänzen.

Der Testname sagt „encodiert“, prüft aber nur spindle_temp (kein Encoding nötig). Ein Fall mit Leerzeichen// sichert den eigentlichen Contract besser ab (Line 22).

Vorgeschlagene Ergänzung
   it("machineTrendUrl → datapoint + hours (encodiert)", () => {
     expect(machineTrendUrl(7, "spindle_temp", 24)).toBe(
       "/api/v1/machines/7/trend?datapoint=spindle_temp&hours=24",
     );
+    expect(machineTrendUrl(7, "spindle temp/°C", 24)).toBe(
+      "/api/v1/machines/7/trend?datapoint=spindle%20temp%2F%C2%B0C&hours=24",
+    );
   });
🤖 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/machine/url.test.ts` around lines 22 - 26, The test
"machineTrendUrl → datapoint + hours (encodiert)" claims to test encoding but
only uses a simple datapoint "spindle_temp" that doesn't require encoding. Add
an additional test case to the file that calls machineTrendUrl with a datapoint
containing reserved characters (such as spaces or forward slashes) and verify in
the assertion that these characters are properly URL-encoded in the resulting
URL string, thereby actually testing the encoding contract that the test name
suggests.
frontend/lib/machine/roles.test.ts (1)

9-39: ⚡ Quick win

Fallback-Pfad explizit testen.

Die Matrix-Rollen sind gut abgedeckt, aber der dokumentierte Fallback-Pfad fehlt als Regressionstest. Das macht Änderungen an FALLBACK_VIEW anfälliger für unbemerkte Regressionsfehler (Line 9).

Vorgeschlagener Test
 describe("machineRoleView", () => {
+  it("Unbekannte Rolle fällt stabil auf Fallback-View zurück", () => {
+    const v = machineRoleView("unknown_role" as never);
+    expect(v).toEqual(machineRoleView("worker"));
+  });
+
   it("Werker: Notiz ja, kein Vorhersage-Trigger, reduzierte Sensorauswahl", () => {
🤖 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/machine/roles.test.ts` around lines 9 - 39, The test suite for
machineRoleView covers all documented matrix roles but is missing a regression
test for the fallback path that handles invalid or unknown role inputs. Add a
new test case that calls machineRoleView with an invalid role string and verify
that it returns the expected FALLBACK_VIEW configuration with appropriate
property values. This ensures that changes to the fallback behavior are caught
during regression testing and prevents unintended side effects.
frontend/components/machine/time-series-chart.test.tsx (1)

76-101: ⚡ Quick win

Es fehlt ein Positivtest für profileBand !== null.

Aktuell prüfst du nur den Null-Fall (kein Strich). Ergänze bitte einen Test, der bei gesetztem profileBand eine tatsächlich gezeichnete Referenzlinie (inkl. nichtleerem d) erwartet. Das verhindert genau diese Regression künftig.

🤖 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/machine/time-series-chart.test.tsx` around lines 76 -
101, Add a positive test case after the existing "Eigenprofil graceful:
profileBand null" test that verifies the opposite scenario. When a profileBand
is provided to the TimeSeriesChart component, the test should render it with
non-null profileBand data and assert that the profile-band element is present in
the DOM (not null). Additionally, verify that the element contains a non-empty
`d` attribute to confirm the reference line is actually drawn. This ensures that
the profileBand functionality works correctly and prevents regression when
profileBand transitions from null to an actual value.
🤖 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/machine/machine-alarms.tsx`:
- Around line 46-47: The `now` variable calculated from `nowMs ?? Date.now()` is
captured in the `onShelve` callback closure, causing the TTL calculation to use
the render timestamp rather than the click timestamp, shortening the effective
shelf duration. Additionally, the `machines` Map is recreated on every render,
causing unnecessary allocations. Fix this by moving the `Date.now()` call inside
the `onShelve` callback to capture the current time at click time rather than
render time, and memoize the `machines` Map construction so it only recalculates
when `machineId` or `machineLabel` or `lineId` dependencies change, using
useMemo or similar memoization technique.

In `@frontend/components/machine/time-series-chart.tsx`:
- Around line 126-128: The path element for the profile band is rendered without
a `d` attribute, which is required to draw the SVG path, making the reference
line invisible. Add the `d` attribute to the path element within the condition
checking `series.profileBand !== null` and bind it to the appropriate value
derived from `series.profileBand` so the reference line is actually rendered.

In `@frontend/lib/machine/url.ts`:
- Around line 11-43: The URL builder functions currently interpolate numeric
parameters without validation, allowing invalid values like NaN, Infinity, or
negative numbers to be included in API requests. Add validation to the functions
machineTrendUrl, machinesUrl, maintenanceEventsUrl, and workerNotesUrl to ensure
that numeric parameters (hours, limit, offset, machineId) are valid positive
integers before they are used in the URL string construction. Validate that
values are not NaN or Infinity, and that limit and offset values are
non-negative integers to prevent malformed requests from reaching the backend.

In `@frontend/lib/ui/pii.ts`:
- Around line 17-23: The current implementation in the token parsing logic
removes invalid characters from the digest instead of strictly validating the
token format, which allows malformed tokens to still produce seemingly valid hex
values. Replace the lenient approach that strips non-hex characters with strict
validation: after extracting the digest (the portion after the colon separator),
verify that it contains ONLY hexadecimal characters and matches the expected
length requirement. If the digest contains any non-hex characters or fails
strict validation, return null immediately rather than attempting character
removal. This ensures only tokens matching the exact expected format are
accepted, following fail-closed principles for PII handling.

---

Nitpick comments:
In `@frontend/components/machine/time-series-chart.test.tsx`:
- Around line 76-101: Add a positive test case after the existing "Eigenprofil
graceful: profileBand null" test that verifies the opposite scenario. When a
profileBand is provided to the TimeSeriesChart component, the test should render
it with non-null profileBand data and assert that the profile-band element is
present in the DOM (not null). Additionally, verify that the element contains a
non-empty `d` attribute to confirm the reference line is actually drawn. This
ensures that the profileBand functionality works correctly and prevents
regression when profileBand transitions from null to an actual value.

In `@frontend/lib/machine/roles.test.ts`:
- Around line 9-39: The test suite for machineRoleView covers all documented
matrix roles but is missing a regression test for the fallback path that handles
invalid or unknown role inputs. Add a new test case that calls machineRoleView
with an invalid role string and verify that it returns the expected
FALLBACK_VIEW configuration with appropriate property values. This ensures that
changes to the fallback behavior are caught during regression testing and
prevents unintended side effects.

In `@frontend/lib/machine/trend-series.ts`:
- Around line 18-29: The toTrendSamples function uses Date.parse on p.bucket
which can return NaN for invalid date strings, potentially breaking the sort
operation and downstream logic. Add a defensive check after the map operation to
either filter out entries where the parsed time t is NaN, or throw an error to
alert callers of invalid data, improving robustness even though the backend
contract should guarantee ISO-8601 formatted bucket values.

In `@frontend/lib/machine/url.test.ts`:
- Around line 22-26: The test "machineTrendUrl → datapoint + hours (encodiert)"
claims to test encoding but only uses a simple datapoint "spindle_temp" that
doesn't require encoding. Add an additional test case to the file that calls
machineTrendUrl with a datapoint containing reserved characters (such as spaces
or forward slashes) and verify in the assertion that these characters are
properly URL-encoded in the resulting URL string, thereby actually testing the
encoding contract that the test name suggests.

In `@frontend/lib/machine/use-machine-trend.ts`:
- Around line 84-88: The API response is being directly cast to MachineTrendOut
without any runtime validation. If the backend returns malformed or incomplete
data, downstream functions like mergeTrendSeries or deriveDriftSegments could
fail unexpectedly. Add runtime validation to the response object before casting
it to MachineTrendOut using either a Zod schema or defensive null-checks to
ensure all required fields are present with correct types. This will catch data
structure issues at the source rather than allowing them to propagate
downstream.
🪄 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: b3e5e81e-bbbd-4f90-8a50-eb3eca710ff3

📥 Commits

Reviewing files that changed from the base of the PR and between bbffbeb and ee015ce.

📒 Files selected for processing (44)
  • GROUND_TRUTH.md
  • docs/WALKTHROUGH.md
  • frontend/app/(app)/machines/[id]/page.tsx
  • frontend/app/(app)/machines/page.tsx
  • frontend/components/machine/machine-alarms.test.tsx
  • frontend/components/machine/machine-alarms.tsx
  • frontend/components/machine/machine-cross-links.test.tsx
  • frontend/components/machine/machine-cross-links.tsx
  • frontend/components/machine/machine-detail-view.test.tsx
  • frontend/components/machine/machine-detail-view.tsx
  • frontend/components/machine/machine-header.test.tsx
  • frontend/components/machine/machine-header.tsx
  • frontend/components/machine/machine-history.test.tsx
  • frontend/components/machine/machine-history.tsx
  • frontend/components/machine/machine-list.test.tsx
  • frontend/components/machine/machine-list.tsx
  • frontend/components/machine/machine-specs.test.tsx
  • frontend/components/machine/machine-specs.tsx
  • frontend/components/machine/machine-trend-panel.test.tsx
  • frontend/components/machine/machine-trend-panel.tsx
  • frontend/components/machine/sensor-picker.test.tsx
  • frontend/components/machine/sensor-picker.tsx
  • frontend/components/machine/time-series-chart.test.tsx
  • frontend/components/machine/time-series-chart.tsx
  • frontend/components/machine/time-window-picker.test.tsx
  • frontend/components/machine/time-window-picker.tsx
  • frontend/lib/api/contracts.ts
  • frontend/lib/machine/geometry.test.ts
  • frontend/lib/machine/geometry.ts
  • frontend/lib/machine/history.test.ts
  • frontend/lib/machine/history.ts
  • frontend/lib/machine/roles.test.ts
  • frontend/lib/machine/roles.ts
  • frontend/lib/machine/time-window.test.ts
  • frontend/lib/machine/time-window.ts
  • frontend/lib/machine/trend-series.test.ts
  • frontend/lib/machine/trend-series.ts
  • frontend/lib/machine/types.ts
  • frontend/lib/machine/url.test.ts
  • frontend/lib/machine/url.ts
  • frontend/lib/machine/use-machine-history.ts
  • frontend/lib/machine/use-machine-trend.ts
  • frontend/lib/ui/pii.test.ts
  • frontend/lib/ui/pii.ts

Comment on lines +46 to +47
const machines: ReadonlyMap<number, MachineMeta> = new Map([[machineId, { label: machineLabel, lineId }]]);
const now = nowMs ?? Date.now();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale Zeitstempel im onShelve-Callback und ineffiziente Map-Neuerstellung

Zwei Probleme in diesem Code-Segment:

  1. Funktionale Korrektheit (Minor): now wird bei jedem Render berechnet (nowMs ?? Date.now()) und im onShelve-Callback (Zeile 50) aus der Closure erfasst. Wenn der Nutzer mehrere Sekunden nach dem letzten Render auf „Shelf" klickt, wird der TTL ab dem Render-Zeitpunkt berechnet, nicht ab dem Klick-Zeitpunkt. Das verkürzt die effektive Shelf-Dauer.

  2. Performance (Recommended): Die machines-Map wird bei jedem Render neu erzeugt, was unnötige Allokationen verursacht und potentiell nachgelagerte Re-Renders auslöst, falls buildAlarmViewModel die Map-Identität prüft.

🛠️ Vorgeschlagener Fix
+import { useMemo, useCallback } from "react";
-import { useState } from "react";

 export function MachineAlarms({
   machineId,
   machineLabel,
   lineId,
   canAcknowledge,
   nowMs,
 }: MachineAlarmsProps) {
   const online = useOnline();
   const { state, newIds, stampedAt, refetch } = useAlarms({ signalTopics: [`machine:${machineId}`] });
   const [shelf, setShelf] = useState<ReadonlyMap<number, number>>(new Map());

-  const machines: ReadonlyMap<number, MachineMeta> = new Map([[machineId, { label: machineLabel, lineId }]]);
-  const now = nowMs ?? Date.now();
+  const machines = useMemo(
+    () => new Map([[machineId, { label: machineLabel, lineId }]]) as ReadonlyMap<number, MachineMeta>,
+    [machineId, machineLabel, lineId]
+  );

-  const onShelve = (id: number): void => {
-    setShelf((prev) => new Map(prev).set(id, now + SHELF_TTL_MS));
-  };
+  const onShelve = useCallback((id: number): void => {
+    const now = nowMs ?? Date.now();
+    setShelf((prev) => new Map(prev).set(id, now + SHELF_TTL_MS));
+  }, [nowMs]);
+
-  const onUnshelve = (id: number): void => {
+  const onUnshelve = useCallback((id: number): void => {
     setShelf((prev) => {
       const next = new Map(prev);
       next.delete(id);
       return next;
     });
-  };
+  }, []);
+
+  const now = nowMs ?? Date.now();

Die Berechnung von now für die Nutzung in buildAlarmViewModel (Zeile 70) bleibt erhalten, wird aber nicht mehr im Callback erfasst.

🤖 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/machine/machine-alarms.tsx` around lines 46 - 47, The
`now` variable calculated from `nowMs ?? Date.now()` is captured in the
`onShelve` callback closure, causing the TTL calculation to use the render
timestamp rather than the click timestamp, shortening the effective shelf
duration. Additionally, the `machines` Map is recreated on every render, causing
unnecessary allocations. Fix this by moving the `Date.now()` call inside the
`onShelve` callback to capture the current time at click time rather than render
time, and memoize the `machines` Map construction so it only recalculates when
`machineId` or `machineLabel` or `lineId` dependencies change, using useMemo or
similar memoization technique.

Comment on lines +126 to +128
{series.profileBand !== null ? (
<path data-testid="profile-band" fill="none" stroke="var(--color-fg-secondary)" strokeDasharray="4 3" />
) : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

profileBand wird nie gezeichnet, obwohl es vorhanden ist.

Auf Line 127 renderst du bei series.profileBand !== null einen <path> ohne d-Attribut. Dadurch bleibt die Referenzlinie effektiv unsichtbar. Bitte d aus series.profileBand ableiten und an den Path binden, sonst ist das Feature funktional nicht vorhanden.

🤖 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/machine/time-series-chart.tsx` around lines 126 - 128,
The path element for the profile band is rendered without a `d` attribute, which
is required to draw the SVG path, making the reference line invisible. Add the
`d` attribute to the path element within the condition checking
`series.profileBand !== null` and bind it to the appropriate value derived from
`series.profileBand` so the reference line is actually rendered.

Comment thread frontend/lib/machine/url.ts
Comment thread frontend/lib/ui/pii.ts Outdated
Comment on lines +17 to +23
const separator = token.indexOf(":");
const digest = separator >= 0 ? token.slice(separator + 1) : token;
const hex = digest
.replace(/[^a-fA-F0-9]/g, "")
.slice(0, 6)
.toLowerCase();
return hex.length > 0 ? `#${hex}` : null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ungültige Pseudonym-Tokens sollten fail-closed zu null führen.

In Line 17–23 werden Nicht-Hex-Zeichen entfernt; dadurch können fehlerhafte Tokens (z. B. v1:xyz123) trotzdem als gültig wirkendes Handle ausgegeben werden. Für die PII-Disziplin besser nur strikt erwartete Token-Formate akzeptieren, sonst null.

🔒 Vorschlag für eine striktere Validierung
 export function maskPseudonym(token: string | null | undefined): string | null {
   if (!token) {
     return null;
   }
-  const separator = token.indexOf(":");
-  const digest = separator >= 0 ? token.slice(separator + 1) : token;
-  const hex = digest
-    .replace(/[^a-fA-F0-9]/g, "")
-    .slice(0, 6)
-    .toLowerCase();
-  return hex.length > 0 ? `#${hex}` : null;
+  const prefixed = token.match(/^v\d+:([a-fA-F0-9]+)$/);
+  const unprefixed = token.match(/^([a-fA-F0-9]+)$/);
+  const digest = prefixed?.[1] ?? unprefixed?.[1];
+  if (!digest) {
+    return null;
+  }
+  return `#${digest.slice(0, 6).toLowerCase()}`;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const separator = token.indexOf(":");
const digest = separator >= 0 ? token.slice(separator + 1) : token;
const hex = digest
.replace(/[^a-fA-F0-9]/g, "")
.slice(0, 6)
.toLowerCase();
return hex.length > 0 ? `#${hex}` : null;
export function maskPseudonym(token: string | null | undefined): string | null {
if (!token) {
return null;
}
const prefixed = token.match(/^v\d+:([a-fA-F0-9]+)$/);
const unprefixed = token.match(/^([a-fA-F0-9]+)$/);
const digest = prefixed?.[1] ?? unprefixed?.[1];
if (!digest) {
return null;
}
return `#${digest.slice(0, 6).toLowerCase()}`;
}
🤖 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/ui/pii.ts` around lines 17 - 23, The current implementation in
the token parsing logic removes invalid characters from the digest instead of
strictly validating the token format, which allows malformed tokens to still
produce seemingly valid hex values. Replace the lenient approach that strips
non-hex characters with strict validation: after extracting the digest (the
portion after the colon separator), verify that it contains ONLY hexadecimal
characters and matches the expected length requirement. If the digest contains
any non-hex characters or fails strict validation, return null immediately
rather than attempting character removal. This ensures only tokens matching the
exact expected format are accepted, following fail-closed principles for PII
handling.

- TimeSeriesChart: remove the dead profile-band <path> (it had no `d`
  attribute and was never drawn); profile_band stays graceful
  (reserved/null -> omitted, no placeholder), kept as a documented F4 seam.
- MachineAlarms: compute Date.now() inside onShelve so the shelf TTL
  starts at click time, not render time.
- lib/ui/pii.ts maskPseudonym: fail-closed — return null when fewer than
  6 hex chars remain (no partial handle).

Declined: per-builder numeric validation in lib/machine/url.ts — params
are number-typed and the [id] route guards Number.isInteger(id) && id>0
before any machine-scoped fetch; hours comes only from timeWindow. A throw
in pure URL builders would be speculative error handling (simplicity-first).

Gates: tsc strict 0, eslint 0, vitest green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant