Changelog
v0.1.2 alpha — July 2026 (in flight)#
Quality pass on the PPTX pipeline, hardening MCP tool schemas, and a long-overdue architectural cleanup: brand themes are now tenant-owned rather than hardcoded into the shipped bundle.
Added#
- Office-native default theme — new
officebuiltin theme uses Aptos (Microsoft Office 2024+ default) with Calibri fallback and a neutral palette. This is the theme every caller gets whenthemeis omitted. If the reader doesn't have Aptos installed, the deterministic fallback chain lands on Calibri → Segoe UI → Helvetica Neue → Arial → sans-serif — so decks render the way most readers expect. - PPTX layout engine — slide elements now stack sequentially in a theme-declared body zone with heights proportional to estimated content weight, instead of every element rendering at the same fixed
(x=0.5, y=1.5, w=9, h=5)rectangle.paragraph+bullet_liston the same slide no longer overlap. Themes carry per-masterzonesinpptx.masters[]— override defaults per tenant without touching renderer code. paragraphon slides — the docxparagraphelement type is now accepted insideslide.elements[]. The renderer parses embedded multi-line markdown, detects lines starting with-/*/•/—/–as real bullets (<a:buChar>) and1./1)as ordered lists (<a:buAutoNum>), and preserves inline**bold**/*italic*on every line including bulleted ones. Content the LLM used to emit as one wall of text now stacks with real bullet indentation.- Deck-appropriate slide typography — new optional
pptx.typographyblock on themes overrides docx-derived slide sizes.officeships withslide_title: 36pt / slide_subtitle: 22pt / bullet_body: 18pt— matching Microsoft PowerPoint's stock master defaults. - Image elements accept
url— as an alternative toasset_ref. When a spec carries{type: image, url: "https://..."}, the worker fetches the URL server-side during finalize, subject to SSRF protection (private/loopback/link-local/metadata IPs blocked, hostname re-checked per redirect hop), a 3-redirect ceiling, 10 s timeout, 20 MB size cap, and an image/* content-type allowlist. Failed fetches surface a labeled placeholder in the render ([image: <alt> — fetch failed: <reason>]) instead of tanking the whole deck. Preferasset_reffor repeatable renders;urlis the "just make it work" convenience path. - Asset resolver walks the whole spec — pre-fix, it only descended into
spec.body[], silently ignoring everyspec.slides[*].elements[]image reference. Now walks body + slides + sheet charts uniformly. - Tenant-owned themes — new
tenant_themestable stores per-tenant brand themes.resolve_themechecks tenant themes first, then builtins; a tenant asking for a theme that isn't theirs gets a clean404 theme not found(per §15.3 isolation).scribe_list_themesreturns the visible set with each entry stampedscope: "builtin"orscope: "tenant".
Changed#
scailabsmoved off the builtin bundle — it's now a tenant-registered theme owned by the ScaiLabs tenant. Non-ScaiLabs callers can no longer resolvetheme="scailabs"— the error message names the visible set and is marked non-retriable so LLM tool-calling loops break cleanly. Migration path: no action needed for tenants that never referencedscailabs. ScaiLabs-owned docs continue to render unchanged. Third-party docs currently taggedtheme="scailabs"will 404 on next re-render — set the theme explicitly or omit it to get theofficedefault.officeis now the code default everywheretheme=had a default value:scribe_create,scribe_ingest,scribe_instantiate_template, the corresponding REST routes, template + document services. Callers that already passed a theme explicitly are unaffected.- MCP tool schemas advertise
additionalProperties: false— strict-mode agents (Mistral Large in strict mode, recent Claude tool-calling) refuse to emit fields the schema doesn't declare, so invented arguments like{queries: [...]}onscribe_create_documentno longer make it out of generation. Agents that don't enforce at generation time hit a runtime rejection with a structured, LLM-legible error naming the offending field, listing valid fields, and markedNOT a retriable errorto break retry loops. - Sharpened format-mismatch errors — the previous "op 'add_element' requires a docx document, not 'pptx'" message trapped LLM agents in retry loops because it didn't point at the correct alternatives. Now: names the doc's actual type, lists the correct ops + MCP tools for that type, marks the failure non-retriable. Same treatment for slide-op-on-docx and sheet-op-on-non-xlsx.
- Format-specific tool docstrings — every
scribe_add_*tool now leads with**DOCX only.**/**PPTX only.**/**XLSX only.**and points at the correct alternative family. Well-behaved LLMs pick the right tool at discovery time instead of hitting a 422.
Fixed#
- PPTX bullets rendered as literal dashes in slides that came through the
paragraphelement type. Root cause:bullet: {type: "bullet"}in pptxgenjs is silently no-op (onlytype: "number"is handled). Switched tobullet: truefor default bullet characters — real<a:buChar char="•"/>markers now emit correctly. - Slide element overlap on the
contentmaster when both a subtitle and a body element were present — the body zone started at y=1.5 while the subtitle occupied y=1.3-1.9. Now zones are non-overlapping by construction.
Operator notes#
- Migration required:
alembic upgrade headapplies0011_tenant_themeswhich creates the table + seeds the scailabs theme JSON as owned bytnt_tpljy20lf7svct66. Idempotent to re-run. - Worker restart recommended — the theme registry is loaded via
@lru_cacheat worker startup. Long-running workers from before the migration will hold stale theme state until restarted. - Docs: Themes and the MCP tool catalogue updated.
v0.1.1 alpha — June 2026 (shipped)#
Iterative hardening on top of v0.1.0. No breaking API changes; existing SDKs work unchanged.
Added#
- Native DOCX chart emission — chart parts (
word/charts/chartN.xml) are now hand-emitted via OOXML post-processing for kindsbar,column,line,pie. Charts open in Word and LibreOffice as editable native charts, with series data preserved. Other kinds (area, scatter, sankey, etc.) still go through the ECharts SVG fallback. - Native XLSX chart emission — same coverage matrix; charts anchor to the spec'd cell and reference proper drawing parts.
- Pandoc-merge DOCX ingest — when
pandocis on the worker host, ingest runs pandoc alongside mammoth to detect footnotes, math, citations, definition lists, and line blocks. Findings surface as structured fidelity warnings (INGEST_PANDOC_FOOTNOTE_DETECTEDand friends). No-op when pandoc is absent. - Partner quota rollup —
GET /v1/admin/quotas/partner/{partner_id}?axis=…aggregates usage across every tenant of a partner. Access: superadmin or partner_admin on that specific partner. - Per-user quota rollup —
GET /v1/admin/quotas/tenant/{tenant_id}/users?axis=…breaks down a tenant's usage by acting user. Full coverage forrenders_per_day(windowed) anddocuments_count(via Spec creator);coverage=noneforassets_bytes/templates_count(model has no created_by on those tables yet). - PPTX layout-to-theme-master matching on ingest — slide layouts now recover via OOXML
<p:cSld name>/<p:sldLayout type>against the theme registry instead of always defaulting to"content". - HTML preview for PPTX/XLSX — wraps per-page Gotenberg PNGs into a self-contained HTML preview file.
- OOXML §4.7 breadcrumbs — round-trip equivalence now covers
code_block,callout, andpage_breakon the slow path (when the JSON breadcrumb is absent). - PPTX and XLSX templates (Tier 1) — register a
.pptxor.xlsxwith{{ var }}placeholders, instantiate viaPOST /v1/documents/from_template. Same{{ … }}syntax as DOCX; same dotted-path resolution. Plain substitution only — FOR/IF/JS-expressions are DOCX-only via docx-templates. Missing variables in PPTX/XLSX render as empty + emit aTEMPLATE_VARIABLE_MISSINGwarning instead of erroring (partial fills are common). - Templates admin UI — new
Templatespage in the admin SPA at/admin/templateslists every registered template across the tenants you can see, with per-row expansion showing variable + grammar-token breakdown. Scope: superadmin (all), partner_admin (their partners' tenants), tenant_admin (own tenant only). Backed by the newGET /v1/admin/templatesendpoint. - PPTX + XLSX template Tier 2 — FOR loops + IF conditionals + a safe JS-flavoured expression evaluator now ship in PPTX and XLSX templates. PPTX gets text-frame-level FOR (replicate paragraphs within a shape) AND slide-level FOR (clone whole slides per item, rewiring rels + content-types + presentation.xml). XLSX gets row-level FOR with row renumbering and relative-cell-ref rewriting in formulas. The expression evaluator is a recursive-descent parser (no
eval, no VM) with a per-type method allowlist — supports{{ amount.toFixed(2) }},{{ name.toUpperCase() }}, ternaries, arithmetic, comparisons. Method calls outside the allowlist render empty + emit aTEMPLATE_EXPRESSION_ERRORwarning. - Groups admin surface — new
Groupspage in the admin SPA at/admin/groupsbrowses every ScaiKey group across the tenants you can see. Per-row expansion shows direct + transitive members, parent-group + child-group context. User detail (Users → Roles drawer) now also surfaces the user's group memberships. Backed by three new endpoints:GET /v1/admin/groups,GET /v1/admin/groups/{id},GET /v1/admin/users/{id}/groups. Read-only — group lifecycle stays in ScaiKey; ScaiScribe mirrors via the daily sync. - Per-user job isolation (security) — the non-admin
GET /v1/jobsandGET /v1/jobs/{id}now scope strictly tosubmitted_by == caller. A mortal user in a tenant where colleagues handle sensitive content no longer sees colleagues' job history or result payloads. Migration0010adds thesubmitted_bycolumn onjobs; new jobs stamp it from the auth context at creation. Pre-migration rows (submitted_by IS NULL) are inaccessible via the non-admin endpoints — they only show up in the admin endpoint. Cross-user visibility for operators is unchanged via/v1/admin/jobs. /v1/admin/meenriched — now also returnstenant_name,partner_name, andeffective_roles(a structured view of the caller's superadmin / tenant_admin / partner_admin grants). The admin SPA useseffective_rolesto filter sidebar nav so users don't see menu items for endpoints they can't reach. The backend STILL enforces 403 on every endpoint regardless of what the SPA renders.- Templates upload / download / delete from the admin UI — operators can now register templates on behalf of any tenant in their scope, download the source binary, and soft-delete templates directly from the Templates page. Three new endpoints:
POST /v1/admin/templates(multipart upload withtenant_idform field; counts against the target tenant'stemplates_countquota),GET /v1/admin/templates/{id}/download(presigned URL, ordownload_url: nullfor the file backend),GET /v1/admin/templates/{id}/file(direct stream — the fallback when presigning isn't supported), andDELETE /v1/admin/templates/{id}(soft-delete; binary stays in storage). Authorization: superadmin on any tenant, partner_admin on tenants under their partner, tenant_admin on their own tenant. - SDKs updated to
0.1.0-alpha.2— Python (sync + async), TypeScript, and .NET all gain the seven new endpoints from this iteration: cross-tenant template listing/upload/download/delete, groups listing/detail/user-groups.AdminMenow also carriestenant_name,partner_name, andeffective_roles(structuredis_superadmin/tenant_admin_on/partner_admin_on). The main client getslist_jobs()/listJobs()/ListJobsAsync()(non-admin tenant-scoped, strict per-user isolation) anddelete_template()/deleteTemplate()/DeleteTemplateAsync(). Artifacts live at scailabs.ai/downloads. - MCP — two new
scribe_*tools (44 → 46):scribe_list_jobs(enumerate your pending/recent jobs without remembering IDs) andscribe_delete_template(clean up templates you registered during experimentation). The non-adminDELETE /v1/templates/{id}endpoint backs the new tool — any authenticated user can delete a template in their own tenant.
Changed#
- Chart slow-path recovery and TOC slow-path recovery formally reclassified as fast-path-only by design (the JSON breadcrumb covers ScaiScribe-authored docs; externally-authored docs degrade gracefully with documented fidelity warnings). Not deferrals — closed by design.
Operator notes#
- Run
alembic upgrade headagainst your MariaDB to pick up thequota_periods_usertable (migration0009). - Install
pandoc(apt install pandoc) on the ingest worker hosts to enable the merge audit.
v0.1.0 alpha — June 2026#
First public alpha. Authenticate against ScaiKey, render DOCX / PPTX / XLSX from a JSON spec, ingest existing DOCX, plus a SolidJS admin UI at /admin/.
What works#
- REST API (19 endpoints) — documents, jobs, assets, fonts, templates, health.
- MCP server (42
scribe_*tools) — the primary surface for AI agents. - Three official SDKs:
- Python 0.1.0a1 (
scaiscribeon PyPI) — sync + async clients. - TypeScript 0.1.0-alpha.1 (
@scaiscribe/sdkon npm) — single async client; Node 20+ and browsers. - .NET 0.1.0-alpha.1 (
ScaiLabs.ScaiScribeon NuGet) — .NET 8+.
- Python 0.1.0a1 (
- Admin SPA + sub-client — operator surface for jobs / users / roles / tenants / partners.
- ScaiKey integration — full identity sync, OAuth Authorization Code for the admin UI, OAuth
client_credentialsfor service-to-service. - Themes —
scailabsand per-tenant themes, with single-levelextendsinheritance and a compilation cache. - Templates —
docx-templatesplaceholder fill viafrom_template. - Ingestion — DOCX, PPTX, XLSX with fidelity scoring and structured warnings.
- PDF rendering via Gotenberg.
What works (correcting earlier draft)#
These are shipped and have fixture coverage — they were misclassified as deferred in the initial alpha changelog draft:
- Chart rendering via ECharts SSR — DOCX charts embed as
<w:drawing>SVG images, XLSX charts useexceljs.addImage, PPTX uses nativepptxgenjs.addChart. The "native chart XML" (chart data lives in the workbook) variant for DOCX/XLSX is the remaining v1.x roadmap item. - HTML preview for DOCX — mammoth-based HTML pipeline.
- Per-page screenshots — PDF + pdftoppm.
Known deferrals (v1.x)#
These are honest gaps, not silent omissions — every relevant code path emits a structured warning. Many of these closed in v0.1.1 (see above).
- OCR ingestion — no image-to-text yet.
- ScaiSign / ScaiCMS bridges — integrations come with those products.
Breaking changes#
None — this is the initial release.
Caveats#
- Pin SDK versions during alpha. Minor breaking changes possible through v1.0 GA.
- The spec format is at
schema_version: "0.1". Migration policy starts at v1.0.