Preview panel phase 2: office formats (docx, xlsx, pptx) via vendored client-side renderers #297

Closed
opened 2026-08-20 20:40:35 +00:00 by Cordy · 7 comments
Owner

Phase 2 of #295 (phasing decision 2026-08-20: native formats ship first).

Approach — client-side, sovereign, no conversion server:

  • docx: docx-preview (MIT, needs jszip) — renders Word → HTML with good fidelity for typical documents.
  • xlsx: SheetJS CE (Apache-2.0) parse + own table render, or xlsx-preview (exceljs-based, styled HTML).
  • pptx: PptxViewJS — canvas-based client-side slide renderer (zoom/thumbnails); evaluate fidelity on real decks first, fall back to "no preview" card if it disappoints.

Constraints:

  • Vendor the dist bundles into web/static/vendor/ (served from the Go binary via the existing static embed) — no CDN, LAN + air-gapped instances must work.
  • Lazy-load via dynamic <script> injection on first office preview — keeps initial page weight unchanged (~1 MB total across the three libs, cached after first use).
  • Same size cap policy as #295 (~20 MB; larger → download card).
  • License notices for vendored libs in a NOTICE/credits section.
  • Fidelity expectation: preview-grade, not print-grade (complex Word layouts approximate). Set that expectation in the docs handbook.

Legacy binary formats (.doc/.xls/.ppt) stay download-only.

Phase 2 of #295 (phasing decision 2026-08-20: native formats ship first). **Approach — client-side, sovereign, no conversion server:** - **docx:** [docx-preview](https://github.com/VolodymyrBaydalka/docxjs) (MIT, needs jszip) — renders Word → HTML with good fidelity for typical documents. - **xlsx:** SheetJS CE (Apache-2.0) parse + own table render, or [xlsx-preview](https://www.npmjs.com/package/xlsx-preview) (exceljs-based, styled HTML). - **pptx:** [PptxViewJS](https://github.com/gptsci/pptxviewjs) — canvas-based client-side slide renderer (zoom/thumbnails); evaluate fidelity on real decks first, fall back to "no preview" card if it disappoints. **Constraints:** - Vendor the dist bundles into `web/static/vendor/` (served from the Go binary via the existing static embed) — **no CDN**, LAN + air-gapped instances must work. - Lazy-load via dynamic `<script>` injection on first office preview — keeps initial page weight unchanged (~1 MB total across the three libs, cached after first use). - Same size cap policy as #295 (~20 MB; larger → download card). - License notices for vendored libs in a `NOTICE`/credits section. - Fidelity expectation: preview-grade, not print-grade (complex Word layouts approximate). Set that expectation in the docs handbook. Legacy binary formats (.doc/.xls/.ppt) stay download-only.
Author
Owner

Office previews are now implemented and live on the dogfoods (v0.6.70).

Shipped (all vendored, no CDN — served from Cairn's own origin behind auth, lazy-loaded on first use, each falling back to the existing "No preview" card on error):

  • docx — docx-preview + JSZip (MIT). PR #317, v0.6.68.
  • xlsx / xls / ods — SheetJS CE (Apache-2.0), HTML-table render with a sheet picker for multi-sheet books. PR #318, v0.6.69.
  • pptx — PptxViewJS 1.1.9 + Chart.js (MIT), canvas render with ‹/› slide nav and an i / N counter. PR #319, v0.6.70.

Verification note: each PR was node --check-gated in CI, but functional rendering fidelity needs a logged-in browser with real files — that's an operator visual check on the dogfood. Every renderer call is defensively wrapped so an unrenderable file degrades to the download card rather than breaking the panel.

Remaining / suggested follow-up (leaving this open for it):

  • Fidelity spot-check across real-world docs/sheets/decks (esp. pptx, which varies most by deck; chart slides depend on the Chart.js registration path).
  • Decide whether legacy formats — .doc, .ppt, .odt, .odp — are worth additional converters. They currently still fall through to the download card.
  • Bundle-size note: the pptx path lazy-loads ~920 KB (jszip + chart.js + PptxViewJS) on first use; docx ~170 KB; xlsx ~930 KB. All lazy, none on initial page load.
Office previews are now implemented and live on the dogfoods (v0.6.70). **Shipped (all vendored, no CDN — served from Cairn's own origin behind auth, lazy-loaded on first use, each falling back to the existing "No preview" card on error):** - **docx** — docx-preview + JSZip (MIT). PR #317, v0.6.68. - **xlsx / xls / ods** — SheetJS CE (Apache-2.0), HTML-table render with a sheet picker for multi-sheet books. PR #318, v0.6.69. - **pptx** — PptxViewJS 1.1.9 + Chart.js (MIT), canvas render with ‹/› slide nav and an `i / N` counter. PR #319, v0.6.70. **Verification note:** each PR was `node --check`-gated in CI, but functional rendering fidelity needs a logged-in browser with real files — that's an operator visual check on the dogfood. Every renderer call is defensively wrapped so an unrenderable file degrades to the download card rather than breaking the panel. **Remaining / suggested follow-up (leaving this open for it):** - Fidelity spot-check across real-world docs/sheets/decks (esp. pptx, which varies most by deck; chart slides depend on the Chart.js registration path). - Decide whether legacy formats — `.doc`, `.ppt`, `.odt`, `.odp` — are worth additional converters. They currently still fall through to the download card. - Bundle-size note: the pptx path lazy-loads ~920 KB (jszip + chart.js + PptxViewJS) on first use; docx ~170 KB; xlsx ~930 KB. All lazy, none on initial page load.
Author
Owner

Deep dive 2026-09-04: the "docx broken" report, legacy formats, and the conversion question.

1. The reported regression is not one

Verified live on cairn-openbao (browser session, console + network instrumented): docx preview renders, no errors, vendor bundles served. The operator's failure coincided with the v0.6.122/123 rollout window — Recreate wiped the session, the preview's blob fetch failed, and the catch path displayed pvNoPreview, whose copy claims "No preview for this file type". Two real defects fall out:

  • Error card lies. All three office renderers reuse pvNoPreview in their catch blocks (index.html ~4787/4817/4862). A transient failure masquerades as a capability gap. Fix: dedicated pvRenderFailed copy + a Retry button.
  • Fidelity jank is real. docx-preview paints at document width with no scale-to-fit; a wide title heading overflows the panel. Fix: constrain/scale the render container (CSS transform scale to panel width, or docx-preview's inWrapper + width capping).

2. Legacy formats (.doc, .ppt, .odt, .odp) — the "easy win" isn't, client-side

  • .doc / .ppt (OLE2 binaries): no maintained client-side JS renderer exists. Full stop. Anything found is abandonware or text-extraction only.
  • .odt / .odp: the only real client-side renderer is WebODF — AGPL and dormant since ~2015. Licensing friction + bitrot; not vendoring that.
  • .xls / .ods: already covered — SheetJS handles both since v0.6.69.

So client-side has reached its ceiling. The industry answer for legacy (and for print-grade fidelity generally) is server-side conversion to PDF, feeding the existing PDF viewer:

  • Seafile: office-preview container running LibreOffice with a worker pool — exactly this pattern.
  • Nextcloud/ownCloud: either full WOPI suites (Collabora/OnlyOffice) or LibreOffice-based preview generation.
  • Gotenberg: purpose-built HTTP API around LibreOffice (+Chromium), multi-arch images incl. arm64, request queueing built in.

Costs and constraints (Pi-class honesty): a LibreOffice-based converter is a ~1 GB image, ~200–500 MB RAM idle, spikes to ~1 GB per conversion, 1–6 s per typical document, and LibreOffice's internal lock serialises conversions per instance (scale = more replicas). Security: it parses hostile documents — containerised, no network egress, tight limits, treat as untrusted-input surface. On this cluster it belongs on the x86 Proxmox side as one shared service, not per-Pi.

Sovereignty fit: all self-hosted, but a hard dependency would break the single-binary ethos — so it must be an optional preview.converterUrl-style config. Unconfigured ⇒ legacy formats keep today's download card. Cache converted PDFs (bounded, ETag-keyed, local scratch — never written plaintext to the backend, which holds ciphertext only).

Rejected: vendoring WebODF (AGPL + dead), client-side .doc (does not exist), full WOPI editor suites (heavyweight editors, not previews; licensing/seat limits).

3. Recommendation, tiered

  1. Now, frontend-only: honest error card + Retry; scale-to-fit for docx (kills the jank). Small, ships immediately.
  2. Legacy, properly: optional converter service (Gotenberg preferred: queueing + multi-arch + maintained; plain soffice sidecar as the minimal alternative) → convert doc/ppt/odt/odp → PDF → existing viewer. Also gives a "high fidelity" path for complex docx/pptx that the JS renderers butcher. Design-first: mockup the converting/unconfigured/failed card states before building.
  3. Never: silently shipping AGPL abandonware for two formats.
**Deep dive 2026-09-04: the "docx broken" report, legacy formats, and the conversion question.** ## 1. The reported regression is not one Verified live on cairn-openbao (browser session, console + network instrumented): docx preview renders, no errors, vendor bundles served. The operator's failure coincided with the v0.6.122/123 rollout window — Recreate wiped the session, the preview's blob fetch failed, and the catch path displayed `pvNoPreview`, **whose copy claims "No preview for this file type"**. Two real defects fall out: - **Error card lies.** All three office renderers reuse `pvNoPreview` in their catch blocks (index.html ~4787/4817/4862). A transient failure masquerades as a capability gap. Fix: dedicated `pvRenderFailed` copy + a Retry button. - **Fidelity jank is real.** docx-preview paints at document width with no scale-to-fit; a wide title heading overflows the panel. Fix: constrain/scale the render container (CSS transform scale to panel width, or docx-preview's `inWrapper` + width capping). ## 2. Legacy formats (.doc, .ppt, .odt, .odp) — the "easy win" isn't, client-side - **.doc / .ppt (OLE2 binaries):** no maintained client-side JS renderer exists. Full stop. Anything found is abandonware or text-extraction only. - **.odt / .odp:** the only real client-side renderer is WebODF — **AGPL and dormant since ~2015**. Licensing friction + bitrot; not vendoring that. - **.xls / .ods:** already covered — SheetJS handles both since v0.6.69. So client-side has reached its ceiling. The industry answer for legacy (and for print-grade fidelity generally) is **server-side conversion to PDF**, feeding the existing PDF viewer: - **Seafile:** office-preview container running LibreOffice with a worker pool — exactly this pattern. - **Nextcloud/ownCloud:** either full WOPI suites (Collabora/OnlyOffice) or LibreOffice-based preview generation. - **Gotenberg:** purpose-built HTTP API around LibreOffice (+Chromium), multi-arch images incl. arm64, request queueing built in. **Costs and constraints (Pi-class honesty):** a LibreOffice-based converter is a ~1 GB image, ~200–500 MB RAM idle, spikes to ~1 GB per conversion, 1–6 s per typical document, and LibreOffice's internal lock serialises conversions per instance (scale = more replicas). Security: it parses hostile documents — containerised, no network egress, tight limits, treat as untrusted-input surface. On this cluster it belongs on the x86 Proxmox side as one shared service, not per-Pi. **Sovereignty fit:** all self-hosted, but a hard dependency would break the single-binary ethos — so it must be an **optional** `preview.converterUrl`-style config. Unconfigured ⇒ legacy formats keep today's download card. Cache converted PDFs (bounded, ETag-keyed, local scratch — never written plaintext to the backend, which holds ciphertext only). **Rejected:** vendoring WebODF (AGPL + dead), client-side .doc (does not exist), full WOPI editor suites (heavyweight editors, not previews; licensing/seat limits). ## 3. Recommendation, tiered 1. **Now, frontend-only:** honest error card + Retry; scale-to-fit for docx (kills the jank). Small, ships immediately. 2. **Legacy, properly:** optional converter service (Gotenberg preferred: queueing + multi-arch + maintained; plain `soffice` sidecar as the minimal alternative) → convert doc/ppt/odt/odp → PDF → existing viewer. Also gives a "high fidelity" path for complex docx/pptx that the JS renderers butcher. Design-first: mockup the converting/unconfigured/failed card states before building. 3. **Never:** silently shipping AGPL abandonware for two formats.
Author
Owner

Decision 2026-09-04 — blessed by Nikola, design-first.

  1. Tier 1 ships first (frontend-only): split the shared pvNoPreview copy — unknown-type card stays; render/fetch failures get their own "Preview failed" copy + Retry. Scale-to-fit for the docx render.
  2. Optional converter service is the legacy path (.doc/.ppt/.odt/.odp → PDF → existing viewer). Gotenberg-style, default-off, one URL knob; Cairn-alone stays the floor and an unreachable converter degrades to the download card, never an error.
  3. Packaging: both distribution artifacts gain the converter as opt-in — Helm chart converter.* values block (renders Deployment + ClusterIP Service, optional NetworkPolicy, injects CAIRN_PREVIEW_CONVERTER_URL, or url to reuse an external instance) and docker-compose service under profiles: ["preview"] with no published ports. Env override wins over config.json (CAIRN_LICENSE_KEY precedent).
  4. Fidelity pivot reserved: per-format routing knob so docx/pptx/odt previews can be switched to the converter path if the JS renderers still disappoint after scale-to-fit. Explicitly part of the design.
  5. Docs are in scope, not optional: handbook must document the converter for both Helm and Docker deployments — trust model (plaintext Cairn→converter hop, internal-only, no keys, no backend access), exact values/compose snippets, routing knob, degrade behaviour.

Order: mockups of the preview panel states → tier-1 fixes → converter client + packaging + docs.

**Decision 2026-09-04 — blessed by Nikola, design-first.** 1. **Tier 1 ships first (frontend-only):** split the shared `pvNoPreview` copy — unknown-type card stays; render/fetch failures get their own "Preview failed" copy + Retry. Scale-to-fit for the docx render. 2. **Optional converter service is the legacy path** (.doc/.ppt/.odt/.odp → PDF → existing viewer). Gotenberg-style, default-off, one URL knob; Cairn-alone stays the floor and an unreachable converter degrades to the download card, never an error. 3. **Packaging:** both distribution artifacts gain the converter as opt-in — Helm chart `converter.*` values block (renders Deployment + ClusterIP Service, optional NetworkPolicy, injects `CAIRN_PREVIEW_CONVERTER_URL`, or `url` to reuse an external instance) and docker-compose service under `profiles: ["preview"]` with no published ports. Env override wins over config.json (CAIRN_LICENSE_KEY precedent). 4. **Fidelity pivot reserved:** per-format routing knob so docx/pptx/odt previews can be switched to the converter path if the JS renderers still disappoint after scale-to-fit. Explicitly part of the design. 5. **Docs are in scope, not optional:** handbook must document the converter for both Helm and Docker deployments — trust model (plaintext Cairn→converter hop, internal-only, no keys, no backend access), exact values/compose snippets, routing knob, degrade behaviour. Order: mockups of the preview panel states → tier-1 fixes → converter client + packaging + docs.
Author
Owner

Tier 1 shipped — v0.6.124 (PR #420, merged; both dogfoods rolled at 02:13 UTC).

  • Render/fetch failures in docx/sheet/pptx previews now show "The preview could not be loaded" + Try again (pvFail, re-runs renderPvBody in place). "No preview for this file type" survives only on the genuinely-unpreviewable path — one use left in the file, verified by post-condition.
  • docx scale-to-fit: pvFit zooms .docx-wrapper to the panel width and re-fits on panel resize (ResizeObserver).
  • i18n: pvFailTitle / pvFailBody / pvRetry ×4 locales.

Remaining on this issue: converter service (client + CAIRN_PREVIEW_CONVERTER_URL + per-format routing), Helm converter.* block + compose preview profile, handbook docs. Mockups approved (states A–E, single Download button in the footer only).

**Tier 1 shipped — v0.6.124** (PR #420, merged; both dogfoods rolled at 02:13 UTC). - Render/fetch failures in docx/sheet/pptx previews now show "The preview could not be loaded" + **Try again** (`pvFail`, re-runs `renderPvBody` in place). "No preview for this file type" survives only on the genuinely-unpreviewable path — one use left in the file, verified by post-condition. - docx scale-to-fit: `pvFit` zooms `.docx-wrapper` to the panel width and re-fits on panel resize (ResizeObserver). - i18n: `pvFailTitle` / `pvFailBody` / `pvRetry` ×4 locales. Remaining on this issue: converter service (client + `CAIRN_PREVIEW_CONVERTER_URL` + per-format routing), Helm `converter.*` block + compose `preview` profile, handbook docs. Mockups approved (states A–E, single Download button in the footer only).
Author
Owner

Converter shipped end-to-end — v0.6.125 (PRs #421 code, #422 packaging+docs; both dogfoods rolled at 03:33 UTC).

Code (internal/preview + internal/api/convert.go): Gotenberg-compatible client with streaming multipart upload, version-keyed bounded disk cache (path+ETag+size+mtime; preview.cacheMB, default 200, oldest-evicted), per-key single-flight, 2-min timeout, 100 MB source cap. Endpoints GET /api/v1/preview/formats (boot probe) and GET /api/v1/preview/pdf (auth'd, inline PDF, records recents, 502 on converter failure). Config preview.converterUrl / convertFormats / cacheDir / cacheMB; env CAIRN_PREVIEW_CONVERTER_URL wins. Test suite: cache hit, single-flight (1 upstream call for 2 concurrent viewers), eviction order, error path caches nothing, URL-route rules.

Frontend: feat.convert probe; converting card → converted PDF in the existing viewer with "Converted preview" chip; legacy formats without a converter get the honest card + admin-only deployment-guide link; failures land on the retryable card. Default routing doc/ppt/odt/odp/rtf/docx/pptx (Nikola's call after the scale-to-fit fix still looked poor on template-heavy docx); spreadsheets stay client-side, overridable both ways.

Packaging: Helm converter.* (default off; Deployment + ClusterIP + optional NetworkPolicy; env injection; converter.url to share one converter across releases; chart 0.2.0/appVersion v0.6.125). Compose profiles: ["preview"], no published ports.

Docs: deployment.md "Preview converter" — trust model (single plaintext hop, converter sees no keys/backend), config table, Helm/compose/bare-binary walkthroughs, security notes. Anchor #preview-converter matches the UI admin hint.

Not yet done: no converter instance deployed for the dogfoods — currently both instances show the new legacy card on .doc/.odt and keep client renderers for docx/pptx. Deploying one (shared, x86 side) + setting the env on both is the remaining dogfood step.

**Converter shipped end-to-end — v0.6.125** (PRs #421 code, #422 packaging+docs; both dogfoods rolled at 03:33 UTC). **Code** (`internal/preview` + `internal/api/convert.go`): Gotenberg-compatible client with streaming multipart upload, version-keyed bounded disk cache (path+ETag+size+mtime; `preview.cacheMB`, default 200, oldest-evicted), per-key single-flight, 2-min timeout, 100 MB source cap. Endpoints `GET /api/v1/preview/formats` (boot probe) and `GET /api/v1/preview/pdf` (auth'd, inline PDF, records recents, 502 on converter failure). Config `preview.converterUrl` / `convertFormats` / `cacheDir` / `cacheMB`; env `CAIRN_PREVIEW_CONVERTER_URL` wins. Test suite: cache hit, single-flight (1 upstream call for 2 concurrent viewers), eviction order, error path caches nothing, URL-route rules. **Frontend**: `feat.convert` probe; converting card → converted PDF in the existing viewer with "Converted preview" chip; legacy formats without a converter get the honest card + admin-only deployment-guide link; failures land on the retryable card. Default routing doc/ppt/odt/odp/rtf/**docx/pptx** (Nikola's call after the scale-to-fit fix still looked poor on template-heavy docx); spreadsheets stay client-side, overridable both ways. **Packaging**: Helm `converter.*` (default off; Deployment + ClusterIP + optional NetworkPolicy; env injection; `converter.url` to share one converter across releases; chart 0.2.0/appVersion v0.6.125). Compose `profiles: ["preview"]`, no published ports. **Docs**: deployment.md "Preview converter" — trust model (single plaintext hop, converter sees no keys/backend), config table, Helm/compose/bare-binary walkthroughs, security notes. Anchor `#preview-converter` matches the UI admin hint. **Not yet done**: no converter instance deployed for the dogfoods — currently both instances show the new legacy card on .doc/.odt and keep client renderers for docx/pptx. Deploying one (shared, x86 side) + setting the env on both is the remaining dogfood step.
Author
Owner

Decision + shipped — packaging defaults flipped (v0.6.126, PR #424). Nikola's product line, recorded: either fully in Kubernetes or fully in Docker — the packaged paths are batteries-included, the bare binary stays the converter-less minimal floor.

  • Helm: converter.enabled: true by default (chart 0.3.0). Opt-out one line; networkPolicy stays default-false (CNI-dependent), recommended true in docs.
  • Compose: converter is a normal always-on service with CAIRN_PREVIEW_CONVERTER_URL wired; profile removed. Opt-out = delete the service + env line.
  • Costs accepted knowingly and documented loudly in deployment.md: the packaged default pulls gotenberg/gotenberg:8 from Docker Hub (airgapped/private-registry installs mirror the image or disable; a blocked pull leaves the converter in ImagePullBackOff while Cairn runs fine), and the default footprint grows by ~1 GB image + a 1 Gi-limit workload.
  • No code changes; image functionally identical to v0.6.125. Both dogfoods bumped.

Dogfood converter (previous session step, for the record): shared cairn-converter (gotenberg:8) runs in k3s ns cairn on the workers — ClusterIP only, requests 100m/256Mi, limits 1000m/1Gi, nodeAffinity off the control planes, deliberately not critical-tier. Both instances log preview converter enabled endpoint=http://cairn-converter:3000/forms/libreoffice/convert formats=[doc ppt odt odp rtf docx pptx].

Still open on this issue: visual verification of converted previews on the dogfoods (docx/pptx/odt/doc through the PDF viewer), then #297 can close.

**Decision + shipped — packaging defaults flipped (v0.6.126, PR #424).** Nikola's product line, recorded: *either fully in Kubernetes or fully in Docker* — the packaged paths are batteries-included, the bare binary stays the converter-less minimal floor. - Helm: `converter.enabled: true` by default (chart 0.3.0). Opt-out one line; `networkPolicy` stays default-false (CNI-dependent), recommended true in docs. - Compose: converter is a normal always-on service with `CAIRN_PREVIEW_CONVERTER_URL` wired; profile removed. Opt-out = delete the service + env line. - Costs accepted knowingly and documented loudly in deployment.md: the packaged default pulls `gotenberg/gotenberg:8` from Docker Hub (airgapped/private-registry installs mirror the image or disable; a blocked pull leaves the converter in ImagePullBackOff while Cairn runs fine), and the default footprint grows by ~1 GB image + a 1 Gi-limit workload. - No code changes; image functionally identical to v0.6.125. Both dogfoods bumped. **Dogfood converter (previous session step, for the record):** shared `cairn-converter` (gotenberg:8) runs in k3s ns `cairn` on the workers — ClusterIP only, requests 100m/256Mi, limits 1000m/1Gi, nodeAffinity off the control planes, deliberately not critical-tier. Both instances log `preview converter enabled endpoint=http://cairn-converter:3000/forms/libreoffice/convert formats=[doc ppt odt odp rtf docx pptx]`. Still open on this issue: visual verification of converted previews on the dogfoods (docx/pptx/odt/doc through the PDF viewer), then #297 can close.
Author
Owner

Closing — visually verified by Nikola on the dogfoods (2026-09-05): converted previews render cleanly through the PDF viewer with the chip; caching makes reopens instant.

Final state of this issue, shipped across v0.6.124–v0.6.127:

  • v0.6.124: honest "preview could not be loaded" card + Try again (split from the unsupported-type copy); docx scale-to-fit.
  • v0.6.125: optional Gotenberg-compatible converter — internal/preview client (version-keyed bounded cache, single-flight), /api/v1/preview/formats + /api/v1/preview/pdf, frontend routing with converting/converted/legacy/failed states, default formats doc/ppt/odt/odp/rtf/docx/pptx (spreadsheets client-side, overridable).
  • v0.6.126: converter on by default in the packaged paths (Helm + compose); bare binary stays the converter-less floor; loud airgapped note.
  • v0.6.127 (#310): scheduling knobs for the converter pod.
  • Dogfood: shared cairn-converter in k3s serving both instances; false-alert ServiceMonitor overlap fixed same day in homelab-config.
  • Docs: deployment.md "Preview converter" (trust model, config table, Helm/compose/bare-binary, security notes).

Legacy support (.doc/.ppt/.odt/.odp/.rtf) and the modern-format fidelity pivot both land through the converter path — the client-side-renderer-only era of this issue is over.

**Closing — visually verified by Nikola on the dogfoods (2026-09-05):** converted previews render cleanly through the PDF viewer with the chip; caching makes reopens instant. Final state of this issue, shipped across v0.6.124–v0.6.127: - v0.6.124: honest "preview could not be loaded" card + Try again (split from the unsupported-type copy); docx scale-to-fit. - v0.6.125: optional Gotenberg-compatible converter — `internal/preview` client (version-keyed bounded cache, single-flight), `/api/v1/preview/formats` + `/api/v1/preview/pdf`, frontend routing with converting/converted/legacy/failed states, default formats doc/ppt/odt/odp/rtf/docx/pptx (spreadsheets client-side, overridable). - v0.6.126: converter on by default in the packaged paths (Helm + compose); bare binary stays the converter-less floor; loud airgapped note. - v0.6.127 (#310): scheduling knobs for the converter pod. - Dogfood: shared `cairn-converter` in k3s serving both instances; false-alert ServiceMonitor overlap fixed same day in homelab-config. - Docs: deployment.md "Preview converter" (trust model, config table, Helm/compose/bare-binary, security notes). Legacy support (.doc/.ppt/.odt/.odp/.rtf) and the modern-format fidelity pivot both land through the converter path — the client-side-renderer-only era of this issue is over.
Cordy closed this issue 2026-09-05 00:46:34 +00:00
Sign in to join this conversation.
No labels
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: Cordy/Cairn#297
No description provided.