Skip to content
Back to skills

Deck Management

ASecurity

How decks are stored in SQL, how to create/read/update/delete decks. Read before working with deck data.

  • 6,969 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 3, 2026
ai-agentsgobashsqlapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 30, 2026

npx -y skills add BuilderIO/agent-native --skill deck-management --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Deck Management?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Deck Management
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/builderio-deck-management/badge)](https://www.skillsdirectory.com/skills/builderio-deck-management)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: deck-management
description: How decks are stored in SQL, how to create/read/update/delete decks. Read before working with deck data.
---

# Deck Management

Decks are stored in the `decks` SQL table via Drizzle ORM. Each deck row contains the full deck JSON (slides, metadata) in a `data` TEXT column.

## Schema

```sql
CREATE TABLE decks (
  id TEXT PRIMARY KEY,
  title TEXT NOT NULL,
  data TEXT NOT NULL,       -- Full deck JSON (slides array, metadata)
  design_system_id TEXT,
  created_at TEXT DEFAULT (current_timestamp),
  updated_at TEXT DEFAULT (current_timestamp),
  owner_email TEXT NOT NULL DEFAULT 'local@localhost',
  org_id TEXT,
  visibility TEXT NOT NULL DEFAULT 'private'
);
```

## Deck JSON Structure

The `data` column stores a JSON object:

```json
{
  "title": "My Presentation",
  "slides": [
    {
      "id": "slide-1",
      "content": "<div class=\"fmd-slide\" style=\"...\">...</div>",
      "layout": "title"
    },
    {
      "id": "slide-2",
      "content": "<div class=\"fmd-slide\" style=\"...\">...</div>",
      "layout": "content"
    }
  ]
}
```

Each slide has an `id`, HTML `content`, and optional `layout` type.

## Reading Decks

**From scripts:**

```bash
# List all decks (metadata only)
pnpm action list-decks

# Get a specific deck with all slides
pnpm action get-deck --id=<deckId>

# Get one slide's full HTML without loading the rest of the deck
pnpm action get-deck --id=<deckId> --slideId=<slideId> --compact=false

# See what the user is looking at
pnpm action view-screen
```

**From actions:**

- `list-decks` -- list all decks (returns id, title, slide count, timestamps)
- `get-deck` -- get a single deck; Slides chat calls are compact by default.
  Pass `slideId` for one targeted slide (full HTML by default), or use
  `compact=false` when a full deck read is actually needed

## Writing Decks

Never write the `decks` table directly -- no `db-exec` (this app has no such
action), no raw SQL. Every write goes through an action so ids, access checks,
`notifyClients` events, and version snapshots stay correct.

- `create-deck` -- create a deck for generation; `add-deck` is the browser
  editor's optimistic client-id flow and is hidden from the agent
- `list-deck-templates` / `get-deck-template` -- browse built-in starters or
  read their complete editable slide HTML (`content`) and speaker notes
- `create-deck-from-template` -- copy a starter into a new deck without AI,
  provider setup, or a default design system; optional `newId` retries the
  same owner/org/template/title request without overwriting subsequent edits
- `add-slide` -- append one slide
- `update-slide` -- edit one slide (see `slide-editing`)
- `patch-deck` -- delete/reorder slides, deck-wide or multi-slide changes
- `delete-deck` -- delete a deck and its version history

`save-deck` is a full-payload replace reserved for undo/redo and bulk
replacement; it is hidden from the agent so concurrent writers on different
slides do not clobber each other.

## Important Rules

1. **Always use the API or Drizzle** -- never write raw JSON files for deck storage
2. **Deck IDs are stable** -- once created, a deck's ID doesn't change
3. **Slide IDs within a deck are stable** -- used for referencing specific slides
4. **The `data` column is the full source of truth** -- title is duplicated at the top level for listing queries
5. **SSE events** (`source: "resources"`) fire when decks change, keeping the UI in sync

## Google Slides Export Availability

Two different things are both called "Google Slides export":

- The `export-google-slides` **action** builds a PPTX and hands back a download
  URL plus the File → Import dialog URL. It never touches OAuth, so it keeps
  working no matter what state the Google connection is in.
- The editor's **"Export to Google Slides" menu item** uploads that PPTX to the
  user's Drive so Drive converts it into a native deck. That needs Google OAuth,
  and starting it is a top-level navigation away from the editor.

Because the consent screen lives on Google's domain, a Google-side
misconfiguration (an unregistered redirect URI, a deleted client) is invisible
to the app once the user has left. `server/lib/google-oauth-preflight.ts` asks
Google up front whether it would accept the authorization request, and
`/_agent-native/google-docs/status` reports the verdict as `googleSlidesExport`.
When it is `available: false` the menu item is disabled and badged Unavailable
rather than sending the user to an error page they cannot act on.

The probe has three outcomes, not two. `unknown` means the probe reached no
verdict, and it must never be reported as a pass: the export stays enabled,
because hiding a working export on a failed probe is worse than the bug the gate
prevents. Only an explicit rejection from Google disables the item.

`reason: "oauth-rejected"` is not a user problem and not something the app can
fix — the redirect URI has to be registered in the Google Cloud Console for the
deployment's own origin (`https://<host>/_agent-native/google/callback`). Point
users at the PPTX export and Google Slides' File → Import meanwhile.

## PDF Round Trip

A PDF page is a picture of a slide, not the slide. `exportDeckAsPdf`
(`app/lib/export-pdf-client.ts`) therefore writes three layers per page: the
rendered page image, the slide's own text drawn over it invisibly, and — once
for the document — the deck source as base64 JSON in the PDF's XMP metadata
(`shared/pdf-sidecar.ts` owns that format and its size cap).

`import-file` reads them back in that order of preference
(`server/handlers/import/pdf-sidecar-reader.ts`):

- **Sidecar found** — the PDF came from Slides. The original slide HTML, notes,
  layouts, and aspect ratio are restored verbatim, with fresh slide ids. The
  result carries `restoredFromExport: true`.
- **Sidecar absent** — a foreign PDF. `parsePdfFidelity` rebuilds positioned
  text boxes and placed images from the page itself.
- **Sidecar present but unreadable** — logged loudly, then treated as absent.
  Never report that import as a clean restore.

A PDF import that yields one full-slide image means the page carried nothing
else — a scan or a flattened render. Say that rather than presenting it as a
faithful import, and offer OCR or the original source file instead.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…