Pad/doc
Greg Pomerantz 6dedffcba7 editor: fix tap-to-position-cursor clamping on large files
Tapping in the editor repositions the cursor, but it was never actually
verified (the old unit test only checked in-bounds/no-panic). On-device
verification found that on a large file scrolled deep, the cursor clamped to
the bottom of the viewport regardless of tap position.

Root cause: the tap handler computed the tap's text-local Y in content space
(pt.Y - region.Y + full ScrollOffset) and passed it to SetCursorFromPoint,
whose visualLine = y/lineHeight then produced a huge content-line number
(e.g. 91,640). But the GlyphLayout is window-relative (layout.Y==0 is the top
of the visible window), so the number far exceeded the window's line count and
clamped to the last group (bottom line). The Phase 3 windowing refactor
introduced the windowed layout without updating the tap handler.

Fix: tapLocalY() converts the tap Y to window-relative space by adding only the
sub-line remainder (ScrollOffset mod lineHeight), never the full scroll.
Extracted as a named helper so it is unit-testable. Added two regression tests
that fail on the pre-fix formula (cursor clamps to the bottom line) and pass on
the fix. Verified on-device: taps now map linearly across the viewport.

Also removed the per-tap/per-glyph log.Printf debug lines in SetCursorFromPoint.
2026-08-16 17:38:24 -04:00
..
architecture.md perf: default-off in-app profiler + debug scroll jumps; verify scroll perf & clamping 2026-08-16 16:53:28 -04:00
development_plan.md editor: fix tap-to-position-cursor clamping on large files 2026-08-16 17:38:24 -04:00
README.md perf: default-off in-app profiler + debug scroll jumps; verify scroll perf & clamping 2026-08-16 16:53:28 -04:00
spec.md perf: default-off in-app profiler + debug scroll jumps; verify scroll perf & clamping 2026-08-16 16:53:28 -04:00

Pad documentation

Three documents, kept at the level of what, why, and invariants — not line-by-line code — so they stay true as the implementation evolves.

Doc What it is
spec.md What the app actually does, measured performance, the code layout, and an explicit list of deferred features.
architecture.md How it works: single-owner concurrency model, channel topology, Frame handoff contract, ownership rules, editor/browser/render internals.
development_plan.md The active plan: completed phases, remaining work, and the on-device observation loop.

Package inventory

Package Role
internal/editor Chunked buffer, line index, virtualized viewport, IME ops, autosave, logic goroutine, state, Frame
internal/browser Directory browsing, search, sort, pagination, browser state machine
internal/ui Element tree, frame layout, renderer (op-based), IME wiring, gestures
internal/perf Default-off performance profiler (per-logic-frame cadence + scroll state → CSV)
internal/io/pool Worker pool (8 workers, 2 priority lanes) + file/dir tasks
internal/test/e2e Harness driving the real Logic + Inspect
cmd/pad main.go (entry), impl_android.go (base path)

Documentation policy

  1. Docs describe invariants and contracts, not code lines. If a document has to change on every refactor, it is too detailed — delete it or raise its level of abstraction. Point-in-time implementation plans are deleted once implemented (the previous *_implementation_plan.md, touch.md, element_model.md, layout_rendering.md, virtual_scroll_render_optimization.md, and conflict_resolution.md were removed in the 2026-08 doc reorganization for this reason).
  2. Unbuilt behavior is not spec'd. Requirements that are not in the code live in spec.md §7 (deferred), never as if they worked.
  3. Code wins. When doc and code disagree, fix the doc.

Build & install (Android, this workstation)

Prereqs on this box (already installed): JDK 17, Go, Android SDK at ~/android-sdk (platform-tools, build-tools 35.0.0, emulator, NDK), gogio@v0.10.0 in ~/go/bin, apktool at /tmp/apktool.jar, debug keystore at ~/.android/debug.keystore. Env vars are set in ~/.bashrc.

The full recipe is /tmp/build_pad.sh (machine-local). Steps:

  1. gogio -target android -targetsdk 35 -arch amd64 -o /tmp/pad-raw.apk . (from cmd/pad/)
  2. apktool d /tmp/pad-raw.apk -o /tmp/pad_decoded -f
  3. Inject <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> into the decoded AndroidManifest.xml (Android 15 needs it for /storage/emulated/0).
  4. apktool b /tmp/pad_decoded -o /tmp/pad-unsigned.apk
  5. apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android --key-pass pass:android --ks-key-alias androiddebugkey --out cmd/pad/pad-emu.apk /tmp/pad-unsigned.apk
  6. adb install -r cmd/pad/pad-emu.apk

Run the emulator (headless, AVD pad_avd, API 35):

nohup emulator -avd pad_avd -no-window -no-audio -no-boot-anim \
  -gpu swiftshader_indirect >/tmp/emulator.log 2>&1 &

App package/activity: pad.pad / org.gioui.GioActivity. Default root directory: /storage/emulated/0/Notes (push test files there with adb push file /storage/emulated/0/Notes/).

On-device observation loop (quick reference)

Gio renders into one GL surface, so the authoritative debug signals are data, not pixels:

  • adb logcat -s pad.pad — app log (IME commits, open-file timing, errors).
  • adb shell input tap|swipe|text — drive the UI (see development_plan.md for the tap coordinates and IME-tap cadence that works).
  • Autosave debounce is 1 s: wait ~1.6 s before reading a file back from disk after typing.
  • Memory: adb shell dumpsys meminfo pad.pad (watch PSS/RSS; the 3.8 GB emulator OOMs the app above ~2.5 GB RSS).
  • Screenshots (adb exec-out screencap -p > /tmp/x.png) are an auxiliary check only — state, logcat, and file diffs are authoritative.