227 lines
14 KiB
Markdown
227 lines
14 KiB
Markdown
# 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.
|