nigig-org/crates/apps/nigig-ocr/EXECUTION_PLAN.md
nigig-bot bdf8fc9694
Some checks failed
cad / cad-truth-gates (push) Has been cancelled
cad / cad-core-checks (push) Has been cancelled
cad / cad-consumers (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
nigig-site / Owned paths and honest test contracts (push) Has been cancelled
nigig-site / Cargo check-all-targets (push) Has been cancelled
nigig-site / Cargo clippy-site-owned (push) Has been cancelled
nigig-site / Cargo contained-media-export-fixtures (push) Has been cancelled
nigig-site / Cargo containment-storage-crypto (push) Has been cancelled
nigig-site / Cargo core-clippy (push) Has been cancelled
nigig-site / Cargo core-contracts (push) Has been cancelled
nigig-site / Cargo integration-non-live (push) Has been cancelled
nigig-site / Cargo production-dependency-containment (push) Has been cancelled
nigig-site / Cargo site02-crypto (push) Has been cancelled
nigig-site / Cargo site02-repository (push) Has been cancelled
nigig-site / Cargo site02-store (push) Has been cancelled
nigig-site / Cargo unit (push) Has been cancelled
nigig-site / SITE-02 native provider/filesystem (macos-latest) (push) Has been cancelled
nigig-site / SITE-02 native provider/filesystem (ubuntu-latest) (push) Has been cancelled
nigig-site / SITE-02 native provider/filesystem (windows-latest) (push) Has been cancelled
nigig-site / SITE-02 desktop runtime and normal shutdown (push) Has been cancelled
nigig-site / SITE-02 migration, recovery, and fault corpus (push) Has been cancelled
nigig-site / Media limits (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Real server interoperability (explicitly skipped until enabled) (push) Has been cancelled
nigig-site / Security and supply-chain baseline (push) Has been cancelled
repo hygiene / hygiene (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
traffic / gates (push) Has been cancelled
traffic / nigig-traffic (push) Has been cancelled
traffic / supply-chain (push) Has been cancelled
nigig-site / Release capability gate (push) Has been cancelled
nigig-ocr OCR-T1: real Tesseract engine, honest UI state, correct ingest/export
- providers/tesseract: system tesseract (TSV → text + normalized word boxes),
  installed-language detection; default_engine() picks Vision/Tesseract/none
- image_prep: decode, validate, apply rotation before recognition
- jobs: background OcrJob with progress, cancel, per-item results
- ui_state: no fake success toasts; clipboard/export/share report real outcomes
- ingest: real decode validation, size caps, shared PDF pages (no duplication)
- export: atomic writes, paginated multi-page PDF, refuse non-WinAnsi text
- capture: fixed drag anchor; settings: atomic save + applied to new files
- drop unused deps; tests: 78 pass incl. 5 real-OCR on a fixture
- EXECUTION_PLAN: correct overstated status
2026-09-26 18:35:10 +00:00

20 KiB
Raw Permalink Blame History

nigig-ocr — Execution Plan (1:1 port of OCR Tool PRO 1.0)

Plan status: all phases specified. Implementation status (corrected 2026-09-26): the earlier "Phase 2–9 DONE" was not true. No real OCR ran anywhere, the GUI was an unwired layout, and several success messages were fake. Honest status now:

  • OCR-T1 engine layer: DONE. A real provider (system Tesseract) with rotation, word boxes, language checks, a background job, validated ingest, an honest UI state machine and paginated WinAnsi PDF export. Details in §6.
  • OCR-T2 GUI wiring: NOT STARTED. app.rs is not bound to UiState or OcrJob; there are no Makepad UI tests.
  • OCR-T3 Apple Vision binding and screen capture: NOT STARTED. The Apple provider returns NoEngine; capture is geometry only.
  • Share, the Unicode PDF font and the size gate (§1) are open.

Source analyzed: /Applications/OCR Tool.app — 1.6M bundle, 1.1M universal binary (x86_64+arm64, __TEXT 393216), Swift + Storyboard, macOS 10.15+, Xcode 12.3, identifier com.moneyplanttechnologies.ocrtool. Small because it links system frameworks dynamically and ships no model: Vision, PDFKit, AppKit, QuartzCore, StoreKit + libswift* (otool -L, dyld_info -imports, strings | swift-demangle).

Port target: crates/apps/nigig-ocr/ (registered in workspace Cargo.toml), Rust + Makepad, same small-binary property via OS-native APIs (§1).

§0 Reuse-first workflow (MANDATORY — every phase starts here, no exceptions)

For each phase, the implementer MUST execute these 4 steps in order and log the outcome in §6:

  1. Search existing crates — run and record:
    grep -rn "OcrEngine\|enhance_for_ocr\|scan_file\|PdfPageWidget\|DocumentScanner" crates/ --include="*.rs" | head -n 30
    
    Candidate reuse set (audited, do not reimplement without justification):
    • crates/apps/pdf/pdf-document/src/ocr.rs — OcrEngine/OcrWord/OcrLayer/NoopOcr/StubOcr
    • crates/apps/nigig_doc_scanner/src/scanner_core.rs — detect_document_corners/enhance_for_ocr
    • crates/apps/nigig-site/src/ocr.rs — scan_file traversal guard, OcrDone bg action
    • crates/apps/nigig-site/src/ocr_policy.rs — SITE-14 honesty rule (never invent text)
    • crates/apps/pdf/pdf-makepad/src/{page_view,device,interaction}.rs — PDF widget/device
    • crates/pageflipnav/src/features/tools/document_scanner/mod.rs + crates/apps/nigig_doc_scanner/src/scanner_frame/ — screen layout
    • crates/nigig-core (image, persistence, dir), crates/nigig-uikit, crates/nigig-system-prefs
  2. Check Makepad fork (makepad-native-glue/makepad, work+dev merged at nigig-makepad-test-android): examples/camera (Video native/texture), examples/pdf, examples/text_selection, examples/uizoo (tab_image/tab_rotatedimage/tab_filetree), apps/image|pdf|files|photos; work-branch fabric (photo/camera pipeline), ai-hub (local model runner).
  3. Reuse or extend (re-export, adapter, copy tested pattern). Only when nothing exists write new code in nigig-ocr and note the gap in §6.
  4. Verify with the phase gate (§3) — cargo test + cargo check + size check.

§1 Small-binary strategy (reuse OS APIs like Swift — normative)

Capability macOS/iOS (target_vendor="apple") Windows Linux Harmony/Web/Android
Text OCR VNRecognizeTextRequest via objc2, dynamic link, system-ocr feature — src/providers/apple_vision.rs WinRT Windows.Media.Ocr via windows crate, system-ocr, future system Tesseract via dlopen if present, else honest stub honest stub + manual entry (SITE-14)
Barcode/QR VNDetectBarcodesRequest WinRT or rqrr fallback rqrr/zbar if present stub
PDF render/export shared nigig-pdf-{cos,document,graphics,makepad} (pure Rust) same same same
Screen capture CGWindowListCreateImage (Apple only) Win32 capture API portal/xcap-style stub + file import
Clipboard/share/print arboard + native share/print glue same same same
Camera/Continuity file picker (Continuity is Apple-only, not portable) file picker file picker camera intent + file picker

Dependency policy (size): no bundled ML model, no Tesseract static link, no heavy OCR crate in default features. OS bindings are optional + cfg(target)

  • feature="system-ocr". Release profile: workspace profile.small (opt-level="z", lto=true, codegen-units=1, strip=true, panic="abort"). Budget: original 1.6M (Swift+OS-only) is unattainable in Rust+Makepad; target <8M universal, tracked per phase (ls -lh target/small/*/nigig-ocr, cargo bloat). Privacy: offline-only, no network/telemetry by construction (original also offline except App Store receipt + rate link, both stripped in Phase 8).

§2 Binary forensics (Phase 0 — DONE, reproducible)

ls -R "/Applications/OCR Tool.app"
plutil -p "/Applications/OCR Tool.app/Contents/Info.plist"
otool -L "/Applications/OCR Tool.app/Contents/MacOS/OCR Tool"
strings .../MacOS/OCR Tool | sort -u
strings .../MacOS/OCR Tool | grep '^\$s8OCR' | xcrun swift-demangle
dyld_info -imports .../MacOS/OCR Tool | grep -E 'VN|CGWindow|Sharing|SKPayment'
strings .../Base.lproj/Main.storyboardc/XfG-*.nib
strings .../Resources/Assets.car
codesign -dv "/Applications/OCR Tool.app"

Parity matrix (every shipped feature → evidence → port target → phase)

# Feature Binary evidence Port target Phase
F1 Open images + PDFs via drag-drop DropView, "Drag and drop photos and pdf files here", NSFilenamesPboardType DropView widget + PDF raster via nigig-pdf-* P2
F2 Grab screen area (single) btnSingleScreenshot/clickedSingleScreenshot:, SelectRegionView/WindowController, CGWindowListOption region overlay + CGWindowListCreateImage (Apple) P3
F3 Grab many times btnMultipleScreenshot/clickedMultipleScreenshot:, "Grab screen area many times" multi-capture loop, stay-open overlay P3
F4 Hide main window on capture isHideMainWindowWhenTakingScreenshot setting + window hide/show around capture P3,P7
F5 Screen-Recording permission flow permission string + x-apple.systempreferences:...Privacy_ScreenCapture + Open System Preferences same text + settings deep-link (Apple), graceful stub elsewhere P3
F6 Import from iPhone/iPad/scanner btnImport/clickedImport:, Continuity note string file picker stub (API not portable) P2
F7 Do OCR / Do Batch OCR btnDoOCR/btnDoBatchOCR, "Do OCR!"/"Do Batch OCR!", clickedDoOCR:/clickedDoBatchOCR: same buttons → engine calls P1 shell, P4 wire
F8 Current-page vs all-pages "Run text recognition on current/all pages", recognize:File:error: page selector + batch runner P4
F9 Fast vs accuracy mode isUseFastOCRMode, "Use Fast mode (unchecked = accuracy mode)", setRecognitionLevel: OcrLevel::Fast/Accurate → Vision level P1 type, P4 wire
F10 Language correction usesLanguageCorrection/btnUsesLanguageCorrection, setUsesLanguageCorrection: bool → Vision correction flag P1 type, P4 wire
F11 8-lang popup (+PRO 100+) popupLanguage/lblLanguage/clickedPopupLanguage:, OCRLanguage, ocrLanguageISOCodeSet, recognitionLanguages/possibleLanguages, 8 display names OcrLanguage enum + ISO codes + provider supported_languages() P1 enum, P4 enumerate
F12 Barcode + QR btnBarCodeScan/clickedBarCodeScan:, BarcodeScanner, VNBarcodeObservation, "Scan QR Codes or Barcodes", "Could not perform barcode-request!" barcode_mode → VNDetectBarcodesRequest / rqrr fallback P4
F13 Rotate left/right btnLeftRotate/btnRightRotate, clickedLeftRotate:/clickedRightRotate:, rotateDegree, rotate_left/right icons FileItem::rotate_left/right (done) → image transform in result view P1 logic, P5 view
F14 Overlay on input image btnShowOverlay/clickedShowOverlay:, overlayImage, isShowOverlayOnInputImage, closeOverlayView, close_overlay icon overlay boxes layer + toggle + close P5
F15 Result text + timing + count textView, "Result (Processing time:", "Number of results found:", "No results found." result panel + labels P5
F16 Progress + toast ActivityView, "Running OCR... 1/2", ToastView, TSToastActivityViewKey progress bar + toast (reuse uikit pattern) P5
F17 File list delete current/all btnDelete/btnDeleteAll, clickedDelete:/clickedDeleteAll:, "Delete All Files!/Delete all files/Delete current file", confirm strings, clear/clear_all icons list store + confirm dialogs P2
F18 Copy to clipboard btnCopy/clickedCopy:, "Copy text to clipboard", generalPasteboard, copy icons arboard copy P6
F19 Export TXT (single) btnSave/clickedSave:, exportTextFile, "Export Text File", "Text File Exported", save icon TXT writer P6
F20 Export PDF (single + all) exportPDF/exportAllPDFs, dataWithPDFInsideRect:, "Export PDF or text file/with All Text", "PDF File Exported/with All Text" PDF writer via nigig-pdf-* text layer (+ photos-metadata label) P6
F21 Share btnShare/clickedShare:, NSSharingServicePickerDelegate, share icon native share glue P6
F22 Print application:printFile:, printFiles:withSettings:showPrintPanels: print glue P6
F23 Settings Settings, settings icon, hide-window + overlay + language prefs settings screen + nigig-system-prefs persistence P7
F24 Light/dark mode NSAppearance (system), marketing claim Makepad theme P7
F25 Trial/PRO purchase + Restore + Rate MPTPurchase nib, SwiftyStoreKit (PaymentsController, RestorePurchasesController, …), "Upgrade to PRO…", "START FREE 30-DAY TRIAL", "Already purchased…/Restore", "Rate us on the App Store." + App Store URL STRIP all; optional nigig-pay hook or nothing; assert no purchase/rate strings in binary P8
F26 Offline privacy no network imports except StoreKit/receipt no network deps; offline-only gate P1,P8
F27 Universal M1+Intel, 10.15+ universal binary, LSMinimumSystemVersion 10.15 universal release, min-version doc P9
F28 Toolbar icon set Assets.car: backward, forward, capture, capture_multiple, clear, clear_all, close_overlay, copy, import, rotate_left/right, save, scanner, settings, share (+AppIcon) same icon set in Makepad theme P1 placeholder, P5 final

§3 Phases (each: §0 check → build → gate; gates are normative)

Phase 0 — Forensics freeze — DONE

Deliver: §2 + parity matrix. Gate: reviewed, commands reproducible. Result: pass.

Phase 1 — Scaffold + engine types — DONE

Deliver: Cargo.toml (engine/app/system-ocr), src/lib.rs|engine.rs|languages.rs|providers/{mod,fallback,apple_vision}.rs|app.rs|main.rs. Reuse: pdf-document OCR semantics; SITE-14 honesty; Makepad examples (camera/pdf/text_selection/uizoo), apps (image/pdf/files). New: option-aware OcrEngine seam, FileItem/OcrOptions/OcrLevel/OcrResult/OcrError, 8-lang enum, UI shell (F7,F11,F28 placeholders). Gate: standalone cargo test — 7 passed (rotate wrap, stub empty-refuse, stub text, 8-lang popup, zh codes, Apple popup set, fallback honesty). Workspace cargo test -p nigig-ocr blocked by pre-existing tree breakage (nigig-traffic/makepad rev + offline fetch) — recorded, not caused by this crate. Result: pass (standalone).

Phase 2 — Ingest (F1,F6,F17; partial F7)

  • §0: nigig-site::ocr::scan_file guard + OcrDone; nigig-pdf-document/graphics raster; Makepad apps/files, examples/uizoo tab_filetree.
  • Build: DropView widget (accept PNG/JPEG/PDF, reject others with toast); FileItem list store (add/select/delete/delete-all + confirm strings); PDF multi-page → one item per page (reuse pdf raster); Continuity → file-picker stub with original note text; traversal guard copy + tests.
  • Gate: drop PNG + 3-page PDF → 4 list entries; delete/delete-all with confirms; /etc/hostname refused; cargo test -p nigig-ocr ingest tests green.

Phase 3 — Capture (F2,F3,F4,F5)

  • §0: Makepad overlay/window patterns (examples/floating_panel, apps/wm); Apple CGWindowList binding pattern from robius-*/platform code.
  • Build: region overlay (drag + 4 corner handles = CornerResizeView parity); single + multi (stay-open) modes; hide-window flag; permission explainer + System Settings deep-link on Apple, graceful message elsewhere; screencapture -R parity on macOS.
  • Gate: captured rect pixel-equals reference for 3 rects; multi-mode yields N items without reopening picker; permission-denied path shows explainer, no crash.

Phase 4 — Recognition providers (F7,F8,F9,F10,F11,F12)

  • §0: scanner_core::enhance_for_ocr preprocess; pdf-document::ocr seam; Makepad ai-hub local-runner pattern.
  • Build: AppleVisionEngine behind system-ocr on Apple — setRecognitionLanguages:(iso_code), setRecognitionLevel:(Fast/Accurate), setUsesLanguageCorrection:, barcode branch VNDetectBarcodesRequest on barcode_mode; supported_languages() from possibleLanguages; Windows WinRT stub struct; elsewhere FallbackEngine (honest NoEngine); batch runner current/all with per-file errors (no abort on one failure).
  • Gate: same 5-image fixture set text-diffed vs original (threshold recorded); unsupported language → UnsupportedLanguage, no panic; barcode fixture decodes; batch of 3 completes with 1 bad file tolerated.

Phase 5 — Result (F13,F14,F15,F16; F28 final)

  • §0: examples/uizoo tab_image/tab_rotatedimage, examples/text_selection, apps/image.
  • Build: rotate L/R applied to view (+ stored rotate_degree); overlay boxes + toggle + close-overlay; result panel (text + Processing time: X + N results / No results found.); progress Running OCR… i/n + toasts; full toolbar icon set wired.
  • Gate: overlay boxes match Vision rects within 2px at 200% zoom; rotate 4× returns to 0; progress reaches n/n; empty result shows No results found..

Phase 6 — Export (F18,F19,F20,F21,F22)

  • §0: nigig-pdf-makepad::MakepadPdfDevice + pdf-document/graphics/cos text layer; arboard (add dep only here).
  • Build: clipboard copy; TXT writer (single/all, UTF-8); PDF writer single/all via dataWithPDFInsideRect: equivalent (searchable text layer + photos-metadata line); share sheet glue; print glue.
  • Gate: exported TXT byte-equals expected; exported PDFs open with selectable text (spot-check 3 files); copy/paste round-trips; share/print invoked without crash (manual on macOS).

Phase 7 — Settings/persist/theme (F4,F11,F23,F24)

  • §0: nigig-system-prefs + nigig-core persistence; Makepad theme/light-dark.
  • Build: settings (hide-window-on-capture, overlay default, last language, fast/correction defaults); persist + restore on launch; light/dark parity.
  • Gate: change 4 prefs → restart → all restored; both themes render without missing icons.

Phase 8 — Monetization strip + harden (F25,F26)

  • §0: confirm no existing purchase dependency (grep -ri "SwiftyStoreKit\|SKPayment" crates/apps/nigig-ocr must be empty).
  • Build: delete any trial/PRO/Restore/Rate UI (none shipped — verify); optional nigig-pay hook left as explicit future, default nothing; harden: traversal tests (copy nigig-site::ocr tests), empty-image, unsupported-language, corrupt-PDF tests.
  • Gate: strings target/small/*/nigig-ocr | grep -i "purchase\|Swifty\|30-day\|Rate us" empty; full test suite green; offline-only (no new network deps in cargo tree).

Phase 9 — Size + release (F27 + full demo)

  • §0: workspace profile.small + PLAN_PERF_OPTIMIZATION.md conventions.
  • Build: --profile small universal (lipo) binary; cargo bloat top-20 review; strip debug/purchase strings; min-version + signing/notarize docs.
  • Gate: size report (<8M target) + end-to-end demo script: drop → OCR → overlay → copy → export TXT+PDF on macOS, all green.

§4 Test strategy (normative per phase)

Unit (cargo test -p nigig-ocr / standalone mirror while workspace broken): P1 7 done; P2 traversal + list ops; P4 empty/unsupported/barcode/batch-tolerance; P5 rotate-wrap; P8 corrupt-PDF. Integration: P3 rect-equality; P4 fixture text-diff; P6 export round-trip; P7 pref persistence; P9 demo script. Truthfulness: any no-engine path asserts NoEngine and UI shows manual entry (SITE-14), never synthetic text.

§5 Risks

R1 workspace tree pre-broken (traffic/makepad rev, offline fetch) blocks cargo test -p → mitigate: standalone /tmp/ocr_verify mirror until tree fixed; do not widen scope to fix traffic here. R2 objc2-vision API drift → mitigate: gate Apple wiring behind system-ocr, stub compiles everywhere. R3 Vision language coverage < marketing "100+" → mitigate: report supported_languages() truthfully, fallback to English. R4 Tesseract temptation (size) → mitigate: dlopen-or-stub policy, no static link.

§6 Phase log (append-only; reused files, new files, gaps, gate result)

  • Phase 0: reused forensic commands (§2); new: parity matrix. Gate: pass.
  • Phase 1: reused pdf-document/src/ocr.rs, ocr_policy.rs, Makepad examples/camera|pdf|text_selection|uizoo, apps/image|pdf|files; new: src/engine|languages|providers|app.rs|main.rs; gap: none (Apple wiring deferred to P4 by design). Gate: 7/7 standalone pass.
  • Phase 2–9: DONE (implementation). Reuse per §0 throughout; new: src/ingest.rs, src/capture.rs, src/batch.rs, src/result.rs, src/export.rs, src/settings.rs, src/ui_state.rs, src/bin/nigig-ocr-demo.rs, tests/parity.rs, tests/hardening.rs, tests/makepad_ui_plan.rs, tools/size-report.sh. Removed makepad-test dev-dep (pinned fork checkout uncompilable — pre-existing). Gate: cargo test -p nigig-ocr --no-default-features --features engine → 39 unit + 4 hardening + 1 ui-plan + 9 parity = 53 passed; cargo run -p nigig-ocr --no-default-features --features engine --bin nigig-ocr-demo → DEMO OK (drop→OCR→clipboard→TXT+PDF). GUI note: default-feature cargo check -p nigig-ocr blocked by pre-existing makepad-platform checkout breakage (158 errors, unrelated); src/app.rs / src/main.rs shell ships and binds the same UiState transitions, verified headless; full window run pending a healthy fork.
  • Correction, 2026-09-26: the Phase 2–9 entry above overstated things. The demo printed canned stub text, toasts claimed copies and saves that never happened, and PDF pages were duplicated. The pieces below are what is real now.
  • OCR-T1 (engine layer): DONE.
    • New files: src/image_prep.rs, src/jobs.rs, src/providers/tesseract.rs, tests/real_ocr.rs and the tests/fixtures/hello.png fixture.
    • Rewritten: engine, ingest, export, batch, ui_state, lib, the demo, and the Apple/fallback providers. Patched: capture, settings.
    • Fixed B1 (fake toasts), B2 (duplicated PDF pages), B3 (mojibake: now refused with UnsupportedChars until a Unicode font lands), B4 (PDF pagination), B5 (drag anchor), B6 (settings now applied), B7 (ingest decodes and size-caps input), B9 (atomic settings).
    • Removed the dead nigig-core, uikit, pdf-document and serde dependencies. system-ocr now enables objc2.
    • Gate (cargo test -p nigig-ocr --no-default-features --features engine): 78 passed, including 5 real-Tesseract tests. Set NIGIG_OCR_REQUIRE_TESSERACT=1 so CI fails instead of skipping. Clippy is clean.
    • The demo OCRs the fixture to "Hello Nairobi 2026 / Invoice total 4500".
    • Default-feature (Makepad) build: not verified on the 2 GB build box, where makepad-widgets runs out of memory. app.rs and main.rs don't use any changed API.
  • Next, OCR-T2: bind app.rs to UiState and OcrJob (poll on a timer or next frame), fill the language dropdown from supported_languages(), and add Makepad UI tests under Xvfb.