mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
widgets: wrap each room + furniture widget in its own WidgetErrorBoundary
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.
This commit is contained in:
@@ -260,7 +260,7 @@ into `configurePreviewServer` so `yarn preview` keeps working.
|
|||||||
| Zustand | `NavigatorRoomCreatorView` (`useRoomCreatorStore`) |
|
| Zustand | `NavigatorRoomCreatorView` (`useRoomCreatorStore`) |
|
||||||
| God-hook split (state + actions + shim) | `doorbell`, `poll`, `furni-chooser`, `user-chooser`, `friend-request`, `chat-input` |
|
| 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) |
|
| 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 |
|
| 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) |
|
| 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 |
|
| Cherry-picked from `duckietm` PR #126 | `UserAccountSettingsView` (reset password / email / username under user settings), plus the wear-badge popup `canShowWearButton` gating |
|
||||||
|
|||||||
+8
-17
@@ -302,12 +302,12 @@ takes down the whole UI.
|
|||||||
Implementation lives at `src/common/error-boundary/WidgetErrorBoundary.tsx`.
|
Implementation lives at `src/common/error-boundary/WidgetErrorBoundary.tsx`.
|
||||||
|
|
||||||
**Status.** Implemented + applied to `RoomWidgetsView` as the umbrella for
|
**Status.** Implemented + applied to `RoomWidgetsView` as the umbrella for
|
||||||
all in-room widgets. A widget crash now degrades gracefully (the offending
|
all in-room widgets, **plus** a per-widget pass that wraps each of the 13
|
||||||
widget disappears) instead of unmounting the room.
|
direct children of `RoomWidgetsView` and each of the 20 sub-widgets in
|
||||||
|
`FurnitureWidgetsView`. A crash in any single widget now silently logs
|
||||||
A more granular pass could wrap each individual widget for finer-grained
|
through `NitroLogger` and renders `null` for that widget only — its
|
||||||
fallbacks, but the umbrella alone already prevents the worst class of
|
siblings keep rendering. Each boundary carries a `name` prop matching
|
||||||
failures.
|
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
|
siblings under `src/hooks/catalog/`). Only after step 1 — React
|
||||||
Query removes ~60% of the file's responsibility, Zustand can absorb
|
Query removes ~60% of the file's responsibility, Zustand can absorb
|
||||||
the UI state slice.
|
the UI state slice.
|
||||||
3. **Per-widget `WidgetErrorBoundary` wrapping** inside `RoomWidgetsView`.
|
3. **Hoist `WiredCreatorToolsView`'s shared state to a Zustand slice.**
|
||||||
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.**
|
|
||||||
The 4-tab split is done but the parent still passes ~25 props to
|
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`
|
each tab. A slice at `src/components/wired-tools/wiredToolsStore.ts`
|
||||||
would make each tab subscribe to the keys it needs.
|
would make each tab subscribe to the keys it needs.
|
||||||
5. **Address the two open logic bugs** (see the "Known logic bugs"
|
4. **Widen the component/hook Vitest coverage.** The renderer-SDK
|
||||||
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
|
|
||||||
mock layer is in place (`tests/mocks/renderer-mock.ts`) and the
|
mock layer is in place (`tests/mocks/renderer-mock.ts`) and the
|
||||||
first two pilots — `WidgetErrorBoundary` and `useDoorbellState` —
|
first two pilots — `WidgetErrorBoundary` and `useDoorbellState` —
|
||||||
pass. Good follow-up targets: other `*State` hooks built on event
|
pass. Good follow-up targets: other `*State` hooks built on event
|
||||||
|
|||||||
@@ -161,20 +161,20 @@ export const RoomWidgetsView: FC<{}> = props =>
|
|||||||
return (
|
return (
|
||||||
<WidgetErrorBoundary name="RoomWidgets">
|
<WidgetErrorBoundary name="RoomWidgets">
|
||||||
<div className="absolute top-0 left-0 pointer-events-none size-full">
|
<div className="absolute top-0 left-0 pointer-events-none size-full">
|
||||||
<FurnitureWidgetsView />
|
<WidgetErrorBoundary name="FurnitureWidgets"><FurnitureWidgetsView /></WidgetErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
<AvatarInfoWidgetView />
|
<WidgetErrorBoundary name="AvatarInfoWidget"><AvatarInfoWidgetView /></WidgetErrorBoundary>
|
||||||
<ChatWidgetView />
|
<WidgetErrorBoundary name="ChatWidget"><ChatWidgetView /></WidgetErrorBoundary>
|
||||||
<ChatInputView />
|
<WidgetErrorBoundary name="ChatInput"><ChatInputView /></WidgetErrorBoundary>
|
||||||
<DoorbellWidgetView />
|
<WidgetErrorBoundary name="DoorbellWidget"><DoorbellWidgetView /></WidgetErrorBoundary>
|
||||||
<RoomToolsWidgetView />
|
<WidgetErrorBoundary name="RoomToolsWidget"><RoomToolsWidgetView /></WidgetErrorBoundary>
|
||||||
<RoomFilterWordsWidgetView />
|
<WidgetErrorBoundary name="RoomFilterWordsWidget"><RoomFilterWordsWidgetView /></WidgetErrorBoundary>
|
||||||
<RoomThumbnailWidgetView />
|
<WidgetErrorBoundary name="RoomThumbnailWidget"><RoomThumbnailWidgetView /></WidgetErrorBoundary>
|
||||||
<FurniChooserWidgetView />
|
<WidgetErrorBoundary name="FurniChooserWidget"><FurniChooserWidgetView /></WidgetErrorBoundary>
|
||||||
<PetPackageWidgetView />
|
<WidgetErrorBoundary name="PetPackageWidget"><PetPackageWidgetView /></WidgetErrorBoundary>
|
||||||
<UserChooserWidgetView />
|
<WidgetErrorBoundary name="UserChooserWidget"><UserChooserWidgetView /></WidgetErrorBoundary>
|
||||||
<WordQuizWidgetView />
|
<WidgetErrorBoundary name="WordQuizWidget"><WordQuizWidgetView /></WidgetErrorBoundary>
|
||||||
<FriendRequestWidgetView />
|
<WidgetErrorBoundary name="FriendRequestWidget"><FriendRequestWidgetView /></WidgetErrorBoundary>
|
||||||
</WidgetErrorBoundary>
|
</WidgetErrorBoundary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
import { WidgetErrorBoundary } from '../../../../common';
|
||||||
import { FurnitureBackgroundColorView } from './FurnitureBackgroundColorView';
|
import { FurnitureBackgroundColorView } from './FurnitureBackgroundColorView';
|
||||||
import { FurnitureAreaHideView } from './FurnitureAreaHideView';
|
import { FurnitureAreaHideView } from './FurnitureAreaHideView';
|
||||||
import { FurnitureBadgeDisplayView } from './FurnitureBadgeDisplayView';
|
import { FurnitureBadgeDisplayView } from './FurnitureBadgeDisplayView';
|
||||||
@@ -24,26 +25,26 @@ export const FurnitureWidgetsView: FC<{}> = props =>
|
|||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FurnitureAreaHideView />
|
<WidgetErrorBoundary name="FurnitureAreaHide"><FurnitureAreaHideView /></WidgetErrorBoundary>
|
||||||
<FurnitureBackgroundColorView />
|
<WidgetErrorBoundary name="FurnitureBackgroundColor"><FurnitureBackgroundColorView /></WidgetErrorBoundary>
|
||||||
<FurnitureBadgeDisplayView />
|
<WidgetErrorBoundary name="FurnitureBadgeDisplay"><FurnitureBadgeDisplayView /></WidgetErrorBoundary>
|
||||||
<FurnitureCraftingView />
|
<WidgetErrorBoundary name="FurnitureCrafting"><FurnitureCraftingView /></WidgetErrorBoundary>
|
||||||
<FurnitureDimmerView />
|
<WidgetErrorBoundary name="FurnitureDimmer"><FurnitureDimmerView /></WidgetErrorBoundary>
|
||||||
<FurnitureExchangeCreditView />
|
<WidgetErrorBoundary name="FurnitureExchangeCredit"><FurnitureExchangeCreditView /></WidgetErrorBoundary>
|
||||||
<FurnitureExternalImageView />
|
<WidgetErrorBoundary name="FurnitureExternalImage"><FurnitureExternalImageView /></WidgetErrorBoundary>
|
||||||
<FurnitureFriendFurniView />
|
<WidgetErrorBoundary name="FurnitureFriendFurni"><FurnitureFriendFurniView /></WidgetErrorBoundary>
|
||||||
<FurnitureGiftOpeningView />
|
<WidgetErrorBoundary name="FurnitureGiftOpening"><FurnitureGiftOpeningView /></WidgetErrorBoundary>
|
||||||
<FurnitureHighScoreView />
|
<WidgetErrorBoundary name="FurnitureHighScore"><FurnitureHighScoreView /></WidgetErrorBoundary>
|
||||||
<FurnitureInternalLinkView />
|
<WidgetErrorBoundary name="FurnitureInternalLink"><FurnitureInternalLinkView /></WidgetErrorBoundary>
|
||||||
<FurnitureMannequinView />
|
<WidgetErrorBoundary name="FurnitureMannequin"><FurnitureMannequinView /></WidgetErrorBoundary>
|
||||||
<FurniturePlaylistEditorWidgetView />
|
<WidgetErrorBoundary name="FurniturePlaylistEditorWidget"><FurniturePlaylistEditorWidgetView /></WidgetErrorBoundary>
|
||||||
<FurnitureRoomLinkView />
|
<WidgetErrorBoundary name="FurnitureRoomLink"><FurnitureRoomLinkView /></WidgetErrorBoundary>
|
||||||
<FurnitureSpamWallPostItView />
|
<WidgetErrorBoundary name="FurnitureSpamWallPostIt"><FurnitureSpamWallPostItView /></WidgetErrorBoundary>
|
||||||
<FurnitureStackHeightView />
|
<WidgetErrorBoundary name="FurnitureStackHeight"><FurnitureStackHeightView /></WidgetErrorBoundary>
|
||||||
<FurnitureStickieView />
|
<WidgetErrorBoundary name="FurnitureStickie"><FurnitureStickieView /></WidgetErrorBoundary>
|
||||||
<FurnitureTrophyView />
|
<WidgetErrorBoundary name="FurnitureTrophy"><FurnitureTrophyView /></WidgetErrorBoundary>
|
||||||
<FurnitureContextMenuView />
|
<WidgetErrorBoundary name="FurnitureContextMenu"><FurnitureContextMenuView /></WidgetErrorBoundary>
|
||||||
<FurnitureYoutubeDisplayView />
|
<WidgetErrorBoundary name="FurnitureYoutubeDisplay"><FurnitureYoutubeDisplayView /></WidgetErrorBoundary>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user