Files
ARCADIA-Studio/harness/experiments/focus-optical-20261001/proposal.md
T

14 KiB
Raw Blame History

Private Focus optical comparison: implementation seams

Read-only proposal, 2026-10-02. Reviewed clean branch codex/focus-workspace-experiment-01a0d717-20261001, commit e144e32e9984477a258759a9dcc75f6e499fc224. No implementation, Cargo, GUI, Gitea or acceptance action performed. Parent owns #38 and implementation choice; #43 navigation ownership is untouched. Read local AGENTS and the supplied C:/ar-diagram-01a0d717/.local/diagram-next-action-design.md.

Recommendation

Use a private, explicitly opted-in separate Iced application runner inside the existing desktop binary, with Baseline selected initially. It holds a frozen fixture and private comparison/camera state. Do not invoke production App::update or App::subscription in this runner. Reuse the exact production DiagramCanvas drawing implementation through a draw-only Program adapter; never invoke DiagramCanvas::update. Compare ordinary Baseline chrome against a compact header/footer and full two-axis Focus canvas. This is an optical test rig, not a usable editable Focus workspace or a #38 closure candidate.

An opt-in flag such as --focus-optical PROJECT.json DIAGRAM_UUID OUTPUT_DIR is a proposal, not an existing interface. No flag follows the untouched production boot path. A fresh process per fixture is sufficient: no file dialog, model chooser, navigation, Save or history integration is needed to answer the optical question. Focus/Exit focus, Fit and an explicit capture measurement action are the only initial interactive controls. An optional manual zoom trial must be separately scored after full-scene Fit reading.

Why the apparent shortcuts are unsafe

  1. DiagramCanvas { editable: false, .. } is not an inert renderer. Its Program::update starts at diagram.rs:609; it owns private modifier, pointer, pan, selection and gesture state. State at line 73 includes node, route, attachment, port and group previews. Painting consumes previews via preview_diagram at 473 and scene at 516. Its mouse_interaction at 1675 advertises Grab even without edit permission. Mapping published messages to an ignored enum happens too late to prevent private previews.
  2. Reusing production messages through App::update risks its allocation, navigation/reveal, tool, draft and pending intent handling. A private optical measurement should not call Message::Page, Diagram, Select, Fit or CanvasResized on the production reducer. In particular #43's reveal ownership and unstamped ordinary resize should not be changed for this experiment.
  3. An independent hand-built Baseline made of blank 260/320-pixel panels can answer approximate viewport arithmetic but is a weaker visual comparator. It can miss wrapping toolbar height, footer, palette and real typography. Prefer a small presentation seam that reuses the actual Baseline layout.

Smallest defensible source boundary

Preferred new private module: crates/oa-desktop/src/focus_optical.rs (or a directory for the test rig and its tests). Minimal wiring in main.rs:47–70 selects the alternate runner before production iced::application. No new Cargo dependency, core change, schema, persisted preference or public command is necessary. Keep the experiment off normal launch and out of releases.

The rig can construct App::with_project(project) at app.rs:605 as an immutable presentation fixture for existing chrome. Unlike App::new at 597, this avoids loading home/recovery history. It initializes Document without launching subscriptions or commands. Set the chosen saved non-scenario Architecture diagram/layer during admission and selection to project ID, with diagram_selection=None, no navigation focus, no active tool or inline editor. Do not initialize through navigation messages. Require the admitted complete Project to equal the input after Document creation. Do not assume construction or view rendering is a substitute for that check.

views.rs:67 builds the actual outer Baseline; sidebar width 260 at 224–233, main body/Inspector at 235–262, footer at 278–300. views.rs:442 is Architecture view; toolbar starts at 470, canvas program at 527, sensor at 574, and final toolbar+canvas column at 732. The focused seam should accept an optical canvas Element, or a tiny private canvas-mode parameter, while the normal public view(app) still selects the existing editable branch unchanged. This avoids duplicating the large view and avoids a boolean inside the general reducer. The chosen canvas must occupy the same existing Baseline sensor/layout slot.

The usual stack at 554 has the interactive focus wrapper, PortLabels, WorkFeedback and optional inline editor. For the optical branch install only the draw-only adapter. Use selection=None, selected=project.id, navigation_focus=None, selected_bend/attachment=None, allocation=None, connection=None, placing=false, editable=false. Fresh isolated State eliminates previews; those neutral fields eliminate existing assisted highlights/tool overlays. PortLabels at diagram/port_labels.rs:5–90 is assisted hover/selection feedback, not part of unassisted drawing. Keep it absent for both variants; do not selectively add tooltip names to Focus. WorkFeedback has no role with no allocation capability.

Draw-only adapter outline (types adjusted to the existing aliases):

struct OpticalCanvas<'a>(DiagramCanvas<'a>);
impl canvas::Program<app::Message> for OpticalCanvas<'_> {
    type State = diagram::State;
    // Omit update: default returns no action and never touches State.
    fn draw(&self, state: &Self::State, renderer: &Renderer,
            theme: &Theme, bounds: Rectangle, _: mouse::Cursor)
            -> Vec<canvas::Geometry> {
        <DiagramCanvas<'_> as canvas::Program<app::Message>>::draw(
            &self.0, state, renderer, theme, bounds, mouse::Cursor::Unavailable)
    }
    // Default mouse interaction; do not delegate production Grab/Crosshair.
}

diagram::State is public and Default; no access to its private fields or renderer changes is required. The production draw delegates projection/routing through the existing scene cache. Use one fresh optically owned Tree/state, never a state transplanted from an interactive canvas. Context must identify fixture/diagram; camera-only Baseline/Focus changes need not alter geometry context. No label/route cache algorithm needs modification.

For the remaining Baseline chrome, a narrow pre-dispatch inert wrapper blocks mouse, keyboard, touch, IME and focus operations before reaching children; suppresses overlays and uses unavailable cursor for drawing/interaction. Forward only Window::RedrawRequested to permit actual Sensor measurement, collecting child messages in a temporary Shell and forwarding only the expected canvas measurement with a private generation stamp. Copy redraw and invalidation requests deliberately. No production message is dispatched to App. This is not the rejected message-dropping interactive canvas: the canvas cannot process inputs and chrome never receives input in the first place.

guide/context_help.rs:95–280 is a Widget delegation example, not a wrapper to reuse unchanged: it delegates operate and sends synthetic release events on entry. Neither is wanted for an always-inert optical subtree. iced_widget/src/sensor.rs:198–264 shows on_show/on_resize are emitted on RedrawRequested; forwarding pointer/key events is unnecessary. Layout/draw remain delegated normally. Test this wrapper rather than assuming no messages means no child-state changes.

Place the rig's visible Baseline/Focus/Fit controls over a reserved part of the existing header, or explicitly replace only that header's action group in optical mode. Avoid adding a new row that silently shrinks Baseline canvas. If overlay placement cannot remain readable at minimum size, use a documented same-height replacement header. It must preserve Baseline header height and record any residual deviation; a mock Baseline must never be labeled exact. Focus may use its own compact 40–48px header, one stable status/footer row and Fill canvas; remove sidebar/Inspector/task/diagram tool rows and palette there. Keep a plainly visible Exit focus and read-only experiment notice.

Measurement and Fit, without production navigation

Private runner state: fixture identity, diagram ID, variant, layout generation, positive measured viewport, fit camera and optional measurement sequence. Map Baseline's allowed sensor message into Measured(generation, size); Focus's own Sensor emits the same private type. Ignore stale generations, nonfinite/zero dimensions and identical repeated measurements. Increment generation on variant/fixture transition; wait for a matching measurement before declaring a screenshot/capture ready. Sensor .key(generation) can make an on_show repeat explicit; its documented key seam is at sensor.rs:85.

Replicate exactly the small pure Fit formula at app.rs:5403–5451 in the private rig, preferably as one local pure function with direct formula tests: route the complete projected diagram; use its actual geometry.bounds, 48px total margin, .max(1.) usable dimensions and clamp 0.0001..1.5; center with the same left/top offset. Empty scenes use the existing 1.0, (30,30) rule. Do not use window_size fallback, old bounds or integer footer percentages as measured data. App::canvas_size is private and the ordinary handler at 4443 is unnecessary here. Write only presentation zoom/pan into the chrome fixture; never its Document. For this first Fit-only rig no manual-camera preservation, pending reveal or editable-pane lifecycle contract is claimed.

Measurements should include input SHA256, source/build hash, full Project digest, diagram/layer/projection, exact projected/routed digest, actual bounds, viewport, zoom, pan, logical client size, observed physical size/DPI, renderer, variant/generation, palette/selection state and a screenshot identifier. Record a measurement only after settled size. Use a fresh output directory; never write to fixture/source paths. If no trustworthy DPI API is added, record an external Windows observation and mark it external, not inferred from window dimensions.

Existing probes and fixture admission

diagram.rs:1946::export_svg is the unchanged production export path. Compare its exact output for the fixed fixture before/after variant switches; it must not depend on viewport. Complete Project, projections, nodes/ports, edge point sequences, label bounds/text/ownership and authored route metadata must also match. This does not establish Canvas text readability.

The separate diagram worktree has crates/oa-core/examples/diagram_readability_probe.rs, with admission against expected diagnostics, whole-scene geometry, route/label queries and fixtures. It is not present in the reviewed Focus checkout. Do not claim to reuse it through a nonexistent local command or import its E/F candidate machinery. Either use its pinned original output as historical context and emit fresh geometry directly with current projection::project_diagram and routing::route_diagram, or explicitly copy only a reviewed read-only report function into the experiment. Production release_dense_views.rs:395–433 also illustrates complete-model conservation and diagnostic reporting, but its generator changes fixture view options and is not the native comparator.

Load an explicitly named JSON fixture read-only via persistence decode, require the exact predeclared file hash and expected warning set, reject missing/scenario diagram, and hold the frozen Project in memory. A new process/CLI selection for another fixture is enough. Preserve the full original eight-card/12-edge case, plus an unrelated nested OA/SA case with long labels/association ends and a small diagram. Do not use the manual-label-drag saved derivative as the original comparison. Historical bounds 1506×814 and assumed 1566×874 are different; report current measured routing rather than picking either.

Bounded tests and evaluator

Before native comparison: unit-test pure Fit across width/height limiting, nonzero left/top, empty scene and clamped zoom; stale generation and repeated measurement handling; unknown messages cannot reach production App reducer. Exercise all mouse buttons/move/wheel, Delete/Insert, modifiers, text/IME, focus traversal and drop-like events against optical adapter/wrapper. Require no action, no child interactive update, no clipboard access and no model, source, selection or preview changes. A spy Widget can prove input never arrives while redraw sizing still works. Compare geometry/SVG/model after Baseline→Focus→Baseline and resize. Test opt-out production launch unchanged.

Native optical evaluator: same input/build/window/DPI, actual full scene at Fit; Baseline first by default but counterbalance order across fresh readers to avoid attributing learning to Focus. At least one reader not exposed to the semantic key identifies critical exchange endpoints/direction, unoriented conversation participants, allocation versus port allocation, and port owners. Record exact answers, uncertainty, wrong plausible alternatives, elapsed time and illegible names before hover/selection/zoom. No hints or chosen zoom for the unassisted pass. Record magnification gain as a measurement, not success. The provisional 0.85 Fit goal is only feasibility, never a readability threshold.

Reject Focus as a sufficient #38 remedy if bigger text leaves wrong or unresolved critical ownership. Preserve a possible workspace-space benefit separately. If optical gain succeeds, a later editable workspace needs its own retained draft/focus/tool/gesture/camera/navigation contract and coordinated #43 review; none is established by this inert rig. No production promotion from compilation, matching geometry, a screenshot or this proposal alone.