Natives media + system utilities
This document is a subsystem deep-dive for the system/media/conversion primitives layer described in docs/natives-architecture.md: image, html, clipboard, and work profiling.
Implementation files
crates/pi-natives/src/image.rscrates/pi-natives/src/html.rscrates/pi-natives/src/clipboard.rscrates/pi-natives/src/prof.rscrates/pi-natives/src/task.rspackages/natives/src/image/index.tspackages/natives/src/image/types.tspackages/natives/src/html/index.tspackages/natives/src/html/types.tspackages/natives/src/clipboard/index.tspackages/natives/src/clipboard/types.tspackages/natives/src/work/index.tspackages/natives/src/work/types.ts
Note: there is no
crates/pi-natives/src/work.rs; work profiling is implemented inprof.rsand fed by instrumentation intask.rs.
TS API ↔ Rust export/module mapping
| TS export (packages/natives) | Rust N-API export | Rust module |
|---|---|---|
PhotonImage.parse(bytes) | PhotonImage::parse (js_name = "parse") | image.rs |
PhotonImage#resize(width, height, filter) | PhotonImage::resize (js_name = "resize") | image.rs |
PhotonImage#encode(format, quality) | PhotonImage::encode (js_name = "encode") | image.rs |
htmlToMarkdown(html, options) | html_to_markdown (js_name = "htmlToMarkdown") | html.rs |
copyToClipboard(text) | copy_to_clipboard (js_name = "copyToClipboard") + TS fallback logic | clipboard.rs + clipboard/index.ts |
readImageFromClipboard() | read_image_from_clipboard (js_name = "readImageFromClipboard") | clipboard.rs |
getWorkProfile(lastSeconds) | get_work_profile | prof.rs |
Data format boundaries and conversions
Image (image)
- JS input boundary:
Uint8Arrayencoded image bytes. - Rust decode boundary: bytes are copied to
Vec<u8>, format is guessed withImageReader::with_guessed_format(), then decoded toDynamicImage. - In-memory state:
PhotonImagestoresArc<DynamicImage>. - Output boundary:
encode(format, quality)returnsPromise<Uint8Array>(RustVec<u8>).
Format IDs are numeric:
0: PNG1: JPEG2: WebP (lossless encoder)3: GIF
Constraints:
qualityis only used for JPEG.- PNG/WebP/GIF ignore
quality. - Unsupported format IDs fail (
Invalid image format: <id>).
HTML conversion (html)
- JS input boundary: HTML
string+ optional object{ cleanContent?: boolean; skipImages?: boolean }. - Rust conversion boundary:
Stringinput is converted byhtml_to_markdown_rs::convert. - Output boundary: Markdown
string.
Conversion behavior:
cleanContentdefaults tofalse.- When
cleanContent=true, preprocessing is enabled withPreprocessingPreset::Aggressiveand hard-removal flags for navigation/forms. skipImagesdefaults tofalse.
Clipboard (clipboard)
- Text path:
- TS first emits OSC 52 (
\x1b]52;c;<base64>\x07) when stdout is a TTY. - Same text is then attempted via native clipboard API (
native.copyToClipboard) as best-effort. - On Termux, TS attempts
termux-clipboard-setfirst.
- TS first emits OSC 52 (
- Image read path:
- Rust reads raw image from
arboard. - Rust re-encodes it to PNG bytes (
imagecrate), returns{ data: Uint8Array, mimeType: "image/png" }. - TS returns
nullearly on Termux or Linux sessions without display server (DISPLAY/WAYLAND_DISPLAYmissing).
- Rust reads raw image from
Work profiling (work)
- Collection boundary: profiling samples are produced by
profile_region(tag)guards intask::blockingandtask::future. - Storage format: fixed-size circular buffer (
MAX_SAMPLES = 10_000) storing stack path + duration (μs) + timestamp (μs since process start). - Output boundary:
getWorkProfile(lastSeconds)returns object:folded: folded-stack text (flamegraph input)summary: markdown table summarysvg: optional flamegraph SVGtotalMs,sampleCount
Lifecycle and state transitions
Image lifecycle
PhotonImage.parse(bytes)schedules a blocking decode task (image.decode).- On success, a native
PhotonImagehandle exists in JS. resize(...)creates a new native handle (image.resize), old and new handles can coexist.encode(...)materializes bytes (image.encode) without mutating image dimensions.
Failure transitions:
- Format detection/decode failure rejects parse promise.
- Encode failure rejects encode promise.
- Invalid format ID rejects encode promise.
HTML lifecycle
htmlToMarkdown(html, options)schedules a blocking conversion task.- Conversion runs with defaulted options (
cleanContent=false,skipImages=false) unless specified. - Returns markdown string or rejects.
Failure transitions:
- Converter failure returns rejected promise (
Conversion error: ...).
Clipboard lifecycle
copyToClipboard(text) is intentionally best-effort and multi-path:
- If TTY: attempt OSC 52 write (base64 payload).
- Try Termux command when
TERMUX_VERSIONis set. - Try native
arboardtext copy. - Swallow errors at TS layer.
readImageFromClipboard() strictness differs by stage:
- TS hard-gates unsupported runtime contexts (Termux/headless Linux) to
null. - Rust
arboardread runs only when TS allows it. ContentNotAvailablemaps tonull.- Other Rust errors reject.
Work profiling lifecycle
- No explicit start: profiling is always on when task helpers execute.
- Every instrumented task scope records one sample on guard drop.
- Samples overwrite oldest entries after buffer capacity is reached.
getWorkProfile(lastSeconds)reads a time window and derives folded/summary/svg artifacts.
Failure transitions:
- SVG generation failure is soft-fail (
svg: null), while folded and summary still return. - Empty sample window returns empty folded data and
svg: null, not an error.
Unsupported operations and error propagation
Image
- Unsupported decode input or corrupted bytes: strict failure (promise rejection).
- Unsupported encode format ID: strict failure.
- No best-effort fallback path in TS wrapper.
HTML
- Conversion errors are strict failures (rejection).
- Option omission is best-effort defaulting, not failure.
Clipboard
- Text copy is best-effort at TS layer: operational failures are suppressed.
- Image read distinguishes "no image" (
null) from operational failure (rejection). - Termux/headless Linux are treated as unsupported contexts for image read (
null).
Work profiling
- Retrieval is strict for function call itself, but artifact generation is partially best-effort (
svgnullable). - Buffer truncation is expected behavior (ring buffer), not data loss bug.
Platform caveats
- Clipboard text: OSC 52 depends on terminal support; native clipboard access depends on desktop environment/session.
- Clipboard image read: blocked in TS for Termux and Linux without display server.