Engineering Journal
Pdf Processor
Pdf Processor

Under the Hood: Headless Web Worker Pools and Cached Navigation Panel Ingestion

2026-08-06

TLDR

Transitioning document processing pipelines into platform-agnostic IP assets requires decoupling batch worker delegation from main-thread UI state. In this deep dive, we detail migrating core batch modules (batchGraph.js, batchQueue.js, workerPool.js) into /assets/pdf-processor/batch/ using Vite @batch aliases, embedding the document queue into the navigation panel (nav-view-batch), and building an instant (<10ms) cached memory mount pipeline that eliminates main-thread worker re-extractions upon card focus.
Batch Architecture ComponentNaive ImplementationPlatform IP Implementation
Worker Task DispatchDirect inline Web Worker instantiationWorkerPool hardware-concurrency pool
Asset Path ResolutionRelational relative pathing (../../)Vite path alias @batch $\rightarrow$ /assets/pdf-processor/batch
Focus State HydrationRe-run handleFile() parsing on clickDirect memory mount from cached gxDoc IR & HTML

Technical problem statements & solutions

1. Platform IP module isolation via Vite path aliasing

Hardcoding relative paths across separate developer utility tools creates tight coupling and makes shared IP assets difficult to package into standalone packages.

Configure Vite path aliases to point to central IP asset directories:

// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({ resolve: { alias: { '@batch': path.resolve(__dirname, '../../assets/pdf-processor/batch') } } });

Using @batch allows UI controllers (batchViewController.js) to import queue managers cleanly:

import { BatchQueueManager } from '@batch/batchQueue.js';
import { WorkerPool } from '@batch/workerPool.js';

2. Off-thread Web Worker pool task distribution

Running multi-document extraction (PDF, DOCX, HTML, Markdown, JSON) on the main UI thread freezes DOM rendering and produces frame drops during drag-and-drop ingestion.

Delegate extraction tasks to a Web Worker pool sized dynamically to hardware concurrency:

export class WorkerPool {
    constructor(poolSize) {
        this.poolSize = poolSize || Math.max(1, (navigator.hardwareConcurrency || 4) - 1);
        this.workers = [];
        this.idleWorkers = [];
        this.taskQueue = [];
        this._initPool();
    }

processItem(item) { return new Promise((resolve, reject) => { this.taskQueue.push({ item, resolve, reject }); this._dispatchNext(); }); }

_dispatchNext() { if (this.taskQueue.length === 0 || this.idleWorkers.length === 0) return;

const workerEntry = this.idleWorkers.shift(); const task = this.taskQueue.shift(); workerEntry.busy = true;

task.item.file.arrayBuffer().then(arrayBuffer => { workerEntry.worker.postMessage({ type: 'process_batch_item', id: task.item.id, name: task.item.name, format: task.item.format, buffer: arrayBuffer }, [arrayBuffer]); // Transferable ArrayBuffer to prevent main thread copy }); } }


3. Instant cached memory mounting without re-extraction

Re-parsing raw document files when switching focus between batch cards causes memory growth (~8GB) and introduces latency.

Mount pre-extracted gxDoc IR and pre-computed HTML directly from the batch cache store:

export async function focusBatchItem(itemId, slotNum = 1) {
    const item = batchQueue.getItem(itemId);
    if (!item || item.status !== 'completed') return;

const slot = slotNum === 1 ? state.pdf1 : state.pdf2;

// 1. Mount pre-extracted cached results directly into slot state slot.file = item.file; slot.extractedHTML = item.extractedHTML || ''; slot.extractedText = item.extractedText || ''; slot.bytes = item.bytes || null; slot.gxDoc = item.gxDoc || null;

// 2. Hydrate DOM view surfaces instantly from cached IR/HTML (<10ms) applyHtmlEverywhere(slot.extractedHTML || '<p>No extracted content available.</p>', null);

if (item.format === 'pdf' && item.bytes) { await renderPDFToCanvas(item.bytes, 'pdf-canvas-container'); switchView('pdf'); } else { switchView('html'); } }

Rule of thumb: Offload multi-document parsing to Web Workers using Transferable ArrayBuffers, and mount pre-computed IR cache directly to state on focus events to achieve instant UI responsiveness.
Read this post in the full Engineering Journal →