---
audience: developers
summary: User-visible changes per release.
title: Changelog
path: changelog
status: published
---


# 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 `office` builtin theme uses Aptos (Microsoft Office 2024+ default) with Calibri fallback and a neutral palette. This is the theme every caller gets when `theme` is 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_list` on the same slide no longer overlap. Themes carry per-master `zones` in `pptx.masters[]` — override defaults per tenant without touching renderer code.
- **`paragraph` on slides** — the docx `paragraph` element type is now accepted inside `slide.elements[]`. The renderer parses embedded multi-line markdown, detects lines starting with `- ` / `* ` / `•` / `—` / `–` as real bullets (`<a:buChar>`) and `1.` / `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.typography` block on themes overrides docx-derived slide sizes. `office` ships with `slide_title: 36pt / slide_subtitle: 22pt / bullet_body: 18pt` — matching Microsoft PowerPoint's stock master defaults.
- **Image elements accept `url`** — as an alternative to `asset_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. Prefer `asset_ref` for repeatable renders; `url` is the "just make it work" convenience path.
- **Asset resolver walks the whole spec** — pre-fix, it only descended into `spec.body[]`, silently ignoring every `spec.slides[*].elements[]` image reference. Now walks body + slides + sheet charts uniformly.
- **Tenant-owned themes** — new `tenant_themes` table stores per-tenant brand themes. `resolve_theme` checks tenant themes first, then builtins; a tenant asking for a theme that isn't theirs gets a clean `404 theme not found` (per §15.3 isolation). `scribe_list_themes` returns the visible set with each entry stamped `scope: "builtin"` or `scope: "tenant"`.

### Changed

- **`scailabs` moved off the builtin bundle** — it's now a tenant-registered theme owned by the ScaiLabs tenant. Non-ScaiLabs callers can no longer resolve `theme="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 referenced `scailabs`. ScaiLabs-owned docs continue to render unchanged. Third-party docs currently tagged `theme="scailabs"` will 404 on next re-render — set the theme explicitly or omit it to get the `office` default.
- **`office` is now the code default** everywhere `theme=` 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: [...]}` on `scribe_create_document` no 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 marked `NOT a retriable error` to 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 `paragraph` element type. Root cause: `bullet: {type: "bullet"}` in pptxgenjs is silently no-op (only `type: "number"` is handled). Switched to `bullet: true` for default bullet characters — real `<a:buChar char="•"/>` markers now emit correctly.
- **Slide element overlap** on the `content` master 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 head` applies `0011_tenant_themes` which creates the table + seeds the scailabs theme JSON as owned by `tnt_tpljy20lf7svct66`. Idempotent to re-run.
- **Worker restart recommended** — the theme registry is loaded via `@lru_cache` at worker startup. Long-running workers from before the migration will hold stale theme state until restarted.
- **Docs**: [Themes](/docs/scaiscribe/concepts/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 kinds `bar`, `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 `pandoc` is 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_DETECTED` and 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 for `renders_per_day` (windowed) and `documents_count` (via Spec creator); `coverage=none` for `assets_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`, and `page_break` on the slow path (when the JSON breadcrumb is absent).
- **PPTX and XLSX templates (Tier 1)** — register a `.pptx` or `.xlsx` with `{{ var }}` placeholders, instantiate via `POST /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 a `TEMPLATE_VARIABLE_MISSING` warning instead of erroring (partial fills are common).
- **Templates admin UI** — new `Templates` page in the admin SPA at `/admin/templates` lists 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 new `GET /v1/admin/templates` endpoint.
- **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 a `TEMPLATE_EXPRESSION_ERROR` warning.
- **Groups admin surface** — new `Groups` page in the admin SPA at `/admin/groups` browses 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/jobs` and `GET /v1/jobs/{id}` now scope strictly to `submitted_by == caller`. A mortal user in a tenant where colleagues handle sensitive content no longer sees colleagues' job history or result payloads. Migration `0010` adds the `submitted_by` column on `jobs`; 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/me` enriched** — now also returns `tenant_name`, `partner_name`, and `effective_roles` (a structured view of the caller's superadmin / tenant_admin / partner_admin grants). The admin SPA uses `effective_roles` to 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 with `tenant_id` form field; counts against the target tenant's `templates_count` quota), `GET /v1/admin/templates/{id}/download` (presigned URL, or `download_url: null` for the file backend), `GET /v1/admin/templates/{id}/file` (direct stream — the fallback when presigning isn't supported), and `DELETE /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. `AdminMe` now also carries `tenant_name`, `partner_name`, and `effective_roles` (structured `is_superadmin` / `tenant_admin_on` / `partner_admin_on`). The main client gets `list_jobs()` / `listJobs()` / `ListJobsAsync()` (non-admin tenant-scoped, strict per-user isolation) and `delete_template()` / `deleteTemplate()` / `DeleteTemplateAsync()`. Artifacts live at [scailabs.ai/downloads](https://www.scailabs.ai/downloads).
- **MCP** — two new `scribe_*` tools (44 → 46): `scribe_list_jobs` (enumerate your pending/recent jobs without remembering IDs) and `scribe_delete_template` (clean up templates you registered during experimentation). The non-admin `DELETE /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 head` against your MariaDB to pick up the `quota_periods_user` table (migration `0009`).
- 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 (`scaiscribe` on PyPI) — sync + async clients.
  - TypeScript 0.1.0-alpha.1 (`@scaiscribe/sdk` on npm) — single async client; Node 20+ and browsers.
  - .NET 0.1.0-alpha.1 (`ScaiLabs.ScaiScribe` on NuGet) — .NET 8+.
- **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_credentials` for service-to-service.
- **Themes** — `scailabs` and per-tenant themes, with single-level `extends` inheritance and a compilation cache.
- **Templates** — `docx-templates` placeholder fill via `from_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 use `exceljs.addImage`, PPTX uses native `pptxgenjs.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.
