26 lines
9.6 KiB
Markdown
26 lines
9.6 KiB
Markdown
# Saved Scenario identity routing: independent source critique
|
||
|
||
Read-only review of frozen renderer68a81c3f50d846e34cae96090979543878514c1e and plan920b718f62c20ba3c58f664dae3c5ec6b0e65ce6 (`harness/scenario-renderer-bridge.md`). No source changes, compilation or native evidence. Line references below are relative to `crates/oa-desktop/src/` at68a81c3. These are next-integration hazards, not claims that the still-disabled saved workspace currently admits them.
|
||
|
||
**Recommendation:** Page::Behavior is the smallest reasonable routing candidate, following the existing saved-state adapter. Explicit Representation must be authoritative across entry, events, Back, presentation and usages; changing only canvas_workspace::Scope is insufficient. Retain semantic owner and selected IDs independently of saved Diagram identity. A new page does not remove these shared assumptions and would duplicate form/canvas dispatch, so no materially better page-level alternative is established.
|
||
|
||
## Concrete unsafe assumptions and minimum changes
|
||
|
||
1. **Entry is not a single setter.** `app.rs:1757–1791` currently refuses saved pick/reveal before Apply. Removing that check alone reaches Diagram choice (`3093–3119`), which sets active_diagram and calls the architecture fit, without routing to Behavior. `app/state_diagram.rs:12–42` only recognizes StateMachine. `behavior.rs:1041–1051` resolves pending owner, active owner, selection ancestry, then the first behavior; a selected Diagram UUID has no semantic ancestry (`1078–1107`). `Page(Behavior)` can preselect an owner (`app.rs:2454–2462`). Add one guarded saved-Scenario entry/reconciliation adapter: validate Diagram→Scenario/layer, set Representation::Saved, matching active_diagram and active_owner atomically, then retire canvas/input lifetime and render the resolved scene. Never let a missing/retargeted saved view fall through to the first Scenario. Keep architecture mutation/export refusal independently scoped; enabling rendering does not authorize those operations.
|
||
|
||
2. **Same-owner views alias at several retained and passive boundaries.** Context has document/scenario/revision/epoch only (`behavior/scenario.rs:71–76`). Presentation identity and enter use(document,owner) (`329–339`); workspace sensor key also omits view (`scenario/workspace.rs:75–127`). Shared Scope and control IDs omit view (`behavior/canvas_workspace.rs:60–100`); toolbar validation explicitly assumes scope.owner==context.scenario (`app.rs:1284–1288`). Do not put Diagram UUID into Scope.owner as a shortcut. Add representation identity alongside semantic owner and propagate it through Scope::current, Intent validation, widget IDs, sensor keys, Context and camera boundary (`app.rs:1341–1355`). In particular, Viewport/Scrolled/FormShown accept owner/revision while intentionally ignoring epoch (`1810–1857`), and Restored relaxes revision too. They must check representation. A stable navigation-visit token is also needed for A→B→A without an intervening rendered frame; keying sensors by canvas_epoch would cause feedback because viewport/pan themselves change that epoch. Keep existing epoch relaxation within the same valid visit.
|
||
|
||
3. **Back stores the caller owner, not its representation or active diagram.** Caller/DetailsRestore (`scenario.rs:218–239`) and open_use/valid_caller/back/restore (`377–451`) currently carry owner, occurrence, target, camera and scroll. App OpenUse/Back (`1921–1971`) forcibly restore active_owner before select to defeat its same-UUID fast path; keep that fix. Capture caller Representation and destination visit plus any active_diagram value that the saved transition replaces. Opening a definition should deliberately enter Derived(callee) unless the user explicitly chooses a saved callee view; do not guess among saved views. Back must validate that the captured saved Diagram still exists and projects the captured owner, as well as the existing ScenarioUse target predicate. Retarget/deletion must refuse/skip that caller frame with the existing unavailable feedback, not silently restore automatic geometry. Rename with stable identity can still return. Restore page/representation/active_diagram/owner independently of whether selection already equals the occurrence. Back restores presentation values into a fresh visit; it must never revive the caller's old event token. Scroll observer identity (`behavior.rs:38–205`) and delayed form focus Stamp (`scenario/focus.rs:11–37`) need the same destination check when they can survive navigation.
|
||
|
||
4. **active_diagram is also remembered architecture state, not a reliable Behavior selector.** Current derived Scenario reveal/OpenUse leave active_diagram untouched (`app/diagram_usages.rs:381–393`, `app.rs:1921–1961`). Page(Diagram) deliberately replaces non-architecture views with an architecture default (`2435–2452`). sync only checks active diagram existence/layer (`792–805`); Apply's UUID-following updates active_diagram/active_owner/state diagram but knows no new Representation (`853–925`). Introduce an explicit consistency rule: visible Saved(d) implies active_diagram==d and projection owner matches; visible Derived must not retain an unrelated saved Scenario as its apparent current view. Preserve remembered architecture where useful, but never infer Saved solely from a lingering active_diagram. Reconcile after property UUID edits, source adoption, Undo/Redo, deletion, projection-owner retarget, duplication and layer changes. Any identity change must invalidate pending gestures/measurements even when owner or selected UUID remains equal. A shared accessor returning validated(owner,representation) is smaller and safer than independently rebuilding that pair in each caller.
|
||
|
||
5. **Passive routing must preserve pending semantics and selection.** apply (`app.rs:853–863`) refuses source drafts and pending behavior forms, but can commit valid property drafts. select (`1062–1093`) calls Apply and can change active_owner; its equal-ID path returns early. A new entry helper must not use select/Apply merely to display a view or recover an owner. Keep explicit choice's established property-Apply policy separate from the passive routing postlude; validate a stale destination before any Apply, and preserve both valid/invalid source-child/parent form buffers on rejected navigation. Existing usage navigation requires every modal/source/property/behavior guard to be clean (`app/diagram_usages.rs:16–29`), and OpenUse/Back explicitly do so. Reuse those predicates for equivalent new paths. Saved-view selection remains either a semantic object or an explicitly inspected Diagram; do not convert a message/lifeline selection into the saved Diagram just to retain workspace identity. Semantic form commands continue to address owner/occurrence IDs; view-only lane/send edits address the captured Diagram. Merely entering Saved must not change the existing Select/reorder tool into spacing-drag semantics.
|
||
|
||
6. **The current usage/reveal contract is owner-only and automatic.** Scenario usage builds Scene::sequence separately, emits Surface::Scenario(owner), and Whole uses origin-zero Size (`scenario/usage.rs:25–40,140–165`); reveal requeries that automatic surface (`167–204`). collect deduplicates by subject/surface/target (`diagram_usage.rs:180–207`), so saved views must remain distinct surfaces. Current Surface::Saved is categorized/routed as architecture or state, and saved Scenario is explicitly refused (`app/diagram_usages.rs:293–299,335–365,434–445`). Introduce a typed surface→representation mapping (existing Saved(d) can remain the saved identity if every category/query/router discriminates its projection). Use the same resolved representation and full bounds for paint, query, Choose revalidation, focus and camera. Scenario viewport→surface currently hardcodes Scenario(owner) (`app/diagram_reveal.rs:49–54`); it must report the selected Saved(d), and final deferred reveal must verify current representation plus visit before consuming a measurement. Otherwise A/B with the same owner can jump using automatic/A bounds or satisfy the wrong first-layout completion. Keep one-surface Choose revalidation and existing async token/revision release rules. Measured-label/operation visibility remains the separately documented dependency, not something identity wiring alone solves.
|
||
|
||
## Ordered narrow integration and discriminating controls
|
||
|
||
First establish validated representation/owner access, entry/reconciliation and all context/retained-widget stamps, while preserving existing semantic forms and architecture refusal. Then carry complete caller representation through OpenUse/Back and shared scroll/focus ownership. Finally wire the resolved Scene and explicit surface into paint/query/deferred reveal together; never enable a Saved chooser row whose actual reveal still rebuilds automatic geometry. Full bounds/fit changes remain necessary at that last step; the current0.05 floor/origin-zero fit is already recorded in the bridge note.
|
||
|
||
Critical controls for the parallel acceptance matrix: two saved views of one owner with equal viewport size; A→B→A before a render with held Viewport/Scrolled/toolbar/FormShown; SavedA→DerivedC→Back while an edit has already selected the caller UUID; saved caller deleted or retargeted during the visit; valid/invalid pending source-child and property buffers when selecting another view; semantic occurrence rename/UUID edit and saved Diagram UUID edit followed by Undo/Redo; asynchronous Choose for SavedA while SavedB/Derived is displayed; negative-origin Saved Whole reveal after the first exact measurement. All refused/passive cases need whole Project/source/history equality and retained raw draft checks. A separate page would still require all these checks.
|