feat(f5-fe): machine detail (Section B) with bespoke TimeSeriesChart - #26
Conversation
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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughNeue Frontend-Sektion B (Maschinen-Detail): API-Verträge, transport-agnostische Lib-Utilities ( ChangesMaschinen-Detail-Ansicht (Sektion B)
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (5)
frontend/lib/machine/trend-series.ts (1)
18-29: 💤 Low valueOptional: Erwäge defensive Prüfung für Date.parse-Fehler.
Date.parsegibt bei ungültigen DatumswertenNaNzurü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 valueOptional: Erwäge Runtime-Validierung der API-Antwort.
Die Antwort wird direkt als
MachineTrendOutgecastet, ohne Struktur-Validierung. Wenn das Backend fehlerhafte Daten liefert (z. B. fehlende Felder), könnten nachgelagerte Fehler inmergeTrendSeriesoderderiveDriftSegmentsauftreten. 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 winEncoding-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 winFallback-Pfad explizit testen.
Die Matrix-Rollen sind gut abgedeckt, aber der dokumentierte Fallback-Pfad fehlt als Regressionstest. Das macht Änderungen an
FALLBACK_VIEWanfä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 winEs 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
profileBandeine tatsächlich gezeichnete Referenzlinie (inkl. nichtleeremd) 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
📒 Files selected for processing (44)
GROUND_TRUTH.mddocs/WALKTHROUGH.mdfrontend/app/(app)/machines/[id]/page.tsxfrontend/app/(app)/machines/page.tsxfrontend/components/machine/machine-alarms.test.tsxfrontend/components/machine/machine-alarms.tsxfrontend/components/machine/machine-cross-links.test.tsxfrontend/components/machine/machine-cross-links.tsxfrontend/components/machine/machine-detail-view.test.tsxfrontend/components/machine/machine-detail-view.tsxfrontend/components/machine/machine-header.test.tsxfrontend/components/machine/machine-header.tsxfrontend/components/machine/machine-history.test.tsxfrontend/components/machine/machine-history.tsxfrontend/components/machine/machine-list.test.tsxfrontend/components/machine/machine-list.tsxfrontend/components/machine/machine-specs.test.tsxfrontend/components/machine/machine-specs.tsxfrontend/components/machine/machine-trend-panel.test.tsxfrontend/components/machine/machine-trend-panel.tsxfrontend/components/machine/sensor-picker.test.tsxfrontend/components/machine/sensor-picker.tsxfrontend/components/machine/time-series-chart.test.tsxfrontend/components/machine/time-series-chart.tsxfrontend/components/machine/time-window-picker.test.tsxfrontend/components/machine/time-window-picker.tsxfrontend/lib/api/contracts.tsfrontend/lib/machine/geometry.test.tsfrontend/lib/machine/geometry.tsfrontend/lib/machine/history.test.tsfrontend/lib/machine/history.tsfrontend/lib/machine/roles.test.tsfrontend/lib/machine/roles.tsfrontend/lib/machine/time-window.test.tsfrontend/lib/machine/time-window.tsfrontend/lib/machine/trend-series.test.tsfrontend/lib/machine/trend-series.tsfrontend/lib/machine/types.tsfrontend/lib/machine/url.test.tsfrontend/lib/machine/url.tsfrontend/lib/machine/use-machine-history.tsfrontend/lib/machine/use-machine-trend.tsfrontend/lib/ui/pii.test.tsfrontend/lib/ui/pii.ts
| const machines: ReadonlyMap<number, MachineMeta> = new Map([[machineId, { label: machineLabel, lineId }]]); | ||
| const now = nowMs ?? Date.now(); |
There was a problem hiding this comment.
Stale Zeitstempel im onShelve-Callback und ineffiziente Map-Neuerstellung
Zwei Probleme in diesem Code-Segment:
-
Funktionale Korrektheit (Minor):
nowwird bei jedem Render berechnet (nowMs ?? Date.now()) und imonShelve-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. -
Performance (Recommended): Die
machines-Map wird bei jedem Render neu erzeugt, was unnötige Allokationen verursacht und potentiell nachgelagerte Re-Renders auslöst, fallsbuildAlarmViewModeldie 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.
| {series.profileBand !== null ? ( | ||
| <path data-testid="profile-band" fill="none" stroke="var(--color-fg-secondary)" strokeDasharray="4 3" /> | ||
| ) : null} |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
| 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>
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) + Livetrend:{data_point_id}(by ID, pusht das ganze 1-h-Fenster neu) auf dembucket-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_bandnull → kein erfundener Strich), mehrkanalig (Linie+Fläche+Schraffur+aria). Drift ist ein Akzent, NIE Alarm-Rot.machine:{id}), Stammdaten, Historie (chronologisch, blätterbar, PII maskiert#hex6), offene Alarme über die wiederverwendete C-AlarmRow(maschinengefiltert, kein dupliziertes Rendering).ACCESS_MATRIX.B, ohne bedingte Hooks): Werker reduziert + Notiz / Schichtleiter voll + Vorhersage + quittieren / Techniker Diagnose-Tiefe + Offline-Cache / Manager verdichtet.lib/machine/(TDD), geteilte PII-Primitivelib/ui/pii.ts.Markierte Anschlusspunkte (bewusst, nicht erfunden)
F4-Eigenprofil-Overlay (
profile_bandreserviert/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 übermachine:{id}-WS-Snapshot; Querlinks J/D graceful.Getestet (lokal grün)
tsc --strict0 · ESLint 0 · Vitest 301 gesamt (58 neu für B) ·tokens:checksynchron ·next buildok (/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
Documentation