nigig-org/crates/apps/doc/doc-ui/DEVICE_VERIFICATION.md
andodeki 1d8b3a6053
Some checks failed
makepad-table / model (push) Has been cancelled
makepad-table / widget (push) Has been cancelled
makepad-table / hygiene (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
merge(local): reapply local WIP onto merged main -- cad dashboard/explode/script_parts/xray merged with remote Phase-5 LOD, plus doc-ui extraction, spreadsheet xls-import, android ussd, camera and test work
2026-09-01 21:13:52 +03:00

304 lines
20 KiB
Markdown
Raw Permalink 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.

# Doc workspace device verification (Android/iOS)
The doc workspace logic that can be verified without hardware already
is: the real-`Cx` runtime harness (`tests.rs` in this folder) covers
event routing, selection/caret math, cell ranges, clipboard semantics,
gesture arbitration, and multi-line layout against stub areas. What it
cannot prove is anything the platform owns: the soft keyboard, the
native clipboard action menu, touch-event delivery and hit
transformation, scroll handoff to the parent `ScrollYView`, and actual
pixels. THIS runbook is the last-remaining checklist — every section
maps to a behavior the milestones deliberately deferred to hardware:
- IME opening deferred to device verification (mobile Edit/Done
milestone).
- Clipboard menu placement and keyboard-shift deferred (mobile
clipboard menu milestone; `cx.keyboard_shift` is passed straight
through to the platform).
- ScrollYView parent handoff is the one unchecked roadmap box.
- Painting/clipping visual pass was scoped here by the legacy
perf-box retirement (draw logic is harness-covered; pixels are not).
Execute the sections in order against BOTH the CRDT workspace
(`CrdtDocWorkspace`, active) and the legacy `DocWorkspace` (fallback)
where a section says BOTH; otherwise the CRDT workspace is the target.
Record each row PASS/FAIL/notes in section 10.
## 0. Prerequisites — build to device
The Makepad fork pinned in `Cargo.lock` ships `tools/cargo_makepad`;
install once from the checkout (or from a fetched copy):
```
cargo install --path <makepad-checkout>/tools/cargo_makepad
```
Android (device on USB, adb visible):
```
cargo makepad android install-toolchain
cargo makepad android run -p nigig-build
```
iOS (Xcode + device; provisioning per the tool's notes):
```
cargo makepad apple list # certificates/profiles/devices
cargo makepad apple ios run-device -p nigig-build --org=<ORG> --app=<APP>
```
Flag reference lives in the tool itself (`cargo makepad --help`;
`--package-name`, `--app-label`, `--abi`, `--sdk-path` on Android;
`--profile`, `--cert`, `--device` on iOS). Logs: `adb logcat` on
Android, Xcode devices console on iOS — filter for the app process.
Before starting, open the document workspace and set some content: at
least two paragraphs, one 2x2 table with cell text `a | bc` / `d | e`,
one cell edited through an external plain-text editor to hold
`x<newline>y` (paste the two-line string into the cell through the
system clipboard — see section 6; it round-trips verbatim by design).
## 1. Interaction mode: View ↔ Edit
Code anchors: first real `TouchUpdate` flips `interaction_mode` to
View (`crdt_widget.rs`, the `mobile_mode_initialized` arm); the mobile
toolbar's AdaptiveView button (`edit_mode_btn` in both workspaces)
calls `toggle_interaction_mode` and relabels Edit ↔ Done
(`widgets/workspace.rs`).
| # | Action | Expected |
|---|--------|----------|
| 1.1 | Cold-launch on device, tap inside the document once | Keyboard does NOT open (first touch dropped the session to View; the tap parks a passive cursor) |
| 1.2 | Drag vertically right after 1.1 | The page scrolls (View skips the editor's area hits entirely) |
| 1.3 | Tap the toolbar Edit button | Button relabels Done; a subsequent tap in text opens the IME (keyboard shows, caret blinks at the tap char boundary) |
| 1.4 | Tap Done | Keyboard closes; taps are passive again |
Fail criteria: IME opens in View mode; Edit/Done desynchronizes from
the label; a tap in Edit does not open the keyboard (see section 2's
frame-driven reassert before calling it a bug).
## 2. IME opening and text input
Code anchors: taps in Edit call `cx.show_text_ime(self.draw_bg.area(),
abs)`; a frame-driven reassert mirrors the request on backends that
honor it only after the focus area has been drawn (mobile milestone
note in `crdt_widget.rs`).
| # | Action | Expected |
|---|--------|----------|
| 2.1 | Edit mode: tap mid-word in a paragraph, type | Characters insert at the caret; the caret tracks |
| 2.2 | Tap inside a table cell, type | Characters insert at the char under the pointer (midpoint split); no whole-cell stomping |
| 2.3 | While typing in a cell, accept an autocorrect/autocomplete suggestion | The committed text lands as the cell content, once (whole-cell write path consumes the commit; composing text should not stack) |
| 2.4 | Tap an empty cell and type | Text appears; undo once restores the empty cell |
| 2.5 | Type a long word that passes the cell's right edge | Text visually overflows the cell (known single-line behavior — multi-character overflow is drawn, not wrapped; NOT a failure, note only) |
Fail criteria: doubled commits, keyboard opening but input dropping,
caret/IME disagreement (the IME anchor is the caret rect + its height;
a misplaced suggestion popup usually means the anchor rect is off).
## 3. Long-press selection, handles, clipboard menu
Code anchors: the gesture router arms long-press at 24 frames and
hands control to `SelectWord` (`mobile_gesture.rs`,
`long_press_frames: 24`); handles come from
`compute_selection_handles`; the menu is requested through
`cx.show_clipboard_actions(has_selection, rect, cx.keyboard_shift)`
with `rect` = the selection's handle union (text) or the pressed
cell's rect (cell range), mirrored in `widget.clipboard_menu`.
| # | Action | Expected |
|---|--------|----------|
| 3.1 | Edit mode: press and hold on a word WITHOUT moving for ~0.5s | Word selects, handles appear at its edges, the native menu floats near the selection with Copy/Cut (and Paste if the system clipboard is non-empty) |
| 3.2 | Repeat 3.1 with the keyboard OPEN | The menu floats clear of the keyboard area (platform places it from `keyboard_shift`) |
| 3.3 | Drag the START handle onto another word; drag the END handle | Selection follows the fingers; menu does NOT re-open WHILE dragging (initiating matches TextInput cadence) |
| 3.6 | Lift the finger after a handle drag | The native menu re-floats, anchored on the NEW selection span (not the stale word rect), with Copy/Cut offered for the copyable span. Only in Edit mode; in View the drag still adjusts the highlight but no menu appears |
| 3.4 | Long-press on empty space between paragraphs | Nothing arms; no menu |
| 3.5 | Long-press inside a table cell (not on a handle) | The cell range arms on the pressed cell; dragging expands the range rectangle; the menu offers the full action set (a range copies tabular text, so has_selection = true) |
Fail criteria: long-press fires while the finger has moved (should
have routed to scroll, section 5); the menu appears under the keyboard
or at stale coordinates; handles select inverted (start/end swapped).
## 4. Table gestures: ranges, merge/split, in-cell editing
Code anchors: long-press cell → `start_cell_range`; drag with a live
range → `extend_cell_range_to` (the touch-only spanning path; the
router idles in Selecting while a range is active); Merge/Split
toolbar buttons → `merge_selected_cells` / `split_cell_at_cursor`.
| # | Action | Expected |
|---|--------|----------|
| 4.1 | Long-press a cell, drag diagonally across four cells, release | 2x2 range highlighted (per-cell bands; covered cells draw under the anchor when merged) |
| 4.2 | With the range armed, tap Merge | Cells merge; my content keeps in the anchor; Undo reverses it in one step |
| 4.3 | Tap into the merged cell, tap Split | The merge splits back to individual cells |
| 4.4 | Long-press a cell, drag past the table edge mid-gesture | Range clamps at the table bounds; no wrap to other rows/columns |
| 4.5 | Insert a second table (toolbar +Table), tap its top-left cell, and Paste the range copied in 3.5/4.1 | The rectangle distributes one cell per tab stop; caret parks at the last written cell; one undo restores the pasted cells |
Fail criteria: ranges extending after lift-off; merge/undo splitting
into many undo steps; paste redistributing shifted (quoting covered in
section 6).
## 5. Scroll handoff to the parent ScrollYView — the open roadmap box
Code anchors: router `PendingLongPress` + move > 10 px before the
24-frame arm → `PassToScroll` and the drag is never claimed; in View
mode the area-hit match is skipped outright. Both workspaces embed the
editor in a `ScrollYView` (`crdt_body` for CRDT, `body_scroll` for
legacy `DocWorkspace`). Run BOTH editors through this section — the
legacy box on the roadmap names exactly this handoff.
| # | Action | Expected |
|---|--------|----------|
| 5.1 | CRDT workspace, View mode: drag up/down inside the document area | The page pans; no selection arms, no caret moves |
| 5.2 | CRDT workspace, Edit mode: quick vertical drag over text | Same: the gesture routes to the scroll view BEFORE long-press arms (10 px / 24 frames), so the page pans and no selection appears |
| 5.3 | Edit mode: long-press a word (selection arms), lift, then drag vertically | With no handle touched, a fresh drag still scrolls; the existing selection stays |
| 5.4 | Long-press a word, keep the finger down and drag WITHOUT lifting | Selection-adjust path: the selection follows the finger instead of scrolling (Selecting state owns the drag) |
| 5.5 | Legacy DocWorkspace: repeat 5.15.4 | Identical behavior (shared router + legacy hit-skip path) |
| 5.6 | Scroll to the document's end and keep dragging | Rubber-band/stop at content end per platform convention — no stuck gestures after release |
Fail criteria: a quick flick selects text instead of scrolling; a
long-press drag scrolls the page while adjusting the selection; the
scroll position jumps when the keyboard opens/closes. ANY failure here
closes the roadmap box as FAILED — file it, don't check it.
## 6. Clipboard round-trips through the system clipboard
Code anchors: range/copy payloads via `table_grid_tsv`, quoting via
`quote_tabular_field`/`split_tabular_payload` (RFC-4180-style),
document-level payloads splice table grids at block position.
Prerequisite for 6.2: use an external app to prepare two forms of the
same content — the RAW two-line string `x` newline `y`, and the QUOTED
form `"x` newline `y"` (exactly what spreadsheet apps emit when copying
a single cell that contains a newline; a desktop text editor plus a
shared note is the easiest path).
| # | Action | Expected |
|---|--------|----------|
| 6.1 | Copy a 2x2 range from the doc table, paste into the external notes app | Rows/columns appear as tab/newline text, cells in reading order |
| 6.2 | Paste the RAW `x\ny` into a cell | It distributes across TWO ROWS (one line per row — the spreadsheet convention for raw text). Then paste the QUOTED form `"x\ny"` into a cell: it lands as ONE cell holding both lines verbatim (the tokenizer honors quoted fields). Finally copy a range INCLUDING that multi-line cell → paste it elsewhere: the value round-trips as one two-line cell (our payload quoted it on copy) |
| 6.3 | Select-all in the document, Copy, paste into the notes app | Paragraph text lines with the table's grid spliced in at the table's position |
| 6.4 | Cut the full selection, verify document empties, undo once | Blocks AND every cell value restore (range tombstones + grouped cell writes) |
Fail criteria: tab/newline/quote characters mangled in either
direction (round-trip must be verbatim once the payload is ours);
external apps receiving nothing (the TextCopy hit must answer with
`copyable_selection_text()`).
## 7. Multi-line cell rendering (visual)
Code anchors: rows grow 18 px per extra display line over the 28 px
baseline (`layout_projected_table`); runs draw split at `\n`
vertically centered.
| # | Action | Expected |
|---|--------|----------|
| 7.1 | After 6.2, look at the cell holding `x\ny` | Two stacked lines inside one taller row; borders outline the grown row; the document below reflows down |
| 7.2 | Tap the second line's text | The caret parks on the tapped line/char (not the first line) |
| 7.3 | ArrowDown/ArrowUp on a hardware or virtual keyboard inside that cell | Caret steps between the two lines keeping its column; inert at first/last line |
| 7.4 | Merge a grown row's cell with a plain row's cell; split it back | Anchor spans the summed heights; split restores both rows' geometries |
Fail criteria: lines clipped by the row bottom; caret drawn on the
wrong band; the row below overlapping the grown row.
## 8. Visual painting/clipping sweep (draw-pass scope)
No automation exists — this is the GPU-bound residual. Sweep and
eyeball; photograph failures.
| # | What to look at | Expected |
|---|-----------------|----------|
| 8.1 | Selection overlay on text and on cell ranges | Blue tint stays inside glyph/line bands and cell rects; no bleed into neighbors or across page margins |
| 8.2 | Table borders, including merged regions | 1 px grid outlines; merged anchor outlines the whole span; no double borders inside a merge |
| 8.3 | Caret | Blue 2 px bar on the correct band (single- and multi-line cells), never floating outside its cell |
| 8.4 | Selection handles after a long-press | Both handles at selection edges, above content, trigger drag on touch with the 6 px slop |
| 8.5 | Advanced placeholders (image/divider nodes) | Fills/borders/labels drawn once per node; divider is one centered line |
| 8.6 | Rapid typing for 30 seconds in a ~200-line document | Frame pacing stays smooth; no visible full-document flicker between keystrokes (layout cache: unchanged content redraws from the cached tree; this is the perf smoke check) |
## 9. Boot content and persistence round trip
Since the Android empty-doc fix, saves live in the platform app-data
store (`app_data_dir()/nigig_build_store/generated/current.doc.json`)
— NOT the source tree (dev checkouts get a one-way read fallback for
old saves; new writes never go there). Boot emits `[DOC_TRACE] CRDT
init:` lines in logcat naming the branch that fired.
| # | Action | Expected |
|---|--------|----------|
| 9.0 | Fresh install, first launch (no save on device) | Demo document renders: bold title, styled paragraphs, divider, image placeholder, 4x3 table (bold header), closing hint. logcat: `CRDT init: no saved document; seeding demo document` |
| 9.1 | Edit, force-close, relaunch | Document restores (platform save/load path); table contents AND cell text intact. logcat: `CRDT init: loading saved document (N bytes)` |
| 9.2 | Open a previously saved file with a table | Grid renders; merges present; no phantom rows/cols |
| 9.3 | Boot the CRDT workspace with a CLASSIC-format save present | Demo document seeds (classic saves are not shadowed); logcat notes the classic-format branch. The legacy workspace still opens the classic file on Open. FAIL criteria: blank page, or the classic file silently dropped |
## 10. Sign-off
Device / OS / build: Galaxy A60 (SM-A6060, `R28M52LJP2Y`) · Android ·
`pageflipnav` release APK, driven by the `doc_*` tests in
`crates/pageflipnav/tests/ui.rs`. All 14 tests PASS on device (8
nav/state + 6 content-operation: `doc_type_text_inserts_document`,
`doc_bold_italic_underline_ops`, `doc_insert_table_and_cell_text`,
`doc_merge_split_cell_ops`, `doc_long_press_empty_space_does_not_arm`,
`doc_diagonal_cell_range_merges`, plus `doc_diag_*` used to pin geometry).
Verified devices: Galaxy A60 (SM-A6060, `R28M52LJP2Y`, 411 dp) and Galaxy
A16 (SM-A165F, `RF8Y103NERA`, 384 dp). One A60 full-suite run had 3
`adb: device not found` USB drops (test-infra flakes, not logic); all 3
reran clean on the A16.
Coverage legend: **A** = automated (ran green on device),
**A⚠** = automated but only proves a subset, **M** = manual-only (cannot
be driven by `makepad_test` — reason in the Note column).
| # | Row | Result | Test / Note |
|---|-----|--------|-------------|
| 1.1 | Cold-launch tap → no keyboard | A | `doc_view_mode_scroll_by_touch` (View cold-release path); first tap never opens IME |
| 1.2 | Vertical drag → page scrolls | A | `doc_view_mode_scroll_by_touch` |
| 1.3 | Edit button relabels Done; tap opens IME | A | `doc_interaction_mode_view_and_edit`, `doc_ime_text_input_in_edit_mode` |
| 1.4 | Done closes keyboard | A⚠ | `doc_interaction_mode_view_and_edit` relabels back to Edit; keyboard visibility itself is not snapshot-observable, so "closes" is inferred from the Edit state + subsequent passive taps |
| 2.1 | Edit: tap mid-word, type | A | `doc_type_text_inserts_document` (stats `2 words | 11 chars`) |
| 2.2 | Tap in a cell, type | A | `doc_insert_table_and_cell_text` (cell "alpha beta" joins stats → `4 words | 21 chars`) |
| 2.3 | Accept autocorrect suggestion | M | `makepad_test` cannot press the OS keyboard's suggestion bar; needs a human tap |
| 2.4 | Type in empty cell, undo once | A⚠ | Undo not asserted on device; covered by `doc-ui` unit `runtime_cell_backspace_edits_cell_and_ctrl_z_restores_it` |
| 2.5 | Long word overflows cell | M | Visual single-line overflow check — note only, eyeball |
| 3.1 | Long-press word → word selects | A⚠ | `doc_long_press_arms_selection` proves the arm runs and the doc stays alive; handle/menu pixels are visual |
| 3.2 | Long-press with keyboard open | M | Cannot open/clamp the OS keyboard from the harness |
| 3.3 | Drag start/end handle | M | Handle hit-testing + native menu placement need visual confirmation |
| 3.4 | Long-press empty space → nothing arms | A | `doc_long_press_empty_space_does_not_arm` |
| 3.5 | Long-press cell → cell range arms | A | arming used by `doc_merge_split_cell_ops` (drag spans col1) |
| 3.6 | Menu re-floats after handle drag | M | Native menu is OS-owned, not a snapshot-able widget |
| 4.1 | Diagonal 2x2 range + highlight | A | `doc_diagonal_cell_range_merges` |
| 4.2 | Merge; undo in one step | A | `doc_merge_split_cell_ops` (merge); undo is unit-covered |
| 4.3 | Split merged cell | A | `doc_merge_split_cell_ops` (`Split merged cell`) |
| 4.4 | Drag past table edge clamps | A⚠ | Merge path proves the range stays in-col; out-of-table clamp is unit-covered |
| 4.5 | Paste range into second table | M | Requires system clipboard content + external app (section 6) |
| 5.1 | View drag → pans | A | `doc_view_mode_scroll_by_touch` |
| 5.2 | Edit quick drag → pans, no selection | A | `doc_scroll_handoff_view_and_edit` |
| 5.3 | Selection armed, lift, then drag scrolls | M | Requires a held selection + OS interactions not snapshot-able; unit/gesture-covered in `doc-ui` |
| 5.4 | Hold after long-press → selection tracks | M | Gesture requires frame-true finger sequencing only partially reproducible; covered by `doc-ui` `runtime_*` gesture tests |
| 5.5 | Legacy DocWorkspace 5.15.4 | M | Legacy workspace untested; blocked by in-progress user work (`nigig-build`, `xls_import`) |
| 5.6 | End-of-content rubber-band | M | Platform convention, visual |
| 6.16.4 | Clipboard round-trips | M | Needs `adb` clipboard + an external notes app to paste INTO — no harness API for system clipboard reads/veto |
| 7.17.4 | Multi-line cell rendering | M | Visual inspection of row growth / caret bands / overlap |
| 8.18.6 | Visual paint/clip sweep | M | GPU painting — eyeball, photograph failures |
| 9.0 | Fresh install demo doc | M | Needs app data wipe (reinstall) between launches; harness can't reset app-data |
| 9.1 | Force-close + relaunch restores | M | Harness cannot kill/relaunch the process to exercise the load path |
| 9.2 | Open saved file with table | M | Same process-restart limitation |
| 9.3 | Classic-format save fallback | M | Needs a pre-written classic save on the device + relaunch; unit-covered elsewhere |
Two real mobile-only bugs were found and fixed by these device runs
(unit-testable parts covered in `doc-ui/src/tests.rs`):
1. The CRDT/legacy toolbars overflowed the ~411 dp phone screen,
clipping Italic and pushing Underline/Table/Merge/Split off-screen —
the toolbars now wrap (`flow: Right {wrap: true}` in `doc-ui/src/lib.rs`).
2. `insert_table` produced a zero-size, un-typeable table — it now seeds a
2x2 grid and parks the caret in the top-left cell (`crdt_widget.rs`,
covered by `runtime_insert_table_seeds_default_grid_and_parks_caret_in_first_cell`).
Closing notes:
- The roadmap's ScrollYView handoff box (section 5) is NOT closed: most
of section 5 and the entire legacy `DocWorkspace` column remain manual
(5.3, 5.4, 5.5, 5.6).
- Rows still open: 2.3, 2.5, 3.1 (pixels), 3.2, 3.3, 3.5/3.6 (menu),
4.4 (pixels), 4.5, 5.35.6 (legacy + gestures), 6 (clipboard), 7
(visual), 8 (visual), 9 (process-restart). Most are genuinely not
automatable through `makepad_test`; the reasons are in the table.