A brutalist, off-the-grid Wikipedia reader for ZIM archives. Built with Electron + React + @openzim/libzim, designed to run comfortably on small low-power devices (Hackberry CM5 / Ubuntu ARM) down to a 720×720 screen.
- Reads any Kiwix
.zimarchive dropped into the project root orpublic/(or linked via the import dialog). Multiple archives, switchable live. - Full media support - images, math SVGs, audio/video are streamed straight out of the archive through a custom
zim://Electron protocol. No extraction, no temp files. (Note:nopicandminiflavour ZIMs ship without photographs - use amaxiflavour to get pictures.) - Real article rendering - infoboxes, tables, figures and captions survive parsing and are restyled into the app's design language. Navigation clutter (navboxes, sidebars, reference lists, edit links) is stripped.
- Full-text search (Xapian index) with live title suggestions, falling back to title search when the archive has no full-text index.
- Featured feed - the illustrated featured-articles grid that ships on the ZIM's main page (the only photographs in a
nopicarchive) is harvested and shown on the home view. - Expedition feed - a shuffleable grid of random articles for aimless discovery.
- Reader affordances - numbered table of contents, back/forward history, bookmarks, recent searches, reading-time estimate, image lightbox, paper/ink themes. All state persists per archive.
- The Pretext ribbon - every article opens with a slim canvas band where the lead paragraph is typeset by a miniature Pretext-style layout engine: words are measured once, then re-wrapped with pure arithmetic at 60 fps while the wrap margin animates in. Drag the dashed margin with your pointer to reflow the text live; click to replay. The caption shows the honest numbers (
n WORDS / m LINES / <0.1 MS).
| Action | Key |
|---|---|
| Focus search | / |
| Back / Forward | Alt+← / Alt+→ (or Cmd on macOS) |
| Navigate suggestions | ↑ ↓ Enter, Esc to dismiss |
electron/
main.js # library management, zim:// protocol, article parsing IPC
preload.cjs # context-isolated bridge
src/
store/auraStore.ts # zustand app state: views, history, bookmarks
components/ # TopBar, HomeView, SearchView, ArticleView, StatusBar
styles.css # the brutalist design system (paper + ink themes)
Article pipeline (main process): libzim entry → linkedom DOM → strip chrome/scripts → resolve every img/src, a/href against the article path → rewrite media to zim://<archiveId>/<path> and internal links to data-zim-path attributes → sanitize-html allowlist → renderer intercepts clicks and routes them through the store.
Search snippets and the random feed use a regex-based lightweight summarizer (no DOM parse) with an LRU cache, keeping cold-start costs low on ARM hardware.
bun install
bun dev # vite + electron with live reload
bun run build # production renderer build into dist/Place .zim files in the project root or public/. Get archives from library.kiwix.org - for a picture-rich experience pick a maxi flavour (e.g. wikipedia_en_top_maxi).
Two ways to produce the installable artifacts (.deb + .AppImage, output in dist-electron/):
Cross-build from a Mac (Apple Silicon) via Docker - the easy path. Start Docker Desktop, then:
bun run package:linux-arm64:dockerscripts/build-linux-arm64.sh runs the whole build in a native linux/arm64 node:22 container (node_modules isolated in a Docker volume, so your macOS install is untouched). Note that bun run package:linux-arm64 - electron-builder directly on macOS - cannot work: node-gyp can't cross-compile the libzim native module from macOS to Linux; that's what the container is for. The electronuserland/builder image doesn't work either - it's amd64-only, and under emulation the libzim installer downloads x86_64 binaries.
Or build on the device - @openzim/libzim ships prebuilt aarch64 binaries and Electron ships linux-arm64, so the stack runs natively on the CM5 (Pi 5):
# one-time toolchain (node-gyp needs these to link the libzim prebuilt)
sudo apt install -y build-essential python3 libatomic1
# install bun (or use node >= 20 + npm)
curl -fsSL https://bun.sh/install | bash
git clone <this repo> && cd pretexter-wiki
bun install # downloads libzim aarch64 + builds the binding
bun run start # build renderer + launch
# optional: produce installable artifacts (deb + AppImage)
bunx electron-builder --linux --arm64Install with:
sudo dpkg -i dist-electron/pretexter_*_arm64.deb # installs to /opt/Aura, desktop entry "Aura"
# or run the AppImage directly:
chmod +x Aura-*-arm64.AppImage && ./Aura-*-arm64.AppImageThe package ships a proper .desktop entry (aura.desktop, StartupWMClass=aura) and a 512 px icon, so it behaves in launchers and docks.
Device niceties, all automatic or via environment:
- Small screens: on displays ≤ 800 px the window opens fullscreen;
F11toggles. - ZIM location: archives are discovered in
AURA_ZIM_DIR,~/Zim, the app's user-datazim/folder, and the repo root/public/when running from source. - GPU trouble:
AURA_NO_GPU=1 auraforces software rendering.
To autostart on boot (labwc/wayfire session), add aura (or bun run start from the repo) to your compositor's autostart file.
The app is tuned for slow storage and a modest GPU out of the box:
- Library state lives in memory - navigation, bookmarks and searches never trigger a disk scan; the workspace file is written asynchronously and coalesced (single write per ~300 ms burst, flushed on quit). SD-card latency stays off the UI path.
- Article/summary LRU caches in the main process mean back/forward navigation re-renders without re-parsing, and
zim://media responses are markedimmutableso Chromium's HTTP cache serves repeat images without touching the archive. - Spellcheck is disabled (an offline device would otherwise waste memory and periodically try to download dictionaries).
- Search snippets and the random feed use a regex summarizer (no DOM parse) to keep cold starts cheap.
Things you control:
- Put ZIMs on the fastest storage you have. Full-text search (Xapian) and cluster decompression are I/O-bound; NVMe via the CM5's PCIe lane ≫ eMMC ≫ SD card.
- Prefer the GPU. Leave
AURA_NO_GPUunset unless you see rendering glitches - software rasterization costs far more CPU on a Pi-class SoC than the V3D GPU does. - On a Wayland session (labwc/wayfire), Electron picks its backend automatically; if you see poor scrolling under XWayland, try
aura --ozone-platform=wayland. - Pick a ZIM that fits your RAM/storage budget -
mini/nopicflavours keep the archive (and its search index) dramatically smaller thanmaxi.