Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Books (built-in plugin)

The built-in Books reader for EPUB and PDF books you own: importing a book onto the shelf, the paginated EPUB reader and its reading settings, the separate PDF path, where a book's data and reading position are stored, and how the plugin is changed.

What it is

Books is a personal reader for EPUB and PDF books you own. Import a book from your computer and it lands on a shelf; open it to read. EPUBs get a paginated reader with a table of contents and reading settings (font, size, line spacing, margins, justify, and a light/sepia/dark theme); PDFs get a page-by-page pdf.js reader. Either way it remembers your place per book. Highlights, notes and AI study tools are planned (Phases 3–4), not yet built. It shows up in the sidebar as "Books" once the app loads.

It is a plugin — but a bundled built-in, not Marketplace-only

Books is a first-party, sandboxed plugin living at src/plugins/books/, but unlike a Marketplace-only plugin (e.g. Reading Queue) it is bundled into the app: it is not in plugin-loader.ts's BUILTIN_EXCLUDE, so it ships and loads as a built-in with no separate install.

  • Its UI is hand-authored vanilla JS served into an isolated webview — no React, no bundler, no build step, and no access to the app's component kit. It reaches the host only through the window.AgentMC bridge (its db namespace); it cannot import src/renderer/** or @shared/**.
  • Strict CSP. The webview runs under script-src 'self' with no unsafe-inline — every handler is attached with addEventListener in plugin.js, so no inline handler is ever needed (deliberately stricter than its sibling plugins).

Where to find it

Books appears as its own entry in the sidebar once the app loads — there is nothing to enable and no separate install, because it ships inside the app as a built-in plugin. Open it to see your shelf, import a book from your computer, then click a book to start reading.

How it behaves

Reading a book (the reader engine)

Opening a book mounts a vendored foliate-js fork (7 modules, pinned by SHA) in the webview. The subtle part is HOW chapters are served — same-origin, never blob::

  • The main process already unpacked the EPUB at import and serves its files under a scoped @data path (/plugin/books/@data/<dir>/<href>), each response carrying a strict script-src 'none' CSP.
  • foliate is driven over those same-origin files: reader-engine.js uses foliate only to PARSE (spine, table of contents, CFI) via a fetch-backed loader, then overrides each section.load() to return the same-origin @data URL. foliate's native loader would mint a blob: URL per chapter, which the plugin CSP floor forbids (frame-src 'self') — and a blob doc carries no response CSP, so the same-origin path is also the stricter one.
  • Your place is saved as { sectionIndex, progression, cfi } in the book's position column (throttled, and when you leave), and restored on open (precise CFI first, then a clamped section index). Fixed-layout EPUBs are refused with a message (that renderer is not vendored).
  • Reading settings (the "Aa" panel) — font, size, line spacing, margins, justify, and a light/sepia/dark theme — become a stylesheet (lib/typography.js cssFromPrefs, html/body !important so a book's own CSS can't win) applied via view.renderer.setStyles, which foliate re-applies on every chapter load. They persist in the plugin's KV storage (amc.storage, the storage permission) and are normalized on read, so a stale value can never break the reader.

PDFs take a separate path — they bypass foliate entirely. The importer stores the file as book.pdf and validates it in the main process via pdf.js (page count + metadata, with scripting and eval off); a password-protected, empty, or corrupt PDF is refused with a friendly message and no half-imported book. The reader fetches those bytes same-origin from the @data route (never navigating to them) and draws the current page onto a canvas with vendored pdf.js — one visible page at a time, fit to width, at a capped resolution so a very large page can't exhaust memory. Page turns use the same arrow / on-screen controls, your page is saved as { pdfPage } (in the same position column EPUBs use for their CFI), and the EPUB-only contents + "Aa" controls are hidden. A PDF shows a placeholder cover on the shelf (page-1 thumbnails are a later polish). Because Books is a built-in plugin its CSP already allows what pdf.js needs (the same-origin worker + eval), so no CSP change was required.

Full invariants + the tests that lock them: books-plugin-contract.md.

How it stores data

The plugin owns a books collection declared in its manifest (storage.collections) — the host creates and evolves the table. Each row records the book's title, author, format, on-disk dir, cover_path, spine (JSON), added_at, a progress fraction, the exact reading position (JSON — { sectionIndex, progression, cfi } for an EPUB, { pdfPage } for a PDF), and last_opened_at. No PDF-specific column was needed (the page count is read live from the document at open). New fields are added additively — a manifest column plus a plugin.version bump, and the host runs ALTER TABLE ADD COLUMN on upgrade (existing books untouched). Highlights and notes get their own collections in a later phase.

Usage telemetry

Books is tracked in FEATURE_REGISTRY (id books, status instrumented) — importing a book emits an imported feature_events datum so the feature's usage is counted, like every other tracked surface. Only the anonymous action is recorded; no book content or personal data is sent.

For agents

How the pieces fit

Concern Where
Manifest src/plugins/books/manifest.json
UI shell ui/index.html, ui/plugin.js, ui/styles.css
Shelf view ui/views/shelf.js
Reader view ui/views/reader.js (chrome + table of contents)
Reader engine ui/lib/reader-engine.js (foliate glue + position)
PDF reader ui/lib/pdf-reader.js (pure page/scale) + plugin.js render
PDF engine ui/vendor/pdfjs/ (pinned pdf.js + VENDOR.md)
Book-data URLs ui/lib/urls.js (validated @data builder)
Reading engine ui/vendor/foliate-js/ (pinned fork + VENDOR.md)
Import (main) src/main/services/books/import-book.ts (EPUB + PDF)
Data layer ui/lib/data.js (over AgentMC.db)
Escaping ui/lib/escape.js
Serving + MIME src/main/services/plugin/plugin-server.ts

Changing it

Books is a sandboxed webview plugin: new capability goes in its ui/, never in core src/renderer/** or src/main/** (the main-process touchpoints the reader needs are the .xhtml/.pdf content-type map in plugin-server.ts and the import parse in import-book.ts). The pure logic + engine mapping have node unit tests (tests/unit/plugins/books-*.test.ts); the full open→render→navigate→resume loop is covered by tests/e2e/ui/books-reader.spec.ts, and otherwise verify UI behavior by hand in the dev instance. Because Books is bundled (not Marketplace-only), it must not be added to BUILTIN_EXCLUDE, or it would stop shipping inside the app. Invariants are locked in books-plugin-contract.md.

Related

Last verified 2026-09-23