A client-only, statically-built web UI for every conversion and editing tool in the documents.js ecosystem — convert and edit docx, pptx, xlsx, odt, odp, ods, odg, pdf, and markdown documents entirely in the browser, with no server component.
Private (unpublished to npm); deployed as a static site to GitHub Pages.
Requires Node.js >=20 and pnpm 11.6.0.
pnpm install
pnpm dev # vite dev serverpnpm build # tsc (app + worker tsconfigs) then vite build -> dist/
pnpm preview # serve the production build locally
pnpm typecheck # tsc across tsconfig.json, tsconfig.worker.json, tsconfig.node.json
pnpm lint # eslint . --cache --max-warnings 0
pnpm test # vitest run --project unit
pnpm test:watch # vitest --project unit
pnpm test:coverage # vitest run --project unit --coverage
pnpm test:e2e # playwright testTo run a single unit test file, pass its path: pnpm exec vitest run --project unit src/shared/transferables.test.ts.
test:e2e has no Playwright config or spec files in the repository yet — the script is wired up but there is nothing for it to run until both exist.
The app is split into a main-thread UI and a Web Worker that holds the only code allowed to touch real document bytes:
src/workers/documents.worker.tsrunssrc/rpc/router.ts, an oRPC router that is the sole caller ofdocuments.js's conversion, metadata, and font functions, and the sole importer of the sibling codec packages (odf.js,ooxml.js,pdf-codec,markdown-codec).src/rpc/client.tsis how everything on the main thread reaches the worker — UI code, routes, hooks, and features never importdocuments.jsor its sibling packages directly (see Conventions below).src/routes/are TanStack Router file-based routes (src/routeTree.gen.tsis generated, not hand-edited).src/ports/+src/adapters/hold a small ports-and-adapters boundary for browser capabilities that need a fallback:FileAccessPorthas anativeFileAccessimplementation (File System Access API) and afallbackFileAccessimplementation for browsers without it, selected bycreateFileAccess.ts.src/db/dexie.tsis the local IndexedDB store (recent files, preferences) via Dexie.src/hooks/wrap the RPC client and Dexie store in React Query-friendly hooks consumed bysrc/routes/andsrc/ui/.
- UI code (
src/routes/,src/features/,src/hooks/,src/ui/) may not importdocuments.js's conversion/editor functions or any sibling package (odf.js,ooxml.js,pdf-codec,markdown-codec) directly — enforced by an ESLintno-restricted-importsrule. Onlysrc/workers/**may import them; everything else goes throughsrc/rpc/client.ts. A handful ofdocuments.jsexports with no non-Zod runtime dependencies (DocumentFormatSchema,DOCUMENT_FORMATS, and the plainContent*/Diagnostic/DocumentPayloadtypes) are allowlisted for direct import since they don't pull the conversion engine into the main bundle. - Uses this org's shared
@exadev/eslint-config(exadevRecommendedTypeChecked), which bans type assertions and@ts-expect-erroroutside test files and defaults its barrel-policy rule tobanned— this app has no public npm entry point, so that default is left as-is rather than overridden. - Route files under
src/routes/**/*.tsxare exempt fromexadev/barrel-policy,react-refresh/only-export-components, and@typescript-eslint/only-throw-error— all three collide with TanStack Router's own file-based-routing conventions (index-file naming, the exportedRoute'scomponent:property, andredirect()/notFound()as thrown control-flow objects) rather than being an avoidable choice in this codebase.
- The production build is served from
/documents/on GitHub Pages (set viabaseinvite.config.tswhenCIis set) but from/in local dev — a build produced locally withCIunset will have the wrong base path if deployed as-is. - This is a PWA (
vite-plugin-pwa,autoUpdate). The worker bundle (by far the largest built asset) is deliberately excluded from the Workbox precache list and instead cached at runtime on first use via aCacheFirstrule, so it doesn't block install or blow the default precache size budget. src/workers/documents.worker.tsis a browser Web Worker, unrelated to Cloudflare Workers — this repo has nowranglerconfig and doesn't deploy to Cloudflare, unlike some sibling packages in the ecosystem.
Releases are fully automated: a push to main runs semantic-release in CI (Conventional Commits, no manual version bump or publish step), and the GitHub Pages deploy is built from the post-release commit so a release's deployed site always matches its tagged version.
- documents.js ecosystem overview — how this app relates to the sibling packages it depends on.