feat: add Perch and playable Flight graphs - #5
Conversation
Add the fifth Map view, native virtualized canvas, durable multi-board placement, nested membership rules, dependency editing, and the append-only v9 history foundation. Co-Authored-By: Codex <noreply@openai.com>
Land the user-owned spatial workspace, event-indexed history, forking and merge flow, and Zoetrope-inspired Flight Graph playback. Keep live observation responsive by narrowing invalidations, bounding clocks, and removing replay/layout feedback loops. Co-Authored-By: Codex <noreply@openai.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4b61077139
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for membership in state.memberships.values | ||
| where membership.boardID == source.id && membership.isVisible { |
There was a problem hiding this comment.
Carry hidden memberships into fork branches
When a custom board contains a rule-matching node with a manual .exclude, this filter omits that membership from the fork. A later merge interprets the missing/default branch value as a branch-side change from the historical base and automatically removes the parent's exclusion; the next live synchronization can then make the card visible again. Copy all membership state into the fork, even when a membership is not currently visible.
Useful? React with 👍 / 👎.
| branch.mergeBaseEventID = db.lastInsertedRowID | ||
| branch.updatedAt = now | ||
| try Self.update(branch, in: db) |
There was a problem hiding this comment.
Reconcile resolved conflicts before advancing the merge base
When both sides changed a position and the user explicitly chooses the parent, applyMerge leaves the conflicting branch position untouched but advances the base to the parent's current state. On the next merge, the parent equals the new base while the branch still differs, so the previously rejected branch position becomes an automatic change and silently overwrites the parent. Before advancing the base, reconcile the branch projection with the chosen result or otherwise persist the resolution so it cannot be reapplied.
Useful? React with 👍 / 👎.
| guard let self else { return } | ||
| if case .failure(let error) = result { errorDescription = error.localizedDescription } |
There was a problem hiding this comment.
Refresh cached placements after a successful move
After a drag succeeds, only the optimistic cards value and the database are updated; synchronizedBySource still contains the old placement. On an active board, the next coalesced adoptLiveSeeds rebuilds the card from that stale cache and snaps it back to its previous position until a board switch or unrelated workspace synchronization reloads the database. Update the cached synchronized node or schedule a synchronization on success.
Useful? React with 👍 / 👎.
| title: snapshot?.identity.title ?? fact.title, | ||
| state: snapshot?.state ?? fact.placeholderState, | ||
| isStale: snapshot?.isStale ?? false, |
There was a problem hiding this comment.
Let subagent completion override a stale child snapshot
When the archive includes events emitted by the child, sessions[fact.key] exists, so this expression always prefers the child's last reduced state over the .ended placeholder set by the parent's subagentFinished event. A child whose last own event was thinking or using a tool therefore remains active after its explicit completion, and its graph edge is also rendered active. Track the completion in the effective node state rather than only in the fallback placeholder.
Useful? React with 👍 / 👎.
| updatedAt: now | ||
| ) | ||
| if membership != previousMembership { |
There was a problem hiding this comment.
Compare membership semantics before updating timestamps
Because synthesized MapMembership equality includes updatedAt, assigning now before this comparison makes every existing membership unequal on every synchronize call, even when its rule result and override did not change. On a selected custom board, a single rule-facing state change can consequently upsert and append history for every node; with the repository's expected hundreds of sessions, repeated bursts cause needless SQLite work and unbounded playback-history growth. Compare ruleMatches and override first, and only stamp/write actual changes.
AGENTS.md reference: AGENTS.md:L108-L118
Useful? React with 👍 / 👎.
Summary
Verification
Release boundary
This PR does not contain a release bump or tag. A Dev release will be cut from main after this PR merges.