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

20 KiB
Raw Permalink Blame History

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
2.2 Tap in a cell, type A doc_insert_table_and_cell_text (cell "alpha beta" joins stats → `4 words
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.