From 8cdd146c8115c7854080e6e144aa9cb9a04bc19a Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Thu, 1 Oct 2026 19:24:57 +0200 Subject: [PATCH] Remove what the migration left unreachable, and record what it left broken The shared-wallet shim's fan-out in the data context fed the ORM subscriptions this provider used to open: readGraphs, writeGraphs, protectedScope, protectedGraph and the effect that filled them from storeRegistry. Every entity reads through a Refine backend now, which takes no graph list, so none of it was reachable. Same for findNg, mapUser and mapParticipation -- reading a record into a domain type belongs to a backend's binding, not here. storeRegistry itself stays: the @data multistore scenarios drive it directly. This is debt the migration created, not debt it inherited, which is why it goes rather than waiting for a tidy-up. What the removal COSTS is written where it happened: placement by scope no longer narrows what the app reads. The backend's declaration is where that belongs and nothing honours it yet; applyIsolation is the only narrowing left. And setLike.delete is now marked as the defect it is. It is a no-op lifted from a branch where the fixture seed is off, so nothing needs deleting before it -- false here, where the step that empties the wallet therefore does nothing. Fixing it needs a decision the comment states, because the app exposes no deleteEvent. --- src/shared/context/FestipodDataContext.tsx | 79 +++++----------------- src/shared/test-harness/harness-ng.tsx | 12 +++- 2 files changed, 26 insertions(+), 65 deletions(-) diff --git a/src/shared/context/FestipodDataContext.tsx b/src/shared/context/FestipodDataContext.tsx index e5a287d..ec8c3d0 100644 --- a/src/shared/context/FestipodDataContext.tsx +++ b/src/shared/context/FestipodDataContext.tsx @@ -18,8 +18,6 @@ import { useList, useCreate, useUpdate, useDelete } from '@refinedev/core'; import { useNextGraph } from './NextGraphContext'; import { useAccount, normalizeUsername } from './AccountContext'; import { applyIsolation } from '../utils/isolation'; -import { ensureAccount, resolveReadGraphs, resolveWriteGraph } from '../utils/storeRegistry'; -import { useShapeWithDefaults, type ShapeScope } from '../hooks/useShapeWithDefaults'; import { useBackend } from '../refine/RefineProvider'; // Multi-document mode (storeRegistry): one document per (account × scope), @@ -96,13 +94,6 @@ function nextId(prefix: string): string { return `${prefix}-${++idCounter}`; } -function findNg(set: Set, predicate: (item: T) => boolean): T | undefined { - for (const item of set) { - if (predicate(item)) return item; - } - return undefined; -} - // EVENTS are read and written through the selected Refine BACKEND, so the record // shape, the vocabulary and the two identifiers are its business, not this file's // — see refine/providers/types.ts. The binding is read from `useBackend()` inside @@ -111,21 +102,9 @@ function findNg(set: Set, predicate: (item: T) = // backend unswitchable. Everything else in this file still speaks `fp:` directly, // because profiles and participations have not moved to Refine yet. -const mapUser = (u: FpUserProfile): FpUserData => ({ - id: u["@id"], - name: u.name, - initials: u.initials, - username: u.username, - role: u.role, - isPublic: u.isPublic, -}); - -const mapParticipation = (p: FpParticipation): FpParticipationData => ({ - id: p["@id"], - eventId: p.event, - userId: p.user, - isConfirmed: p.isConfirmed, -}); +// NO mapUser / mapParticipation HERE ANY MORE. Reading a backend record into a +// domain type is the backend's binding (refine/providers/types.ts): the record +// shape is its vocabulary, not this file's. // ============================================================================ // Shared queries builder — same logic for both local and NG modes @@ -291,40 +270,17 @@ function useNgData(): FestipodDataContextValue { // repo in the verifier (enables reads + writes), per the data-layer rule. const privateNuri = session ? `did:ng:${session.private_store_id}` : undefined; - // Multi-document state (storeRegistry): read fan-out (all accounts' docs per - // scope) and the current account's write docs. Populated by the effect below. - const [readGraphs, setReadGraphs] = useState<{ protected: string[] }>({ protected: [] }); - const [writeGraphs, setWriteGraphs] = useState<{ protected?: string }>({}); - - useEffect(() => { - if (!MULTISTORE || !privateNuri || !username) return; - let cancelled = false; - (async () => { - try { - await ensureAccount(username); // create this account's index docs on first sight - // Protected = grouped in each account's protected index document. - // NO public fan-out any more: events are a Refine resource, and the data - // provider addresses them by shape across every graph it can see — it - // never takes a graph list, so the public index has no reader left. - const [prot, wProt] = await Promise.all([ - resolveReadGraphs('protected'), - resolveWriteGraph(username, 'protected'), - ]); - if (cancelled) return; - setReadGraphs({ protected: prot }); - setWriteGraphs({ protected: wProt }); - } catch (err) { - console.error('[FestipodData] storeRegistry init failed:', err); - } - })(); - return () => { cancelled = true; }; - }, [privateNuri, username]); - - // Scope for profiles + participations, which still read through the ORM. - // Mono-store mode collapses them to the private store. - const protectedScope: ShapeScope = MULTISTORE - ? (readGraphs.protected.length ? { graphs: readGraphs.protected } : undefined) - : privateNuri; + // NO SCOPE WIRING LEFT HERE. The shared-wallet shim's fan-out (readGraphs / + // writeGraphs / protectedScope, and the effect that filled them from + // storeRegistry) fed the ORM subscriptions this provider used to open. Every + // entity now reads through a Refine backend, which takes no graph list, so all + // of it became unreachable and is gone. `storeRegistry` itself stays — the + // @data multistore scenarios still drive it directly. + // + // What this COSTS, stated rather than buried: placement by scope no longer + // narrows what the app reads. The backend's `visibility` declaration + // (refine/providers/types.ts) is where that belongs, and no backend honours it + // yet; `applyIsolation` below is the only narrowing still applied. // --- Events: read through REFINE ----------------------------------------- // `useList` goes to `@ng-org/refine-providers`, which resolves the resource @@ -529,10 +485,9 @@ function useNgData(): FestipodDataContextValue { '| users:', users.length, '| participations:', participations.length, '| selectedEvent:', selectedEvent?.title ?? '(none)'); - // --- Mutations (NG) --- - // Participations stay GROUPED in the account's protected index document. - // Mono-store mode collapses everything to the private store. - const protectedGraph = (MULTISTORE ? writeGraphs.protected : undefined) || privateNuri || ''; + // --- Mutations: every entity through REFINE ------------------------------ + // No write graph to resolve any more: the backend creates a document per + // record, so nothing here names where a write lands. const createEvent = useCallback(async (event: Omit): Promise => { console.log('[FestipodData] createEvent (Refine):', event.title); diff --git a/src/shared/test-harness/harness-ng.tsx b/src/shared/test-harness/harness-ng.tsx index c66ff83..24ad660 100644 --- a/src/shared/test-harness/harness-ng.tsx +++ b/src/shared/test-harness/harness-ng.tsx @@ -119,9 +119,15 @@ function ConnectedHarness() { })); /** * A read-only Set-like over an app-backed array snapshot: supports - * `[...x]`, `x.size`, and a no-op `delete`. The "empty wallet" scenario runs - * before any seed, so there is nothing for it to delete — and a seeded - * record is not this bridge's to remove. + * `[...x]`, `x.size`, and `delete`. + * + * KNOWN DEFECT, recorded rather than left to be rediscovered: `delete` is a + * no-op. It came from the sibling branch, where it is justified — there the + * fixture seed is off, so the "empty wallet" scenario has nothing to delete. + * HERE the seed writes, so that premise is false and the step which empties + * the wallet silently does nothing. Fixing it needs a decision, because the + * app exposes no `deleteEvent`: the bridge would have to read the app's view + * and delete through the ORM, a hybrid worth stating before adopting. */ const setLike = (snapshot: () => T[]) => ({ get size() { return snapshot().length; },