Install any skill in seconds. Free to start, no credit card required.
Get Started Free →WASM build constraints for the crates/xberg-wasm crate — the wasm-target feature set, no-tokio sync-only internal APIs, the mandatory SyncExtractor trait for WASM-compatible extractors, the 2 MB HTML size limit, size-optimized build config (opt-level="z"), and the async-wrapper/sync-internal API pattern. Load when building for wasm32, adding or modifying a WASM-compatible extractor, or debugging WASM build/runtime failures.
.claude/skills/xberg-io-wasm-constraints/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | -20% | 0% |
| case-03 | ✗→✓ | ▲ Improved | -40% | 0% |
| case-04 | ✗→✓ | ▲ Improved | -31% | 0% |
| case-05 | ✗→✓ | ▲ Improved | -1% | 0% |
| case-06 | ✗→✓ | ▲ Improved | -33% | 0% |
WASM target lives in crates/xberg-wasm/, built with wasm-bindgen over sync-only internal APIs. Note that crates/xberg-wasm/src/lib.rs is Alef-generated — do not hand-edit it.
toml# crates/xberg/Cargo.toml wasm-target = [ "no-ort-target", "excel-wasm", "ocr-wasm", "layout-tract", "auto-rotate-tract", "ner-candle-wasm", ]
RT-DETR layout detection and PP-LCNet document orientation run through the pure-Rust tract engine; weights are streamed in from JS, never fetched by Rust (hf-hub/reqwest are native-only). Deliberately no tree-sitter: the 371-language grammar pack pushes the browser .wasm past jsDelivr's 50 MB per-file cap.
All operations must be synchronous internally. Use #[cfg(not(feature = "tokio-runtime"))] paths.
Every WASM-compatible built-in extractor must implement SyncExtractor (crates/xberg/src/extractors/mod.rs). It is pub(crate), so only in-crate extractors can implement it — out-of-crate plugins cannot. This is not part of the public API; public callers still use extract / extract_batch.
rustimpl SyncExtractor for MyExtractor { fn extract_sync(&self, content: &[u8], mime_type: &str, config: &ExtractionConfig) -> Result<InternalDocument> { /* sync implementation */ } }
There is no as_sync_extractor() method on DocumentExtractor — do not write one.
rust// crates/xberg/src/extraction/html/stack_management.rs pub const MAX_HTML_SIZE_BYTES: usize = 2 * 1024 * 1024; // 2 MB — stack constraint
toml# crates/xberg-wasm/Cargo.toml [lib] crate-type = ["cdylib"] # root Cargo.toml [profile.release.package.xberg-wasm] opt-level = "z" # codegen-units = 1 comes from the global [profile.release]
The generated surface exposes async wasm-bindgen functions over sync internals:
rust#[wasm_bindgen] pub async fn extract(input: JsValue, config: JsValue) -> Result<WasmExtractionResult, JsValue>
Functions can be async for JS ergonomics; extraction underneath is synchronous.
SyncExtractor for every WASM-compatible in-crate extractor.MAX_HTML_SIZE_BYTES (2 MB) due to stack constraints.opt-level = "z" on the package profile only.#[cfg(target_arch = "wasm32")], and use the two-arm cfg_attr async_trait form on any plugin trait impl.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 45,978 | 41,222 | -10% | 1 | 1 | 0% | 2,989 | 2,972 | -1% | 0 | 0 | — |
case-02 | fail→pass | 24,489 | 39,604 | +62% | 1 | 1 | 0% | 3,272 | 2,625 | -20% | 0 | 0 | — |
case-03 | fail→pass | 23,699 | 8,813 | -63% | 1 | 1 | 0% | 3,921 | 2,369 | -40% | 0 | 0 | — |
case-04 | fail→pass | 46,402 | 6,448 | -86% | 1 | 1 | 0% | 2,509 | 1,728 | -31% | 0 | 0 | — |
case-05 | fail→pass | 11,511 | 5,452 | -53% | 1 | 1 | 0% | 1,561 | 1,541 | -1% | 0 | 0 | — |
case-06 | fail→pass | 13,658 | 4,204 | -69% | 1 | 1 | 0% | 2,078 | 1,400 | -33% | 0 | 0 | — |
case-07 | pass→pass | 3,732 | 2,257 | -40% | 1 | 1 | 0% | 586 | 1,064 | +82% | 0 | 0 | — |
case-08 | fail→pass | 14,688 | 3,241 | -78% | 1 | 1 | 0% | 1,750 | 1,231 | -30% | 0 | 0 | — |
case-09 | fail→pass | 15,779 | 3,052 | -81% | 1 | 1 | 0% | 2,129 | 1,239 | -42% | 0 | 0 | — |
case-10 | fail→pass | 17,796 | 8,101 | -54% | 1 | 1 | 0% | 2,597 | 2,200 | -15% | 0 | 0 | — |
case-11 | pass→pass | 7,057 | 2,111 | -70% | 1 | 1 | 0% | 1,017 | 1,042 | +2% | 0 | 0 | — |
case-12 | fail→pass | 11,294 | 2,830 | -75% | 1 | 1 | 0% | 1,702 | 1,060 | -38% | 0 | 0 | — |
case-13 | pass→pass | 12,572 | 4,145 | -67% | 1 | 1 | 0% | 1,717 | 1,334 | -22% | 0 | 0 | — |
case-14 | fail→pass | 15,924 | 2,888 | -82% | 1 | 1 | 0% | 1,861 | 1,192 | -36% | 0 | 0 | — |
case-15 | fail→pass | 11,610 | 2,408 | -79% | 1 | 1 | 0% | 1,696 | 1,168 | -31% | 0 | 0 | — |
case-16 | pass→pass | 7,153 | 5,769 | -19% | 1 | 1 | 0% | 1,185 | 1,443 | +22% | 0 | 0 | — |
case-17 | pass→pass | 15,879 | 6,983 | -56% | 1 | 1 | 0% | 2,195 | 1,824 | -17% | 0 | 0 | — |
case-18 | fail→pass | 13,656 | 2,381 | -83% | 1 | 1 | 0% | 1,460 | 1,185 | -19% | 0 | 0 | — |
case-19 | pass→fail | 26,070 | 16,112 | -38% | 1 | 1 | 0% | 2,716 | 3,009 | +11% | 0 | 0 | — |
case-20 | pass→pass | 19,102 | 21,322 | +12% | 1 | 1 | 0% | 3,243 | 3,127 | -4% | 0 | 0 | — |
case-21 | pass→pass | 18,775 | 17,862 | -5% | 1 | 1 | 0% | 3,483 | 4,053 | +16% | 0 | 0 | — |
case-22 | fail→pass | 14,582 | 8,315 | -43% | 1 | 1 | 0% | 2,216 | 1,930 | -13% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +55 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 8/11/2026 | +36% |
Other measured skills in the registry, with their headline benchmark lift.