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.
This commit is contained in:
Sylvain Duchesne
2026-10-01 19:24:57 +02:00
parent 0c4f6b6650
commit 8cdd146c81
2 changed files with 26 additions and 65 deletions
+17 -62
View File
@@ -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<T extends { "@id": string }>(set: Set<T>, 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<T extends { "@id": string }>(set: Set<T>, 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<FpEventData, 'id'>): Promise<FpEventData> => {
console.log('[FestipodData] createEvent (Refine):', event.title);
+9 -3
View File
@@ -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 = <T,>(snapshot: () => T[]) => ({
get size() { return snapshot().length; },