# #32 connection-aware placement: source audit and next design Read-only audit at reset checkout HEAD `956e33e568c6b799d0330803c74927e205977dfd`; line references below are repository-relative at that pin. No source edits, compilation, routing execution or GUI actions. The finished reset behavior is separate from this proposal; neither the old layered prototype nor J/L routing experiments are adopted here. ## What current placement considers—and omits | Source | Observed behavior | Consequence for #32 | | --- | --- | --- | | `crates/oa-core/src/session.rs:970–974` | Children come from the projection-parent forest into vectors in ordered-map traversal order. | Nested siblings remain placement-ID ordered; no internal exchange order or boundary-neighbor order is considered. | | `session.rs:986–1012` | Port-aware minimum node size, State header/content sizing, then one vertical child column with48 horizontal padding and16 sibling gap; authored size remains a lower bound. | Valid enclosure scaffolding exists, but neither connection demand nor edge label dimensions affect child spacing or container aspect ratio. | | `session.rs:1017–1040` | Every child starts at parent x+24, stacked beneath the header. | Reversed children in adjacent containers can force intersecting/shared routes even when a sibling permutation would separate them. | | `session.rs:1052–1070` | Every visible edge is lifted to the two outermost roots; same-root edges disappear; duplicate root pairs collapse into a set. `edge.oriented` is not checked. | Some connectivity is used, but internal demands, multiplicity, ports and labels are discarded. Undirected serialized endpoint order also influences rank-like root ordering. | | `session.rs:1072–1089` | Choose a remaining root with no remaining predecessor, tie by semantic alias/ID; if none, choose alias/ID anyway. | Deterministic cycle fallback exists, but this is a total topological order, not explicit layers or crossing minimization. | | `session.rs:1111–1129` | Lay roots left-to-right with240 gaps; wrap after every fourth root with60 row gap. | A seven-node flow is broken into rows regardless of connections. Disconnected graphs and dense bipartite graphs receive the same packing policy. | | `session.rs:1131–1159` | Apply final placement deltas, existing route translation, and pre-frozen eligible reset. | Useful commit boundary already exists; a better placement planner can replace candidate positions/sizes without redesigning history or route persistence. | No call inside Layout computes edge-label geometry or scores its proposed final routed result. `routing.rs:1363–1395` derives visible text and estimates label width as7 per scalar plus16, height24; `1410+` tries automatic locations only after paths exist. Therefore source alone confirms that node arrangement is not label-aware. Core estimates must not be advertised as rendered glyph bounds; the independent #36 label work needs explicit reconciliation when integrated. ## Evidence that narrows the choice The first prototype report, `harness/experiments/auto-layout-20260925/results-01.md` at the separate experiment checkout, documents useful placement effects: reversed children shared length148→0 and label/node collisions1→0; seven-node chain length2560→492 with8→0 bends; long-label collisions2→0. But diamond and bipartite shared strokes worsened. Its nine fixtures omitted folded/filtered topology, Allocation/State, cycles/undirected graphs and genuine other-view conservation. It explicitly rejected omitted/folded nodes, so it cannot be dropped into production Layout. `harness/experiments/routing-independent-20260925/decision-review.md` retains the released router: J/L pass the finite timing guardrails but expose label and ID-order regressions, and native trace tasks were not met. The next placement experiment should therefore keep the released router fixed. Improving a routing penalty again would obscure whether ordering/space allocation solves the actual placement defect. ## Material alternatives | Approach | Scope and tradeoff | Decision criterion | | --- | --- | --- | | Hierarchical sibling layers and boundary order | Replace vertical/4-per-row packing with connection-informed order, ranks and local corridor sizing, retaining current sizing/transaction machinery. Native Rust and bounded; compound boundary constraints are the real complexity. | Preferred first experiment: improves known nested ordering/label fixtures without corrupting latent folded geometry or worsening named trace tasks. | | Bounded local improvement of existing positions | Treat current arranged boxes as a baseline; evaluate a small fixed set of sibling swaps, row breaks or gap changes using actual routed results. Preserves more organization and avoids a full layering engine. | Useful control, but reject as primary if it cannot discover the two-container sibling swap without excessive route evaluations or gets trapped by a needed rank change. | | Compound layout engine adapter | Feed ports, nesting and label rectangles to a mature engine such as ELK via elkjs; validate and commit returned geometry. | Reopen only if native bounded candidates fail concrete compound cases. Additional runtime and mapping cost remain, and the app must still define folded/hidden scope, fixed anchors, measurement and transaction semantics. No engine adoption justified by these results alone. | These differ in geometry construction/search strategy, not just router coefficients. Do not spend another iteration rebuilding the prototype's known unsupported scope without resolving it. ## Minimal proposed architecture Keep one pure candidate computation inside the existing core Layout transaction. Inputs are the staged Project, target Diagram, actual projection-parent forest, visible projected connectivity, and deterministic core text-size estimates. Outputs are target-diagram positions/sizes; existing final route translation/reset remains authoritative. Do not call desktop font APIs from core commands or give GUI/API different layouts for identical inputs. Use two views of the **same** forest: full stored placement ancestry for latent geometry/conservation, and visible connectivity for current arrangement goals. Reuse `projection::parents` (`projection.rs:81+`), which includes Allocation-specific allocation grouping, rather than substituting semantic parent fields. Folded rendering hides descendants and substitutes compact outer heights (`projection.rs:303–335`); that compact footprint is not permission to shrink/delete stored descendants. Preserve descendants' relative geometry when treating a collapsed subtree as one visible box, translating its latent members with the box. This is a proposed improvement over today's rearrangement of hidden descendants and requires an explicit contract choice before implementation. An alternative is retaining today's hidden-descendant reflow with full stored enclosure; it is simpler but may waste visible space. Do not silently mix these policies. For each sibling group, lift visible edges to the two direct child subtrees at their lowest common ancestor. Use oriented edges for layer direction; undirected edges influence proximity/order only. Keep parallel demands and port-side/order information rather than reducing them to a set. External boundary connections should provide order constraints for the local children too: lifting only at the common ancestor would still miss the reversed-child case. Separate these boundary-order hints from rank constraints to avoid counting one edge as an independent rank demand at every ancestor. Use deterministic SCC handling for cycles, canonical ties, and explicit self-loop space. Start with a fixed number of sibling order sweeps and one coordinate assignment. Compute container sizes bottom-up from frozen node minima plus content/header/footer/padding, using max constraints instead of repeatedly adding clearance to already enlarged sizes. Reserve corridor width/height using relevant visible label rectangles and edge demand per cut/group, not one global longest label inflated into every gap. Real fixed port anchors are hard constraints; automatic port choices remain derived and must be reprojected after placement, because their side/order and node capacity depend on new peer geometry (`projection/port_layout.rs:156+`). A final reprojection/enclosure check is mandatory before scoring. Route only a small fixed number of complete candidates with the released router: baseline plus one main layered candidate, optionally one local-swap control. Keep actual path/label metrics and identity-preserving invariants, rather than accepting by straight-line crossing proxies alone. Bound topology preprocessing and routing work globally; on exhaustion return the declared baseline or fail atomically, never partially apply geometry. Selecting a baseline is a fallback outcome, not proof the new layout succeeded. Avoid a weighted scalar that hides new label collisions behind shorter paths. ## Non-negotiable invariants and evaluator changes - Freeze reset eligibility before placement (`session.rs:955–969`). Route entry/order, attachments and port anchors follow the accepted reset contract; filtered/promoted metadata keeps existing actual-owner translation and State boundary rules. Other diagrams and all semantics remain exact. Generated elbows stay derived. - Preserve staged Batch semantics and enclosing history. Apply one complete candidate with final validation; no new intermediate validation or hidden history entries. Keep Scenario's existing rejection (`session.rs:950–953`) and do not claim Process support from this work. - Reuse reset tests in `tests/release_routing.rs:652+`, especially filtered/missing routes792+, folding864+, staged Batch930+. Add independent known-order witnesses for sibling crossing separation, seven-node flow without arbitrary wrapping, local long-label clearance, undirected endpoint reversal, cycle/self-loop, distinct/fixed/shared ports and disconnected components. - Extend the placement evaluator with the frozen real workshop Allocation views, fold→Arrange→expand, hidden-edge sentinel and untouched other view. Record latent descendants as well as visible nodes; reject node/header penetration and lost port constraints. A visible-only geometry check misses broken expansion. - Require exact repeat-input determinism and ten-call stability across save/reparse, including periods2 and cumulative container growth. Compare against current baseline and reset-only at the same router pin. Retain pairwise shared intervals, proper/T/bend contacts, label conflicts, warnings, area, route length and workload individually. - Geometry remains a screen for failures. Parent must verify predeclared connection-tracing tasks and readable labels at normal zoom, then actual Arrange/Undo/Redo/Save/reopen. Current core text estimates and previous offline images do not establish these outcomes. Recommendation: settle the folded latent-geometry policy, then prototype sibling ordering/layers with boundary hints and local label corridor space. Keep the reset candidate and released router intact until that experiment meets the larger scope. The first milestone is a falsifiable placement result, not a new framework or a claim that #32 is complete. ## Bounded ELK reference comparison: recommended now, dependency decision deferred An external layout-only reference is feasible and worthwhile before another bespoke placement iteration. This does not contradict deferring a production compound-engine dependency: the comparison buys evidence about ordering and space allocation without committing the application to a JavaScript runtime. Pin elkjs 0.12.0, package integrity, Node executable and adapter/options; retain exact input/output. No ELK execution was performed in this audit. Verified official capabilities: [ELK Layered](https://eclipse.dev/elk/reference/algorithms/org-eclipse-elk-layered.html) supports ports, labels, compound graphs and cross-hierarchy layout with the appropriate option. [JSON input](https://eclipse.dev/elk/documentation/tooldevelopers/graphdatastructure/jsonformat.html) supports nested children and explicit port references; callers supply label dimensions. [Port constraints](https://eclipse.dev/elk/reference/options/org-eclipse-elk-portConstraints.html) are node-level, including FIXED_POS and FIXED_RATIO. [Coordinate conventions](https://eclipse.dev/elk/documentation/tooldevelopers/graphdatastructure/coordinatesystem.html) default shapes to parent-relative coordinates; recursively flatten returned nodes before transfer. These capabilities do not establish adapter correctness or resulting Rust-route quality. The frozen geometry rows have sufficient visible inputs: `before.projected_nodes` carries placement IDs, visual_parent, collapsed and allocation_summary; `projected_ports` carries semantic element plus owner placement, side and world position; `projected_edges` carries oriented, source/target placements and optional semantic ports; `routed_edges` supplies existing approximate label dimensions. Full `before_project` supplies authored anchor and dormant geometry records. Use `(owner placement, semantic port)` adapter identities, not semantic element ID alone. Shared ports must remain shared, not be cloned per edge. Use projected ancestry, not semantic parents (Allocation summaries are material here). Proposed first reference: one fixed Layered configuration, compound hierarchy enabled, explicit direction and seed, fixed visible leaf dimensions, actual header padding for containers, existing label rectangle dimensions. For the initial diagnostic, fixing all imported port positions is an explicit overconstraint: automatic port choices will be recomputed by Rust after placement, and resized containers complicate absolute versus ratio anchors. Keep authored anchor records exact and report final per-port drift from the engine assumptions. Do not persist returned port coordinates to manufacture agreement. A mixed node containing manual and automatic anchors cannot be naively represented by one node-level FREE constraint. Reject unsupported mappings or document the stronger constraint before comparison. Folded nodes are opaque visible boxes. Their compact projected height must never overwrite stored expanded size. Require planned compact size unchanged, move only their stored position, and translate hidden descendants with the nearest visible collapsed ancestor using full visual ancestry. Other omitted placements remain exact. This is an explicit experiment policy, not automatic adoption of new production folding semantics. Reprojection can enlarge expanded containers or move derived ports; record that difference, since it means the final Rust geometry differs from the engine's output assumptions. Import only flattened node geometry into the original Project clone, without an initial Layout. Apply the same pre-operation visible/unpromoted actual-owner reset predicate to existing route entries; preserve attachments/order and use original-to-final translation for retained routes. Ignore ELK path sections and label positions. Therefore the result measures ELK-informed placement followed by released Rust routing, not full ELK routing quality; labels reserved by ELK may still collide when the Rust router selects different corridors. Compare raw authored input, existing Layout/reset control, old layered candidate (unsupported rows explicit), and ELK placement with equivalent reset scope. Do not rank omitted folded rows as successes. Evaluate complete Project conservation with geometry/reset exceptions, compact and expanded containment, fixed anchors, final projected ports, actual Rust routes/labels, named trace tasks, and ID permutation sensitivity. One frozen-input repeated run establishes deterministic transfer only; repeated arrange/reproject feedback is a separate idempotence check. Reject nonfinite/missing/duplicate/out-of-scope nodes and invalid dimensions before any model mutation. A five-case benchmark can justify the next experiment, not production adoption.