Scope allocation inspection accessor to tests and record resize falsifiers
This commit is contained in:
@@ -66,3 +66,4 @@ harness/experiments/canvas-a31b40d-windows-native-20260911/** -text whitespace=-
|
||||
harness/experiments/allocation-d9b3f0d-windows-20260912/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
|
||||
harness/experiments/allocation-3840ca2-pause-20260912/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
|
||||
harness/experiments/diagram-removal-windows-20260912/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
|
||||
harness/experiments/diagram-resize-foundation-20260912/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
|
||||
|
||||
@@ -192,6 +192,7 @@ impl SemanticPlan {
|
||||
pub fn project(&self) -> &Project {
|
||||
&self.candidate
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub fn index(&self) -> &ViewIndex {
|
||||
&self.index
|
||||
}
|
||||
|
||||
@@ -414,3 +414,15 @@ experiments; the implementer must surface those choices before locking the profi
|
||||
The current local numeric gate runs against frozen root source. New core contract
|
||||
work proceeds in an isolated checkout, preserving root's Cargo/native leases and
|
||||
the other hosts' CLI, staged-source and B4 ownership.
|
||||
|
||||
## Direct resize geometry — 2026-09-12
|
||||
|
||||
Retain eight-handle resizing with fixed opposite edges, one placement edit and
|
||||
exact preview/reopen equality. The [executed foundation falsifiers](diagram-resize-foundation-20260912.md)
|
||||
reject unconditional visible-size normalization and a universal preferred floor
|
||||
on inactive dimensions. Preserve inactive authored fields where possible and
|
||||
evaluate actual shared projection; distinguish proven constraints from a search
|
||||
that has not found a feasible candidate. Pure inverse-geometry experiments and
|
||||
native port/handle arbitration remain prerequisites for accepting production
|
||||
interaction. Process/scenario authored layouts retain their separate persistence
|
||||
dependency; the persisted-rectangle slice does not complete that broader need.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Direct resizing foundation
|
||||
|
||||
Gitea issue #16 owns the live task. The [initial design](experiments/diagram-resize-foundation-20260912/design.md) and [fresh critique](experiments/diagram-resize-foundation-20260912/critique.md) are preserved as proposals, including their original unexecuted-evidence qualifications. The experiment below was subsequently run by root; it does not establish a completed resizing feature.
|
||||
|
||||
The desired interaction remains eight handles on selected persisted rectangles, fixed opposite edges, unchanged authored child placements and semantic ownership, exact projected preview/reopen parity, and one ordinary placement edit with Undo. Process/scenario view persistence is a separate dependency. Positive-edge-only handles would be an intermediate experiment, not completed acceptance.
|
||||
|
||||
## Executed falsifiers
|
||||
|
||||
The standalone [actual-core probe](experiments/diagram-resize-foundation-20260912/probe.rs) compiled with Rust 1.98.1 Windows GNU LLVM and `-D warnings`, then passed both automatic/manual-anchor cases and the small-card case. Its `oa_core-145180297aa692e4` library was identified through the dependency fingerprint of the desktop binary whose full 818-test suite passed at `42511f0f8ae46f29e11ae18c1030face654c2994`. The [manifest](experiments/diagram-resize-foundation-20260912/manifest.json), build receipt and runtime receipt record source/library/executable hashes and exact commands. No Linux evidence is claimed.
|
||||
|
||||
Eight neutral component ports connect to ordinary separated actors above and below their owner. Initial stored size 224×160 projects to 224×232. Copying those visible dimensions into storage makes the next projection 232×232 because automatic capacity changes sides. An east-edge shrink can instead preserve stored height160 and produce exactly 214×232; normalizing stored height232 makes the same intended shrink project to232×232. Manual anchors preserve this counterexample because automatic capacity is computed independently. All model/candidate validation passed; only the selected placement changed.
|
||||
|
||||
A valid portless 100×50 card can grow from its east edge to110×50. Requiring both a new184×94 preferred floor and unchanged inactive height50 would incorrectly refuse this useful operation.
|
||||
|
||||
## Accepted corrections and remaining experiment
|
||||
|
||||
Exact saved preview is required; unconditional copying of every visible dimension into storage is rejected. The planner must first preserve inactive authored fields and solve only the fields needed for the requested frame, evaluating candidates through the actual shared projector. Retain the exact original placement for zero motion and cancellation. A preferred interactive minimum must allow existing small cards to grow continuously and must not impose a new minimum on an inactive axis. Actual state/content/port constraints still apply.
|
||||
|
||||
Dynamic port geometry means feasibility is not proven monotone. A bounded search that fails to find a candidate must report an unresolved constraint, not assert a false minimum. The next foundation experiment compares all eight handles and candidate strategies with a bounded independent search on leaves, nested containers, collapsed views, state rows and port counterexamples before production gesture implementation. Port and handle hit testing also need joint arbitration because existing invisible port hit slop can swallow a visible resize handle. These are unresolved implementation/interaction questions, not reasons to remove required handles.
|
||||
@@ -123,5 +123,35 @@
|
||||
"file": "clippy-42511f0.result.json",
|
||||
"bytes": 379,
|
||||
"sha256": "9cb5a9254386859fd01f5f108cb400db1a88d52077d8f6db809b426d8b3005bb"
|
||||
},
|
||||
{
|
||||
"file": "arrange-260d773.log",
|
||||
"bytes": 499,
|
||||
"sha256": "411ae1217c6cb061fc7ccee073eb9575797fd9f3ac640aef88ed2580b698d330"
|
||||
},
|
||||
{
|
||||
"file": "arrange-260d773.inputs.json",
|
||||
"bytes": 98922,
|
||||
"sha256": "c91c14a6b1261b7d014993ee18096c969f726d57ddaeb45a64da041382816bda"
|
||||
},
|
||||
{
|
||||
"file": "arrange-260d773.result.json",
|
||||
"bytes": 428,
|
||||
"sha256": "23544c93b4399bc27f23113c82580bf8dab4ebbf03d1df874072dae6e6c13f9b"
|
||||
},
|
||||
{
|
||||
"file": "clippy-260d773.log",
|
||||
"bytes": 578,
|
||||
"sha256": "48db017f386ee1f45d86b89391144d4f15cc3c4e793d18f797e8d3c8d6c9a5cc"
|
||||
},
|
||||
{
|
||||
"file": "clippy-260d773.inputs.json",
|
||||
"bytes": 98922,
|
||||
"sha256": "c91c14a6b1261b7d014993ee18096c969f726d57ddaeb45a64da041382816bda"
|
||||
},
|
||||
{
|
||||
"file": "clippy-260d773.result.json",
|
||||
"bytes": 379,
|
||||
"sha256": "8c5edebb668a45dcd002d0e4bb9ee8e1d23be14898c2bbf2e3c53f821334703c"
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 56.02s
|
||||
Running unittests src\main.rs (target\debug\deps\arcadia_studio-e358a5ed59b1de41.exe)
|
||||
|
||||
running 1 test
|
||||
test app::allocation_drag::tests::full_tree::actual_keyboard_arrange_shortcut_consumes_once_escape_cancels_and_dirty_owner_refuses ... ok
|
||||
|
||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 817 filtered out; finished in 0.71s
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"source": "260d77385236ab93c66551f0338d876d94e8cd96",
|
||||
"start": "2026-09-12T12:49:49.405026+00:00",
|
||||
"end": "2026-09-12T12:50:46.682861+00:00",
|
||||
"exit": 0,
|
||||
"command": [
|
||||
"cargo",
|
||||
"test",
|
||||
"--locked",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"actual_keyboard_arrange_shortcut",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"inputs": "arrange-260d773.inputs.json",
|
||||
"changed_inputs": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
Checking oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
|
||||
error: method `index` is never used
|
||||
--> crates\oa-desktop\src\allocation_drag.rs:195:12
|
||||
|
|
||||
190 | impl SemanticPlan {
|
||||
| ----------------- method in this implementation
|
||||
...
|
||||
195 | pub fn index(&self) -> &ViewIndex {
|
||||
| ^^^^^
|
||||
|
|
||||
= note: `-D dead-code` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[expect(dead_code)]` or `#[allow(dead_code)]`
|
||||
|
||||
error: could not compile `oa-desktop` (bin "arcadia-studio") due to 1 previous error
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"source": "260d77385236ab93c66551f0338d876d94e8cd96",
|
||||
"start": "2026-09-12T12:51:05.098256+00:00",
|
||||
"end": "2026-09-12T12:54:28.300198+00:00",
|
||||
"exit": 101,
|
||||
"command": [
|
||||
"cargo",
|
||||
"clippy",
|
||||
"--locked",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--all-targets",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings"
|
||||
],
|
||||
"inputs": "clippy-260d773.inputs.json",
|
||||
"changed_inputs": []
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"source": "42511f0f8ae46f29e11ae18c1030face654c2994",
|
||||
"start": "2026-09-12T12:48:38.537600+00:00",
|
||||
"end": "2026-09-12T12:48:40.682930+00:00",
|
||||
"command": [
|
||||
"rustc",
|
||||
"--edition=2024",
|
||||
"-D",
|
||||
"warnings",
|
||||
"-C",
|
||||
"linker=clang.exe",
|
||||
"-L",
|
||||
"dependency=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\target\\debug\\deps",
|
||||
"--extern",
|
||||
"serde_json=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\target\\debug\\deps\\libserde_json-da2c4f333fc47818.rlib",
|
||||
"--extern",
|
||||
"oa_core=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\target\\debug\\deps\\liboa_core-145180297aa692e4.rlib",
|
||||
"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\diagram-resize-probe-20260912.rs",
|
||||
"-o",
|
||||
"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\native-qa\\resize-probe-42511f0\\resize-probe-42511f0.exe"
|
||||
],
|
||||
"exit": 0,
|
||||
"files": [
|
||||
{
|
||||
"path": ".local\\diagram-resize-probe-20260912.rs",
|
||||
"bytes": 5446,
|
||||
"sha256": "251e0d8f8c12cd32f67274c20b2a38993a98b7dea845452135d275a28e2b0a0b"
|
||||
},
|
||||
{
|
||||
"path": "target\\debug\\.fingerprint\\oa-desktop-e358a5ed59b1de41\\test-bin-arcadia-studio.json",
|
||||
"bytes": 1079,
|
||||
"sha256": "cbf2edff63ac3601b264065345b7c77e456ed8e34b09ebd204d5785d2324a2dd"
|
||||
},
|
||||
{
|
||||
"path": "target\\debug\\deps\\libserde_json-da2c4f333fc47818.rlib",
|
||||
"bytes": 2991440,
|
||||
"sha256": "485300d4125a69de7da5413f832eb4b9cde547e60f103f3b284e49aee4194d05"
|
||||
},
|
||||
{
|
||||
"path": "target\\debug\\deps\\liboa_core-145180297aa692e4.rlib",
|
||||
"bytes": 102820446,
|
||||
"sha256": "d395dbe1f761d41bce3cb850f44cdc0343b0db172bd03abf89e455c44f4b7e86"
|
||||
},
|
||||
{
|
||||
"path": ".local\\native-qa\\resize-probe-42511f0\\resize-probe-42511f0.exe",
|
||||
"bytes": 11328000,
|
||||
"sha256": "9f807b4c397fa4dc9b4f17a67686239cc1fa44cecfc99e8be92fd8839084d546"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
# Independent resize critique — 2026-09-12
|
||||
|
||||
Scope: fresh read-only challenge to `diagram-resize-design-20260912.md`, actual core projection/port/state code and both canvas paths. Root owns Gitea issue 16 and shared coordination. Only this report and the separately requested standalone `.local/diagram-resize-probe-20260912.rs` were authored. No production/test/build input, Git, Gitea, native app, Linux or compiler was touched. The Rust probe has **not been compiled or run**; its expected outputs below are analytical predictions, not successful executable evidence.
|
||||
|
||||
User basis: `PROJECT_GOAL.md` requires ergonomic modern diagram editing, useful visual nesting, full graphical/textual equivalence, maintainability and direct verification. The current resize request concerns arranging entities, activities and other objects. The eight-handle frame-editing direction serves that requirement; E/S/SE-only acceptance would weaken it. The design is not ready to adopt verbatim because normalization is a stronger and sometimes counterproductive condition than exact persisted preview/reopen parity.
|
||||
|
||||
## Findings
|
||||
|
||||
### High — normalizing all visible bounds can destroy an existing feasible resize
|
||||
|
||||
Source: `projection.rs:314–350,515–519`, `projection/port_layout.rs:54–72,131–156,164–188,203–264`. Port choices are made from geometry before the second capacity/enclosure pass. They are frozen only inside that one projection invocation. Reprojecting a placement whose stored dimensions have been replaced by the previous visible dimensions can choose different sides. Projection is not generally an idempotent normalizer of stored bounds.
|
||||
|
||||
Concrete analytical fixture using normal-sized, nonoverlapping peers:
|
||||
|
||||
- System A stored `(0,0), 224×160`, with eight neutral Standard/Unset component ports.
|
||||
- Two SystemActors B/C stored `(38,-996), 184×132` and `(38,1004), 184×132`; each has one Standard/Unset port. Connect every A port to both peer ports with distinct BoundaryExchanges, so each A port sees average peer center `(130,70)`.
|
||||
- Initial neutral balancing gives A minimum height 160. Its center is `(112,80)`; facing compares `dx=18/224=.08036` with `dy=-10/160=-.0625`, so all eight choose Right. Both separated peers leave Right and Top exits clear. Right capacity requires `36+28×7=232`, yielding visible `224×232`.
|
||||
- Persisting that visible size gives initial center `(112,116)`: `dx=.08036`, `dy=-46/232=-.19828`. All eight now choose Top; Top capacity enlarges width to 232. The same visible-bound normalization produces **232×232**, not 224×232.
|
||||
- E shrink to x214 is feasible with stored `214×160`: center `(107,80)` still faces Right and visible geometry is **214×232**, preserving all fixed/inactive edges. Proposed full normalization to stored `214×232` faces Top and projects **232×232**, falsely refusing that feasible shrink.
|
||||
|
||||
The probe builds this fixture with current `make_port`, `relate`, `place`, validates it, uses actual `prepare_command(EditNode)` and actual projection, and compares these alternatives. It repeats with manual Right anchors: automatic capacity is deliberately independent of manual overrides (`port_layout.rs:206–207`), so manually positioning the ports does not remove this issue. Core validation and execution remain required before treating the analytical prediction as verified.
|
||||
|
||||
**Required revision:** exact preview/reopen equality should be an invariant; replacing every stored dimension with every visible dimension should not. Preserve original inactive authored dimensions when they already realize the required visible edges. Normalize/rebase only fields needed to realize the requested frame, and prove the resulting projection. This is an inverse geometry problem, not an instruction to copy `ProjectedNode` into `NodePlacement`.
|
||||
|
||||
### High — a universal new minimum conflicts with fixed inactive axes for valid existing cards
|
||||
|
||||
Source: `projection.rs:74–75,314–327`; `validation.rs:805–823`; dedicated state minima are separate in `projection/state.rs:71–86`. A non-state, portless leaf with stored 100×50 is valid and visibly 100×50. E enlargement to 110×50 is straightforward in the actual core. A solver that requires both 184×94 and unchanged inactive height50 must reject it, even while the user enlarges the object. A corner-only recovery is needless friction and leaves ordinary side handles unreliable. The standalone probe includes this core-level fixture.
|
||||
|
||||
**Required revision:** distinguish hard projection/content constraints from a new preferred UI minimum. On active axes, permit existing below-preferred-minimum cards to improve continuously without a press-time jump; a practical monotone threshold is `min(baseline_visible_extent, preferred_minimum)`. On inactive axes do not demand a new preferred floor. This does not grandfather violations of actual core constraints: baseline visible geometry already includes those. Clip/elide the type header as well as the name so rendering remains bounded while old small cards are repaired (`diagram.rs:1613–1635` currently uses infinite text width).
|
||||
|
||||
### Medium — a monotone enlargement loop is not justified by this projection
|
||||
|
||||
Source: `port_layout.rs:54–112,217–263`, plus `projection.rs:541–572`. `facing` changes at geometric thresholds, `clear_exit` changes at peer/stub intersection thresholds, and other nodes can grow after initial choices. This means requested-width feasibility need not be monotone, and port-driven width/height constraints are not just a constant rectangular lower bound. More iterations alone are not evidence that a solver is complete or stable.
|
||||
|
||||
**Required experiment:** sweep E/W/N/S and corners over both directions around the fixture above; include manual anchors, an enclosing parent and a child peer. Log requested moving edges, stored candidate, projected edges and chosen port sides. For each claimed infeasible request, compare at least an original-inactive-field candidate with a normalized candidate before rejecting. Test deterministic repeated calls from the press baseline and shrink→reverse→grow. An iteration cap must yield an explicit unresolved result, never a false “minimum reached” diagnosis. Keep the existing single-pass projector; do not change every view to worst-case port capacity or persist implicit anchors to force a resize.
|
||||
|
||||
### Medium — current port hit testing cannot simply precede new handles
|
||||
|
||||
Source: `diagram.rs:202–226,843–891`. `hit_port` includes invisible slop: radius is `max(10/zoom,10)` in world units, hence at zoom2 a 20-pixel radius. A resize handle drawn eight screen pixels outside a corner port is swallowed if the old port check runs first. “Actual port wins” in the proposal must refer to the actual visible glyph, not the current enlarged hit function. The proposal's later nearest-center rule is sensible but needs one shared arbitration step across competing control kinds, not two ordered independent hit checks.
|
||||
|
||||
**Required native falsifier:** manual port at side offset0 and1, selection handles at all corners, zoom .25/1/2, then at least one partially offscreen container. Click the center of each visible handle and visible port; perform its distinct operation. Verify the deterministic winner at genuinely coincident centers and keep another discoverable means for each operation. State views also require clipping below the 42-pixel guidance strip (`state_canvas.rs:22,48–99`).
|
||||
|
||||
### Medium — stationary content means stored placements, not necessarily identical projected child frames
|
||||
|
||||
The report correctly allows automatic derived adjustments, but “children stay in place” can be read more strongly than the implementation. A selected parent's changing size/center can alter a connected child's automatic ports, its projected size, and consequent ancestor enclosure. `port_layout.rs:164–188,239–264` can change other owners although only one stored record changes. Entry/Exit vertices intentionally follow the containing State/Machine (`projection/state.rs:90–118`). Keep the one-record/unchanged-authored-children invariant; explicitly show derived child changes in the ghost and inspect readability. Do not promise byte-equal projected children or silently add child edits to manufacture that promise.
|
||||
|
||||
## What survives the critique
|
||||
|
||||
Eight handles, fixed opposite edges, stationary authored descendants, no automatic semantic allocation/reparenting, ordinary overlap allowance, preserved route pins/anchors and one `EditNode` through `Document::execute` are appropriate. `session.rs:531–549` and `document.rs:315–326` support the atomic path; no new backend command/schema is required for existing placements. Freeze the accepted snapshot and semantic index for preview, and revalidate admission at commit. Reuse the existing allocation token pattern rather than inventing a generic gesture framework (`app/allocation_drag.rs:230,332,534–550`).
|
||||
|
||||
Collapsed width-only editing should preserve authored expanded height. Exact visible inactive height remains subject to automatic port geometry, so present an honest constraint when a width request cannot realize it; do not normalize expanded height to the collapsed visible94. State rectangles share the solver but use state header/content constraints; fixed state symbols are not rectangular resize targets. Process/scenario authored layout remains a separately tracked prerequisite rather than a desktop-only size map.
|
||||
|
||||
Numeric Size entry remains useful as a keyboard alternative. It must invoke the same inverse geometry policy and display effective dimensions, not silently switch to raw stored dimensions. Exact source/API parity means replaying the accepted authored placement fields reproduces the accepted scene. It does not require the raw fields to equal that scene's derived bounds.
|
||||
|
||||
## Recommended next bounded implementation
|
||||
|
||||
1. After the current serialized build gate, root compiles/runs the standalone probe against the identified current `oa_core` rlib, using the reader-lease pattern in `harness/experiments/textual-authoring-20260911/README.md`. Record rlib identity/hash and output; no Cargo/build-input edit is necessary. If fixture validation fails, resolve the fixture before claiming this exact counterexample. The source is `.local/diagram-resize-probe-20260912.rs` and only depends on `oa_core`.
|
||||
2. Amend the design's unconditional normalization rule and preferred-minimum admission first. Implement one pure planner with baseline original placement plus projected bounds, active edges, desired moving-edge delta and candidate constraints. Its first candidate preserves inactive authored fields; rebasing/enclosure correction belongs to the same solver. Use actual shared projection as the evaluator. Introduce only the smallest shared core constraint extraction the experiments prove necessary, not a second desktop port-layout implementation.
|
||||
3. Establish useful candidate success for all eight handles on leaves, auto-enclosed OA/SA containers, folded parents, state actions/regions, and the port falsifiers. Fixed-edge checks need a documented `f32` representability policy at off-grid/large coordinates; arbitrary broad epsilon must not conceal drift. Reject NaN/overflow and report representability/content/port causes distinctly. Keep original N0 as the exact zero-motion fallback.
|
||||
4. Wire architecture and state adapters, visible-control arbitration and ordered gesture tokens. Exercise fast input without redraw, stale release, cancellation, dirty drafts and one-undo behavior. Follow with source/API replay, save/reopen and native engineer tasks. Do not wait for a generalized process/scenario layout schema to deliver this persisted-rectangle capability, and do not close the broader modern editing requirement with this slice alone.
|
||||
|
||||
This critique supports proceeding with the full resizing feature after correcting the two high-impact policies. It does not recommend reducing the feature to positive-edge handles or replacing direct manipulation with a form.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Direct diagram resize investigation — 2026-09-12
|
||||
|
||||
Status: independent read-only design for Gitea issue 16, delegated by root. This report is a candidate for root synthesis and fresh criticism, not an accepted specification or implemented capability. Production, user-owned input, Git/index, Gitea, compiler, native applications and WSL were not changed. Only this report was written.
|
||||
|
||||
Source was inspected on `codex/behavior-authoring-20260910`; HEAD read back as `c3c2d0145340e8960341e97f04edf1898d36a7c5`. Root is concurrently integrating its separately owned Delete work, so this is a source-mapping report, not frozen executable evidence. Root owns shared-remote instruction and ownership reconciliation. No external research was needed beyond the existing local Capella study.
|
||||
|
||||
## Recommendation and decision criteria
|
||||
|
||||
Provide ordinary **eight-handle resizing of selected rectangular diagram placements**, with precise live projection, fixed opposite edges, stable world coordinates for authored child placements, one atomic accepted edit, and exact reopen/source/API parity. Container resize never implies allocation, decomposition, deployment, reparenting or rearrangement of children. The full eight-handle behavior belongs in feature acceptance; an east/south/southeast-only prototype is useful for testing geometry but does not by itself close the modern direct-resize request.
|
||||
|
||||
Criteria come from PROJECT_GOAL.md: reduce engineers' cognitive load, make direct diagram editing consistent, retain useful visual nesting, preserve full graphical/textual equivalence, and support maintainable implementation. The immediate user problem is arranging entities, activities and other objects for comprehensible layout/routing. A size form alone would not solve the direct manipulation problem.
|
||||
|
||||
| Candidate | Ergonomic and semantic consequences | Decision |
|
||||
| --- | --- | --- |
|
||||
| Numeric properties only | Precise but separates the hand from the object and requires repeated value guessing. | Useful accessible alternate entry, insufficient alone. |
|
||||
| Eight handles scale/translate all child coordinates with the frame | Familiar in drawing/group-scale tools; surprises an engineer who is making room around existing work and changes many route/port relationships. Hidden descendants make the effects harder to predict. | Reject as ordinary resize. A separately named future group-scale operation would need independent evidence. |
|
||||
| Eight handles freely shrink the frame and clip or let children overflow | Pointer follows freely, but visually nested semantics become ambiguous and header/action content can disappear. | Reject. |
|
||||
| Eight handles keep child placements stationary and clamp to required content/port/header geometry | Familiar frame editing; frame grows around work and can shrink to a visible minimum without moving the work. North/west handles give essential control over which margin grows. | Recommend. |
|
||||
| East/south/southeast only | Sufficient to test persistence and positive-edge geometry; engineers must move the whole group to add space on the left/top, which also moves its contents/routes. | Foundational experiment only, not completed feature acceptance. |
|
||||
| Desktop-only resized rectangles | Fast demonstration but source, API, reopen, undo and SVG disagree. | Reject. |
|
||||
|
||||
The main unresolved implementation choice is how to derive an exact constraint solution from the current projection's two-stage port capacity pass. It must be settled by the foundational experiment below, not hidden behind an approximate UI rectangle.
|
||||
|
||||
## Confirmed current behavior and authoritative sources
|
||||
|
||||
1. `crates/oa-core/src/model.rs:1484–1560`: `Diagram.nodes` holds `NodePlacement { id, alias, element, position, size, collapsed, port_anchors }`. Position and size are persisted; placement identity is distinct from semantic identity. A semantic element occurs at most once in one diagram, but can have independent placements in multiple diagrams. `docs/language.rst:64`, and its diagram/node section around 1100–1200, already state placement does not determine parent/allocation/realization and coordinates are absolute diagram coordinates.
|
||||
2. `crates/oa-core/src/session.rs:34,531`: `EditNode` can edit size and position today. It is an exact record edit; it does not translate descendants or route pins. `MoveNode` at 771 walks visual descendants when requested and translates both-ended group route pins. A resize must not fall through that command. Node IDs/aliases/element/anchors must be copied unchanged.
|
||||
3. `crates/oa-core/src/validation.rs:733–827`: placements require existing compatible elements and unique placement membership; x/y/width/height must be finite and bounded by MAX_COORDINATE, and width/height positive. It does **not** currently validate a graphical header/content minimum. Positive tiny authored sizes remain structurally valid data, even when projection enlarges them. Do not turn a new pointer minimum into an incompatible schema rule.
|
||||
4. `crates/oa-core/src/projection.rs:75,82,275,538`: one core projection drives normal drawing, hit testing, Fit, SVG and movement. Projection first derives visual parents from semantic ancestors; Allocation may instead group by effective performer/allocation summary; Traceability deliberately has no visual parents. Visual parenthood is not discovered from rectangle overlap. Projection enlarges and can shift ancestors to enclose descendants with 24-unit left/right/footer margins and a 64-unit header (state-specific header in dedicated state views). Stored `position`/`size` therefore need not equal visible bounds.
|
||||
5. `crates/oa-core/src/projection/port_layout.rs:131,161–264`: there are **two** port size effects. A semantic-side count grows base owner size (vertical: 76 + 28*(count+1)); automatic side choices then depend on geometry and connection peers, followed by capacity growth (36 + 28*(count-1)), another enclosure pass, and final port positioning. Moving a side during resize can change automatic port-side choice/capacity. A simple min-width/min-height copied from the first pass is not a complete constraint model. Manual anchors preserve side and fractional offset; closely packed manual anchors degrade spacing rather than relocating authored anchors.
|
||||
6. `crates/oa-core/src/projection/state.rs:24–144`: stable State/Mode are rectangles (minimum width 184); Machine/Region rectangles have width minimum 224. Height reserves dynamic entry/exit/do-action and internal-transition rows. Pseudostates have fixed body sizes regardless of `NodePlacement.size`: Initial 16x16, Junction 10x10, Fork/Join 8x36, Choice/History 28x28, Final/Entry/Exit/Terminate 24x24. Entry/Exit symbols are semantically owned by a Region but snap to their containing State/Machine boundary; they do not enlarge its content envelope. Missing containing boundary does not invent a placement.
|
||||
7. `crates/oa-desktop/src/behavior/state_canvas.rs:48–99` and `symbol_labels`: dedicated state editing has its own ordered pointer gestures, scene cache, dynamic symbol-label geometry and clipped guidance strip. Its existing press/event contracts do not contain resize. `behavior/state_edit.rs` has a move event/planner, again not size editing. Symbol labels and enlarged pointer targets are derived UI, not independently sized persisted nodes.
|
||||
8. `crates/oa-core/src/model.rs:1500–1547`, `projection.rs:167–222`, `routing.rs`, `harness/release-routing-contract.md`: route pins are world coordinates; label is fraction along the computed route plus relative offset; ports are semantic child references anchored on an owner placement, not independent placement rectangles. Resizing changes actual endpoints and obstacles, so recompute routes. Preserve stored pins/labels exactly. Raw EditNode is expressly exact and must not implicitly translate pins. Fold-promoted endpoint routes remain stored and resume on expansion.
|
||||
9. `crates/oa-desktop/src/document.rs:315–326`: `Document::execute` prepares a validated candidate, updates/compiles source with the identity map, then commits. Source preparation failure occurs before adoption. Use this path and one command; do not modify the accepted snapshot during motion. `docs/api.rst` and existing transaction API already support normal EditNode. This resize slice needs no new persistence schema or API command.
|
||||
10. `crates/oa-desktop/src/diagram.rs:46–78,2976` (the latter message handler is in `app.rs`): architecture canvas and its legacy MoveNode message have no resize gesture. Legacy App movement calls `apply()`, which can apply a draft; this is not acceptable resize admission.
|
||||
11. `allocation_drag.rs:449–619,700–749`, `app/allocation_drag.rs`, `diagram/work_drag.rs`, and `harness/release-activity-allocation-drag-contract-v2-20260911.md`: allocation-body dragging freezes accepted target geometry/context, may change allocations, carries semantic work/placement closure and exact preview/final validation. Geometry is frozen for target resolution during a **single gesture**, not an immutable model layout. Existing work-body paths must retain their semantics; a resize handle must enter a separate layout-only gesture before that body path. The 2026-09-12 feedback amendment keeps destination identity in a toolbar slot, not a duplicate canvas label.
|
||||
12. `harness/release-capella-workflow-benchmark.md:91` records actual Capella resizing of a reused-chain container to reveal nested content without changing the referenced object. `harness/release-capella-direct-manipulation-gap-20260911.md` separately distinguishes arranging a use, reparenting a definition, allocating work, and transferring an occurrence. These support stationary-content resizing; they do not prove every proposed Studio handle policy.
|
||||
|
||||
## Proposed contract
|
||||
|
||||
### Scope and identity
|
||||
|
||||
Eligible objects are visible selected rectangular `NodePlacement`s in existing architecture projections and dedicated state diagrams: entities/actors/components, activities/functions including allocation summaries, ordinary dictionary/capability cards, State/Mode/Region/Machine, and other objects already drawn as resizable rectangles. Use projection/presentation shape, not a small hard-coded set of entity types. A rectangular representation of a state in a general architecture view is distinct from its fixed symbol in a dedicated state view.
|
||||
|
||||
Handle attachment and commits target `(document instance, accepted revision/snapshot, active diagram, placement ID, semantic element ID)`. Resize one placement in the active view. The complete final Project differs only in that placement's admitted position/size fields. All semantic records, allocations/roles/parents, identities, other diagrams, other stored node fields, routes, anchors, traces, artifacts and view filters stay byte/value equivalent as appropriate. No placements are materialized by this existing-view operation.
|
||||
|
||||
Fixed state symbols do not show rectangular resize handles. Ports keep their existing repositioning gesture; route bends and route labels keep their existing editing gestures. This is an explicit shape/persistence distinction, not treating all graphical items as immutable. Independently sized pseudostates, symbol labels or port glyphs would require a reviewed shape/label contract before promising them. Fork/Join bar-length editing may be valuable, but current projection ignores stored size; it is not honestly delivered by writing `size` alone.
|
||||
|
||||
For a collapsed rectangle, expose east/west width resizing; retain the complete stored expanded height and collapse flag. Show concise guidance to expand for height resizing. Do not write the projected collapsed height (normally 94 before port minima) back over expanded height. A folded body may still have port-driven effective height; width changes preview that exact derived height.
|
||||
|
||||
### Geometry and fixed edges
|
||||
|
||||
Capture the **visible projected body rectangle** R0=(L0,T0,R0,B0) at press, together with the original stored placement N0. Pointer delta is measured from the ordered native press in the captured transform, in diagram units. Compute every candidate from this baseline, never cumulatively from the last preview.
|
||||
|
||||
Eight handles move these edges: W=L, E=R, N=T, S=B; corners move the corresponding pair. Every opposite edge stays fixed exactly. Edges on an inactive axis stay fixed. Crossing the opposite edge clamps at the permitted minimum and never flips/rotates the object or changes handle identity. Snap **moving-edge deltas** in the ordinary 10-world-unit increments, so existing off-grid geometry does not jump at press or a zero delta. Clamping may land off-grid when that is the exact content minimum. Keyboard exact numeric sizing need not snap.
|
||||
|
||||
Resize is frame editing. Existing child placement positions and sizes, including folded descendants, stay stored at their world coordinates. West/north changing the selected top-left must not call MoveNode or add that delta to descendants. The shared projection may adjust derived automatic ports/child sizes and ancestor enclosures; preview the whole affected scene. Entry/Exit symbols follow the resized containing boundary through their existing projection rule while retaining their authored coordinates. No semantic parent/allocation change follows from one box geometrically crossing another.
|
||||
|
||||
Expanded containers cannot shrink through visible nested content or reserved header/actions/rows. For fixed projected content envelope C, the familiar first constraints are:
|
||||
|
||||
- W: L <= min(R0 - minimum_width, C.left - 24); E: R >= max(L0 + minimum_width, C.right + 24).
|
||||
- N: T <= min(B0 - minimum_height, C.top - header_height); S: B >= max(T0 + minimum_height, C.bottom + footer).
|
||||
- Empty content omits its corresponding C terms. State boundary Entry/Exit vertices are excluded exactly as in core enclosure.
|
||||
|
||||
Example: visible parent [100,100]–[500,400], child [160,200]–[360,300], ordinary header 64/margin24. West cannot pass x136; east cannot pass x384; north cannot pass y136; south cannot pass y324, subject also to node/port minima. Dragging W to x50 yields parent [50,100]–[500,400], child unchanged. Dragging N to y0 yields [100,0]–[500,400], child unchanged. Dragging NW toward [200,250] clamps to the content/minimum boundary; it must not move the child down/right or move fixed R0/B0.
|
||||
|
||||
Those formulas are explanatory lower bounds, **not a sufficient implementation** with geometry-dependent ports. Each admissible candidate must pass the real core projection, and the selected projected rectangle must equal the offered rectangle with the captured fixed edges. Any necessary secondary extent change on an inactive axis must be exposed as a constraint/refusal (for example “Ports need more height; resize a corner”), not silently changed by an east/west-only drag. Corner gestures can change both dimensions.
|
||||
|
||||
Proposed pointer floor for ordinary rectangular cards is 184x94 world units, strengthened by the existing state/content/port constraints. This is a **reversible UX assumption requiring native evaluation**, not a fact or new source-validation rule. Kind/header text must remain within the drawn card at that minimum; current `paint_node` elides the name but gives the type header infinite max width. Measure the actual longest OA/SA labels and summary headers before accepting 184. Do not use full user name length as an unbounded minimum; enlarge to reveal more text, preserving concise labels and full identity in details.
|
||||
|
||||
### Persisting exact visible edits
|
||||
|
||||
Enclosure can make N0 smaller or displaced relative to R0. A successful expanded resize writes the selected placement's **final visible top-left and size** into its existing record. It is intentional normalization of that one edited frame, needed so later shrink operations do not encounter its stale authored origin/floor. No normalization occurs on press, stationary click, below-threshold motion, canceled gesture, or a constrained result visually equal to R0. An authored/effective mismatch alone is not a reason to add history.
|
||||
|
||||
A folded resize preserves N0.height and collapse flag; only the final visible width and admitted x are persisted. Other stored fields remain N0. Document that collapsed/port/content projections still have derived extents; exact persistence means the reopened scene equals the accepted preview, not a false claim that every stored node has exactly its projected dimensions.
|
||||
|
||||
The candidate is a cloned Diagram over accepted semantic Project/ViewIndex, not a mutable Project updated per pixel. Reproject and reroute it. At release, regenerate the plan from the baseline plus final ordered pointer, prepare its EditNode, compare the entire candidate Project with exactly the expected single-record edit, and confirm the final projected/routed scene matches the final preview. Execute through Document once only after these checks. Refresh the selected placement's clean details without changing semantic selection or running Fit. All errors preserve the accepted model/source/history/camera and a useful explanation.
|
||||
|
||||
Existing ancestor enclosures are derived: resizing a child may grow or shrink an ancestor down to its own authored floor. Show this in preview. Do not persist normalization to all ancestors or automatically move siblings; that would turn one resize into an implicit layout operation. Overlap with unrelated rectangles is presentation, not ownership. Show routing/overlap consequences, but do not copy allocation-drag's blanket no-new-overlap refusal: a general layout tool must allow engineers to arrange overlapping geometry and recover. Exact content/header containment is a stronger invariant than unrelated-card overlap.
|
||||
|
||||
### Admission, lifetime, feedback and keyboard
|
||||
|
||||
Show handles only for the selected eligible placement. Handle glyph/hit targets are screen-sized at every zoom and positioned outside the body so ports at the border remain reachable. Explicit hit precedence: overlay/tool ownership first; actual port or active connection/route controls keep their gesture; then visible resize handles; then ordinary body/allocation drag. If targets overlap at extreme zoom, select nearest visible control center with deterministic ties; enlarged invisible slop must not override a different visible control. Native evaluation must prove both a port and a neighboring resize handle remain discoverable/reachable. A resize cursor and small “Resize · width × height” feedback make the distinction observable.
|
||||
|
||||
Use clean selection/admission without `apply()`. Dirty properties, source, behavior draft or input-owning overlays prevent arming resize and preserve drafts/focus. Use an App-registered monotonically unique token plus full context, input epoch, captured bounds/pan/zoom and snapshot. Retire on owner/diagram/document/revision change, modal or tool roundtrip, dirty draft transition, focus loss, Escape, camera/Fit or actual canvas/window bounds change. Even equal-value roundtrips cannot resurrect a token. Press/move/release may arrive before a redraw; preserve native event order and permit at most one commit per token. An old release cannot cancel a newer gesture. Outside-canvas release cancels; no auto-unassign, reparent or hidden fallback move.
|
||||
|
||||
Show the exact candidate node, affected routes/ports, and constrained minimum reason during motion. A constrained edge stays at its minimum; neither the selection nor whole canvas moves to keep it under the cursor. Do not require the entire selected header to fit in the viewport to allow a resize: that allocation-drag restriction would prevent useful editing of a large existing container. Keep camera stable; user can pan/Fit after finishing. Escape returns to the unchanged accepted scene.
|
||||
|
||||
Provide an accessible context action “Size…” for the selected rectangle with width/height (width only when collapsed), full selected identity and explicit minimum explanation. It uses the same pure planner, fixed top-left and transaction invariants. Eight pointer handles remain required; numeric controls are an alternate means of accurate/keyboard operation, not a replacement. Enter accepts once, Escape cancels; invalid input retains the draft and original scene.
|
||||
|
||||
## Foundational experiments before UI implementation
|
||||
|
||||
These are required proposed gates, **not executed evidence**. E/S/SE is a bounded experiment for the first row, then full eight handles must pass.
|
||||
|
||||
1. **Can an exact candidate be produced with current projection?** Build a pure planner fixture using EditNode and project_diagram. Start with empty leaf; auto-enclosed performer; nested work parent; mixed allocation-summary work; collapsed descendants; State with dynamic action/internal rows; geometry-dependent input/output/neutral ports and crowded manual anchors. Compare direct formula only, bounded monotone minimum refinement, and extracting shared core constraint facts. Measure convergence and confirm fixed-edge equality. A tentative refinement enlarges only toward active moving edges when projection reveals extra minimum extent; fixed-edge or inactive-axis violations cannot be silently repaired. Preserve a last admissible ghost while showing refusal, but a routine feasible case rejected by a chosen iteration cap is a failing evaluator and reason to revise the solver, not a successful feature. Do not fork a second UI-only port/enclosure algorithm.
|
||||
2. **North/west falsifiers.** Start with stored parent [200,200], size224x94 whose child projects its visible top-left to [100,100]. Press/zero-delta must be clean. W/N/NW resize uses [100,100] as baseline; fixed right/bottom remain exact; children and pins stay unchanged. Shrink to content bounds, then reverse the pointer and expand. Repeat with off-grid coordinates, nested allocated work, hidden grandchildren and parent absent from a partial view. Verify no cursor-jump and no group translation.
|
||||
3. **Ports and states.** On each side, manual offset0, .25, 1 retains side/offset while world endpoint follows the new boundary. Automatic ports may choose new sides, but semantic endpoints stay exact and candidate preview equals final. Resize State left/top/right/bottom around Entry/Exit and deep Regions; body labels remain readable, symbols remain on the containing boundary, routes and separate labels follow actual geometry. Fixed symbols never imply persisted resizability.
|
||||
4. **Atomic persistence.** One successful gesture gives one undo entry. Undo/Redo, save/reopen, source export/reparse and JSON/API transaction replay reproduce complete Project and diagram geometry. Included source files retain ownership/comments/trivia through normal update path. A source preparation failure leaves both representations/history unchanged. Other view placements/routes, all semantic records and anchors compare equal. Selection remains the semantic element plus exact placement.
|
||||
5. **Lifetime/input.** Fast press→motion→release without redraw; release twice; pointer exits; Escape; dirty source/property/behavior; focus/modal/tool/Fit roundtrips; document replacement with equal values; stale release after newer press. No draft auto-apply, unrelated selection, zero-delta history, allocation creation/removal or hidden MoveNode fallthrough. Resize at min/max coordinate boundaries cannot produce NaN/infinite/unbounded projected rectangles.
|
||||
6. **Actual engineer tasks.** OA: enlarge an entity on the left while its activities stay in place, shrink a work parent around its children, route an interaction through the gained margin, resize the child, undo/reopen. SA: resize System/Actor/function with manual and automatic ports, confirm allocated work remains assigned and readable. State: make room around nested Regions and action rows, then shrink to minimum. At minimum and large windows, several zooms, every handle must be reachable with corresponding resize cursor; dense edge/port neighborhoods and partially offscreen large containers must work. Verify keyboard size entry, cancellation and failure recovery. Screenshots alone are insufficient.
|
||||
|
||||
## Process/scenario gap and ownership boundaries
|
||||
|
||||
Existing process/scenario scenes are not diagram-placement consumers. `behavior/topology.rs:988` constructs ranks and geometry from contributions/sequence links/legacy order; `behavior.rs:1214` collects lifelines and later computes uniform x positions. These scenes do not acquire stored dimensions merely because NodePlacement has a size field. Current ProjectionKind has no owner-bound process/scenario placement projection. The existing `release-capella-direct-manipulation-gap-20260911.md` recommends an owner-bound persisted behavior-view contract with independent views and first-edit materialization. That prerequisite includes identity, source/REST, delete/reidentify, validation and exact occurrence/reference addressing.
|
||||
|
||||
Issue 16 can deliver honest resize for existing persisted architecture/state rectangles while explicitly tracking process node sizes, scenario lane widths and frame sizes as the behavior-layout dependency. This is staged delivery, not full closure of all direct-editing requirements. Do not add desktop size maps, store process definition IDs instead of occurrence/view identities, equate frame width with nonexistent persisted coverage, or invent arbitrary scenario y coordinates. Empty/missing authored behavior views should remain clean deterministic derived scenes until an accepted first placement edit materializes a reviewed view.
|
||||
|
||||
Suggested file/interface partition for implementation:
|
||||
|
||||
- Pure shared desktop resize planner in a new focused module: baseline, Handle, candidate validation, expected single-record command and exact preview. No semantics or accepted-document mutation. Use shared core projection/routing; a needed shared constraint extraction belongs in core projection and is separately reviewed.
|
||||
- Architecture canvas/App owner: handle drawing/hit priority, ordered native events, token/admission, normal document execute and selection. Coordinate `diagram.rs`, `app.rs`, `views.rs` changes with root; preserve allocation and Delete owners' paths.
|
||||
- State canvas/App owner: adapter to the same planner/lifetime contract, state guidance/labels and active-diagram selection. No separate approximate geometry solver.
|
||||
- Core/source/API owner: normally no schema/command addition. Review only constraint extraction and normative clarification if needed; existing EditNode transport must be demonstrated. Behavior-view schema is a separate prerequisite coordinated with its existing owner.
|
||||
- Root: acceptance decision after a fresh independent critique of eight-handle anchoring/content/ports/folding, then isolated builds/native/platform gates and issue/release coordination. This report claims none of those outcomes.
|
||||
@@ -0,0 +1,37 @@
|
||||
[
|
||||
{
|
||||
"file": "design.md",
|
||||
"bytes": 26749,
|
||||
"sha256": "fe5bf66b5d50aef449d7b6a2d55f0cd01dc8ac23629daaad3417fde935287cec"
|
||||
},
|
||||
{
|
||||
"file": "critique.md",
|
||||
"bytes": 12045,
|
||||
"sha256": "6015157f90c50625d4372b2d162a05a1d335d52f31e19e4738e885cae9926f80"
|
||||
},
|
||||
{
|
||||
"file": "probe.rs",
|
||||
"bytes": 5446,
|
||||
"sha256": "251e0d8f8c12cd32f67274c20b2a38993a98b7dea845452135d275a28e2b0a0b"
|
||||
},
|
||||
{
|
||||
"file": "build.json",
|
||||
"bytes": 1882,
|
||||
"sha256": "6eb35529690992486cfd9299ae9a4410bf7913e1cb40b7d03e225007bd504da8"
|
||||
},
|
||||
{
|
||||
"file": "build.log",
|
||||
"bytes": 0,
|
||||
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
},
|
||||
{
|
||||
"file": "projection.json",
|
||||
"bytes": 289,
|
||||
"sha256": "a59628282f6ab3613f1a1b4756ab53f633cec1b715c50dca0a64be8d43772121"
|
||||
},
|
||||
{
|
||||
"file": "projection.log",
|
||||
"bytes": 2427,
|
||||
"sha256": "40c78508b334814cb275e86b790e75966549d1aa84d9429269db30558c735efc"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,120 @@
|
||||
//! Standalone actual-core falsifier. Not compiled or executed by its author.
|
||||
//! Root must select a matching oa_core rlib and serialize compilation with gates.
|
||||
use oa_core::{projection::*, validation, *};
|
||||
|
||||
fn pos(x: f32, y: f32) -> Position { Position { x, y } }
|
||||
fn size(width: f32, height: f32) -> Size { Size { width, height } }
|
||||
|
||||
fn valid(label: &str, p: &Project) {
|
||||
let issues = validation::validate(p);
|
||||
for issue in &issues { println!("{label}: {issue:?}"); }
|
||||
assert!(!issues.iter().any(|i| i.severity == validation::Severity::Error),
|
||||
"Fixture/candidate must be model-valid: {label}");
|
||||
}
|
||||
|
||||
fn standard(p: &mut Project, owner: Id, name: &str) -> Id {
|
||||
let id = Id::new_v4();
|
||||
let port = make_port(p, id, owner, name, PortContract::Component {
|
||||
kind: ComponentPortKind::Standard,
|
||||
orientation: ComponentPortOrientation::Unset,
|
||||
}).unwrap();
|
||||
p.elements.insert(id, port);
|
||||
id
|
||||
}
|
||||
|
||||
fn fixture(manual: bool) -> (Project, Id, Id) {
|
||||
let mut p = Project::empty();
|
||||
let a = p.add_element(Kind::System, Layer::Sa, "Owner", None).unwrap();
|
||||
let b = p.add_element(Kind::SystemActor, Layer::Sa, "Above", None).unwrap();
|
||||
let c = p.add_element(Kind::SystemActor, Layer::Sa, "Below", None).unwrap();
|
||||
let pb = standard(&mut p, b, "Above interface");
|
||||
let pc = standard(&mut p, c, "Below interface");
|
||||
let mut ports = Vec::new();
|
||||
for i in 0..8 {
|
||||
let pa = standard(&mut p, a, &format!("Owner interface {i}"));
|
||||
p.relate(RelationKind::BoundaryExchange, &format!("Above exchange {i}"), pa, pb);
|
||||
p.relate(RelationKind::BoundaryExchange, &format!("Below exchange {i}"), pa, pc);
|
||||
ports.push(pa);
|
||||
}
|
||||
let d = p.ensure_diagram(Layer::Sa);
|
||||
let na = p.place(d, a, pos(0., 0.));
|
||||
// Normal-sized separated peers have centers (130,-930) and (130,1070).
|
||||
// Each Owner port sees the same average peer center (130,70).
|
||||
let nb = p.place(d, b, pos(38., -996.));
|
||||
let nc = p.place(d, c, pos(38., 1004.));
|
||||
let diagram = p.diagrams.get_mut(&d).unwrap();
|
||||
diagram.nodes.get_mut(&na).unwrap().size = size(224., 160.);
|
||||
for n in [nb, nc] { diagram.nodes.get_mut(&n).unwrap().size = size(184., 132.); }
|
||||
if manual {
|
||||
diagram.nodes.get_mut(&na).unwrap().port_anchors = ports.into_iter()
|
||||
.enumerate().map(|(i, port)| PortAnchor {
|
||||
port, side: PortAnchorSide::Right, offset: (i + 1) as f32 / 9.,
|
||||
}).collect();
|
||||
}
|
||||
valid("baseline", &p);
|
||||
(p, d, na)
|
||||
}
|
||||
|
||||
fn projected(p: &Project, d: Id, n: Id) -> ProjectedNode {
|
||||
project_diagram(p, &p.diagrams[&d]).nodes.into_iter().find(|v| v.id == n).unwrap()
|
||||
}
|
||||
|
||||
fn edit(p: &Project, d: Id, n: Id, dimensions: Size) -> Project {
|
||||
let mut node = p.diagrams[&d].nodes[&n].clone();
|
||||
node.size = dimensions;
|
||||
let next = prepare_command(p, &Command::EditNode { diagram: d, original: n, node }).unwrap().project;
|
||||
valid("candidate", &next);
|
||||
next
|
||||
}
|
||||
|
||||
fn normalization_falsifier(manual: bool) {
|
||||
let (p, d, n) = fixture(manual);
|
||||
let baseline = projected(&p, d, n);
|
||||
println!("manual={manual}, baseline {baseline:?}");
|
||||
assert_eq!(baseline.position, pos(0., 0.));
|
||||
assert_eq!(baseline.size, size(224., 232.));
|
||||
let normalized = edit(&p, d, n, baseline.size);
|
||||
let normalized_view = projected(&normalized, d, n);
|
||||
println!("normalized {normalized_view:?}");
|
||||
assert_eq!(normalized_view.size, size(232., 232.), "Visible-size normalization changes projection");
|
||||
|
||||
// Same East edge target x214, same fixed x0/y0/bottom232.
|
||||
// Preserve the authored inactive dimension versus normalize it.
|
||||
let minimal = edit(&p, d, n, size(214., 160.));
|
||||
let minimal_view = projected(&minimal, d, n);
|
||||
let normalized_shrink = edit(&p, d, n, size(214., 232.));
|
||||
let normalized_shrink_view = projected(&normalized_shrink, d, n);
|
||||
println!("minimal shrink {minimal_view:?}");
|
||||
println!("normalized shrink {normalized_shrink_view:?}");
|
||||
assert_eq!(minimal_view.size, size(214., 232.));
|
||||
assert_eq!(normalized_shrink_view.size, size(232., 232.));
|
||||
assert_eq!(minimal_view.position, baseline.position);
|
||||
let mut restored = minimal.clone();
|
||||
restored.diagrams.get_mut(&d).unwrap().nodes.insert(n, p.diagrams[&d].nodes[&n].clone());
|
||||
assert_eq!(restored, p, "Only selected placement changed");
|
||||
for view in [project_diagram(&p, &p.diagrams[&d]), project_diagram(&minimal, &minimal.diagrams[&d])] {
|
||||
for port in view.ports.iter().filter(|v| v.owner == n) {
|
||||
assert_eq!(port.side, PortAnchorSide::Right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn grandfather_floor_falsifier() {
|
||||
let mut p = Project::empty();
|
||||
let a = p.add_element(Kind::Entity, Layer::Oa, "Small valid card", None).unwrap();
|
||||
let d = p.ensure_diagram(Layer::Oa);
|
||||
let n = p.place(d, a, pos(0., 0.));
|
||||
p.diagrams.get_mut(&d).unwrap().nodes.get_mut(&n).unwrap().size = size(100., 50.);
|
||||
valid("small baseline", &p);
|
||||
assert_eq!(projected(&p, d, n).size, size(100., 50.));
|
||||
let grown = edit(&p, d, n, size(110., 50.));
|
||||
assert_eq!(projected(&grown, d, n).size, size(110., 50.));
|
||||
println!("Valid E enlargement 100x50 -> 110x50 exists; global 184x94 pointer floor plus fixed inactive height rejects it.");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
normalization_falsifier(false);
|
||||
normalization_falsifier(true);
|
||||
grandfather_floor_falsifier();
|
||||
println!("PASS: actual-core normalization and minimum-policy falsifiers");
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"utc": "2026-09-12T12:49:04.656843+00:00",
|
||||
"command": [
|
||||
"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\native-qa\\resize-probe-42511f0\\resize-probe-42511f0.exe"
|
||||
],
|
||||
"binary_sha256": "9f807b4c397fa4dc9b4f17a67686239cc1fa44cecfc99e8be92fd8839084d546",
|
||||
"exit": 0
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
manual=false, baseline ProjectedNode { id: dba08b6a-0ff4-4e27-a0ad-6a7d60266b5f, element: 3062821d-31c7-4ad1-8957-fa59a4588a4f, position: Position { x: 0.0, y: 0.0 }, size: Size { width: 224.0, height: 232.0 }, visual_parent: None, depth: 0, collapsed: false, allocation_summary: false }
|
||||
normalized ProjectedNode { id: dba08b6a-0ff4-4e27-a0ad-6a7d60266b5f, element: 3062821d-31c7-4ad1-8957-fa59a4588a4f, position: Position { x: 0.0, y: 0.0 }, size: Size { width: 232.0, height: 232.0 }, visual_parent: None, depth: 0, collapsed: false, allocation_summary: false }
|
||||
minimal shrink ProjectedNode { id: dba08b6a-0ff4-4e27-a0ad-6a7d60266b5f, element: 3062821d-31c7-4ad1-8957-fa59a4588a4f, position: Position { x: 0.0, y: 0.0 }, size: Size { width: 214.0, height: 232.0 }, visual_parent: None, depth: 0, collapsed: false, allocation_summary: false }
|
||||
normalized shrink ProjectedNode { id: dba08b6a-0ff4-4e27-a0ad-6a7d60266b5f, element: 3062821d-31c7-4ad1-8957-fa59a4588a4f, position: Position { x: 0.0, y: 0.0 }, size: Size { width: 232.0, height: 232.0 }, visual_parent: None, depth: 0, collapsed: false, allocation_summary: false }
|
||||
manual=true, baseline ProjectedNode { id: 4655e0db-3d5a-4b92-8a54-7d09bf6fd698, element: 74d13905-2dfa-41bc-b29d-067f259338f2, position: Position { x: 0.0, y: 0.0 }, size: Size { width: 224.0, height: 232.0 }, visual_parent: None, depth: 0, collapsed: false, allocation_summary: false }
|
||||
normalized ProjectedNode { id: 4655e0db-3d5a-4b92-8a54-7d09bf6fd698, element: 74d13905-2dfa-41bc-b29d-067f259338f2, position: Position { x: 0.0, y: 0.0 }, size: Size { width: 232.0, height: 232.0 }, visual_parent: None, depth: 0, collapsed: false, allocation_summary: false }
|
||||
minimal shrink ProjectedNode { id: 4655e0db-3d5a-4b92-8a54-7d09bf6fd698, element: 74d13905-2dfa-41bc-b29d-067f259338f2, position: Position { x: 0.0, y: 0.0 }, size: Size { width: 214.0, height: 232.0 }, visual_parent: None, depth: 0, collapsed: false, allocation_summary: false }
|
||||
normalized shrink ProjectedNode { id: 4655e0db-3d5a-4b92-8a54-7d09bf6fd698, element: 74d13905-2dfa-41bc-b29d-067f259338f2, position: Position { x: 0.0, y: 0.0 }, size: Size { width: 232.0, height: 232.0 }, visual_parent: None, depth: 0, collapsed: false, allocation_summary: false }
|
||||
Valid E enlargement 100x50 -> 110x50 exists; global 184x94 pointer floor plus fixed inactive height rejects it.
|
||||
PASS: actual-core normalization and minimum-policy falsifiers
|
||||
Reference in New Issue
Block a user