feat(xberg-wasm): OCR results include each line's coordinates, and JS apps can plug in their own OCR and NER#1259
Merged
Merged
Conversation
tobocop2
force-pushed
the
feat/wasm-ocr-line-geometry
branch
from
July 18, 2026 00:36
8ec4bae to
c4c9047
Compare
This was referenced Jul 18, 2026
Member
Author
|
@jamon8888 tagging you here since this is your work. One scoped feature (the OCR bridge with per-line geometry and the engine handle). This is the shape we look for when reviewing code for next time. |
Member
Author
|
Follow-up: once xberg-io/alef#182 lands and the pin is bumped, the in-crate wasm-bindgen test suites can come back via |
Member
|
Wow, nice |
Goldziher
approved these changes
Jul 18, 2026
tobocop2
force-pushed
the
feat/wasm-ocr-line-geometry
branch
from
July 18, 2026 05:45
5e459c6 to
86c3317
Compare
…gine handle Adds a hand-written JS bridge layer to the wasm crate: an injectable OCR backend whose results carry per-line text, confidence, and bounding boxes instead of a flat string, a promise-timeout helper for all bridge calls, and an XbergEngine handle that exposes extract() and ocr() to JS hosts. Carried over from PR #1253, trimmed to the OCR surface that compiles against current main. Co-authored-by: Candy Nguyen <cdy.nguyen@gmail.com>
… OCR errors Review fixes from PR #1253: the timeout racer's cleanup chain now hangs off the raced promise with both handlers attached, so a rejecting bridge call can no longer surface as an unhandled rejection (fatal by default in Node); a failure to arm the race logs a console warning instead of silently dropping the timeout; the dead Tesseract fallback arms are gone along with the module doc claiming a fallback exists; and the no-backend error tells the caller what to inject instead of leaking crate internals.
Ports the OCR bridge test cases from PR #1253 to the wasm e2e vitest harness so they run against the actually built package: per-line geometry round-trip, the missing-lines degrade path, language forwarding, and the no-backend / empty-input / malformed-injection error paths.
Registers src/bridge/ and src/engine.rs under custom_rust_modules in alef.toml so regeneration emits their declarations into the generated lib.rs instead of requiring hand edits. Includes the regenerated crates/xberg-wasm/src/lib.rs. Other regen drift (crawlberg 1.0.3 pin in alef.toml vs 1.0.5 in committed manifests, docs formatting) is pre-existing and left untouched.
… split Adds a NER bridge mirroring the OCR bridge: an injected ner(text, categories) object resolves entities through the same timeout machinery, XbergEngine gains a ner() method, and JsNerBridge adapts the injected object to the NerBackend trait for core consumers. NerBackend's async_trait futures become ?Send on wasm32: a JS bridge's futures hold JsValues driven by wasm_bindgen_futures, which are not Send, and the target is single-threaded. Every other target keeps the plain Send contract unchanged. Carried over from PR #1253, trimmed to the injected path. Co-authored-by: Candy Nguyen <cdy.nguyen@gmail.com>
Custom entity categories survive the JS boundary in both directions: category_wire_name sends the raw label instead of serde's object form (which emptied to an unnamed category), and unknown category names from JS parse through EntityCategory::from, becoming zero-shot Custom labels instead of being silently dropped. JsNerBridge is public API and its async_trait form mirrors the trait's per-target split so the module also compiles for host targets. The js_from_any helper is shared from the bridge root instead of duplicated per module, and the no-backend error tells the caller what to inject.
Ports the NER bridge cases from PR #1253 to the wasm e2e vitest harness: entities round-trip through an injected stub, category forwarding (built-in and custom labels as plain strings), and the no-backend and malformed-injection error paths.
In the workspace build, feature unification lets xberg-wasm compile for host targets, where NerBackend keeps its plain Send async_trait form and JsValue is not Send, so the adapter impl failed with 'future cannot be sent between threads safely'. The impl (and its two imports) now exist only on wasm32; a JS bridge has nothing to bridge off-wasm. Verified on host with unified features and on wasm32 with clippy all-targets.
- delete the dead, unwired JsNerBridge / NerBackend impl and resolve_ner/resolve_ocr
wrappers; engine.rs calls resolve_*_with_timeout directly.
- rename OcrBbox { w, h } -> { width, height } (readability; disambiguates from the
other bbox shapes); update e2e TS.
- Promise::from -> Promise::resolve(&result) so a synchronous injected callback yields
a clear error instead of 'TypeError: .then is not a function'.
- validate bridgeTimeoutMs: reject non-finite / negative instead of saturating to 0.
- document the get_timeout_racer new Function(...) CSP ('unsafe-eval') constraint.
Goldziher
force-pushed
the
feat/wasm-ocr-line-geometry
branch
from
July 19, 2026 12:51
373a580 to
c71c60b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1266. Design and implementation by @jamon8888 in #1253; his commits carry his authorship, review fixes are separate commits.
What was broken
When a browser app ran OCR through the wasm package, it got back one undifferentiated blob of text. There was no way to know where each recognized line sits in the image, so the app could not highlight a line, scroll to it, or overlay it on the page. There was also no way for the app to plug in its own OCR or entity-detection engine: the wasm build either bundled one or you had nothing.
What this adds
XbergEngine, constructed with a config and an injection object, exposingextract(),ocr(), andner(). An injected OCR backend returns{ text, lines: [{ text, confidence, bbox? }] }; a backend without geometry degrades to an emptylinesarray. An injected NER backend works the same way, with a publicJsNerBridgeadapter for core consumers. All bridge calls run under a configurable timeout with leak-safe cleanup.NerBackend'sasync_traitfutures are?Sendon wasm32 only (JS futures are notSend; the target is single-threaded). Native contracts are unchanged.The hand-written
bridge/enginemodules are declared throughcustom_rust_modulesin alef.toml; the regeneratedlib.rsis included, no hand edits.Verification
cargo check/clippy --all-targetsclean on wasm32; host build clean under workspace feature unification.engine_ocr.test.tsandengine_ner.test.tscover geometry round-trip, degrade paths, category forwarding, and error paths against the built package. Host-targetcargo check -p xberg-wasmin isolation fails on main already (tokio-gated paths); unchanged here.BLOCKED follow-up (#1269): in-crate
#[wasm_bindgen_test]suites wait on xberg-io/alef#182 shipping in a release.