# 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<Widget> 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<Widget> 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<void> 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<Rect>` 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 |
