Commit Graph

23 Commits

Author SHA1 Message Date
Sylvain Duchesne bc3d270bd4 chore: scrub simulation vocabulary from app comments + settle doc-debt
Enforce the boundary in code-comments and doctrine (adversarial-review cleanup):
- App comments in the data plane no longer narrate the SDK's internals: "emulated
  curator"→"the inbox read", "fan-out"→"discovered", removed store-placement
  reasoning and "polyfill/shim/mono-store" wording (FestipodDataContext,
  registration, storeRegistry, ngSession, AccountContext, isolation, sharedWallet,
  AccessGateScreen). Executable logic unchanged.
- Removed dangling references to the dissolved `nextgraph-platform` concept and
  `brief_2026-06-15_shared-wallet-shim` from app code.
- knowledge_nextgraph-stack: dropped "mécanique d'émulation" from the boundary note.
- Settled and deleted all concept _debt.md (confirmatory; target leaves clean).

(Test-infra under workshop/ + generated features.ts still carry some simulation
vocabulary — parked as a separate below-SDK decision.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 09:58:52 +02:00
Sylvain Duchesne a436c3bd79 feat(data): discover public events via the global index (not fan-out)
On creating a public event, Festipod submits it to the discovery index (an SDK
call); the discovery screen reads the index instead of enumerating accounts. The
app knows nothing of the index's owner, inbox, or materialization — it treats the
lib as a finished SDK whose discovery is a global index. No store ids.

Unit-validated in the lib (79 tests). @data broker validation deferred: the
NextGraph broker (nextgraph.net/eu) was unreachable at run time — to be re-run
in T03.d once the broker recovers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 09:33:42 +02:00
Sylvain Duchesne 337a1e000d feat(data): activate isolation — declare identity + connections to the SDK
Festipod performs the domain acts that make isolation real: AccountContext
declares the current identity at login/change; FestipodDataContext declares its
connections (friendships) to the data SDK. Reads then discriminate by scope
through the SDK (private→owner, protected→owner+connections, public→all) — no
app-side filtering, no store ids, no awareness that isolation is emulated. New
@data scenario proves an unconnected account can't read another's protected
entity but can after connecting; public stays visible. @data 21/21.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:59:03 +02:00
Sylvain Duchesne 619b94ac0e refactor(data): route entities by scope via the SDK — no store ids in the app
Festipod now treats @ng-eventually/client as a finished NextGraph SDK: the app
decides only each entity's logical scope (events/PdR public, profiles/
participations protected, settings private) and calls the lib by scope. The old
mono-store default and the FESTIPOD_MULTISTORE path collapse into ONE scope path.

Removed every physical-store leak from the app data-plane (ngGraph, registration,
FestipodDataContext, NextGraphContext, useShapeWithDefaults): no more
did🆖${store_id} construction. The session is handed to the lib only at the
sanctioned injection point (ngSession/configureStoreRegistry). Product behavior
unchanged. @data 20/20; build + tsc clean.

(_debt.md included; the T03.e doctrine pass settles accumulated doc-debt.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:42:03 +02:00
Sylvain Duchesne 83604cbc16 test(e2e): multibrowser feature scenarios + @data proofs for T02
- New @data scenarios: inscription-inbox (registration + inbox deposit + notif;
  persistent deregistration), decouverte-publique (cross-account public read),
  protected-store (probe: the native protected store opens for ORM+SPARQL).
- New @multibrowser e2e (e2e-multibrowser.feature): registration+host-notif,
  persistent deregistration, and public discovery across two browser contexts.
- cycle-de-vie: @wip lifted on "Se désinscrire" (fixed).
- harness-ng: bridge helpers for the above; domain sets + ReadCap probe doc
  retargeted to the protected store.
- hooks: defensive AfterAll teardown + Before self-heal on Chromium crash under
  full-suite load. cucumber.json excludes @humain (live nextgraph.eu import,
  non-deterministic; passes standalone). Full suite: 86 passed / 0 failed / 71 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 15:51:57 +02:00
Sylvain Duchesne aacc2ec3ee feat(data): PdR registration via inbox, notifications, public discovery, protected store
Polyfill-enabled features (T02). All NextGraph I/O goes through
@ng-eventually/client (docs/inbox/storeRegistry); no direct @ng-org.

- Shapes: FpMeetingPoint + FpNotification are now real SHEX shapes with ORM
  bindings (previously app-TS-only, unpersisted).
- Registration (registration.ts, new): joinEvent persists a Participation +
  deposits to the host's inbox + creates a Notification (from = registrant if
  connected, anonymous otherwise). leaveEvent deletes the Participation
  authoritatively via SPARQL DELETE-WHERE (sweep by event+user AND by subject,
  then re-query to confirm) — the désinscription CRDT-resurrection bug is fixed:
  the reactive delete is applied only once the broker confirms 0 remaining.
- Public discovery: useNgData fans out over every account's public docs so a
  user sees others' public events without a connection (dedup union).
- Cap attribution: createEntityDoc declares the ReadCap (open + makePublic/
  grantRead per scope), activating the per-document read filter.
- Protected store (T02.h): the default path now reads/writes shareable domain
  entities in the native protected store (did🆖${protected_store_id}) instead
  of private — verified openable against the broker — matching the per-wallet
  target. Private still anchors the shim/inbox + settings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 15:51:23 +02:00
Sylvain Duchesne 3f47ea886f Rewire app onto @ng-eventually/client; drop direct @ng-org runtime imports
Consume the shim mechanics now living in the lib (docs/storeRegistry/
isolation/accounts) and remove the remaining direct @ng-org runtime imports.

- storeRegistry.ts  keeps ONLY the Festipod EntityKind/entityScope mapping,
  injects it via configureStoreRegistry({ getSession, normalizeUser }), and
  re-exports the lib's storeRegistry.* (callers unchanged). Drops
  `import { ng } from '@ng-org/web'`.
- harness-ng.tsx  createSmokeDoc now uses docs.docCreate (real injected ng,
  no DataCloneError) instead of ng.doc_create. Drops the @ng-org import.
- AccountContext.tsx  thin React wrapper over accounts.AccountStore +
  normalizeUsername; historical key `festipod.account.username` pinned →
  zero behavior change. Context/Provider stay in the app.
- isolation.ts  Festipod wrapper over the lib's pure isolation.applyIsolation.

Invariant reached: `grep "from '@ng-org'" src/ | grep -v 'import type'` lists
only ngSession (the configure injection point) + the two documented
test-harness exceptions (auth-setup.tsx, harness.tsx mock). No doc_create
goes through the lib's public proxy. App build + harness-ng bundle OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 10:13:11 +02:00
Sylvain Duchesne d69fd7a5f9 Revert doc_create to real ng: lib proxy breaks iframe marshaling (validated)
Full-suite validation of the merge surfaced 4 failures, all multistore: routing
doc_create through the lib's `ng` proxy (685f6d3) breaks @ng-org/web's iframe
postMessage marshaling — DataCloneError "function could not be cloned" (a JS
Proxy over the iframe-RPC proxy = double proxy).

Fix: storeRegistry.ts and harness-ng.tsx (createSmokeDoc) call doc_create /
SPARQL on the real @ng-org/web `ng` directly again. useShape / init / login /
ReadCap still route through the lib. After the fix the 3 multistore scenarios
pass; full suite = 77 passed, 0 merge regressions.

Integration boundary documented in decision_2026-06-17: the in-app shim's
low-level NextGraph calls stay on the real SDK until storeRegistry moves INTO
the lib (where it would use the injected real ng, no double proxy). Lib TODO:
expose a doc_create/SPARQL primitive that uses the injected ng.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 13:39:53 +02:00
Sylvain Duchesne 685f6d379d storeRegistry: route ng through @ng-eventually/client (post-merge integration)
Post-merge audit of main's shared-wallet shim: storeRegistry.ts was the only
runtime path still importing `ng` from @ng-org/web directly, bypassing the lib.
Route it through @ng-eventually/client (the ng proxy forwards doc_create /
sparql_update / sparql_query). Now the only @ng-org runtime imports in the app
are the single injection point (ngSession) + documented exceptions (auth-setup,
mock harness) + generated ORM type-only bindings — the decision_2026-06-17
invariant holds again.

Still in-app, to move into the lib later: storeRegistry, AccountContext, the
isolation filter (distinct from the lib's ReadCap filter).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 13:07:28 +02:00
Sylvain Duchesne c52e581e4f 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>
2026-06-30 13:05:21 +02:00
Sylvain Duchesne 98c796054e e2e désinscription: mark @wip (real CRDT bug, not a stale test) + exclude @wip from default run
The "Se désinscrire" e2e wasn't obsolete: verified against the broker that
join reflects in the UI but leave does NOT — the button stays "✓ Je participe"
(>10s). DeepSignalSet.delete() does fire reactivity (touchIterable), so the
real cause is downstream: the deletion doesn't propagate / the item resurrects
via broker sync (the documented CRDT limitation).

- cycle-de-vie-evenement.feature: rewrite the désinscription scenario to be
  self-contained (join → leave → "J'y serai" in one session, no cross-scenario
  / persistence dependency), and tag it @wip with an accurate comment.
- cucumber.json: add tags "not @wip" so known-incomplete scenarios document an
  expectation without failing the suite (default run: 146 scenarios).
- docs: caveat_participation-deletion records the e2e finding (leave doesn't
  reflect in the UI; delete fires reactivity but the item resurrects via sync);
  knowledge_cucumber-setup documents @wip = excluded from the default run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 12:19:03 +02:00
Sylvain Duchesne 266e33556d feat(auth): staging wallet partagé — import assisté par fichier + e2e multi-navigateur
Stopgap staging multi-user sur wallet partagé (cf. brief_2026-06-15_shared-wallet-shim).

Distribution / import du wallet :
- AccessGateScreen : barrière d'accès ON PAR DÉFAUT (désactivable via
  globalThis.__FESTIPOD_ACCESS_GATE_DISABLED__ pour tests/dev). Fournit le FICHIER
  .ngw + le mot de passe + un guide en 3 étapes (import assisté sur nextgraph.eu —
  le broker hébergé n'autorise pas l'import inline pendant l'auth web-app).
- sharedWallet.ts + build.ts : fichier copié en /shared-wallet.ngw, mot de passe gravé.
- Ancien LoginScreen (/login) retiré ; atterrissage post-login -> /home.
- NextGraphContext : dé-piégeage de l'état "connecting" au retour (pageshow/bfcache).

Couche multistore stopgap : storeRegistry, isolation, AccountContext, FestipodDataContext.

Tests e2e multi-navigateur :
- browserPool + world.openBrowser : contextes frais isolés, 2 axes orthogonaux
  (nb de navigateurs × modèle de wallet own/shared).
- @humain : parcours humain complet (télécharge -> importe le fichier sur
  nextgraph.eu -> Entrer -> pseudo -> accueil).
- Bypass de la barrière pour @e2e via context.addInitScript.
- Convention @wip exclue via cucumber.json.

Docs (concepts) : nextgraph-platform (knowledge_broker-import-constraint,
decision_2026-06-17_assisted-wallet-import), bdd-testing (knowledge_multibrowser-harness).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 12:04:02 +02:00
Sylvain Duchesne 073150ef61 ReadCap read filter: consume the lib's per-document model + validate against broker
Align Festipod's @data read-filter scenario and harness bridge with
ng-eventually's grant→ReadCap refactor: the access unit is the document
(an item's `@graph`), not the item.

- harness-ng.tsx: governDocument(reader, user)/setUser via getCaps()/resetCaps()
  (replaces setupReadFilter/setGrantOf); FilterProbe exposes a lazy snapshot()
  reflecting the current user without remount.
- read-filter.feature/steps: validate per-document ReadCap on the real
  DeepSignalSet — govern the wallet document, grant the cap to another user
  → current user sees 0; current user gets the cap → sees all (all-or-nothing
  in mono-store, the faithful behavior). 5/5 steps pass against the broker.
- doctrine: knowledge_stores-permissions records the verified store/document/
  repo/ReadCap model (containment by reference, no read-cap inheritance);
  decision_2026-06-17_eventually-library updates the access-rights + filter
  status to the ReadCap model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 11:20:12 +02:00
Sylvain Duchesne aec338441c test(data): validate ng-eventually read filter on the real ORM set
Adds a @data scenario (workshop/read-filter) that enables the lib's read filter on the
real reactive ORM set (via a FilterProbe + setupReadFilter harness helper, granting each
participation to its own user) and asserts useShape returns only the target user's
participations. Validates the trickiest piece — filtering a live DeepSignalSet — against the
broker. @data 9/9. Doc: read filter marked implemented & validated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 10:28:56 +02:00
Sylvain Duchesne e270cc6063 refactor(data): route full NextGraph surface through @ng-eventually/client
The app now takes its NextGraph runtime AND types from @ng-eventually/client; the
only place that imports the real @ng-org SDK is ngSession (the single injection point for
configure()). Lifecycle (init/initNg), data (useShape) and types (ShapeType, DeepSignalSet,
NG…) all go through the lib. Test infra (auth-setup, mock harness) and generated ORM
bindings keep a direct @ng-org import (documented). Validated: build, @ui 4/4, @data 8/8
against the real broker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:54:00 +02:00
Sylvain Duchesne 9af128cb22 feat(data): route useShape through @ng-eventually/client (passthrough)
The reactive ORM data-plane now goes through the @ng-eventually/client wrapper
instead of @ng-org/orm directly; ngSession injects the real SDK into the polyfill via
configure(). Currently a transparent passthrough (lib mechanisms still stubbed) →
behavior unchanged. Validated: build, @ui 4/4, @data 8/8 against the real broker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 16:35:58 +02:00
Sylvain Duchesne 5a29938130 Modern UI port, render-based @ui tests, dev seed, layer contracts
- Port modern clean theme (DM Sans, orange accent, app-* CSS classes)
  and screen redesigns from festipod-mockups; replace sketchy Ubuntu
  theme. New shared components: BottomNav, EventCover, EventMeetingPoints,
  Toast, AvatarStack, Tag, RelevanceIcon.

- Restructure from prototyping shell to real mobile web app:
  path-based routing (History API), Gallery/DemoMode/PhoneFrame removed,
  Storybook setup for screen/component browsing.

- ConnectScreen ported from mockup (QR-based user connection); routed
  at /profile/connect, wired from FriendsListScreen.

- Dev-only auto-seed of NG wallet when empty
  (gated on NODE_ENV !== 'production'); bootstrapWallet already
  self-checks for non-empty ngSet so safe even in race conditions.

- Render-based @ui test infrastructure: happy-dom + LocalDataProvider +
  RouterProvider via src/shared/test-harness/renderHelper.tsx, exposed
  on the world as renderedDoc. world.hasText/hasField/hasElement prefer
  the rendered DOM and fall back to source for backward compatibility.

- Migrate 25 brittle @ui assertions from regex-on-source to DOM
  queries; delete implementation-detail tests (showDuplicateWarning,
  importableEvents, importedFrom — anti-patterns per the new contract).
  Update feature files where the UI changed: "Mes amis" → "Mon réseau",
  "Mes événements à venir" → "À venir" on home, Thématique removed
  from create-event wizard, etc.

- Path-based @e2e steps (pushState + popstate dispatch) replacing the
  legacy "#/demo/…" hash routing tied to the deleted Gallery.

- Add .project/knowledge/test-layer-contracts.md defining the role of
  each test layer (@ui = display with seed data + DOM, @data = mutations
  through NG broker, @e2e = critical user journeys) with anti-patterns
  and migration consequences.

Test status: 75 passed / 71 skipped (explicit "non implémenté")
/ 2 failed (pre-existing @wip on ngSet.delete() NG ORM limitation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 09:49:50 +02:00
Sylvain Duchesne 7099c817db Pure useShape API for mutations, event lifecycle e2e tests
Refactor FestipodDataContext to use only the useShape ORM API:
- Remove ng.sparql_update, sessionPromise, ensureGraphNuri imports
- Use privateNuri (useShape scope) directly as @graph for adds
- Create/join are now synchronous (no async wrapper needed)
- Leave uses ngSet.delete() — known limitation: doesn't persist (@wip)

Add event lifecycle e2e scenarios (cycle-de-vie-evenement.feature):
- Create event via form and verify on home screen
- Created event persists after reconnexion
- Consult event detail from home
- Join an event
- Modify event location and verify
- Leave + persistence tagged @wip (ngSet.delete doesn't persist)

Fix DemoMode external navigation: sync initialScreenId prop changes
to internal state via useEffect (was ignored after first mount).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:03:02 +02:00
Sylvain Duchesne 6b95695d34 Fix leaveEvent persistence: use SPARQL DELETE instead of ORM ngSet.delete()
ngSet.delete() updates the local reactive set but does not persist to the
broker. Use ng.sparql_update() with SPARQL DELETE WHERE to remove RDF triples
directly — the broker sends back a GraphOrmUpdate that reactively removes the
item from the ORM set. The two methods must not be combined as they conflict
in the CRDT.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 12:28:02 +01:00
Sylvain Duchesne ea8fbcf8b7 Fix NextGraph write persistence: use private_store_id as useShape scope
Writes (doc_create, orm_frontend_update) failed with RepoNotFound because
useShape with did:ng:i scope doesn't open individual repos in the verifier's
cache. Switched to did🆖${session.private_store_id} as both scope and
@graph, matching the expense-tracker-rdf pattern. This opens the private
store repo via orm_start_graph, making it available for subsequent writes.

Also adds wallet login step to ensureAuth so the verifier bootstraps repos
from the remote broker into localStorage on first run.

Key changes:
- useShapeWithDefaults accepts storeNuri param (private store NURI)
- FestipodDataContext.useNgData() passes private store scope
- ensureGraphNuri() simplified: reuse existing @graph or private_store_id
- ngBootstrap uses ensureGraphNuri + flushAndWait between ORM adds
- harness-ng.tsx uses private store scope for test bridge shapes
- hooks.ts: wallet creation logs in to bootstrap verifier repos
- E2e steps for data loading and persistence verification

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 11:51:12 +01:00
Sylvain Duchesne 708cbeead8 E2E testing infrastructure, NextGraph connection fixes, and documentation
- Add @e2e test layer: real app in broker iframe via Playwright
- Fix broker redirect: conditional auto-init only when inside iframe
- Fix seed data flash: empty data during 'connecting' phase
- Fix Gallery button in iframe: explicit navigate instead of history.back
- Add auth e2e feature scenarios and step definitions
- Update docs: bdd-testing, data-layer-testing, data-layer, AGENTS.md
- Add decision record for conditional NG init approach

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:51:44 +01:00
Sylvain Duchesne 6f9b3ece34 Data-layer BDD testing infrastructure and steps/frontend → steps/ui rename
- Rename steps/frontend/ to steps/ui/ across all modules and shared
- Add data-layer test harness (mock + real broker modes) with Playwright
- Add inscription data-layer steps (@data scenarios)
- Add test auth setup script and browser debug script
- Update docs (architecture, BDD testing, data-layer testing)
- Add ADR for headless wallet creation decision

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:01:46 +01:00
Sylvain Duchesne 901fd659df NextGraph integration (WIP), broker banner, and feature-based architecture
- Add NextGraph data layer with @ng-org/orm, SHEX shapes (Event, UserProfile,
  Participation), session management, and FestipodDataContext with dual-mode
  operation (connected via NextGraph or local seed data)
- Add BrokerBanner and NgStatus components showing connection status
- Refactor to feature-based architecture: organize code by business domain
  (event, user, home, auth, workshop, meeting, notification) instead of
  technical layer. Modules only import from shared/, never from each other
- Collocate BDD features and step definitions with their modules: event-specific
  steps in event/steps/, user steps in user/steps/, shared generic steps remain
  in shared/steps/
- Set up multi-layer BDD structure (frontend/backend/e2e steps per module)
- Add project documentation (AGENTS.md, .project/knowledge/)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 12:19:45 +01:00