All checks were successful
CI / Windows build (push) Successful in 14m22s
Make Surface remote debugging and classroom workflows viable: always-on structured logs with one-click zip export, a single AppShell chrome, OOXML PPTX/DOCX annotation without LibreOffice, and a first-class sticky board. Also drop spike/legacy ink widgets and tighten pen feel (predictor, PenInfoHistory, page-tile layer). Co-authored-by: Cursor <cursoragent@cursor.com>
147 lines
6.0 KiB
Markdown
147 lines
6.0 KiB
Markdown
# BadNote
|
||
|
||
Local-first Surface Pen note-taking app with PDF / PPTX / DOCX annotation.
|
||
|
||
All notes, documents, search, and OCR run on your device. No server is required to use the app.
|
||
|
||
## Features
|
||
|
||
- Unified shell: Library · Sticky board · Search · Settings
|
||
- Ink notes with Surface Pen (pressure, predictor, undo/redo)
|
||
- PDF annotation + native OOXML PPTX/DOCX viewers (no LibreOffice required)
|
||
- Infinite sticky board with `[[wikilinks]]` / backlinks
|
||
- Full-text search over note titles, typed text, and OCR results
|
||
- Always-on diagnostics + one-click diagnostic pack export (Settings)
|
||
- **Local OCR** — ONNX when bundled, else Windows WinRT
|
||
|
||
## Build (Windows)
|
||
|
||
Prerequisites:
|
||
|
||
- Flutter SDK (3.10+)
|
||
- Visual Studio Build Tools with **Desktop development with C++**
|
||
- Developer Mode enabled (for Flutter plugin symlinks)
|
||
|
||
```powershell
|
||
flutter pub get
|
||
flutter build windows --release
|
||
```
|
||
|
||
Output: `build\windows\x64\runner\Release\badnote.exe`
|
||
|
||
The `sqlite3` native binary is **vendored** under `vendor/sqlite3/` (configured via
|
||
`hooks.user_defines` in `pubspec.yaml`), so the build does not download anything
|
||
from GitHub — it works fully offline / behind a firewall. To add another
|
||
platform or architecture, drop its official release binary from
|
||
[sqlite3.dart releases](https://github.com/simolus3/sqlite3.dart/releases) into
|
||
`vendor/sqlite3/` (the build validates each file's SHA-256).
|
||
|
||
In mainland China, point pub/Flutter at the local mirrors:
|
||
|
||
```powershell
|
||
$env:PUB_HOSTED_URL="https://pub.flutter-io.cn"
|
||
$env:FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
|
||
flutter pub get
|
||
flutter build windows --release
|
||
```
|
||
|
||
## Continuous integration
|
||
|
||
`.gitea/workflows/ci.yml` builds a Windows release on a self-hosted **Windows**
|
||
runner. Format/analyze/test run but are non-blocking, so a usable `.exe` is
|
||
produced whenever the compile itself succeeds. It is written for runners behind
|
||
the GFW: the checkout action comes from the `gitea.com` mirror, Flutter is
|
||
expected to be pre-installed on the runner, pub uses `flutter-io.cn`, and the
|
||
sqlite3 native binary is vendored.
|
||
|
||
### Self-hosted runner prerequisites
|
||
|
||
These must hold on the runner machine (they can't be set from the workflow):
|
||
|
||
1. **Flutter SDK on `PATH`** in the runner's shell (the first build step prints
|
||
`flutter --version` and fails fast if it isn't).
|
||
2. **Visual Studio Build Tools** with **Desktop development with C++** (MSVC +
|
||
Windows SDK) — required to compile the Windows runner and the ONNX Runtime.
|
||
3. **The local proxy running** (default `http://127.0.0.1:7890`) so the
|
||
`flutter_onnxruntime` build can fetch the ONNX Runtime native lib. Override
|
||
via repo secrets `HTTP_PROXY` / `HTTPS_PROXY`, or install ONNX Runtime
|
||
system-wide to skip the download.
|
||
4. **`gitea.com` reachable** (for the checkout action). If it isn't, switch to
|
||
the manual-checkout fallback shown in `ci.yml` (it clones from your own Gitea
|
||
instance), or set `DEFAULT_ACTIONS_URL=https://gitea.com` in the runner
|
||
config and use bare `actions/checkout@v4`.
|
||
5. **Runner in host mode** with a sane work directory — a malformed workspace
|
||
path (e.g. `C:\C:\...`) is an `act_runner` config problem, not a workflow one.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
lib/
|
||
├── screens/ # UI (notes, PDF/PPT annotator, search, settings)
|
||
├── services/ # Local business logic
|
||
│ ├── database_service.dart # SQLite + FTS5
|
||
│ ├── ocr_service.dart # Local OCR orchestration
|
||
│ ├── stroke_rasterizer.dart # Ink → PNG for OCR
|
||
│ ├── ocr_engine.dart # OCR entry point (delegates to a backend)
|
||
│ └── ocr/ # Pluggable OCR backends
|
||
│ ├── ocr_backend.dart # Backend interface
|
||
│ ├── ocr_backends.dart # Backend selector (ONNX → native)
|
||
│ ├── onnx_recognition_backend.dart # Embedded ONNX (cross-platform)
|
||
│ ├── native_ocr_backend.dart # OS OCR (Windows WinRT)
|
||
│ └── ctc_decoder.dart # Pure-Dart CTC greedy decode
|
||
├── providers/ # Riverpod state
|
||
└── widgets/ # Ink canvas, toolbars, thumbnails
|
||
```
|
||
|
||
OCR flow on save:
|
||
|
||
1. Extract typed text from text-tool strokes
|
||
2. Rasterize handwriting strokes to PNG
|
||
3. Recognize via the active local OCR backend (embedded ONNX if a model is
|
||
bundled, otherwise the platform's native OCR)
|
||
4. Merge recognized text into the local FTS index for search
|
||
|
||
## Local OCR
|
||
|
||
OCR runs entirely on-device through a pluggable backend (`lib/services/ocr/`).
|
||
`OcrBackends` selects, in order:
|
||
|
||
1. **`OnnxRecognitionBackend`** — embedded, cross-platform recognition via
|
||
`flutter_onnxruntime` (CPU/iGPU; suited to low-power APUs). Active only when
|
||
an ONNX model is bundled.
|
||
2. **`NativeOcrBackend`** — the OS built-in OCR (Windows WinRT today).
|
||
|
||
If no backend is available, OCR is a clean no-op — the app still works.
|
||
|
||
### Enabling the embedded ONNX model
|
||
|
||
The model is **not committed** (it is large). Fetch it onto your dev machine
|
||
before building so it bundles as an asset:
|
||
|
||
```bash
|
||
tool/fetch_ocr_model.sh # downloads PP-OCRv4 rec ONNX + ppocr_keys_v1.txt
|
||
# into assets/models/ocr/ (proxy hint inside)
|
||
```
|
||
|
||
See [assets/models/ocr/README.md](assets/models/ocr/README.md). The recognition
|
||
geometry / CTC-blank assumptions (PP-OCRv4 mobile rec, 3×48×W, blank=0) are
|
||
documented in `onnx_recognition_backend.dart` and should be verified on-device
|
||
against your exact exported model.
|
||
|
||
> **Windows build note:** the `flutter_onnxruntime` plugin downloads the ONNX
|
||
> Runtime native library (v1.22.0) from GitHub at build time. Behind a firewall,
|
||
> set `HTTPS_PROXY` for the build (CMake honours it), or install ONNX Runtime
|
||
> system-wide and build with `-DUSE_SYSTEM_ONNXRUNTIME=ON
|
||
> -DONNXRUNTIME_ROOT_DIR=<path>`.
|
||
|
||
## Optional server
|
||
|
||
The `server/` directory contains an experimental FastAPI backend (sync + EasyOCR). It is **not required** for the desktop app and is kept separately for future multi-device sync experiments. See [server/README.md](server/README.md).
|
||
|
||
## Development
|
||
|
||
```bash
|
||
flutter run -d windows
|
||
flutter test
|
||
```
|