14 KiB
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
DiagramCanvas { editable: false, .. }is not an inert renderer. ItsProgram::updatestarts atdiagram.rs:609; it owns private modifier, pointer, pan, selection and gesture state.Stateat line 73 includes node, route, attachment, port and group previews. Painting consumes previews viapreview_diagramat 473 andsceneat 516. Itsmouse_interactionat 1675 advertises Grab even without edit permission. Mapping published messages to an ignored enum happens too late to prevent private previews.- Reusing production messages through
App::updaterisks its allocation, navigation/reveal, tool, draft and pending intent handling. A private optical measurement should not callMessage::Page,Diagram,Select,FitorCanvasResizedon the production reducer. In particular #43's reveal ownership and unstamped ordinary resize should not be changed for this experiment. - 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.