# BadNote Phase 1 — Performance Gate Results **Status:** IN PROGRESS — MUST #1 (API source-pin) GREEN on pdfrx **2.4.4** (Dart-only verification, done). MUST #2/#4/#5 require a device/profile run; MUST #3 requires the physical Surface Pen. Spike code compiles clean (`flutter analyze`: no issues). --- ## M1 Gate Results | Gate | Target | Result | Device | Commit | PASS/FAIL | |------|--------|--------|--------|--------|-----------| | MUST #1 — pdfrx API source-pin | All `[VERIFY-IN-M1]` tags replaced with pinned signatures from `~/.pub-cache/…/pdfrx-2.4.4/lib/src/` | All APIs confirmed; match plan (see table below) | Dev box (Linux) | feat/m1-pdfrx-spike | **PASS** | | MUST #2 — Coordinate assertion | Stroke at normalized (0.5, 0.5) lands at visual page center at fit / 2× / 4× zoom | **PASS** on real pdfium (`integration_test/coordinate_assertion_test.dart`, "All tests passed", PDF load 74ms). Re-confirm on Windows. | Linux desktop (WSL2) | feat/m1-pdfrx-spike | **PASS (Linux)** | | MUST #3 — Pen/touch arbitration on Surface Pen | Pen draws AND single-finger touch scrolls AND pinch zooms simultaneously in same overlay; no mode switching | PENDING | Surface Pro (physical device) | — | PENDING | | MUST #4 — pdfrx-alone perf (300-page fling-scroll) | Median frame (build+raster) ≤ 16.6 ms, p95 ≤ 22 ms; N ≥ 120 frames; profile mode | Harness ready (`integration_test/perf_scroll_bench.dart`). NOT measured — WSL2 dev box was under heavy concurrent load → invalid signal, discarded. | — | — | PENDING (run on Surface, profile, idle machine) | | MUST #5 — ink-overlay build cost (300 synthetic strokes/page) | Median BUILD time ≤ 16.6 ms during fling-scroll; N ≥ 120 frames; profile mode | Harness ready (same bench, ink-load toggle). NOT measured (same reason). | — | — | PENDING (run on Surface, profile, idle machine) | --- ## Pinned pdfrx APIs To be filled during M1 sub-task 1 (`flutter pub add pdfrx` + read `~/.pub-cache/…/pdfrx-*/lib/src/`). All signatures verified against `~/.pub-cache/hosted/pub.dev/pdfrx-2.4.4/` on 2026-06-21. | API | Pinned signature / notes | Source file:line | |-----|--------------------------|-----------------| | `pageOverlaysBuilder` | `typedef PdfPageOverlaysBuilder = List Function(BuildContext context, Rect pageRectInViewer, PdfPage page)`. `pageRectInViewer` is **already scrolled+zoomed** (viewer coords). Children laid out in a per-page `Stack`. Matches plan §2.1. | `lib/src/widgets/pdf_viewer_params.dart:1623`, field `:554` | | `viewerOverlayBuilder` | `typedef PdfViewerOverlaysBuilder = List Function(BuildContext context, Size size, PdfViewerHandleLinkTap handleLinkTap)`. Docs: a `GestureDetector` here must use `HitTestBehavior.translucent` + `IgnorePointer` child to let events reach the viewer. | `:1572`, field `:513` | | `PdfViewerController.goToPage` | `Future goToPage({required int pageNumber, PdfPageAnchor? anchor, Duration duration = 200ms})`. (Also `goToRectInsidePage({required int pageNumber, required PdfRect rect, ...})`.) | `lib/src/widgets/pdf_viewer.dart:4140` | | `PdfViewerController.currentZoom` | `double get currentZoom => value.zoom;` | `:4318` | | `PdfViewerController.layout` | `PdfPageLayout get layout;` → `layout.pageLayouts` is `List` in **document** coords (index by page). | `:4029`; pageLayouts usage `:895,1068` | | `PdfViewerController.globalToDocument` | `Offset? globalToDocument(Offset global)` (public). Also `Offset documentToGlobal`, `Offset? localToGlobal`. | `:4403` | | `PdfViewerController.documentToLocal` | `Offset documentToLocal(Offset document)` (public). | `:4412` | | `PdfRect.toRect` | Extension in pdfrx_flutter: `Rect toRect({required PdfPage page, Size? scaledPageSize, int? rotation})` — **handles rotation** (removes our bespoke rotate math). Also `toRectInDocument({required PdfPage page, required Rect pageRect})`. | `lib/src/pdfrx_flutter.dart:107` | | Gesture recognizer (pan/zoom) | pdfrx pan+zoom is an **internal InteractiveViewer** using `GestureDetector(onScaleStart/Update/End)` (scale gesture covers pan+pinch). Default `GestureDetector` accepts all device kinds incl. stylus → **a stylus drag WOULD be claimed by the scale recognizer unless intercepted above**. Confirms R1 and justifies the `PenCaptureRegion` arena-bypass. | `lib/src/widgets/interactive_viewer.dart:1560-1564, 839-948` | | `PdfOverlayInteractionRegion` (fallback) | Exists (`class PdfOverlayInteractionRegion extends StatefulWidget`), but pdfrx docs state it is **tap-oriented only** (tap/double-tap/long-press/secondary) — **NOT a freehand-drag stream**. ∴ it cannot be the primary draw transport; `PenCaptureRegion` (RenderProxyBox per-kind hit-test) is THE path, not just a preference. | `lib/src/widgets/pdf_viewer.dart:4659`; docs `pdf_viewer_params.dart:483-538` | | `PdfPage.render` (thumbnails) | To confirm in M6 prep — pdfrx exposes page render via `PdfPage` (pdfrx_engine); verify exact RGBA path before removing syncfusion-viewer dep. | (deferred to M6 prep) | | `panAxis` | Still **[VERIFY-IN-M1 on device]** whether `PanAxis.vertical` blocks the horizontal component of a pinch-zoom pan; spike uses it — confirm on tablet whether `PanAxis.free` is needed when zoomed. | `pdf_viewer_params.dart` (panAxis field) | **Transport finding (R1):** Flutter hit-tests a pointer only on its DOWN and caches the path; `RenderBox.hitTest` receives only the position, **not** the pointer kind. The spike resolves this with `PenCaptureBinding` (overrides `GestureBinding.handlePointerEvent` to stash the in-flight `event.kind` before the synchronous hit-test) which `_RenderPenCapture.hitTest` reads — capturing stylus outside the gesture arena while touch/mouse fall through to pdfrx. Compiles clean; **empirical pen-vs-touch behavior remains MUST #3 (physical Surface Pen).** --- ## Sample Protocol (§7.1) - **Minimum sample size:** N ≥ 120 frames per measurement. - **Mode:** profile build (`flutter run --profile`), **not** debug. - **Cache:** warm cache — scroll once end-to-end before recording. - **Runs:** 3 independent runs; report median across runs. - **Asset:** `test/assets/large_300p.pdf` (300 pages, non-blank content). - **Stroke asset:** `test/assets/dense_strokes.json` key `"2000"` for MUST #5 (~300 strokes/page spread across visible pages). - **Reporting:** paste Flutter DevTools frame chart screenshot + numeric summary (median, p95, p99) into this doc with commit hash and device spec. --- ## Device Specs | Field | Value | |-------|-------| | Device | PENDING | | OS | PENDING | | Flutter version | PENDING | | Dart version | PENDING | | pdfrx version (resolved) | PENDING | | Surface Pen model | PENDING |