From ab93113ce7b8c7b8ffa8e2a91ce51ee16d87cd04 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Thu, 14 May 2026 20:18:38 +0200 Subject: [PATCH] widgets: wrap each room + furniture widget in its own WidgetErrorBoundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The umbrella boundary on RoomWidgetsView caught any widget crash but unmounted every sibling along with the failing widget — a single bad parser in ChatWidget would dark out the avatar info, chat input, doorbell and all furniture overlays until the next remount. Wraps each of the 13 direct children of RoomWidgetsView (AvatarInfo, Chat, ChatInput, Doorbell, RoomTools, RoomFilterWords, RoomThumbnail, FurniChooser, PetPackage, UserChooser, WordQuiz, FriendRequest, plus the FurnitureWidgets umbrella) and each of the 20 sub-widgets inside FurnitureWidgetsView in its own named WidgetErrorBoundary. A crash now silently logs through NitroLogger with the widget name and renders null for that one widget; every sibling keeps rendering. The outer umbrella stays as defense-in-depth for the wrapper div and the listener setup in RoomWidgetsView itself. Closes the "Per-widget WidgetErrorBoundary wrapping" roadmap item; updates CLAUDE.md and docs/ARCHITECTURE.md accordingly. --- CLAUDE.md | 2 +- docs/ARCHITECTURE.md | 25 ++++------- .../room/widgets/RoomWidgetsView.tsx | 26 ++++++------ .../furniture/FurnitureWidgetsView.tsx | 41 ++++++++++--------- 4 files changed, 43 insertions(+), 51 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 982ba69..e9ab4ca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -260,7 +260,7 @@ into `configurePreviewServer` so `yarn preview` keeps working. | Zustand | `NavigatorRoomCreatorView` (`useRoomCreatorStore`) | | God-hook split (state + actions + shim) | `doorbell`, `poll`, `furni-chooser`, `user-chooser`, `friend-request`, `chat-input` | | God-hook split (`useBetween` singleton + state filter + actions filter + shim) | `wired-tools`, `translation`, `notification`, `friends`, `catalog` (three-way: `useCatalogData` / `useCatalogUiState` / `useCatalogActions` — all 48 consumers migrated, deprecated `useCatalog` shim removed) | -| `WidgetErrorBoundary` | `RoomWidgetsView` umbrella | +| `WidgetErrorBoundary` | `RoomWidgetsView` umbrella + per-widget wrap on all 13 room widgets and all 20 furniture widgets (so a crash in one widget no longer takes down its siblings) | | Vitest | 162/162 cases — pure helpers + Zustand store + 2 component-/hook-level pilots (WidgetErrorBoundary, useDoorbellState) on top of the renderer-SDK mock at `tests/mocks/renderer-mock.ts`, 34 cases on the catalog pure helpers, 4 contract cases on the catalog filters | | Form Actions | Login / Register / Forgot (LoginView.tsx) | | Cherry-picked from `duckietm` PR #126 | `UserAccountSettingsView` (reset password / email / username under user settings), plus the wear-badge popup `canShowWearButton` gating | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 18193a8..a305cea 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -302,12 +302,12 @@ takes down the whole UI. Implementation lives at `src/common/error-boundary/WidgetErrorBoundary.tsx`. **Status.** Implemented + applied to `RoomWidgetsView` as the umbrella for -all in-room widgets. A widget crash now degrades gracefully (the offending -widget disappears) instead of unmounting the room. - -A more granular pass could wrap each individual widget for finer-grained -fallbacks, but the umbrella alone already prevents the worst class of -failures. +all in-room widgets, **plus** a per-widget pass that wraps each of the 13 +direct children of `RoomWidgetsView` and each of the 20 sub-widgets in +`FurnitureWidgetsView`. A crash in any single widget now silently logs +through `NitroLogger` and renders `null` for that widget only — its +siblings keep rendering. Each boundary carries a `name` prop matching +the widget so the log line identifies the culprit. --- @@ -729,20 +729,11 @@ Remaining order of value/risk for the next contributor: siblings under `src/hooks/catalog/`). Only after step 1 — React Query removes ~60% of the file's responsibility, Zustand can absorb the UI state slice. -3. **Per-widget `WidgetErrorBoundary` wrapping** inside `RoomWidgetsView`. - The umbrella is in place; granular wrapping means a crash in one - widget (e.g. `ChatWidgetView`) doesn't take down the rest of the - room overlay. Mechanical and safe. -4. **Hoist `WiredCreatorToolsView`'s shared state to a Zustand slice.** +3. **Hoist `WiredCreatorToolsView`'s shared state to a Zustand slice.** The 4-tab split is done but the parent still passes ~25 props to each tab. A slice at `src/components/wired-tools/wiredToolsStore.ts` would make each tab subscribe to the keys it needs. -5. **Address the two open logic bugs** (see the "Known logic bugs" - section above): the `MainView` CREATED/ENDED race needs a session - token; the `LayoutFurniImageView` / `LayoutAvatarImageView` async - fetch race needs a request-id ref (or is solved by migrating the - image fetch to `useNitroQuery` keyed on props). -6. **Widen the component/hook Vitest coverage.** The renderer-SDK +4. **Widen the component/hook Vitest coverage.** The renderer-SDK mock layer is in place (`tests/mocks/renderer-mock.ts`) and the first two pilots — `WidgetErrorBoundary` and `useDoorbellState` — pass. Good follow-up targets: other `*State` hooks built on event diff --git a/src/components/room/widgets/RoomWidgetsView.tsx b/src/components/room/widgets/RoomWidgetsView.tsx index 66745c2..0d86075 100644 --- a/src/components/room/widgets/RoomWidgetsView.tsx +++ b/src/components/room/widgets/RoomWidgetsView.tsx @@ -161,20 +161,20 @@ export const RoomWidgetsView: FC<{}> = props => return (
- +
- - - - - - - - - - - - + + + + + + + + + + + +
); }; diff --git a/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx b/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx index 443a883..f4ca400 100644 --- a/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx +++ b/src/components/room/widgets/furniture/FurnitureWidgetsView.tsx @@ -1,4 +1,5 @@ import { FC } from 'react'; +import { WidgetErrorBoundary } from '../../../../common'; import { FurnitureBackgroundColorView } from './FurnitureBackgroundColorView'; import { FurnitureAreaHideView } from './FurnitureAreaHideView'; import { FurnitureBadgeDisplayView } from './FurnitureBadgeDisplayView'; @@ -24,26 +25,26 @@ export const FurnitureWidgetsView: FC<{}> = props => { return ( <> - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + ); };