[nu-demo] At-click-time libreoffice PDF preview for Office docs (companion to home#174) #178

Closed
opened 2026-04-24 22:32:46 +00:00 by mik-tf · 1 comment
Owner

Demo workaround for Office viewing — at-click-time PDF generation

Companion path to home#174.
home#174 tracks the canonical full-fidelity OnlyOffice Document Server install
(real interactive editor, ~3 GB Docker image, ~6 GB runtime RAM, JWT signing,
hero_router routing, save callbacks). That's the right answer for prod editing.

But for read-only demo viewing of .docx/.xlsx/.pptx, OnlyOffice is
overkill — and the PDF-companion short-circuit shipped on 2026-04-24 (see
home#174 comment) already proves the embed path works. What's missing is an
automatic, server-side generation of those companion PDFs.

Proposal

Add a pdf_preview route to hero_office_server (or _ui) that:

  1. Takes (context, filename).
  2. Looks up the source file via foundry webdav.
  3. Looks up an existing companion ${stem}.pdf in the same context.
  4. If the PDF is missing OR older than the source, runs:
    libreoffice --headless --convert-to pdf \
        -env:UserInstallation="file:///tmp/lo_profile_$$" \
        --outdir <cache-dir> <source-file>
    
    then writes the result back to webdav as ${stem}.pdf (and into a server-side
    cache for fast re-serve).
  5. Returns the PDF bytes inline (Content-Type: application/pdf,
    Content-Disposition: inline). Browser PDF viewer renders.

hero_office_ui::editor_page already has a short-circuit for .pdf and a
companion-PDF lookup for non-PDF types. The change is to make that lookup
trigger generation rather than fall through to OnlyOffice.

Why this is the right tradeoff for now

  • Lighter than OnlyOffice: ~700 MB libreoffice install (no Docker), no
    always-on RAM, only invoked on click.
  • Fidelity: libreoffice renders the actual .pptx slides / .xlsx tables
    / .docx formatting — not a markdown re-render. (This was the gap noticed
    on herodemo: pandoc → PDF previews looked like docs, not slides.)
  • No editing: read-only is fine for a demo + most consumption flows.
  • Composable with OnlyOffice: when home#174 lands, this path becomes the
    fallback for read-only views; OnlyOffice handles editing.
  • Concurrency-safe: each invocation gets its own UserInstallation profile.
  • Cached: regenerated only when source mtime > pdf mtime.

Tasks

  • hero_skills/install/ — add libreoffice-core libreoffice-impress libreoffice-writer libreoffice-calc to apt deps in the bootstrap (or
    a new install_libreoffice.nu).
  • hero_office_server — add pdf_preview(context, filename) JSON-RPC
    method that runs libreoffice headless + caches.
  • hero_office_ui::editor_page — when .docx/.xlsx/.pptx and no
    companion exists, call pdf_preview (which generates) instead of
    falling through to OnlyOffice.
  • Add LO_CONVERT_TIMEOUT_MS env (default 30s) and queue/lock so
    multiple concurrent clicks for the same file don't race.
  • Doc: per-format viewer fallback policy — env flag
    HERO_OFFICE_PDF_PREVIEW=1 (default on) to enable.
  • Smoke test: upload .pptx → click → renders within 5s on first request,
    <500ms on subsequent.

Demo-time hotfix already applied

On herodemo (2026-04-24): libreoffice installed, batch-regenerated companion
PDFs from actual .pptx/.xlsx/.docx files. Companion-PDF short-circuit
already in hero_office_ui (home#174 comment). End-user click renders real
slides/sheets via the browser PDF viewer.

This issue is the prod path — auto-generation on the server, no batch step,
no manual regeneration when files are uploaded.

  • home#174 — OnlyOffice Document Server (canonical full-fidelity editing path; doesn't block this issue)
  • home#160 — consolidated demo state

Signed-off-by: mik-tf

## Demo workaround for Office viewing — at-click-time PDF generation Companion path to [home#174](https://forge.ourworld.tf/lhumina_code/home/issues/174). home#174 tracks the canonical full-fidelity OnlyOffice Document Server install (real interactive editor, ~3 GB Docker image, ~6 GB runtime RAM, JWT signing, hero_router routing, save callbacks). That's the right answer for prod editing. But for **read-only demo viewing** of `.docx`/`.xlsx`/`.pptx`, OnlyOffice is overkill — and the PDF-companion short-circuit shipped on 2026-04-24 (see home#174 comment) already proves the embed path works. What's missing is an **automatic, server-side generation** of those companion PDFs. ## Proposal Add a `pdf_preview` route to `hero_office_server` (or `_ui`) that: 1. Takes `(context, filename)`. 2. Looks up the source file via foundry webdav. 3. Looks up an existing companion `${stem}.pdf` in the same context. 4. **If the PDF is missing OR older than the source**, runs: ``` libreoffice --headless --convert-to pdf \ -env:UserInstallation="file:///tmp/lo_profile_$$" \ --outdir <cache-dir> <source-file> ``` then writes the result back to webdav as `${stem}.pdf` (and into a server-side cache for fast re-serve). 5. Returns the PDF bytes inline (`Content-Type: application/pdf`, `Content-Disposition: inline`). Browser PDF viewer renders. `hero_office_ui::editor_page` already has a short-circuit for `.pdf` and a companion-PDF lookup for non-PDF types. The change is to make that lookup trigger generation rather than fall through to OnlyOffice. ## Why this is the right tradeoff for now - **Lighter than OnlyOffice**: ~700 MB libreoffice install (no Docker), no always-on RAM, only invoked on click. - **Fidelity**: libreoffice renders the actual `.pptx` slides / `.xlsx` tables / `.docx` formatting — not a markdown re-render. (This was the gap noticed on herodemo: pandoc → PDF previews looked like docs, not slides.) - **No editing**: read-only is fine for a demo + most consumption flows. - **Composable with OnlyOffice**: when home#174 lands, this path becomes the fallback for read-only views; OnlyOffice handles editing. - **Concurrency-safe**: each invocation gets its own `UserInstallation` profile. - **Cached**: regenerated only when source mtime > pdf mtime. ## Tasks - [ ] `hero_skills/install/` — add `libreoffice-core libreoffice-impress libreoffice-writer libreoffice-calc` to apt deps in the bootstrap (or a new `install_libreoffice.nu`). - [ ] `hero_office_server` — add `pdf_preview(context, filename)` JSON-RPC method that runs libreoffice headless + caches. - [ ] `hero_office_ui::editor_page` — when `.docx`/`.xlsx`/`.pptx` and no companion exists, call `pdf_preview` (which generates) instead of falling through to OnlyOffice. - [ ] Add `LO_CONVERT_TIMEOUT_MS` env (default 30s) and queue/lock so multiple concurrent clicks for the same file don't race. - [ ] Doc: per-format viewer fallback policy — env flag `HERO_OFFICE_PDF_PREVIEW=1` (default on) to enable. - [ ] Smoke test: upload `.pptx` → click → renders within 5s on first request, <500ms on subsequent. ## Demo-time hotfix already applied On herodemo (2026-04-24): libreoffice installed, batch-regenerated companion PDFs from actual `.pptx`/`.xlsx`/`.docx` files. Companion-PDF short-circuit already in `hero_office_ui` (home#174 comment). End-user click renders real slides/sheets via the browser PDF viewer. This issue is the prod path — auto-generation on the server, no batch step, no manual regeneration when files are uploaded. ## Related - [home#174](https://forge.ourworld.tf/lhumina_code/home/issues/174) — OnlyOffice Document Server (canonical full-fidelity editing path; doesn't block this issue) - [home#160](https://forge.ourworld.tf/lhumina_code/home/issues/160) — consolidated demo state Signed-off-by: mik-tf
Author
Owner

Resolved by lhumina_code/hero_skills@7c823d1 (PR lhumina_code/hero_skills#126).

Part of Phase 2 tracker #185.

Resolved by https://forge.ourworld.tf/lhumina_code/hero_skills/commit/7c823d1 (PR https://forge.ourworld.tf/lhumina_code/hero_skills/pulls/126). Part of Phase 2 tracker https://forge.ourworld.tf/lhumina_code/home/issues/185.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/home#178
No description provided.