Open
Conversation
…bserve hook - Add `usePointerObserve` to @adobe/data-lit: returns Observe<PointerPosition> from pointermove events on the host element. Use with `Observe.toAsyncGenerator(pos, () => false)` for a never-ending presence stream — the data-lit equivalent of the missing 'use-drag-generator' pattern. - Add `movePresence` transaction + `cursorX`/`cursorO` resources to the tictactoe ECS plugin. Fixed envelope IDs (PRESENCE_ID.X / .O) ensure each mouse move replaces the previous cursor entry in the reconciling DB's transient queue rather than accumulating. - Rewrite game-view.ts with a persistent DOM scaffold so the pointermove listener and cursor overlay (.board-wrap / .cursors) survive game-state re-renders. Remote player's cursor rendered as a labeled dot via sendTransient → ReconcilingDB.apply(time=-1) → observe → DOM update. - Add ARCHITECTURE.md with mermaid diagrams: current sync flow, presence data flow, and a diagram showing how persistence could be layered on top without touching the sync code. Co-authored-by: Cursor <cursoragent@cursor.com>
…rker shim Co-authored-by: Cursor <cursoragent@cursor.com>
Full restructure to follow the same patterns as data-lit-tictactoe: - @CustomElement classes, element + presentation + css separation - P2pElement base class (service + syncClient + myMark + withHooks) - useObservableValues for all reactive reads (board, firstPlayer, cursors) - usePointerObserve + Observe.toAsyncGenerator(() => false) in p2p-board for the never-ending presence stream (the "drag sequence" pattern) - sendTransient called from the async generator loop in useEffect - p2p-app is a plain LitElement managing the signaling state machine via @State reactive properties - Delete game-view.ts, ui.ts, game-state.ts (vanilla DOM approach) - Add lit + @adobe/data-lit dependencies; fix tsconfig (experimentalDecorators) Co-authored-by: Cursor <cursoragent@cursor.com>
…ta-lit Remove the hand-rolled base class and use the standard DatabaseElement from @adobe/data-lit as the foundation. DatabaseElement already: - owns the service property - wires withHooks on render() - propagates service via DOM ancestor traversal P2pElement just adds syncClient and myMark on top. p2p-app stays a plain LitElement with @State service so child elements can find it via ancestor traversal — matching how data-lit-tictactoe works. Co-authored-by: Cursor <cursoragent@cursor.com>
…State Phase, offerCode, answerCode, bannerText, bannerError, myMark, and syncClient are now ephemeral ECS resources in p2pPlugin. Transactions (startHostSignaling, startJoinSignaling, setOfferCode, setAnswerCode, setBanner, connected) are applied directly to the local DB — never through syncClient.propose — so they never reach the remote peer. P2pApp now extends P2pElement (DatabaseElement), gets its service for free via ancestor traversal, and reads phase/offerCode/etc. reactively via useObservableValues. No @State, no Phase type, no prop drilling. P2pElement replaces @Property syncClient/myMark with plain getters that read from this.service.resources, so all child elements continue to work unchanged and the game render template needs zero property bindings. Co-authored-by: Cursor <cursoragent@cursor.com>
…gotiation
## @adobe/data — core changes
- Add `TransactionContext<C,R,A>` type (extends Store + `readonly userId`)
- Thread `userId` through `execute()` options in TransactionalStore and
ObservedDatabase so every transaction function sees `t.userId`
- ReconcilingDatabase passes `entry.userId` / `envelope.userId` to each
execute call; removes no-op transient entries from the queue immediately
- Dispatcher suppresses `observe.envelopes` notification for no-op
transactions (empty redo/undo); `hasTransient` only set when effective
- New tests: `t.userId` for local/sync/inbound cases; no-op suppression
## data-lit-tictactoe — library extraction
- Gate `playMove` by `t.userId`: only the current player's mark may move;
undefined userId (standalone/AI) allows all moves
- Retype `TictactoeElement` on `tictactoePlugin` (not `agentPlugin`); child
elements only need the base surface; injecting an agent DB still satisfies
- `main.ts` creates `agentPlugin` DB explicitly and injects it before mount
- Add `src/index.ts` barrel + `exports` map so workspace consumers can
`import { tictactoePlugin, tictactoeTagName } from "data-lit-tictactoe"`
## data-p2p-tictactoe — generic negotiation architecture
- Delete all game-coupled elements: p2p-board/cell/hud, p2p-element,
game-plugin, p2p-plugin, duplicated board-state/player-mark/presence types
- `negotiationPlugin`: remove `myMark` resource; `connected()` takes no args
- New `presencePlugin`: `cursorX`, `cursorO` resources + `movePresence`
transaction keyed by `t.userId`; lives outside data-lit-tictactoe
- New `<p2p-negotiation>`: game-agnostic element with injectable `gamePlugin`,
`gameTagName`, `assignUserId`, optional `presenceTagName`; owns its own
local-only negotiation DB; on WebRTC connect creates the synced game DB and
mounts the game element (optionally wrapped in presence overlay)
- New `<p2p-presence-overlay>`: wraps game via `<slot>`, renders cursor dots,
drives `movePresence` via `usePointerObserve` + async-generator pattern
- `<p2p-app>`: thin shell combining `tictactoePlugin + presencePlugin`,
wiring `assignUserId` role→mark, delegating all logic to `<p2p-negotiation>`
- Update ARCHITECTURE.md with two-database model, new mermaid diagrams
- Update data-sync README with `t.userId` and no-op replication sections
Co-authored-by: Cursor <cursoragent@cursor.com>
Add two always-active Claude rules that capture the locality-of-knowledge and namespace-folder patterns (.claude/rules/data-modelling.md, .claude/rules/namespace.md), then apply them across both tictactoe packages. PlayerMark namespace gains four helpers — values, is, markColor, opponent — so every literal "X" / "O" outside types/player-mark/ disappears: - presence overlay iterates PlayerMark.values and reads markColor, dropping the .cursor--x / .cursor--o CSS rules and the call-site toLowerCase() derivation - presence plugin switches to Partial<Record<PlayerMark, Vec2>> with a PlayerMark.is guard at the boundary - restartGame and currentPlayer use PlayerMark.opponent - defaults use PlayerMark.values[0] instead of "X" - tictactoe-cell narrows with PlayerMark.is(cell) - p2p-app's role-to-mark mapping uses PlayerMark.values index Rules were validated by running parallel subagent proposals on a fresh domain (audit Severity) and on this case; both converged independently. Co-authored-by: Cursor <cursoragent@cursor.com>
…te cast Transaction wrappers previously typed the AsyncArgsProvider path as returning the same `void | Entity` as the sync path, which silently lied about the dispatcher's runtime behaviour (it actually returns Promise<R> when given a Promise/AsyncGenerator factory). Consumers that wanted to await or .catch() the async path were forced to cast. Replace the union signature with a true overload — async-provider first so it wins selection when Input is itself function-shaped — and add a dedicated transaction-functions.type-test.ts covering both green paths (plain → R, async factory → Promise<R>, .catch() compiles without cast) and red paths (.catch on plain result, wrong arg shapes, no-input positional args). Existing Equal<...> assertions in database-schema and create-database-schema-test were strengthened to match the overload. The single call-site cast in p2p-presence-overlay drops out naturally. Co-authored-by: Cursor <cursoragent@cursor.com>
Captures the principle reinforced by the recent ToTransactionFunctions overload fix: `as` asserts a runtime invariant the compiler can't see; it is never the right fix for a declaration that lies. When a cast is the only thing letting consumer code compile, fix the source declaration so the cast disappears for every caller. Co-authored-by: Cursor <cursoragent@cursor.com>
Distil firefly-platform's binding-element / lit / presentation rules into two lean, framework-agnostic rules with no proprietary content: - element.md (~80 lines, scoped to **/*element*) — container element discipline: thin wire between data service and presentation, raw subscription reads, one-line action callbacks, lifecycle through hooks, deletion test as the load-bearing heuristic. - presentation.md (~60 lines, scoped to **/*presentation.ts) — pure function from props to renderable output, only render and unlocalized exports, verbNoun callback names. Per-framework details (Lit / React / Solid) live in a small table at the bottom of each rule rather than scattered through the prose, so the principles read identically regardless of the rendering library. Co-authored-by: Cursor <cursoragent@cursor.com>
The unlocalized bundle pattern is proprietary and Adobe-internal; the presentation rule should describe only the framework-agnostic surface. Render is now the single permitted export. Co-authored-by: Cursor <cursoragent@cursor.com>
The namespace pattern (eponymous folder + file, public.ts barrel, single import surface) applies equally to capability bundles (SyncService.create, SyncService.Options) as it does to data types, not only to files under types/. Loosen the auto-attach glob so the rule surfaces wherever a namespace folder might live. Co-authored-by: Cursor <cursoragent@cursor.com>
…esence filter data/ecs: - Add Database.reset() across core/store/observed/reconciling layers O(archetypes+resources), preserves database identity and observers - Add tests: red/green equivalence, observer fan-out, reconciler queue clear data-sync: - Add hello/welcome handshake protocol: sessionId, watermark-based replay (full vs tail-only), resetRequired flag, onWelcome callback - Add SyncService.sessionId() and lastAppliedTime() for reconnect watermarks - Add ping/pong keep-alive: configurable pingIntervalMs (10s) and livenessTimeoutMs (25s) on both client and server; closes transport on timeout so onClose → reconnect flow triggers automatically - Add opt-in logger option to createSyncService and createSyncServer - Add onClose to SyncTransport interface; implement in loopback, WebSocket - Update README: connection resilience section covering hello/welcome, keep-alive, liveness, logger, and updated API reference tables - Tests: transport-onclose, keepalive (8 cases with fake timers), reconnect data-p2p-tictactoe: - Refactor signaling.ts to pre-negotiate two data channels (sync id=0, signal id=1) using negotiated:true; return pc + signalChannel - Add renegotiator.ts: ICE restart over the signal channel; host triggers restartIce on pc.connectionState===disconnected, trickles candidates, joiner applies offer/answer — heals path changes without re-signaling - negotiation-controller.ts: wire renegotiator, add reconnect() to NegotiationController, add connection/role/sessionId resources to negotiationPlugin, show disconnected banner with reconnect button - Remove error-event-as-close from WebRTC transports (was causing false disconnects); remove loopback onClose from wireHostSync (internal infra) - Wire SyncService/SyncServer logger to console for dev visibility - Fix priorSessionId/initialWatermark capture before dispose() in wireHostSync data-lit-tictactoe: - Add xWins/oWins/draws resources to tictactoePlugin; tally in restartGame - Update HUD element, presentation, and CSS to display score pill - Improve status text: "X's turn" / "X wins!" / "Draw!" data-p2p-tictactoe (presence): - Filter local player mark from presence overlay; peer cursors only Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Related Issue
Motivation and Context
How Has This Been Tested?
Screenshots (if appropriate):
Types of changes
Checklist: