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

227 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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):
```rust
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.