mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
Architecture refactor: docs + 5 pilot implementations + error boundary
This is the structural plan promised in the previous session, with concrete pilots for all five proposals + the bonus error-boundary work. == docs/ARCHITECTURE.md (new, ~370 lines) Living document describing: - where the project stands today (event-bus pattern friction with React 19, god-hooks, oversized files); - the five proposed structural improvements with the why/how/status of each; - what's already in place across this branch; - recommended order for the next refactor PRs. This is the deliverable the rest of this commit references. == Proposal #3 + #4 pilots: src/features/doorbell/ (new) Concrete feature-folder migration on the doorbell widget (chosen because it's small enough to migrate end-to-end in one commit). src/features/doorbell/ index.ts public API views/DoorbellWidgetView.tsx hooks/useDoorbellState.ts reduces 3 events into a users array (data only) hooks/useDoorbellActions.ts answer(name, flag) (imperative actions only) The split (data vs actions) is the pattern proposal #4 wants applied to useCatalog/useChat/useWiredTools later. The original useDoorbellWidget had both concerns + a buggy `useEffect(() => setIsVisible(!!users.length), [users])` derive-state-in-effect. The new view computes visibility in render. Compat shims kept so existing imports keep working: - src/components/room/widgets/doorbell/DoorbellWidgetView.tsx -> 1-line re-export - src/hooks/rooms/widgets/useDoorbellWidget.ts -> deprecated wrapper around the two new hooks, returning the same { users, answer } shape. == Proposal #2 prototype: src/api/nitro-query/ (new) Adapter outline for wrapping composer/parser request-response pairs in TanStack Query. Not yet enabled because @tanstack/react-query is not in package.json. The file documents the activation steps: yarn add @tanstack/react-query @tanstack/react-query-devtools + mount QueryClientProvider in src/index.tsx awaitNitroResponse() throws with a helpful pointer to the doc section if called before activation, so accidental adoption fails loudly. == Proposal #5 skeleton: src/state/createNitroStore.ts (new) Same pattern: skeleton + activation instructions. Not yet enabled because zustand is not in package.json. yarn add zustand + replace the throw with `import { create } from 'zustand'; export const createNitroStore = create;` The doc inside the file shows the recommended slice shape and points to the suggested first migration target (the let isCreatingRoom singleton in NavigatorRoomCreatorView). == Bonus: WidgetErrorBoundary src/common/error-boundary/WidgetErrorBoundary.tsx wraps react-error-boundary with a sensible default (silent fallback, NitroLogger.error). Re-exported from src/common/index.ts. Applied as the umbrella around RoomWidgetsView's children — a widget crash in a room (e.g. malformed pet data) now degrades gracefully instead of unmounting the whole UI. == Verification - yarn eslint on all new + modified files: 0 errors / 0 warnings introduced. RoomWidgetsView still has its 1 pre-existing FC<{}> error (1 before, 1 after). - yarn tsc on all new files: clean (only project-wide pre-existing TS2307 about @nitrots/nitro-renderer not installed locally remains). - No regressions: existing imports of DoorbellWidgetView and useDoorbellWidget keep resolving via the compat shims. == What's NOT in this commit (intentionally) - Mass adoption of the new patterns elsewhere — left as follow-up PRs in the order documented in ARCHITECTURE.md "How to pick the next refactor PR". - Installation of @tanstack/react-query / zustand — explicit team decision, not the LLM's to make. - Test infrastructure (Vitest setup) — listed as the #1 missing piece in the doc, but a separate PR. https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Adapter prototype for proposal #2 (server requests as queries).
|
||||
*
|
||||
* NOT YET ENABLED — `@tanstack/react-query` is not in package.json.
|
||||
* To activate:
|
||||
*
|
||||
* yarn add @tanstack/react-query @tanstack/react-query-devtools
|
||||
*
|
||||
* Then mount the provider once in `src/index.tsx`:
|
||||
*
|
||||
* import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
* const queryClient = new QueryClient({
|
||||
* defaultOptions: { queries: { staleTime: 30_000, retry: 1 } }
|
||||
* });
|
||||
* <QueryClientProvider client={queryClient}><App /></QueryClientProvider>
|
||||
*
|
||||
* Then this file becomes:
|
||||
*
|
||||
* import { useQuery } from '@tanstack/react-query';
|
||||
* ...
|
||||
*
|
||||
* The interface below shows the intended API. Once enabled, replace the
|
||||
* placeholder bodies with the real `useQuery` calls.
|
||||
*/
|
||||
|
||||
import { IMessageEvent, MessageEvent } from '@nitrots/nitro-renderer';
|
||||
import { SendMessageComposer } from '../SendMessageComposer';
|
||||
|
||||
export interface NitroQueryConfig<TParser extends IMessageEvent, TData>
|
||||
{
|
||||
/**
|
||||
* Stable key used for caching/deduping (TanStack Query queryKey).
|
||||
* Convention: ['nitro', '<domain>', '<request>', ...args].
|
||||
*/
|
||||
key: readonly unknown[];
|
||||
/**
|
||||
* Factory for the request composer. Called once per query execution.
|
||||
*/
|
||||
request: () => any;
|
||||
/**
|
||||
* The parser class to listen for as the response.
|
||||
*/
|
||||
parser: typeof MessageEvent;
|
||||
/**
|
||||
* Maps the parser event to the data the component cares about.
|
||||
*/
|
||||
select?: (event: TParser) => TData;
|
||||
/**
|
||||
* Optional max time to wait for the response before failing.
|
||||
*/
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a one-shot Promise that sends a composer and resolves with the
|
||||
* matching parser event. To be passed into TanStack Query's queryFn:
|
||||
*
|
||||
* useQuery({
|
||||
* queryKey: cfg.key,
|
||||
* queryFn: () => awaitNitroResponse(cfg),
|
||||
* });
|
||||
*
|
||||
* Implementation outline (filled in once react-query is added):
|
||||
*
|
||||
* return new Promise<TData>((resolve, reject) => {
|
||||
* const event = new cfg.parser((e: TParser) => {
|
||||
* GetCommunication().removeMessageEvent(event);
|
||||
* resolve(cfg.select ? cfg.select(e) : (e as unknown as TData));
|
||||
* });
|
||||
* GetCommunication().registerMessageEvent(event);
|
||||
* SendMessageComposer(cfg.request());
|
||||
* if (cfg.timeoutMs) setTimeout(() => {
|
||||
* GetCommunication().removeMessageEvent(event);
|
||||
* reject(new Error('NitroQuery timeout'));
|
||||
* }, cfg.timeoutMs);
|
||||
* });
|
||||
*/
|
||||
export const awaitNitroResponse = <TParser extends IMessageEvent, TData>(
|
||||
_cfg: NitroQueryConfig<TParser, TData>
|
||||
): Promise<TData> =>
|
||||
{
|
||||
void SendMessageComposer;
|
||||
throw new Error('useNitroQuery is not enabled. See docs/ARCHITECTURE.md proposal #2.');
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './createNitroQuery';
|
||||
Reference in New Issue
Block a user