|
| 1 | +# Code-Review: Uncommittete React-Änderungen (`components/react`) |
| 2 | + |
| 3 | +> Multi-Perspektiven-Adversarial-Review (47 Agents: 8 Lenses → Skeptiker pro Finding → Synthese) der 73 |
| 4 | +> uncommitteten Working-Tree-Änderungen (50 modifiziert + 23 neu). 37/38 Findings bestätigt, 1 verworfen. |
| 5 | +> Status: ❌ offen · ✅ behoben · ⁉️ kein echtes Issue. **Diese Änderungen gehören dir (WIP) — der Review ist |
| 6 | +> die Bewertung, das Beheben ist deine Entscheidung.** |
| 7 | +
|
| 8 | +## Verdict |
| 9 | + |
| 10 | +Funktional weit gediehen, aber **noch nicht commit-reif**. **11 eindeutige MAJOR-Root-Causes** (13 Einträge), |
| 11 | +davon drei färben Tooling/CI sofort rot und blockieren/gefährden den `yalc push` an die 7 Konsumenten: |
| 12 | + |
| 13 | +- **TS-1** (`coreActions.ts`): `tsc -p tsconfig.lib.json --noEmit` → 2× `TS2345` (verifiziert). `pnpm build` läuft |
| 14 | + durch (`noEmitOnError:false`), aber das typecheck-Gate ist rot. |
| 15 | +- **SLOP-1** (`behaviourEntryIntegrity.test.ts`): committeter Harness-Test rot — 30 Failures, davon 7 Map + 12 |
| 16 | + EmojiPicker aus diesem Changeset (verifiziert). |
| 17 | +- **CONV-1** (`iconBadge/Patterns.tsx`): `pnpm lint` rot (verbotene `text-stone-800`). |
| 18 | + |
| 19 | +Zwei echte Laufzeit-/Korrektheitsdefekte: **SLOP-2** (Suche im HistoryPanel für alle übersetzten Actions kaputt), |
| 20 | +**SLOP-3** (Undo-Stack-Korruption bei Teilgruppen-Undo → State-Korruption). Dazu A11y-/i18n-Lücken in neuen, an |
| 21 | +7 Apps ausgelieferten Komponenten (A11Y-1/-2/-4/-5), Test-Lücken (QA-1/-2/-3) und eine Drag-Perf-Regression (PERF-1). |
| 22 | +Keine CRITICAL. Vor dem Commit sollten mindestens alle MAJOR (v. a. die Tooling-Roten) behoben sein. |
| 23 | + |
| 24 | +| Severity | Anzahl | |
| 25 | +|----------|--------| |
| 26 | +| Critical | 0 | |
| 27 | +| Major | 11 | |
| 28 | +| Minor | 18 | |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +## Major |
| 33 | + |
| 34 | +### ❌ [TS-1] — lib/lib/mowsContext/coreActions.ts:364,374,386,392 |
| 35 | +`REPLACE_SETTINGS_BLOB` ruft `parseBlobPayload(payload, "...")` ohne Typargument; `P` → `{}` → an |
| 36 | +`replaceBlob(next: SettingsBlob)`. `tsc --noEmit` → `TS2345` (374,66)/(392,66). **Fix:** `import type { SettingsBlob }` |
| 37 | ++ `parseBlobPayload<SettingsBlob>(...)`; mit `tsc -p tsconfig.lib.json --noEmit` verifizieren. |
| 38 | + |
| 39 | +### ❌ [SLOP-1] — src/examples/map/MapDocPage.tsx:101-107 + src/examples/emojiPicker/EmojiPickerDocPage.tsx:119-130 |
| 40 | +`Map.test.tsx`/`EmojiPicker.test.tsx` geändert, aber `<BehaviourList>`-`testLine`-Felder nicht nachgezogen → |
| 41 | +`behaviourEntryIntegrity.test.ts` rot. **Fix:** testLine neu ableiten — Map `215→264,220→269,227→276,239→288,254→303,272→321,294→343`; |
| 42 | +EmojiPicker alle 12 um +6. (Die 11 `sidebar`-Failures sind pre-existing Drift, nicht dieses Changeset.) |
| 43 | + |
| 44 | +### ❌ [CONV-1] — src/examples/iconBadge/Patterns.tsx:45-46 |
| 45 | +2× `text-stone-800` (verbotene Neutral-Palette) → `eslint` rot. **Fix:** Farb-Klasse entfernen, einmalig per |
| 46 | +Inline-Style (`color:'#1c1917'`) auf dem Wrapper-Div (Lucide erbt `currentColor`); **nicht** `text-foreground`. |
| 47 | + |
| 48 | +### ❌ [SLOP-2] — lib/components/appShell/historyPanel/HistoryPanel.tsx:150-157 vs 226-231 |
| 49 | +Suchfilter matched Roh-`actionId`, angezeigt wird übersetztes `formatActionLabel(...)` → Suche nach sichtbarem Text |
| 50 | +findet für alle realen Core-Actions nichts. **Fix:** aufgelöstes Label einmal berechnen, für Suche+Render nutzen; |
| 51 | +Regressionstest mit echtem Übersetzungsschlüssel. |
| 52 | + |
| 53 | +### ❌ [SLOP-3] — lib/lib/mowsContext/ActionManager.tsx:813-866 (`runInverts`) |
| 54 | +Bei Undo einer Transaktionsgruppe: trifft ein Entry ohne registrierten Handler → früher `return`, aber bereits |
| 55 | +invertierte Entries werden NICHT vom Stack gepoppt → Undo-Stack inkonsistent → nächstes Ctrl+Z invertiert erneut → |
| 56 | +**Dokument-State-Korruption**. **Fix:** Gruppen-Inversion stackseitig atomar machen (invertierte sammeln, vor jedem |
| 57 | +frühen `return` poppen + auf Redo schieben); Regressionstests. |
| 58 | + |
| 59 | +### ❌ [A11Y-1] — lib/components/map/weatherExpandable/WeatherExpandable.tsx:212-217 |
| 60 | +`role="region" aria-label` liegt INNERHALB des `<CollapsibleTrigger>`-Buttons → ungültiges ARIA, Screenreader prunen |
| 61 | +den einzigen accessible Name. **Fix:** role/aria-label vom Header-Div weg; `regionLabel?`-Passthrough zu |
| 62 | +`ExpandableSection`, auf dem äußeren Collapsible-Div setzen. |
| 63 | + |
| 64 | +### ❌ [A11Y-2] — lib/components/chat/Chat/Composer.tsx:145-178,270,391-392 |
| 65 | +Voice/Attachment geben AT keine Rückmeldung: Fehlerzeile (270) ohne `role="alert"`, Recording-Toggle ohne |
| 66 | +`aria-live`. WCAG 4.1.3. **Fix:** `role="alert" aria-live="assertive"` auf die Fehlerzeile; `role="status"`-Live-Region |
| 67 | +für Recording-State; Strings in beide Locales. |
| 68 | + |
| 69 | +### ❌ [A11Y-4] — lib/components/map/Map.tsx:487,539-694 |
| 70 | +Neuer 3D-Buildings-Toggle + gesamter Control-Stack + `Map failed to load` sind hartkodiertes Englisch; `Map` liest |
| 71 | +`context.t` nicht. **Fix:** Übersetzungen via `MowsContext.t` durchreichen (`map`-Sektion in beide Locales); |
| 72 | +mindestens die zwei neuen 3D-Labels. |
| 73 | + |
| 74 | +### ❌ [A11Y-5] — lib/components/dateTime/scheduler/TimeGridView.tsx:157-220,359-392 |
| 75 | +Drag-to-Reschedule ist pointer-only — kein Tastatur-Pfad, keine `aria-live`-Ansage. **Fix:** `onKeyDown` |
| 76 | +(Arrow nudgt um ±slotMinutes / ±1 Tag, ruft `onItemMove`), Live-Region für neue Zeit; Tests. |
| 77 | + |
| 78 | +### ❌ [QA-1] — lib/components/chat/Chat/Composer.tsx:357-383 (Chat.media.test.tsx fehlt) |
| 79 | +`enableEmojiPicker` komplett ungetestet (Caret-Insert, `maxBodyLength`-Guard, Focus-Restore). **Fix:** Tests im |
| 80 | +`Chat — composer opt-ins`-Block. |
| 81 | + |
| 82 | +### ❌ [QA-2] — lib/components/chat/Chat/Chat.tsx:205-294,462-475 (Chat.test.tsx) |
| 83 | +Die 3 jüngsten Scroll/Paging-Bugfixes haben keine Regressionstests (`handleScroll`→`maybeLoadOlder`, `stickyBottom`, |
| 84 | +`pendingNewCount`-Pill, `scrollToBottom`-Handle). **Fix:** Tests gegen den realen Outer-Node (AutoSizer gemockt). |
| 85 | + |
| 86 | +### ❌ [QA-3] — lib/components/settings/settingsPanel/SettingsPanel.tsx:435-455 (SettingsPanel.test.tsx) |
| 87 | +Neue Temperatureinheit-Select treibt `setTemperatureUnit` — kein Test rendert die Zeile/asserted den Aufruf. |
| 88 | +**Fix:** Select öffnen+wählen, `setTemperatureUnit`-Aufruf asserten; `WeatherExpandable` Context-Fallback testen. |
| 89 | + |
| 90 | +### ❌ [PERF-1] — lib/components/dateTime/scheduler/TimeGridView.tsx:176-220,320-456 |
| 91 | +Drag rendert die ganze View pro `pointermove` (~60-120Hz): `layoutDayEvents` für alle 7 Spalten + `new |
| 92 | +Intl.DateTimeFormat` pro Event pro Frame. **Fix:** Formatter `useMemo([locale])`, `dayLayouts` memoisieren, |
| 93 | +`onMove` per rAF-Latch throtteln. |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +## Minor |
| 98 | +- **[REACT-1]** Chat.tsx:176-203 — `setMeasured` re-ankert rein positional → async Media-Höhen reißen Nutzer nach unten. Gesten-Latch. |
| 99 | +- **[REACT-2]** MowsContext.tsx:597-614 — `setLanguage`/`setTheme` `setState` nach `await` ohne Unmount-Guard. |
| 100 | +- **[REACT-3/SLOP-4]** useScheduler.ts:45-67 — `useCallback`-Deps auf Inline-`opts`-Objekt → tote Memoisierung. |
| 101 | +- **[REACT-4]** TimeGridView.tsx:176-220 — Pointer-Drag-Effekt re-attached Window-Listener pro Frame. |
| 102 | +- **[REACT-5]** Chat.tsx:243 — Auto-Scroll-`requestAnimationFrame` ohne Cleanup/cancel. |
| 103 | +- **[REACT-6]** Composer.tsx:100-109 — `useImperativeHandle` ohne Dep-Array. |
| 104 | +- **[TS-2]** MowsContext.tsx:73-85 — `adapterToAuthContext` Doppel-Cast `as unknown as AuthContextProps` (5/17 Member) → besiegte Typgarantie. Surface verengen. |
| 105 | +- **[TS-3]** EmojiPicker.tsx:120-126 — `readJson<T> as T` ohne Runtime-Check; korrupte `recents` (Nicht-Array) → `.map`-Crash. Validieren. |
| 106 | +- **[A11Y-3]** coreActions.ts UNDO/REDO — hartkodiertes `Nothing to undo/redo` (+LOGIN/LOGOUT) im Tooltip. |
| 107 | +- **[A11Y-6]** Composer.tsx:286-291 — gestagtes `<video>`/`<audio>` ohne aria-label/Dateiname. |
| 108 | +- **[A11Y-7]** WeatherExpandable.tsx:326 — Forecast-Strip ohne `tabIndex`/fokussierbare Kinder → kein Keyboard-Scroll. |
| 109 | +- **[A11Y-8]** CoordinateLinks.tsx:98,126 — `Open in`/`default map app` hartkodiertes Englisch im einzigen accessible Name. |
| 110 | +- **[QA-4]** ActionManager.tsx:697-725 — Audit-Log-Quota-Eviction-Pfad ungetestet. |
| 111 | +- **[QA-5]** scheduler/eventLayout.ts — reine Geometrie (Packing/Clipping/Snap) ohne `eventLayout.test.ts`. |
| 112 | +- **[QA-6]** openingHours/OpeningHours.tsx:237-246 — `locale`-aus-Context-Fallback ungetestet. |
| 113 | +- **[QA-7]** weatherExpandable/icons.tsx:34-65 — `resolveWeatherLucideIcon` nur 1/12 Mappings getestet (Emoji hat Parity-Test). |
| 114 | +- **[PERF-2..7]** Diverse: Intl-Formatter in Render-Loops (calendarMath/Views), O(days×events)-Filter ohne Bucketing/`React.memo`, `MessageRow` nicht memoisiert + inline `itemKey`, `searchEmojis` allokiert Heuhaufen pro Keystroke, `getRecentCommands` sortiert in-place beim Read, `OpeningHours` `week`-useMemo rebaut pro Minute. |
| 115 | + |
| 116 | +## Geprüft & verworfen |
| 117 | +- **SEC-1** (CoordinateLinks `buildUrl`): kein Sicherheitsproblem — App-Code im eigenen Trust-Boundary, lat/lng streng validiert, `_blank`+`rel=noopener` neutralisiert `javascript:`. Severity none. |
| 118 | +- **SEC-2** (Attachment-URLs in media.tsx/MessageRow): out-of-scope — unverändert ggü. HEAD (bereits in 2c643d07 committet); uncommittete Chat-Änderungen sind XSS-sicher (escaped React-Text). |
0 commit comments