Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fee9b46e6 | ||
|
|
66a1f1690f | ||
|
|
911f63e63c |
@@ -35,26 +35,26 @@ Use Gitea for live coordination and the relevant code version for implementation
|
||||
|
||||
## Coordination and integration
|
||||
|
||||
All independent agents coordinating through Gitea run on completely separate machines, each with its own clone, filesystem, tools, processes, and runtime. Gitea and pushed Git history are the shared communication and delivery channels. Local files (including `harness`), unpushed commits, worktrees, test results, and localhost services are not accessible to peers. Matching branch names or paths do not imply synchronized contents.
|
||||
|
||||
1. On starting or resuming, including after compaction, inspect the working branch, local changes, applicable instructions, relevant shared records, and existing implementation. Recover context from authoritative records. Check for newer shared instructions on the actual remote default branch without overwriting local work. Read what is relevant; avoid repeated full-history scans and constant polling.
|
||||
2. Search before creating tasks. Claim a meaningful outcome in its Gitea issue with a stable session identity, scope, branch/worktree, and acceptance criteria. Keep small steps within that task. Recheck after claiming; comments are not atomic locks. Respect existing ownership and resolve overlapping claims before overlapping work.
|
||||
3. Independent sessions are peers. Each owns its contribution and delegated work. Record one integration/release owner when a milestone needs shared coordination. Use separate worktrees or checkouts, partition files/interfaces, and coordinate changes to shared specifications and documents.
|
||||
4. At meaningful checkpoints or handoffs, record changed results, commit/PR links, verification commands and environment, blockers, and next actions. Identify the tested code version and uncommitted or unpublished work; make required artifacts accessible to other sessions. Do not assume another agent is running or has read a message; silence alone does not establish abandonment. Record evidence and preserve existing work when reassigning a task.
|
||||
5. Follow project review and merge conventions. Inspect upstream changes, resolve conflicts while preserving others' edits, and verify the combined result. Do not overwrite others' work or force-push shared history to resolve coordination problems.
|
||||
2. Search before creating tasks. Claim a meaningful outcome in its Gitea issue with a stable session identity, scope, remote repository/branch, and acceptance criteria. Keep small steps within that task. Recheck after claiming; comments are not atomic locks. Respect existing ownership and resolve overlapping claims before overlapping work.
|
||||
3. Independent sessions are peers. Each owns its contribution and delegated work on its own machine. Record one integration/release owner when a milestone needs shared coordination. Use distinct remote work branches, partition files/interfaces, and coordinate changes to shared specifications and documents. Worktrees provide local isolation only; they do not share work between machines.
|
||||
4. At meaningful checkpoints or handoffs, record changed results, commit/PR links, verification commands and environment, blockers, and next actions. Identify the tested code version and label uncommitted or unpublished work as local-only. Before asking peers to consume results, push the required code and versioned documentation, including relevant `harness` updates; publish other required artifacts in Gitea and link them. Use repository-relative paths with commit references, reproducible commands, and necessary environment details; local paths and localhost URLs alone are not handoffs. Do not assume another agent is running or has read a message; silence alone does not establish abandonment. Record evidence and preserve existing work when reassigning a task.
|
||||
5. Follow project review and merge conventions. Fetch the relevant remote branches and inspect the identified commits before reviewing or integrating another machine's work. Resolve conflicts while preserving others' edits, and verify the combined result locally; report material environment differences. Local instruction-file edits reach peers only after publication and retrieval. Do not overwrite others' work or force-push shared history to resolve coordination problems.
|
||||
6. Close work when its acceptance criteria are met and the result is integrated or delivered as appropriate. Mark duplicates and superseded tasks explicitly with replacement links. Then choose further useful unowned work while the requested objective remains incomplete.
|
||||
|
||||
Use Gitea MCP for shared coordination. If unavailable, report the failure, buffer necessary handoff information locally, and continue already-owned or clearly non-overlapping work. Reconcile with current Gitea state when access returns; do not claim an ownership update, review, or publication succeeded without confirmation.
|
||||
|
||||
## Delegation and reasoning
|
||||
|
||||
Use GPT-6 Astra exclusively. Delegate bounded independent research, design, implementation, or review when it improves progress, judgment, or preserves the parent's context. Give subagents the relevant context, ownership boundaries, result format, and evidence requirements. Use fresh critics when practical. The parent owns synthesis and integration and normally consolidates results into Gitea. Choose concurrency according to useful independent work and available resources, including other sessions; there is no agent-count quota.
|
||||
Use GPT-6 Astra exclusively. Gitea peers are independent remote sessions, not subagents controlled by this session. Spawned subagents use the execution environment actually provided by the agent tools; do not assume they share files, and publish their results before Gitea peers need them. Delegate bounded independent research, design, implementation, or review when it improves progress, judgment, or preserves the parent's context. Give subagents the relevant context, ownership boundaries, result format, and evidence requirements. Use fresh critics when practical. The parent owns synthesis and integration and normally consolidates results into Gitea. Choose concurrency according to useful independent work and available resources, including other sessions; there is no agent-count quota.
|
||||
|
||||
Optimize reasoning effort for verified outcomes per cost, including review, retries, and repair:
|
||||
|
||||
Main agent: default to high for planning, synthesis, integration, and acceptance decisions; medium for dispatching a settled plan and routine coordination.
|
||||
|
||||
Subagents: default to medium for bounded implementation and targeted research; low for straightforward retrieval, mechanical edits, and running established checks; high for complex debugging, correctness review, adversarial analysis, or ambiguous design.
|
||||
|
||||
Use extra-high selectively for difficult foundational semantic, methodological, formal, or cross-domain decisions. Reserve max for exceptional unresolved problems where additional reasoning is likely to justify its cost. Retain higher settings when evidence supports their value.
|
||||
- Main agent: default to high for planning, synthesis, integration, and acceptance decisions; medium for dispatching a settled plan and routine coordination.
|
||||
- Subagents: default to medium for bounded implementation and targeted research; low for straightforward retrieval, mechanical edits, and running established checks; high for complex debugging, correctness review, adversarial analysis, or ambiguous design.
|
||||
- Use extra-high selectively for difficult foundational semantic, methodological, formal, or cross-domain decisions. Reserve max for exceptional unresolved problems where additional reasoning is likely to justify its cost. Retain higher settings when evidence supports their value.
|
||||
|
||||
Allocate by task difficulty and consequences, not role or length alone. Start higher when a clearly difficult decision has expensive downstream consequences. Escalate for concrete reasoning difficulties; resolve missing evidence or unclear requirements at their source. Set subagent effort explicitly through supported controls to avoid unintended inheritance. Do not claim an unapplied change or alter global configuration merely to enforce these preferences.
|
||||
|
||||
|
||||
@@ -1117,6 +1117,10 @@ fn in_machine(p: &Project, id: Id, machine: Id) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../../../harness/experiments/behavior-layout-scene-20260912/mod.rs"]
|
||||
mod behavior_layout_scene_experiment;
|
||||
|
||||
struct BoxItem {
|
||||
id: Id,
|
||||
rect: Rectangle,
|
||||
@@ -1435,6 +1439,8 @@ impl Scene {
|
||||
// the preceding action/state/reference box, before recording rows.
|
||||
*y += 16.;
|
||||
}
|
||||
#[cfg(test)]
|
||||
behavior_layout_scene_experiment::resolve_row_anchor(*id, y);
|
||||
if ordered {
|
||||
let slot = scenario::Slot {
|
||||
parent: owner,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Actual Scene anchor experiment — pending execution
|
||||
|
||||
2026-09-12. Prepared by delegated reviewer `/root/diagram_usage_workflows` in branch `codex/diagram-coverage-fixes-ytr83r-20260912`, inspected base `f23615ffebdc3ca6872f98b6889ec49bb4e8abda` plus root's ongoing correction work. Only this experiment directory was authored. **Root applied the test-only wiring on isolated branch `codex/behavior-layout-scene-ytr83r-20260912`, based on `46b4f3c0d0f04050ad6d705051ee0199138e47a2`. Not compiled or executed.** Root owns the build checkpoint. No production schema choice, native acceptance or fix is claimed.
|
||||
|
||||
## Question and candidates
|
||||
|
||||
Can a minimal presentation input feed the actual current desktop Scene before it constructs rows, message polylines, nested frames and lifecycle geometry, while preserving the semantic Project and occurrence identities? Does explicit pointer intent preserve latent authored values on zero motion?
|
||||
|
||||
The candidate is an occurrence-ID-to-desired-row map. A tiny test-only hook raises the current Scene cursor to a requested row before recording an occurrence. This deliberately retains the existing renderer's next-row floor: it is a **downward spacing adapter**, not a general absolute-coordinate projector. The competing gap policy remains meaningful but is not implemented here: this experiment first tests the proposed real-Scene integration boundary. It cannot select a production storage schema.
|
||||
|
||||
Rejected experimental shortcuts: a standalone rectangle model would not test Scene; translating final polylines after rendering would bypass dependent frames/lifecycle; extracting the whole renderer would exceed the uncertainty being tested. This adapter uses the existing code that actually generates all those representations.
|
||||
|
||||
## Files and exact root wiring
|
||||
|
||||
- `fixture.arcadia`: hand-authored current source with explicit UUIDs, two occurrences sharing one communication mean, a fragment containing an operand/action box, and a self-call/reply. The self messages intentionally have unassigned global exchanges: current global relation validation rejects a self edge. Their explicit lifelines permit rendering; incomplete-exchange warnings are expected, semantic errors are not.
|
||||
- `mod.rs`: private child tests of `behavior`, a scoped thread-local anchor adapter, pointer-delta candidate operation, and independent numerical checks against actual Scene outputs.
|
||||
- `wiring.patch`: proposed two-site test-only wiring in `crates/oa-desktop/src/behavior.rs`. It adds the child module and calls `resolve_row_anchor(*id, y)` immediately before the `if ordered` block that records rows/slots in `Scene::occurrences`, **after** any self lifecycle clearance. The relative module path is from `crates/oa-desktop/src`. The line numbers are informational; verify the context against the exact integration version.
|
||||
|
||||
On the isolated wired branch, run:
|
||||
|
||||
```powershell
|
||||
cargo test -p oa-desktop behavior_layout_scene_experiment -- --nocapture
|
||||
```
|
||||
|
||||
Use the project's configured Windows compiler/linker environment. No dependency, Cargo target or main-function changes are needed. The hook is compiled only under cfg(test), inactive unless the current thread installs an AnchorScope, and restored by Drop after normal return or unwind. A test checks that another thread does not inherit the anchor map and that an intentional panic restores the baseline. Do not catch or reinterpret that expected panic output as a product crash; its test must still pass.
|
||||
|
||||
The isolated branch already contains this wiring; `wiring.patch` records its reviewable delta and must not be applied twice. If a compiler/parser assertion fails, preserve the failure, identify the exact reviewed source version, and correct the fixture or adapter at this boundary. Do not weaken independent geometric assertions solely to make the test green. Never claim a passing test from the existence of these files.
|
||||
|
||||
## Independent evaluators
|
||||
|
||||
The baseline is the actual `Scene::sequence` with no installed anchors. The hand-derived expected row coordinates are first148, second240, fragment332, nested action477, self-call636 and self-reply728. These follow the current seed metrics (message92, fragment header55, operand90, action95, trailing fragment64) and are observable implementation oracles, not proposed permanent UX rules. The nested box top is457; fragment height240; self-call endpoint y values are636 and660. Line identities and endpoints are asserted independently, not just compared between two calls to the same projector.
|
||||
|
||||
Tests cover:
|
||||
|
||||
1. Actual `oa_core::language::parse` and canonical format/reparse Project equality; baseline actual Scene equality after semantic source round trip; repeated exchange references retain distinct line UUIDs.
|
||||
2. Move only second delivery by40: its row becomes280, first stays148, nested action becomes517, fragment translates with unchanged height240, and self-call/reply geometry moves by40 with unchanged x and identity. Whole Project and canonical semantic source remain equal.
|
||||
3. Move the nested action by30: actual fragment grows from240 to270 while its top remains332; later self-call shifts. Move that self-call by18 and assert actual send/receive y684/708 and reply776; the nested box remains unchanged.
|
||||
4. Move first by200: second is visibly displaced to440 while its authored desired value stays240. Zero pointer delta returns the exact original candidate. Moving first back restores second240. Explicitly assigning second.desired=440 before moving first back deliberately preserves440 instead, exposing the distinction between pointer no-op and explicit latent-intent change.
|
||||
5. Reorder the repeated occurrences in actual source and parse it; the override stays attached to the second occurrence UUID. Its row is280 and the first follows at372. Projection does not write back any semantic model change.
|
||||
6. Scope restoration on panic and isolation from other test threads.
|
||||
7. Explicit upward-drag counterexample: the requested y200 remains rendered at240 under the current floor.
|
||||
|
||||
Model equality compares the entire Project. Scene equality checks rows, scopes, size, lanes, lifecycle, open calls, lines/labels, boxes and insertion slots; it does not invoke GPU painting. Explicit UUIDs are necessary because parsing declarations without IDs allocates new identities, which would make separate source parses unsuitable for occurrence-address comparison.
|
||||
|
||||
## Limits and next decision
|
||||
|
||||
This is real parser/Scene construction evidence **only after it runs**. Layout maps remain external experimental data; source round trips cover the semantic Project, not persisted layout. No new parser syntax, Document transaction, REST operation, undo stack, dirty-source workflow, route bend, text measurement, native gesture, inspector, resize, or performance result is established.
|
||||
|
||||
The current floor retains automatic whitespace and cannot establish all feasible free positions, upward drag, independent frame resize, gap-policy superiority or minimal displacement. Its limitation is useful evidence: if feeding requested anchors into the existing cursor still feels rigid, a real constraint/inverse policy is required, not more seed spacing. The next bounded choice is a declared anchor/constraint policy, then one typed layout record through source/Document/REST; broad renderer extraction is not justified by this probe.
|
||||
|
||||
Self-call send/receive assertions intentionally follow the actual current polyline contract. They do **not** assert activation correctness or substitute for peer #21's work. Activation boxes are generated by the real existing Scene/lifecycle path, and source roundtrip equality includes them, but that is not an independent oracle for nested activation start/depth/paint order. The fixture does not cover delayed creation/deletion, parallel operands, nested calls, cross-operand semantic moves, hidden views, copy/delete/reidentify lifecycle, or large models. Coordinate any later anchor contract with #16/#21 owners and retain these gaps in published evidence.
|
||||
|
||||
Root should publish this directory and the exact wired code/test result together for remote peer review. Independent machines cannot consume workspace-local links.
|
||||
|
||||
## Root source review before execution
|
||||
|
||||
The panic-restoration evaluator originally accepted any panic inside its scope, so an earlier failed geometry/thread assertion could have looked like successful deliberate unwinding. Root corrected it to require the exact deliberate panic payload. Moved message polylines now also require equal point counts before zipped endpoint comparisons. These are evaluator corrections; execution remains pending.
|
||||
|
||||
Independent bounded review confirmed hook placement, fixture assumptions and the hand-derived coordinates without executing them. Its coverage caveat prompted adding box marker metadata and insertion-slot labels to equality comparisons. Transient selection, camera and interaction state are not part of this geometry comparison. No material source finding remains from that review; compiler/runtime evidence is still required.
|
||||
|
||||
## Peer activation integration boundary
|
||||
|
||||
A read-only check of peer source `8112dea7a371755c7373f4ecc09a6488a73ac136` found that the pre-row hook remains upstream of typed activation construction and final message anchoring. That anchoring preserves generated y coordinates. Its column-zero self-call changes x from the old lane/lane+75/lane+75/lane path to lane/lane+66/lane+66/lane+6; receive y remains660 in this fixture, and the activation begins there rather than at send y636.
|
||||
|
||||
The peer branch and this branch have different lifetime integration states. This branch has production lifetime rendering, message alignment, self-lifecycle clearance and alive_spans clipping. In the peer checkpoint, scenario_lifecycle is cfg(test)-only and typed activation_segment emits an unclipped span. A future union must retain the lifetime behavior and reconcile duplicate activation methods; copying either whole Scene implementation is insufficient. Compare both lifecycle geometry and typed segment identity/rectangle/column/finish, and verify delayed Create/Delete inside and outside operands. Do not remove the existing lifecycle comparison to make the peer version compile. A segment clipped before its semantic reply must not falsely claim a visible reply termination. No peer code has been merged by this experiment.
|
||||
|
||||
[Shared integration finding](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/21#issuecomment-698) and [peer activation source](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/src/commit/8112dea7a371755c7373f4ecc09a6488a73ac136/crates/oa-desktop/src/behavior/scenario_activation.rs). This is source compatibility evidence, not runtime acceptance.
|
||||
@@ -0,0 +1,60 @@
|
||||
arcadia 7;
|
||||
project layout_probe "Behavior layout Scene probe" id "10000000-0000-4000-8000-000000000001" {
|
||||
oa_entity sender "Sender" id "10000000-0000-4000-8000-000000000002" {}
|
||||
oa_entity receiver "Receiver" id "10000000-0000-4000-8000-000000000003" {}
|
||||
oa_activity inspect "Inspect" id "10000000-0000-4000-8000-000000000004" {}
|
||||
allocation inspect_assignment "Receiver performs inspection" id "10000000-0000-4000-8000-000000000005" {
|
||||
source @receiver; target @inspect;
|
||||
}
|
||||
communication_mean channel "Shared channel" id "10000000-0000-4000-8000-000000000006" {
|
||||
source @sender; target @receiver; oriented false;
|
||||
}
|
||||
oa_scenario conversation "Conversation" id "10000000-0000-4000-8000-000000000007" {
|
||||
viewpoint ParticipantInteraction;
|
||||
steps [@first, @second, @fragment, @self_call, @self_reply];
|
||||
}
|
||||
oa_lifeline left "Sender role" id "10000000-0000-4000-8000-000000000008" { parent @conversation; represented @sender; }
|
||||
oa_lifeline right "Receiver role" id "10000000-0000-4000-8000-000000000009" { parent @conversation; represented @receiver; }
|
||||
oa_occurrence first "First delivery" id "10000000-0000-4000-8000-00000000000a" {
|
||||
parent @conversation;
|
||||
body { Exchange {
|
||||
exchange @channel; sender @left; receiver @right;
|
||||
message_kind Asynchronous;
|
||||
}; };
|
||||
}
|
||||
oa_occurrence second "Second delivery" id "10000000-0000-4000-8000-00000000000b" {
|
||||
parent @conversation;
|
||||
body { Exchange {
|
||||
exchange @channel; sender @left; receiver @right;
|
||||
message_kind Asynchronous;
|
||||
}; };
|
||||
}
|
||||
oa_occurrence fragment "Optional inspection" id "10000000-0000-4000-8000-00000000000c" {
|
||||
parent @conversation;
|
||||
body { Fragment { operator Optional; branches [@operand]; }; };
|
||||
}
|
||||
oa_occurrence operand "When requested" id "10000000-0000-4000-8000-00000000000d" {
|
||||
parent @fragment;
|
||||
body { Branch { guard "requested"; occurrences [@nested]; }; };
|
||||
}
|
||||
oa_occurrence nested "Inspection occurrence" id "10000000-0000-4000-8000-00000000000e" {
|
||||
parent @operand;
|
||||
body { Action { action @inspect; lifeline @right; }; };
|
||||
}
|
||||
// Self calls intentionally have no global self-edge: such a relation is
|
||||
// invalid in the current metamodel. Their unassigned exchange is a known
|
||||
// incomplete-model warning, while explicit lifelines make Scene drawable.
|
||||
oa_occurrence self_call "Local call" id "10000000-0000-4000-8000-00000000000f" {
|
||||
parent @conversation;
|
||||
body { Exchange {
|
||||
exchange none; sender @right; receiver @right; message_kind Call;
|
||||
}; };
|
||||
}
|
||||
oa_occurrence self_reply "Local reply" id "10000000-0000-4000-8000-000000000010" {
|
||||
parent @conversation;
|
||||
body { Exchange {
|
||||
exchange none; sender @right; receiver @right;
|
||||
message_kind Reply; reply_to @self_call;
|
||||
}; };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
arcadia 7;
|
||||
// Separate lifetime probe: preserve this authored note and all stable IDs.
|
||||
project lifetime_probe "Delayed receiver lifetime" id "20000000-0000-4000-8000-000000000001" {
|
||||
oa_activity prepare "Prepare request" id "20000000-0000-4000-8000-000000000002" {}
|
||||
oa_activity serve "Serve request" id "20000000-0000-4000-8000-000000000003" {}
|
||||
interaction service "Service request" id "20000000-0000-4000-8000-000000000004" {
|
||||
source @prepare; target @serve;
|
||||
}
|
||||
oa_scenario lifetime_conversation "Receiver created on demand" id "20000000-0000-4000-8000-000000000005" {
|
||||
viewpoint OperationalActivity;
|
||||
steps [@before_birth, @create_receiver, @service_call, @delete_receiver];
|
||||
}
|
||||
oa_lifeline requester "Requester" id "20000000-0000-4000-8000-000000000006" {
|
||||
parent @lifetime_conversation; represented @prepare;
|
||||
}
|
||||
oa_lifeline provider "Provider" id "20000000-0000-4000-8000-000000000007" {
|
||||
parent @lifetime_conversation; represented @serve;
|
||||
}
|
||||
oa_occurrence before_birth "Prepare before receiver exists" id "20000000-0000-4000-8000-000000000008" {
|
||||
parent @lifetime_conversation;
|
||||
body { Action { action @prepare; lifeline @requester; }; };
|
||||
}
|
||||
oa_occurrence create_receiver "Create receiver" id "20000000-0000-4000-8000-000000000009" {
|
||||
parent @lifetime_conversation;
|
||||
body { Exchange {
|
||||
exchange @service; sender @requester; receiver @provider;
|
||||
message_kind Create;
|
||||
}; };
|
||||
}
|
||||
oa_occurrence service_call "Call receiver" id "20000000-0000-4000-8000-00000000000a" {
|
||||
parent @lifetime_conversation;
|
||||
body { Exchange {
|
||||
exchange @service; sender @requester; receiver @provider;
|
||||
message_kind Call;
|
||||
}; };
|
||||
}
|
||||
// Death ends the visible activation but deliberately does not invent a Reply.
|
||||
oa_occurrence delete_receiver "Delete receiver" id "20000000-0000-4000-8000-00000000000b" {
|
||||
parent @lifetime_conversation;
|
||||
body { Exchange {
|
||||
exchange @service; sender @requester; receiver @provider;
|
||||
message_kind Delete;
|
||||
}; };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
# Lifetime extension of the actual Scene experiment — not executed
|
||||
|
||||
Prepared 2026-09-12 in `codex/behavior-layout-scene-ytr83r-20260912`, inspected
|
||||
HEAD `911f63e`. This extension owns only `lifetime.arcadia`, `lifetime.rs`, this
|
||||
note, and the `mod lifetime;` declaration in the existing experiment module.
|
||||
The original fixture and its seven baseline tests are unchanged. Root owns the
|
||||
README compatibility note, compiler resource, source integration and publication.
|
||||
No production renderer, parser or model code was edited here.
|
||||
|
||||
## Question and method
|
||||
|
||||
Does the current test-only row-anchor hook propagate a changed Create/Delete
|
||||
position into the actual Scene's local lifetime, marker hit identity, aligned
|
||||
message endpoints and activation clipping, while leaving semantic/source state
|
||||
and occurrence IDs intact?
|
||||
|
||||
The separate source fixture declares stable UUIDs, a sender action before receiver
|
||||
birth, then Create, Call and receiver Delete. All three messages reference one
|
||||
valid Interaction; explicit sender/receiver lifelines represent its Activity
|
||||
endpoints. A Reply is deliberately absent: receiver death must not manufacture a
|
||||
reply or erase the unanswered-call presentation cue. The test requires real
|
||||
language parsing, no semantic errors, ordinary `Document::from_bundle` admission,
|
||||
canonical format/reparse Project equality and exact original source retention.
|
||||
Warnings are allowed and are not promoted into semantic acceptance requirements.
|
||||
|
||||
The evaluator calls the existing `Scene::sequence` and scoped `render`/`drag`
|
||||
helpers. It reads `Rendering::alive_spans` and `marker_at`, real Scene line
|
||||
polylines and actual activation `BoxItem` records. It does not construct an
|
||||
alternative activation/lifetime implementation or edit final geometry afterward.
|
||||
|
||||
## Independent expectations
|
||||
|
||||
Current Scene metrics give sender action148, Create243, Call335 and Delete427:
|
||||
the action consumes95 and each following message92. The scene height is564 and
|
||||
its root insertion bounds are106–477. These numbers are regression oracles for
|
||||
the inspected implementation, not permanent product spacing requirements.
|
||||
|
||||
The receiver is alive exactly243–427; the sender remains alive throughout the
|
||||
visible root span. Create ends at the 140px birth capsule's near edge, 70px before
|
||||
the receiver lane, while Delete ends at the death cross's center. Marker and
|
||||
Scene hit queries return the original Create/Delete occurrence IDs. The actual
|
||||
8px activation starts at Call335 and ends at death427, with no minimum-height tail
|
||||
after death. The unresolved-call cue stays at receiver-x+12, death-y−16.
|
||||
|
||||
Three tests establish these expectations if they pass:
|
||||
|
||||
1. Baseline parsed Scene has those independent rows, line endpoints, lifetime
|
||||
extents, hit identities, activation bounds and unanswered-call cue. Its real
|
||||
geometry remains equal after source round trip and an empty hook scope.
|
||||
2. Moving Create down60 changes its row to303 and propagates Call395/Delete487;
|
||||
the actual receiver lifetime, birth/death markers and activation follow. The
|
||||
preceding action's box stays fixed, line/row UUIDs stay stable, and the old
|
||||
birth point is no longer a lifecycle marker.
|
||||
3. Moving only Delete down48 changes death to475 and extends the same real
|
||||
activation to475, while Create and Call polylines stay fixed. Moving Create
|
||||
with an authored Delete request still at427 forces displayed death487; either
|
||||
signed-zero pointer delta preserves the entire latent map and Scene. Moving
|
||||
Create back restores baseline geometry and the original authored map.
|
||||
|
||||
Every test compares the entire accepted Project and source bundle against the
|
||||
original snapshot, and requires no dirty state or undo/redo history. No test
|
||||
weakens equality to just the occurrence list.
|
||||
|
||||
## Contract reused and limits
|
||||
|
||||
The inspected implementation builds lifecycle geometry after recording occurrence
|
||||
rows (`behavior.rs:1332–1344`), aligns Create/Delete messages through
|
||||
`scenario_lifecycle::render::Rendering::align_messages`, and constructs actual
|
||||
activation boxes through `activation_sequence`/`activation_segment`
|
||||
(`behavior.rs:1663–1734`). The latter intersects segments with `alive_spans` and
|
||||
keeps a cue when Delete terminates an unanswered call. Existing
|
||||
`scenario_lifecycle/render_tests.rs` tests real root birth/death alignment and
|
||||
branch-local activation clipping. This extension exercises those current
|
||||
contracts through the layout hook; it does not replace them.
|
||||
|
||||
The peer `8112dea` typed activation correction has a different future integration
|
||||
boundary and, as reported to this task, has no production lifecycle clipping yet.
|
||||
These tests are not evidence that that unmerged source is compatible. A future
|
||||
typed activation union needs its own tests for clipping, typed call/segment IDs,
|
||||
self/nested activation receive geometry, branch behavior, depth and paint order.
|
||||
Do not preserve a legacy width/classification assertion blindly when deliberately
|
||||
changing the activation representation; retain independent lifetime endpoints,
|
||||
identity and no-after-death expectations on the new actual records.
|
||||
|
||||
This extension remains uncompiled and unexecuted. It does not establish persisted
|
||||
layout syntax, a production drag transaction, undoable geometry edits, native
|
||||
pointer behavior, GPU painting, performance, or general upward/free positioning.
|
||||
The existing hook still applies a next-row floor. Root should run the existing
|
||||
`cargo test -p oa-desktop behavior_layout_scene_experiment -- --nocapture` gate
|
||||
on the exact identified wired source, then publish fixture, evaluator and result
|
||||
together. Remote peer machines cannot use workspace-local links.
|
||||
|
||||
Independent bounded source review confirmed the fixture ownership/endpoint contract and the numerical expectations above. Formatting and whitespace checks passed. Compilation and execution remain pending; these findings do not establish an editing transaction or native behavior.
|
||||
@@ -0,0 +1,259 @@
|
||||
//! Real current Scene/lifecycle evaluator, independent of the original fixture.
|
||||
//! No replacement activation geometry or future peer implementation is used.
|
||||
use super::*;
|
||||
use crate::document::Document;
|
||||
use oa_core::source_project::{ProjectLimits, SourceBundle};
|
||||
|
||||
fn lifetime_document() -> (Document, Id) {
|
||||
let source = include_str!("lifetime.arcadia");
|
||||
let parsed = language::parse(source).expect("separate lifetime source parses");
|
||||
let errors: Vec<_> = validation::validate(&parsed)
|
||||
.into_iter()
|
||||
.filter(|issue| issue.severity == validation::Severity::Error)
|
||||
.collect();
|
||||
assert!(
|
||||
errors.is_empty(),
|
||||
"lifetime fixture semantic errors: {errors:?}"
|
||||
);
|
||||
let owner = id(&parsed, "lifetime_conversation");
|
||||
let bundle = SourceBundle {
|
||||
entry: "lifetime.arcadia".into(),
|
||||
files: BTreeMap::from([("lifetime.arcadia".into(), source.into())]),
|
||||
};
|
||||
let document = Document::from_bundle(bundle.clone(), ProjectLimits::default())
|
||||
.expect("lifetime fixture is ordinarily admitted");
|
||||
assert_eq!(document.project(), &parsed);
|
||||
assert_eq!(document.bundle(), &bundle);
|
||||
assert_eq!(language::parse(&language::format(&parsed)).unwrap(), parsed);
|
||||
(document, owner)
|
||||
}
|
||||
|
||||
/// Numeric expectations are hand-derived current-Scene metrics, not proposed
|
||||
/// permanent spacing rules: Action95, message92, initial occurrence148.
|
||||
fn assert_lifetime_geometry(
|
||||
scene: &Scene,
|
||||
project: &Project,
|
||||
owner: Id,
|
||||
birth: f32,
|
||||
call_y: f32,
|
||||
death: f32,
|
||||
) {
|
||||
let requester = id(project, "requester");
|
||||
let provider = id(project, "provider");
|
||||
let create = id(project, "create_receiver");
|
||||
let call = id(project, "service_call");
|
||||
let delete = id(project, "delete_receiver");
|
||||
let left = scene.lanes[&requester];
|
||||
let right = scene.lanes[&provider];
|
||||
assert_eq!((left, right), (160., 400.));
|
||||
assert_eq!(row(scene, id(project, "before_birth")), 148.);
|
||||
assert_eq!(row(scene, create), birth);
|
||||
assert_eq!(row(scene, call), call_y);
|
||||
assert_eq!(row(scene, delete), death);
|
||||
assert!(
|
||||
birth > 148.,
|
||||
"receiver creation is delayed beyond the opening action"
|
||||
);
|
||||
assert_eq!(scene.rows[&create].0, owner);
|
||||
assert_eq!(scene.rows[&delete].0, owner);
|
||||
|
||||
// Exercise the actual Rendering bridge, including marker hit identity and
|
||||
// its clipping contract, rather than rebuilding lifetimes from our rows.
|
||||
let alive = scene
|
||||
.lifecycle
|
||||
.alive_spans(provider, owner, 85., scene.size.height - 20.);
|
||||
assert_eq!(
|
||||
alive
|
||||
.iter()
|
||||
.map(|span| (span.start, span.end))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(birth, death)]
|
||||
);
|
||||
assert!(
|
||||
scene
|
||||
.lifecycle
|
||||
.alive_spans(provider, owner, 85., birth)
|
||||
.is_empty()
|
||||
);
|
||||
assert!(
|
||||
scene
|
||||
.lifecycle
|
||||
.alive_spans(provider, owner, death, scene.size.height - 20.)
|
||||
.is_empty()
|
||||
);
|
||||
let sender_alive = scene
|
||||
.lifecycle
|
||||
.alive_spans(requester, owner, 85., scene.size.height - 20.);
|
||||
assert_eq!(
|
||||
sender_alive
|
||||
.iter()
|
||||
.map(|span| (span.start, span.end))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(85., scene.size.height - 20.)]
|
||||
);
|
||||
assert_eq!(
|
||||
scene.lifecycle.marker_at(Point::new(right, birth)),
|
||||
Some(create)
|
||||
);
|
||||
assert_eq!(
|
||||
scene.lifecycle.marker_at(Point::new(right, death)),
|
||||
Some(delete)
|
||||
);
|
||||
assert_eq!(scene.hit(Point::new(right, birth)), Some(create));
|
||||
assert_eq!(scene.hit(Point::new(right, death)), Some(delete));
|
||||
assert_eq!(
|
||||
line(scene, create).points,
|
||||
vec![Point::new(left, birth), Point::new(right - 70., birth)]
|
||||
);
|
||||
assert_eq!(
|
||||
line(scene, call).points,
|
||||
vec![Point::new(left, call_y), Point::new(right, call_y)]
|
||||
);
|
||||
assert_eq!(
|
||||
line(scene, delete).points,
|
||||
vec![Point::new(left, death), Point::new(right, death)]
|
||||
);
|
||||
assert!(line(scene, create).label_at.y + 22. < birth - 14.);
|
||||
|
||||
// Inspect actual activation boxes produced by Scene::activation_sequence.
|
||||
// A correlated Reply is absent, so death must clip the bar, not close the
|
||||
// semantic call. Do not accept a new generic minimum-height tail after death.
|
||||
let bars: Vec<_> = scene
|
||||
.boxes
|
||||
.iter()
|
||||
.filter(|item| item.id == call && item.title.is_empty())
|
||||
.collect();
|
||||
assert_eq!(bars.len(), 1);
|
||||
assert_eq!(
|
||||
bars[0].rect,
|
||||
Rectangle::new(
|
||||
Point::new(right - 4., call_y),
|
||||
Size::new(8., death - call_y)
|
||||
)
|
||||
);
|
||||
assert_eq!(bars[0].rect.y + bars[0].rect.height, death);
|
||||
assert!(
|
||||
bars.iter()
|
||||
.all(|item| item.rect.y >= birth && item.rect.y + item.rect.height <= death)
|
||||
);
|
||||
assert_eq!(scene.open_calls, vec![Point::new(right + 12., death - 16.)]);
|
||||
assert!(!project.elements.values().any(|element| matches!(
|
||||
element.shared(),
|
||||
Some(SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange {
|
||||
message_kind: MessageKind::Reply,
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delayed_create_call_and_delete_have_real_lifetime_markers_and_exact_clipped_activation() {
|
||||
let (document, owner) = lifetime_document();
|
||||
let before = document.snapshot();
|
||||
let source = document.bundle().clone();
|
||||
let scene = Scene::sequence(document.project(), owner, owner);
|
||||
assert_lifetime_geometry(&scene, document.project(), owner, 243., 335., 427.);
|
||||
assert_eq!(scene.size.height, 564.);
|
||||
assert_eq!(scene.sequence_bounds[&owner], (106., 477.));
|
||||
same_geometry(&scene, &render(document.project(), owner, &Layout::new()));
|
||||
let reparsed = language::parse(&language::format(document.project())).unwrap();
|
||||
same_geometry(&scene, &Scene::sequence(&reparsed, owner, owner));
|
||||
assert_eq!(document.project(), &before.decoded.project);
|
||||
assert_eq!(document.bundle(), &source);
|
||||
assert!(!document.dirty() && !document.can_undo() && !document.can_redo());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_create_moves_real_lifetime_and_followers_without_changing_semantics_or_source() {
|
||||
let (document, owner) = lifetime_document();
|
||||
let before = document.snapshot();
|
||||
let source = document.bundle().clone();
|
||||
let p = document.project();
|
||||
let create = id(p, "create_receiver");
|
||||
let provider = id(p, "provider");
|
||||
let baseline = Scene::sequence(p, owner, owner);
|
||||
let layout = drag(&Layout::new(), &baseline, create, 60.);
|
||||
assert_eq!(layout, Layout::from([(create, 303.)]));
|
||||
let moved = render(p, owner, &layout);
|
||||
assert_lifetime_geometry(&moved, p, owner, 303., 395., 487.);
|
||||
assert_eq!(moved.size.height, 624.);
|
||||
assert_eq!(
|
||||
moved
|
||||
.lifecycle
|
||||
.marker_at(Point::new(moved.lanes[&provider], 243.)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
named_box(&moved, id(p, "before_birth")).rect,
|
||||
named_box(&baseline, id(p, "before_birth")).rect
|
||||
);
|
||||
assert_eq!(
|
||||
moved.rows.keys().collect::<Vec<_>>(),
|
||||
baseline.rows.keys().collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
moved.lines.iter().map(|line| line.id).collect::<Vec<_>>(),
|
||||
baseline
|
||||
.lines
|
||||
.iter()
|
||||
.map(|line| line.id)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
let reparsed = language::parse(&language::format(p)).unwrap();
|
||||
same_geometry(&moved, &render(&reparsed, owner, &layout));
|
||||
assert_eq!(p, &before.decoded.project);
|
||||
assert_eq!(document.bundle(), &source);
|
||||
assert!(!document.dirty() && !document.can_undo() && !document.can_redo());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moving_delete_extends_only_to_actual_death_and_zero_motion_preserves_latent_lifetime_intent() {
|
||||
let (document, owner) = lifetime_document();
|
||||
let before = document.snapshot();
|
||||
let source = document.bundle().clone();
|
||||
let p = document.project();
|
||||
let create = id(p, "create_receiver");
|
||||
let delete = id(p, "delete_receiver");
|
||||
let provider = id(p, "provider");
|
||||
let baseline = Scene::sequence(p, owner, owner);
|
||||
let later_delete = drag(&Layout::new(), &baseline, delete, 48.);
|
||||
let extended = render(p, owner, &later_delete);
|
||||
assert_lifetime_geometry(&extended, p, owner, 243., 335., 475.);
|
||||
assert_eq!(extended.size.height, 612.);
|
||||
assert_eq!(
|
||||
extended
|
||||
.lifecycle
|
||||
.marker_at(Point::new(extended.lanes[&provider], 427.)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
line(&extended, create).points,
|
||||
line(&baseline, create).points
|
||||
);
|
||||
assert_eq!(
|
||||
line(&extended, id(p, "service_call")).points,
|
||||
line(&baseline, id(p, "service_call")).points
|
||||
);
|
||||
|
||||
// Create displacement forces the displayed death to487 while its authored
|
||||
// request stays427. Both signed zero deltas must preserve that latent value.
|
||||
let original = Layout::from([(create, 243.), (delete, 427.)]);
|
||||
let displaced = drag(&original, &baseline, create, 60.);
|
||||
let frame = render(p, owner, &displaced);
|
||||
assert_lifetime_geometry(&frame, p, owner, 303., 395., 487.);
|
||||
assert_eq!(displaced[&delete], 427.);
|
||||
for delta in [0., -0.] {
|
||||
let untouched = drag(&displaced, &frame, delete, delta);
|
||||
assert_eq!(untouched, displaced);
|
||||
same_geometry(&frame, &render(p, owner, &untouched));
|
||||
let restored = drag(&untouched, &frame, create, -60.);
|
||||
assert_eq!(restored, original);
|
||||
same_geometry(&baseline, &render(p, owner, &restored));
|
||||
}
|
||||
assert_eq!(p, &before.decoded.project);
|
||||
assert_eq!(document.bundle(), &source);
|
||||
assert!(!document.dirty() && !document.can_undo() && !document.can_redo());
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
//! Isolated test-only adapter. See README.md and wiring.patch before running.
|
||||
//! It drives the actual parent Scene; it contains no replacement geometry model.
|
||||
use super::*;
|
||||
use oa_core::{language, validation};
|
||||
use std::cell::RefCell;
|
||||
|
||||
mod lifetime;
|
||||
|
||||
type Layout = BTreeMap<Id, f32>;
|
||||
|
||||
thread_local! {
|
||||
static ANCHORS: RefCell<Option<Layout>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
struct AnchorScope(Option<Layout>);
|
||||
impl AnchorScope {
|
||||
fn install(layout: &Layout) -> Self {
|
||||
Self(ANCHORS.with(|slot| slot.replace(Some(layout.clone()))))
|
||||
}
|
||||
}
|
||||
impl Drop for AnchorScope {
|
||||
fn drop(&mut self) {
|
||||
ANCHORS.with(|slot| {
|
||||
slot.replace(self.0.take());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Called only by the proposed cfg(test) row hook, before Scene records rows.
|
||||
/// Retains the CURRENT renderer's next-row floor; this is not general free y.
|
||||
pub(super) fn resolve_row_anchor(id: Id, y: &mut f32) {
|
||||
ANCHORS.with(|slot| {
|
||||
if let Some(desired) = slot.borrow().as_ref().and_then(|map| map.get(&id)) {
|
||||
assert!(desired.is_finite());
|
||||
*y = (*y).max(*desired);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn render(project: &Project, owner: Id, layout: &Layout) -> Scene {
|
||||
let _scope = AnchorScope::install(layout);
|
||||
Scene::sequence(project, owner, owner)
|
||||
}
|
||||
|
||||
/// Pointer intent differs from explicit SetDesiredY: zero displacement preserves
|
||||
/// the complete original authored candidate even when desired != displayed.
|
||||
fn drag(original: &Layout, frame: &Scene, target: Id, delta_y: f32) -> Layout {
|
||||
assert!(delta_y.is_finite());
|
||||
if delta_y == 0. {
|
||||
return original.clone();
|
||||
}
|
||||
let mut next = original.clone();
|
||||
next.insert(target, frame.rows[&target].1 + delta_y);
|
||||
next
|
||||
}
|
||||
|
||||
fn id(p: &Project, alias: &str) -> Id {
|
||||
p.identities()
|
||||
.into_iter()
|
||||
.find(|(_, name)| *name == alias)
|
||||
.unwrap()
|
||||
.0
|
||||
}
|
||||
|
||||
fn fixture() -> (Project, Id) {
|
||||
let p = language::parse(include_str!("fixture.arcadia")).expect("real Arcadia fixture parses");
|
||||
let errors: Vec<_> = validation::validate(&p)
|
||||
.into_iter()
|
||||
.filter(|issue| issue.severity == validation::Severity::Error)
|
||||
.collect();
|
||||
assert!(errors.is_empty(), "fixture semantic errors: {errors:?}");
|
||||
let owner = id(&p, "conversation");
|
||||
(p, owner)
|
||||
}
|
||||
|
||||
fn row(scene: &Scene, id: Id) -> f32 {
|
||||
scene.rows[&id].1
|
||||
}
|
||||
fn line(scene: &Scene, id: Id) -> &Line {
|
||||
assert_eq!(scene.lines.iter().filter(|line| line.id == id).count(), 1);
|
||||
scene.lines.iter().find(|line| line.id == id).unwrap()
|
||||
}
|
||||
fn named_box(scene: &Scene, id: Id) -> &BoxItem {
|
||||
scene
|
||||
.boxes
|
||||
.iter()
|
||||
.find(|item| item.id == id && !item.title.is_empty())
|
||||
.unwrap()
|
||||
}
|
||||
fn same_geometry(a: &Scene, b: &Scene) {
|
||||
assert_eq!(a.rows, b.rows);
|
||||
assert_eq!(a.sequence_bounds, b.sequence_bounds);
|
||||
assert_eq!(a.size, b.size);
|
||||
assert_eq!(a.lanes, b.lanes);
|
||||
assert_eq!(a.lifecycle, b.lifecycle);
|
||||
assert_eq!(a.open_calls, b.open_calls);
|
||||
let lines = |s: &Scene| {
|
||||
s.lines
|
||||
.iter()
|
||||
.map(|l| {
|
||||
(
|
||||
l.id,
|
||||
l.points.clone(),
|
||||
l.label_at,
|
||||
l.label.clone(),
|
||||
l.dashed,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let boxes = |s: &Scene| {
|
||||
s.boxes
|
||||
.iter()
|
||||
.map(|b| (b.id, b.rect, b.title.clone(), b.detail.clone(), b.marker))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
assert_eq!(lines(a), lines(b));
|
||||
assert_eq!(boxes(a), boxes(b));
|
||||
let slots = |s: &Scene| {
|
||||
s.slots
|
||||
.iter()
|
||||
.map(|s| (s.slot.parent, s.slot.before, s.y, s.label.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
assert_eq!(slots(a), slots(b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_parser_roundtrip_and_baseline_scene_have_independent_expected_anchors() {
|
||||
let (p, owner) = fixture();
|
||||
let before = p.clone();
|
||||
let source = language::format(&p);
|
||||
let reparsed = language::parse(&source).unwrap();
|
||||
assert_eq!(reparsed, p);
|
||||
let baseline = Scene::sequence(&p, owner, owner);
|
||||
same_geometry(&baseline, &render(&p, owner, &Layout::new()));
|
||||
same_geometry(&baseline, &Scene::sequence(&reparsed, owner, owner));
|
||||
let first = id(&p, "first");
|
||||
let second = id(&p, "second");
|
||||
assert_ne!(first, second);
|
||||
for occurrence in [first, second] {
|
||||
assert!(matches!(p.elements[&occurrence].shared(),
|
||||
Some(SharedBody::Occurrence { body: OccurrenceBody::Exchange { exchange: Some(edge), .. }, .. })
|
||||
if *edge == id(&p, "channel")));
|
||||
}
|
||||
// Hand-derived current metrics: start148, each message92, fragment55,
|
||||
// operand90, action95, fragment trailing64. These are regression oracles,
|
||||
// NOT a claim that those fixed spacings should remain product constraints.
|
||||
for (alias, expected) in [
|
||||
("first", 148.),
|
||||
("second", 240.),
|
||||
("fragment", 332.),
|
||||
("nested", 477.),
|
||||
("self_call", 636.),
|
||||
("self_reply", 728.),
|
||||
] {
|
||||
assert_eq!(row(&baseline, id(&p, alias)), expected, "{alias}");
|
||||
}
|
||||
assert_eq!(baseline.rows[&id(&p, "nested")].0, id(&p, "operand"));
|
||||
assert_eq!(named_box(&baseline, id(&p, "nested")).rect.y, 457.);
|
||||
let frame = named_box(&baseline, id(&p, "fragment")).rect;
|
||||
assert_eq!((frame.y, frame.height), (332., 240.));
|
||||
let left = baseline.lanes[&id(&p, "left")];
|
||||
let right = baseline.lanes[&id(&p, "right")];
|
||||
assert_eq!(
|
||||
line(&baseline, first).points,
|
||||
vec![Point::new(left, 148.), Point::new(right, 148.)]
|
||||
);
|
||||
assert_eq!(
|
||||
line(&baseline, second).points,
|
||||
vec![Point::new(left, 240.), Point::new(right, 240.)]
|
||||
);
|
||||
let local = line(&baseline, id(&p, "self_call"));
|
||||
assert_eq!(
|
||||
local.points,
|
||||
vec![
|
||||
Point::new(right, 636.),
|
||||
Point::new(right + 75., 636.),
|
||||
Point::new(right + 75., 660.),
|
||||
Point::new(right, 660.)
|
||||
]
|
||||
);
|
||||
assert_eq!(p, before);
|
||||
assert_eq!(language::format(&p), source);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn moved_repeat_drives_actual_scene_followers_frames_and_self_message_without_semantic_edits() {
|
||||
let (p, owner) = fixture();
|
||||
let before = p.clone();
|
||||
let source = language::format(&p);
|
||||
let baseline = Scene::sequence(&p, owner, owner);
|
||||
let second = id(&p, "second");
|
||||
let candidate = drag(&Layout::new(), &baseline, second, 40.);
|
||||
assert_eq!(candidate, Layout::from([(second, 280.)]));
|
||||
let moved = render(&p, owner, &candidate);
|
||||
assert_eq!(row(&moved, id(&p, "first")), 148.);
|
||||
assert_eq!(row(&moved, second), 280.);
|
||||
assert_eq!(row(&moved, id(&p, "nested")), 517.);
|
||||
assert_eq!(row(&moved, id(&p, "self_call")), 676.);
|
||||
assert_eq!(row(&moved, id(&p, "self_reply")), 768.);
|
||||
assert_eq!(
|
||||
line(&moved, id(&p, "first")).points,
|
||||
line(&baseline, id(&p, "first")).points
|
||||
);
|
||||
for alias in ["second", "self_call", "self_reply"] {
|
||||
assert_eq!(
|
||||
line(&moved, id(&p, alias)).points.len(),
|
||||
line(&baseline, id(&p, alias)).points.len()
|
||||
);
|
||||
for (old, new) in line(&baseline, id(&p, alias))
|
||||
.points
|
||||
.iter()
|
||||
.zip(&line(&moved, id(&p, alias)).points)
|
||||
{
|
||||
assert_eq!(new.x, old.x);
|
||||
assert_eq!(new.y - old.y, 40.);
|
||||
}
|
||||
}
|
||||
let frame = named_box(&moved, id(&p, "fragment")).rect;
|
||||
assert_eq!((frame.y, frame.height), (372., 240.));
|
||||
assert_eq!(named_box(&moved, id(&p, "nested")).rect.y, 497.);
|
||||
same_geometry(&moved, &render(&p, owner, &candidate));
|
||||
same_geometry(
|
||||
&moved,
|
||||
&render(&language::parse(&source).unwrap(), owner, &candidate),
|
||||
);
|
||||
assert_eq!(p, before); // Entire Project, not only the steps vector.
|
||||
assert_eq!(language::format(&p), source);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_anchor_grows_actual_fragment_and_self_call_keeps_its_receive_offset() {
|
||||
let (p, owner) = fixture();
|
||||
let before = p.clone();
|
||||
let baseline = Scene::sequence(&p, owner, owner);
|
||||
let nested = id(&p, "nested");
|
||||
let candidate = drag(&Layout::new(), &baseline, nested, 30.);
|
||||
let moved = render(&p, owner, &candidate);
|
||||
let frame = named_box(&moved, id(&p, "fragment")).rect;
|
||||
assert_eq!((frame.y, frame.height), (332., 270.));
|
||||
assert_eq!(named_box(&moved, nested).rect.y, 487.);
|
||||
assert_eq!(row(&moved, id(&p, "self_call")), 666.);
|
||||
let call = id(&p, "self_call");
|
||||
let next = drag(&candidate, &moved, call, 18.);
|
||||
let local = render(&p, owner, &next);
|
||||
assert_eq!(row(&local, call), 684.);
|
||||
assert_eq!(line(&local, call).points.first().unwrap().y, 684.);
|
||||
assert_eq!(line(&local, call).points.last().unwrap().y, 708.);
|
||||
assert_eq!(row(&local, id(&p, "self_reply")), 776.);
|
||||
assert_eq!(
|
||||
named_box(&local, nested).rect,
|
||||
named_box(&moved, nested).rect
|
||||
);
|
||||
assert_eq!(p, before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_pointer_delta_preserves_latent_authored_intent_but_explicit_assignment_can_change_it() {
|
||||
let (p, owner) = fixture();
|
||||
let baseline = Scene::sequence(&p, owner, owner);
|
||||
let first = id(&p, "first");
|
||||
let second = id(&p, "second");
|
||||
let original = Layout::from([(first, 148.), (second, 240.)]);
|
||||
let displaced = drag(&original, &baseline, first, 200.);
|
||||
let frame = render(&p, owner, &displaced);
|
||||
assert_eq!(row(&frame, second), 440.);
|
||||
assert_eq!(displaced[&second], 240.);
|
||||
let untouched = drag(&displaced, &frame, second, 0.);
|
||||
assert_eq!(untouched, displaced);
|
||||
let back = drag(&untouched, &frame, first, -200.);
|
||||
assert_eq!(back, original);
|
||||
assert_eq!(row(&render(&p, owner, &back), second), 240.);
|
||||
let mut explicit = displaced.clone();
|
||||
explicit.insert(second, 440.); // Explicit SetDesiredY, not a pointer gesture.
|
||||
let explicit_back = drag(&explicit, &frame, first, -200.);
|
||||
assert_eq!(row(&render(&p, owner, &explicit_back), second), 440.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_reorder_keeps_override_bound_to_occurrence_identity_and_does_not_write_back_projection() {
|
||||
let (p, owner) = fixture();
|
||||
let input = include_str!("fixture.arcadia");
|
||||
let reordered_source =
|
||||
input.replace("[@first, @second, @fragment", "[@second, @first, @fragment");
|
||||
assert_ne!(input, reordered_source);
|
||||
let reordered = language::parse(&reordered_source).unwrap();
|
||||
let before = reordered.clone();
|
||||
let second = id(&p, "second");
|
||||
let first = id(&p, "first");
|
||||
assert_eq!(id(&reordered, "second"), second);
|
||||
let scene = render(&reordered, owner, &Layout::from([(second, 280.)]));
|
||||
assert_eq!(row(&scene, second), 280.);
|
||||
assert_eq!(row(&scene, first), 372.);
|
||||
assert_eq!(
|
||||
&reordered.elements[&owner].steps().unwrap()[..2],
|
||||
&[second, first]
|
||||
);
|
||||
assert_eq!(reordered, before);
|
||||
assert_eq!(
|
||||
language::parse(&language::format(&reordered)).unwrap(),
|
||||
before
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_floor_is_an_explicit_counterexample_to_general_free_positioning() {
|
||||
let (p, owner) = fixture();
|
||||
let baseline = Scene::sequence(&p, owner, owner);
|
||||
let second = id(&p, "second");
|
||||
let requested = drag(&Layout::new(), &baseline, second, -40.);
|
||||
assert_eq!(requested[&second], 200.);
|
||||
assert_eq!(row(&render(&p, owner, &requested), second), 240.);
|
||||
// This expected mismatch records a limitation; it is not acceptance of
|
||||
// silently clamped product gestures or proof that y200 is semantically bad.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scoped_hook_is_inactive_by_default_thread_local_and_restored_after_panic() {
|
||||
let (p, owner) = fixture();
|
||||
let first = id(&p, "first");
|
||||
let baseline = Scene::sequence(&p, owner, owner);
|
||||
let caught = std::panic::catch_unwind(|| {
|
||||
let _scope = AnchorScope::install(&Layout::from([(first, 900.)]));
|
||||
assert_eq!(row(&Scene::sequence(&p, owner, owner), first), 900.);
|
||||
// Another test thread has no access to this thread's temporary anchors.
|
||||
std::thread::spawn(|| {
|
||||
let (p, owner) = fixture();
|
||||
assert_eq!(
|
||||
row(&Scene::sequence(&p, owner, owner), id(&p, "first")),
|
||||
148.
|
||||
);
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
panic!("exercise scoped restoration");
|
||||
});
|
||||
let payload = caught.expect_err("the deliberate scope-restoration panic must occur");
|
||||
assert_eq!(
|
||||
payload.downcast_ref::<&str>().copied(),
|
||||
Some("exercise scoped restoration"),
|
||||
"an earlier failed assertion must not masquerade as the expected panic"
|
||||
);
|
||||
same_geometry(&baseline, &Scene::sequence(&p, owner, owner));
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
diff --git a/crates/oa-desktop/src/behavior.rs b/crates/oa-desktop/src/behavior.rs
|
||||
--- a/crates/oa-desktop/src/behavior.rs
|
||||
+++ b/crates/oa-desktop/src/behavior.rs
|
||||
@@ -1120,1 +1120,5 @@
|
||||
+#[cfg(test)]
|
||||
+#[path = "../../../harness/experiments/behavior-layout-scene-20260912/mod.rs"]
|
||||
+mod behavior_layout_scene_experiment;
|
||||
+
|
||||
struct BoxItem {
|
||||
@@ -1446,2 +1450,4 @@
|
||||
+ #[cfg(test)]
|
||||
+ behavior_layout_scene_experiment::resolve_row_anchor(*id, y);
|
||||
if ordered {
|
||||
let slot = scenario::Slot {
|
||||
Reference in New Issue
Block a user