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

Decks (AI presentation builder) (part 2)

Part 2 of the Decks page: what each card is told when it is generated, how the topic colour and the 13-layout library work, publishing a deck to a web link, real images and generative art, presenter mode and speaker notes, PDF, HTML and PowerPoint export, and the data model, IPC surface and key files.

What it is

This is part 2 of the Decks (AI presentation builder) page. It covers what sits behind a finished deck — how each card is briefed, coloured and laid out, how a deck reaches the web or leaves as a file, and the data model, IPC surface and key files an agent needs in order to work on it.

Where to find it

Decks is the virtual project in the left sidebar, and the first half of this page walks through enabling it and running a generation. Everything here is reached from the deck editor once a deck exists: the theme picker sits in the editor header, the Share button publishes the open deck, and the Download menu saves it as a file.

How it behaves

Cost + credentials

Generation calls the Anthropic Messages API, which rejects OAuth tokens, so the service resolves an API-key account via resolveApiKeyForAiFeatures(). No key → a humanized error, never a crash. Every call is logged with trackApiCost(accountId, …) and checked against a per-day cap (decksDailyCostCapUsd, default $5/day), summed across the whole CLOSED set of decks billing labels — the cap gates on the SUM, so a paid decks call whose label is NOT a member would be neither counted toward the cap nor blocked by it. Every gate reads that pooled figure through the one pooledDecksSpendToday() helper.

The decksPremiumModel setting does two things, and the second one costs money. It picks a stronger model for generation (default off = the cheaper model), and it switches on the visual self-review loop for artifact slides — render the slide, let vision judge its design, repair it, re-judge, for a bounded number of passes. So premium spends noticeably more per artifact slide than the model upgrade alone implies. That extra spend bills under decks-review inside the same daily cap, and the loop is fail-open: at the cap it skips rather than throwing, so a deck still finishes. The outline gets max_tokens: 4096 and each card 3072.

What a card is actually told

A rule in the prompt is worth nothing unless the request carries what it refers to. For a long time the doctrine told the model "you are shown the previous and next slide, so do not reuse the previous slide's skeleton" while the request sent only the card's own title, summary, intent, layout and tone. Every card was therefore written in isolation from identical inputs, and came back the same shape — which is why generated decks looked machine-made no matter how the wording was tuned. It was an information failure, not a taste failure.

Each card request now carries the deck design brief, the previous and next slide (title + archetype — cards generate in parallel, so a neighbour's finished composition may not exist yet, but its planned shape always does), the deck accent, the card's assigned archetype, and — for artifact slides only — the worked exemplars. That last one roughly doubles the per-card prompt (~3.1k → ~5.2k tokens, about +$0.31 of input on a ten-slide premium deck), which is the price of the model seeing what "good" looks like instead of only being told.

Colour: chosen for the topic, overridable by the theme

The outline picks ONE accent for the deck's subject and returns it as a six-digit hex — amber for heat, red for a diagnosis, deep green for finance. Previously the doctrine forbade this outright ("never a hardcoded colour") and the accent was welded to the theme, so every deck came out the same indigo whatever the subject.

The slide applies it as --accent: var(--deck-accent, #hex), and that indirection is deliberate: on the default theme nothing defines --deck-accent, so the topic colour shows; the moment a reader picks a theme, --deck-accent is injected and the theme wins. A plain inline colour could never be reclaimed, because CSS gives an element's own declaration precedence over anything inherited. A malformed hex is dropped and the theme accent stands.

Presenter mode + speaker notes

Each card can carry speaker_notes. The generator now always writes them (2-4 talking-point lines that expand on the slide, never a restatement), they persist through CARD_UPDATE, and they surface in two places:

  • In the app — a notes strip under each card in the editor, editable inline.
  • In the published viewer — a single-screen presenter overlay (press S or the dock Notes button): current slide + next-slide preview + this slide's notes
    • an elapsed timer. It's an in-page overlay (not a second window.open, which is unreliable in the sandboxed Shares iframe) driven by the same slide engine.

Export (PDF + HTML + PowerPoint)

A Download menu in the editor saves the deck as a file (native Save dialog, so desktop-only — all three channels sit in web-access-ws.ts BLOCKED_CHANNELS):

  • PDF — a dedicated static render (renderDeckPrintHtml, no JavaScript — the shared htmlToPdf runs with JS disabled) lays every slide out as its own 16:9 page, then htmlToPdf(html, { preferCSSPageSize: true }) prints it. It reuses the shared slide engine, so the PDF matches the on-screen deck.
  • HTML — saves the self-contained published viewer document as-is.
  • PowerPoint (.pptx) — see below.

All three refuse a 0-card deck and humanize a write failure; a cancelled dialog returns { filePath: null } and never runs the render.

Export to PowerPoint (DECK_EXPORT_PPTX)

A real downloadable presentation that opens in PowerPoint / Google Slides / Keynote. It is image-fidelity: every slide is a full-bleed picture of the deck's OWN static print render, so the .pptx is pixel-identical to the on-screen deck and the PDF — charts, diagrams and artifact slides included.

The trade-off: slide text is a picture, not editable text. You can present and share the file, but you cannot edit its wording in PowerPoint. That is inherent to the approach, not a gap to be patched — editable text would be a different export.

  • Render — deck-pptx-render.ts captures each card via renderDeckPrintHtml(deck, [card]) in a hidden, JavaScript-disabled, sandboxed capturePage window, so an AI-authored artifact slide renders inert. Captures are single-flight with a 20 s per-slide timeout. Capture size is 1280×720 — deliberately not larger: a hidden window bigger than the display gets clamped by the OS, producing a short, non-16:9 image that then stretches. The capture also force-freezes the deck's entrance animations, because capturePage shoots one frame and would otherwise catch text mid-fade.
  • Package — pptx-builder.ts writes a 16:9 OOXML .pptx using Node's built-in zlib (adds no dependency), assembled in memory and written in ONE atomic save.
  • Never a hole — a slide that fails to render becomes a titled placeholder, so the .pptx always has exactly as many slides as the deck (deck-pptx-export.ts).
  • Honest about degraded output — because the render is fail-open, a fully failed export would otherwise save a valid file full of blank placeholders and report success. The export returns a { rendered, total } tally, and the editor warns when any slide fell back — naming it plainly when none rendered.
  • Costs nothing — pure local rendering, no model call.

Locked by deck-pptx-image-fidelity in decks-contract.md.

For agents

Testing seam (AMC_DECKS_AI_FAKE)

Launch with AMC_DECKS_AI_FAKE=1 and generation returns deterministic, schema-valid content with no API call and no cost (mirrors AMC_WRITER_AI_FAKE). This drives the end-to-end E2E (tests/e2e/ui/decks-flow.spec.ts) through the real create → generate → edit → delete flow without paid calls. A separate opt-in, paid smoke (tests/unit/decks/deck-generation-real-smoke.test.ts, gated on AMC_DECKS_REAL_SMOKE=1 + ANTHROPIC_API_KEY) is the live-model proof; it is skipped by default so the gate never spends money.

Data model — decks + deck_cards

Created by migration src/main/db/migrations/20260618225846-decks-tables.ts.

decks: id (PK), title, theme_id, format, status (draft|outline|generating|ready|error), source_prompt, source_ref_json (nullable provenance { kind, id, title, assembledAt } for a context-pulled deck; added by 20260709200336-add-source-ref-json-to-decks.ts), is_deleted, created_at, updated_at.

deck_cards: id (PK), deck_id, position, layout, content_json (the validated block union), style_json, speaker_notes, is_deleted, created_at, updated_at.

Both tables are global per install (no account_id) and soft-deleted — every read filters AND is_deleted = 0. Card content is the small, stable block union in deck-types.ts (heading | paragraph | bullets | image).

Card soft-delete is tri-state so undo stays exact: 0 = active, 1 = user-deleted, 2 = cascade-deleted (hidden because its deck was deleted). Restoring a deck un-hides only the cards its own delete cascaded (2 → 0); a card you deleted on purpose (1) stays deleted and is never resurrected.

IPC channels (decks:*)

Defined in src/shared/ipc-channels/decks.ts; handlers in src/main/ipc/decks-handlers.ts; Zod schemas in src/shared/ipc-schemas/decks.ts; response types in src/shared/ipc-response-map/decks.ts; push payloads in src/shared/push-event-schemas/decks.ts.

Constant Channel string Purpose
DECK_LIST decks:list list all non-deleted decks
DECK_GET decks:get fetch one deck + its cards
DECK_CREATE decks:create create a blank deck
DECK_UPDATE decks:update patch title / theme / status
DECK_DELETE decks:delete soft-delete a deck
DECK_GENERATE_OUTLINE decks:generate-outline create deck shell + stage an AI outline
DECK_GENERATE_CARDS decks:generate-cards generate + persist cards from the staged outline
DECK_INTERVIEW_TURN decks:interview-turn one turn of the optional pre-outline interview
DECK_ASSEMBLE_CONTEXT decks:assemble-context read a picked session/project into an editable grounded brief
DECK_ASSEMBLE_FILE decks:assemble-file read an inserted file into an editable grounded brief
DECK_PUBLISH decks:publish render the deck + publish it to a public Shares URL
DECK_EXPORT_PDF decks:export-pdf download the deck as a PDF (native Save dialog, desktop-only)
DECK_EXPORT_HTML decks:export-html download the deck as a self-contained HTML file (desktop-only)
DECK_EXPORT_PPTX decks:export-pptx download the deck as a PowerPoint .pptx — one slide-image per card (desktop-only)
CARD_CREATE decks:card-create add a blank card (optionally after a given card)
CARD_UPDATE decks:card-update patch a card's layout / content / style / notes
CARD_DELETE decks:card-delete soft-delete one card (undo via CARD_RESTORE)
CARD_RESTORE decks:card-restore restore a user-deleted card
CARD_REORDER decks:card-reorder reorder a deck's cards
DECKS_CHANGED decks:changed push — list/cards changed, refetch
DECKS_GENERATION_PROGRESS decks:generation-progress push — { deckId, done, total, status }

Every request channel is registered in tests/integration/ipc-contract.test.ts.

Renderer

The panel is the built-in Decks plugin's virtual project (__plugin_decks__, panelOwnsLayout). It mounts DecksView (deck list + editor split, mobile aware). State lives in the co-located Zustand store (src/renderer/src/features/decks/decks-store.ts): load, select, generateOutline, generateCards, the optional interview (startInterview / submitInterviewAnswer / skipInterviewToOutline / cancelInterview), addCard, updateCard, deleteCard, restoreCard, reorderCards, remove, with optimistic updates + rollback and a usePushListener on DECKS_CHANGED / DECKS_GENERATION_PROGRESS. Cards render through the layout name→component map in card-layouts.tsx.

Theme selection is visual: the wizard and the editor header use DeckThemePicker (features/decks/DeckThemePicker.tsx) — a keyboard-accessible radiogroup of mini-slide thumbnails rendered from each theme's actual tokens; it derives from DECK_THEMES, so a new theme appears automatically. In the editor it opens as a compact popover and re-themes the open deck instantly via the existing DECK_UPDATE { themeId } path.

Card management follows the deletion convention: a per-card delete is recoverable and undo-toast-only (you delete cards often), while a whole-deck delete confirms first (it drops all of that deck's cards) — both offer Undo. The card editor's CardBlock is React.memo'd so editing one card doesn't re-render the rest of a large deck.

Key files

What Where
Migration src/main/db/migrations/20260618225846-decks-tables.ts
Query layer src/main/db/queries-decks.ts
Generation service src/main/services/decks/deck-generation-service.ts
Context-pull assemble src/main/services/decks/deck-context-service.ts
Board data formatter src/main/services/decks/deck-board-formatter.ts
File extractor (office/text/PDF) src/main/services/decks/deck-file-extract.ts
File-source accept + size cap src/shared/decks/deck-file-types.ts
Published viewer + print (Main) src/main/services/decks/deck-render.ts (renderDeckHtml / renderDeckPrintHtml)
PowerPoint export orchestrator src/main/services/decks/deck-pptx-export.ts
PowerPoint slide capture src/main/services/decks/deck-pptx-render.ts
PowerPoint OOXML/zip builder src/main/services/decks/pptx-builder.ts
Generative art engine src/shared/decks/deck-visuals.ts
Shared theme palette src/shared/decks/deck-themes.ts
IPC handlers src/main/ipc/decks-handlers.ts
IPC channels src/shared/ipc-channels/decks.ts
Shared types src/shared/decks/deck-types.ts
Layout registry src/shared/decks/layout-registry.ts
Gating registry src/shared/unreleased-features.ts (decks)
Renderer (store + UI) src/renderer/src/features/decks/
Contract (invariants) .claude/memory/contracts/decks-contract.md

Related

The first half of this page covers what Decks is, how it is turned on, the two-stage generation flow and the four ways to start a deck. The plugin form described there is one of the built-in plugins, which are managed on the plugin settings page, and a deck built from a board pulls its material from the boards covered in mission control.

Last verified 2026-09-23