Files
ARCADIA-Studio/harness/experiments/diagram-resize-foundation-20260912/critique.md
T

12 KiB
Raw Blame History

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.

  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.