Merge main into ng-eventually: shared-wallet shim + multi-browser e2e
Brings 266e335 (staging shared wallet: file-assisted import + multi-browser
e2e) into the ng-eventually branch. Conflicts resolved so both lines of work
coexist and route through the lib where they overlap:
- harness-ng.tsx: combine ReadCap FilterProbe (ours) with main's SmokeProbe/
FanoutProbe; useShape + ng imported from @ng-eventually/client.
- ngSession.ts (auto): our single-injection-point configure() + main's hidden
logoutNg, which uses the lib's ng.
- useShapeWithDefaults.ts (auto): lib useShape + main's { graphs } multistore
scope.
- cucumber.json: single "tags": "not @wip" (both branches added it).
- brief_2026-06-15_shared-wallet-shim: keep main's implemented status; record
that the read filter now lives in the lib (decision_2026-06-17) while the
rest of the shim (storeRegistry/accounts/isolation) is still in-app, slated
to move into the lib.
Build OK; harness-ng bundles. TODO (next): verify all of main's NextGraph
surface routes through @ng-eventually/client (storeRegistry uses ng directly).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,8 @@ import React, { useEffect, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { NextGraphProvider, useNextGraph } from '../context/NextGraphContext';
|
||||
import { FestipodDataProvider, useFestipodData } from '../context/FestipodDataContext';
|
||||
import { useShape } from '@ng-eventually/client';
|
||||
// useShape + ng routed through the lib (SDK-identical surface); caps from /polyfill.
|
||||
import { useShape, ng } from '@ng-eventually/client';
|
||||
import { getCaps, setCurrentUser, resetCaps } from '@ng-eventually/client/polyfill';
|
||||
import type { DeepSignalSet } from '@ng-eventually/client';
|
||||
import {
|
||||
@@ -71,6 +72,11 @@ function ConnectedHarness() {
|
||||
// Read-filter validation: once a ReadCap policy is active, <FilterProbe> mounts
|
||||
// a useShape that returns the read-filtered VIEW.
|
||||
const [filterActive, setFilterActive] = useState(false);
|
||||
// Stopgap multi-store validation: a doc created on demand via doc_create,
|
||||
// mounted into a real useShape({graphs}) by <SmokeProbe>.
|
||||
const [smokeDoc, setSmokeDoc] = useState<string | null>(null);
|
||||
// Per-entity fan-out validation: several entity docs read together.
|
||||
const [fanoutGraphs, setFanoutGraphs] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
// Small delay for useShape to populate
|
||||
@@ -151,6 +157,8 @@ function ConnectedHarness() {
|
||||
return bootstrapWallet(events as any, users as any, participations as any);
|
||||
},
|
||||
|
||||
// --- ReadCap read-filter validation (see decision_2026-06-17_eventually-library) ---
|
||||
|
||||
/** The document (repo NURI) all wallet entities live in (mono-store). */
|
||||
documentNuri: privateNuri,
|
||||
|
||||
@@ -172,6 +180,54 @@ function ConnectedHarness() {
|
||||
setUser(user: string) {
|
||||
setCurrentUser(user);
|
||||
},
|
||||
|
||||
// --- Stopgap multi-store validation (see brief_2026-06-15_shared-wallet-shim) ---
|
||||
|
||||
/**
|
||||
* Create a fresh graph document via doc_create and mount it into a real
|
||||
* useShape({graphs}) subscription (<SmokeProbe>). Returns the NURI.
|
||||
* Validates: doc_create returns a usable graph NURI.
|
||||
*/
|
||||
async createSmokeDoc() {
|
||||
const nuri = await ng.doc_create(session.session_id, 'Graph', 'data:graph', 'store', undefined);
|
||||
setSmokeDoc(nuri);
|
||||
return nuri;
|
||||
},
|
||||
|
||||
/**
|
||||
* Round-trip the sharedWalletShim through the wallet: create an account
|
||||
* (3 docs + SPARQL INSERT), drop the cache, reload from the wallet via
|
||||
* SPARQL SELECT. Validates: doc_create ×3 + shim sparql_update/query.
|
||||
*/
|
||||
async validateShim(username: string) {
|
||||
const reg = await import('../utils/storeRegistry');
|
||||
reg.resetRegistryCache();
|
||||
const created = await reg.ensureAccount(username);
|
||||
reg.resetRegistryCache();
|
||||
const reloaded = (await reg.allAccounts()).find(
|
||||
a => a.username === username,
|
||||
) ?? null;
|
||||
return { created, reloaded };
|
||||
},
|
||||
|
||||
/**
|
||||
* Per-entity granularity + fan-out: 2 accounts, one event document each
|
||||
* (via createEntityDoc → indexed), then mount a multi-graph useShape over
|
||||
* both (<FanoutProbe>). Returns the two doc NURIs and the index listing.
|
||||
* Validates: 1-doc-per-entity, index append/read, fan-out across N docs.
|
||||
*/
|
||||
async setupFanout() {
|
||||
const reg = await import('../utils/storeRegistry');
|
||||
reg.resetRegistryCache();
|
||||
await reg.ensureAccount('@fan-a');
|
||||
await reg.ensureAccount('@fan-b');
|
||||
const docA = await reg.createEntityDoc('@fan-a', 'public');
|
||||
const docB = await reg.createEntityDoc('@fan-b', 'public');
|
||||
reg.resetRegistryCache();
|
||||
const listed = await reg.listEntityDocs('public');
|
||||
setFanoutGraphs([docA, docB]);
|
||||
return { docA, docB, listed };
|
||||
},
|
||||
};
|
||||
|
||||
console.log('[HarnessNG] Ready — events:', events.size, 'users:', users.size,
|
||||
@@ -187,6 +243,8 @@ function ConnectedHarness() {
|
||||
<>
|
||||
<div id="harness-status">{bridgeReady ? 'READY' : 'LOADING_SHAPES'}</div>
|
||||
{filterActive && privateNuri && <FilterProbe privateNuri={privateNuri} />}
|
||||
{smokeDoc && <SmokeProbe docNuri={smokeDoc} />}
|
||||
{fanoutGraphs.length > 0 && <FanoutProbe graphs={fanoutGraphs} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -211,6 +269,63 @@ function FilterProbe({ privateNuri }: { privateNuri: string }) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FanoutProbe — real useShape({graphs}) over SEVERAL entity documents.
|
||||
// Exposes window.__fanout for the per-entity fan-out @data scenario.
|
||||
// ============================================================================
|
||||
|
||||
function FanoutProbe({ graphs }: { graphs: string[] }) {
|
||||
const set = useShape(FpEventShapeType, { graphs } as any) as DeepSignalSet<FpEvent>;
|
||||
useEffect(() => {
|
||||
(window as any).__fanout = {
|
||||
ready: true,
|
||||
graphs,
|
||||
addEventTo(docNuri: string, title: string) {
|
||||
set.add({
|
||||
'@graph': docNuri,
|
||||
'@type': 'http://festipod.org/Event',
|
||||
'@id': '',
|
||||
title,
|
||||
participantCount: 1,
|
||||
} as FpEvent);
|
||||
},
|
||||
count() { return set.size; },
|
||||
titles() { return [...set].map(e => e.title); },
|
||||
};
|
||||
}, [set, graphs]);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SmokeProbe — real useShape({graphs}) on a doc_create'd document.
|
||||
// Exposes window.__smoke for the multi-store @data validation scenario.
|
||||
// ============================================================================
|
||||
|
||||
function SmokeProbe({ docNuri }: { docNuri: string }) {
|
||||
const set = useShape(FpParticipationShapeType, { graphs: [docNuri] } as any) as DeepSignalSet<FpParticipation>;
|
||||
useEffect(() => {
|
||||
(window as any).__smoke = {
|
||||
ready: true,
|
||||
docNuri,
|
||||
add() {
|
||||
set.add({
|
||||
'@graph': docNuri,
|
||||
'@type': 'http://festipod.org/Participation',
|
||||
'@id': '',
|
||||
event: 'urn:smoke:event',
|
||||
user: 'urn:smoke:user',
|
||||
isConfirmed: true,
|
||||
} as FpParticipation);
|
||||
},
|
||||
count() { return set.size; },
|
||||
items() {
|
||||
return [...set].map(p => ({ '@id': p['@id'], event: p.event, user: p.user }));
|
||||
},
|
||||
};
|
||||
}, [set, docNuri]);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Bootstrap
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user