Local-first Marp presentation editor: your decks stay plain, portable Markdown. Built-in privacy scan and redaction, TLP classification, 32 languages. No backend, no telemetry, open source. https://www.librekat.nl/
  • Dart 86.9%
  • JavaScript 12.4%
  • Makefile 0.2%
  • C++ 0.2%
  • CMake 0.1%
Find a file
Brenno de Winter 8a122c8b6f
Some checks are pending
CI / Gate (Linux) · Format · Analyze · Coverage (push) Waiting to run
CI / Test (macos-latest) (push) Waiting to run
CI / Test (windows-latest) (push) Waiting to run
CI / Web hardening (push) Waiting to run
CI / Docs links (push) Waiting to run
CI / Supply-chain (Trivy · advisory) (push) Waiting to run
Merge pull request 'SSRF-poort: bewaak ook een tweede client in een al toegelaten bestand' (#471) from fix/ssrf-buildguard into main
2026-07-22 00:41:00 +00:00
.github merge: main erin, en de dartcv-modulelijst weer eruit 2026-07-19 20:19:26 +02:00
android Bundle pre-existing in-progress changes 2026-06-11 22:16:57 +02:00
assets feat(ociwacht): EuroVoc er alsnog in — mijn eerste afwijzing was fout 2026-07-19 21:47:19 +02:00
docs fix(export): wacht niet oneindig op een frame dat nooit komt 2026-07-22 01:58:48 +02:00
ios Improve chart rendering and resolve theme logo paths 2026-06-08 12:18:35 +02:00
lib fix(export): wacht niet oneindig op een frame dat nooit komt 2026-07-22 01:58:48 +02:00
linux feat(privacy): een gezicht op een dia is ook een persoonsgegeven 2026-07-19 17:34:01 +02:00
macos fix(macos): demp de tienduizend linkerwaarschuwingen van de DartCvMacOS-pod 2026-07-21 06:51:34 +02:00
sbom feat(privacy): een gezicht op een dia is ook een persoonsgegeven 2026-07-19 17:34:01 +02:00
scripts feat(build): kijken wat je inpakt, vóór je het inpakt 2026-07-19 20:37:56 +02:00
semgrep feat(poort): make sast — Semgrep-regels over de uitgeleverde Dart 2026-07-22 00:48:01 +02:00
server/fetch-proxy fix(proxy,cve): de poort begrensd, en zoeken stopt zodra het genoeg heeft 2026-07-20 20:29:13 +02:00
test test(ssrf): bewaak ook een tweede client in een al toegelaten bestand 2026-07-22 02:40:43 +02:00
third_party feat: slide quality analysis, mermaid rendering, and export quality gating 2026-06-19 17:43:43 +02:00
tool fix(web): CSP krijgt form-action 'none' 2026-07-22 01:24:04 +02:00
web fix(web): CSP krijgt form-action 'none' 2026-07-22 01:24:04 +02:00
windows feat(privacy): een gezicht op een dia is ook een persoonsgegeven 2026-07-19 17:34:01 +02:00
zap feat(poort): make dast — adviserende ZAP-basisscan, en de trivy-omweg eruit 2026-07-22 01:16:40 +02:00
.gitattributes chore: de vertaalbestanden mergen als union, voor iedereen in plaats van alleen hier 2026-07-20 00:30:04 +02:00
.gitignore Add Nextcloud (WebDAV) as a file source 2026-06-29 22:43:05 +02:00
.gitleaks.toml feat(poort): make check-secrets doorzoekt werkboom en historie op geheimen 2026-07-22 00:16:52 +02:00
.metadata Initial commit: OciDeck Marp presentation builder 2026-06-02 23:28:39 +02:00
.tool-versions chore: bump Flutter-pin naar 3.44.6 2026-07-10 09:58:23 +02:00
.trufflehogignore feat(poort): make check-secrets doorzoekt werkboom en historie op geheimen 2026-07-22 00:16:52 +02:00
analysis_options.yaml Add quality-check tooling, an export-sanitisation test, and strict inference 2026-06-29 11:57:22 +02:00
AUDIT_RESPONSE.md docs(audit): hermeet de getallen waarmee dit document argumenteert 2026-07-21 20:51:27 +02:00
AUTHORS.md chore: copyright naar Stichting LibreKAT, credit initiatiefnemer behouden 2026-07-09 12:29:10 +02:00
CHANGELOG.md fix(export): wacht niet oneindig op een frame dat nooit komt 2026-07-22 01:58:48 +02:00
CODE_OF_CONDUCT.md docs(meldpunt): het beveiligingsadres staat op naam van de uitgever 2026-07-21 20:57:15 +02:00
CONTRIBUTING.md docs: elf bestanden waar de tekst de code was ontgroeid 2026-07-19 18:19:21 +02:00
dart_test.yaml Add slide-renderer visual-regression goldens (make test-golden) 2026-06-30 11:09:58 +02:00
LICENSE.md chore: voeg jaartal 2026 toe aan copyrightregel 2026-07-09 12:29:59 +02:00
Makefile feat(poort): make dast — adviserende ZAP-basisscan, en de trivy-omweg eruit 2026-07-22 01:16:40 +02:00
pubspec.lock feat(privacy): een gezicht op een dia is ook een persoonsgegeven 2026-07-19 17:34:01 +02:00
pubspec.yaml docs(toegankelijkheid): een eerlijke beperkingenlijst, en de claim erop aangepast 2026-07-21 20:51:27 +02:00
README.md docs(toegankelijkheid): een eerlijke beperkingenlijst, en de claim erop aangepast 2026-07-21 20:51:27 +02:00
SECURITY.md docs(meldpunt): het beveiligingsadres staat op naam van de uitgever 2026-07-21 20:57:15 +02:00
THIRD_PARTY_NOTICES.md docs(privacy): het ontwerp, de meting en de eerlijkheidsplicht opgeschreven 2026-07-19 17:34:01 +02:00
trivy.yaml feat(ci): advisory Trivy supply-chain scan (Dart-CVE's + secrets) 2026-07-05 23:02:14 +02:00

OciDeck

A desktop application for building Marp presentations through a structured, slide-by-slide editor — no raw Markdown wrangling required. Compose decks from typed slide templates (title, bullets, quotes, tables, images, video, audio, source code, charts, cockpit dashboards, interactive quiz questions), preview them live, present them fullscreen — even across two screens — and export to Marp Markdown, PDF, PPTX, and offline HTML.

And because a deck is something you hand to other people, OciDeck also reads it back to you before you do: it flags the personal data it can recognise, and — if you ask it to — removes that data from everything you show and export, while your Markdown keeps the original.

Built with Flutter for macOS, Windows, Linux, and web.

What's in a name? OciDeck is a small wink: Oci is borrowed from the Ocicats — the cats of Brenno de Winter — and Deck is short for a presentation deck. So: the cats' presentation tool.

Runs on your device, not on a server. OciDeck has no application backend for editing and no telemetry — no analytics, no tracking, no "phoning home". Every deck you edit stays in the app; the privacy scan runs on your device too. On the web the whole app is downloaded into your browser tab and runs there; the server that hosts it sees ordinary web-access logs (which files your browser fetched), not your decks or your edits. Outbound calls are ones you start and point where you choose — opening a deck from a URL, a Nextcloud/WebDAV folder, an S3 bucket, a git repository, the optional (off-by-default) AI helper — each guarded against reaching internal addresses.

Four addresses are not ones you picked, so they are named here rather than left to be discovered. On the web, a URL import that the browser refuses on CORS grounds is retried through a fetch-proxy endpoint on the origin the app was served from, which means the address you typed reaches whoever hosts that build (HOSTING.md §4). The optional, off-by-default CVE lookup defaults to cveapi.librekat.nl — a mirror run by the publisher — and falls back to ENISA's EU vulnerability database and MITRE, neither of which is configurable; the local CVE database downloads its bulk data via api.github.com. A YouTube or Vimeo video you embed loads that service's player script from that service. The full list, with what each one sends, is in PRIVACY.md; see ARCHITECTURE.md for the mechanics. (Corrected 2026-07-21: the earlier wording said "the only outbound calls are ones you start" and listed neither the proxy nor the publisher-run mirror.)

Features

  • Structured slide editors — dedicated editors per slide type: title, bullets, two-column bullets, bullets + image, single/two images, quote, table, section divider, image-only, video, audio, source code, charts, cockpit dashboards, interactive question (quiz) slides, animated timelines, and free-form Markdown.
  • Source-code slides — a "code sheet" with per-language syntax highlighting, stored as a fenced code block. Background, text colour and monospace font come from the style profile, with an optional syntax-colouring toggle (turn it off for a single-colour, CRT-terminal look). The code auto-sizes to fill the panel.
  • Charts — bar, horizontal bar, stacked bar, horizontal stacked bar, combo (bars + a line on a second axis), line, area, pie, donut, spider/radar, scatter, waterfall, and heatmap (doubles as a likelihood × impact risk matrix) charts rendered natively (preview, presenter, PDF, PPTX) and as self-contained SVG in the HTML export. Data is entered in an in-app grid or imported from CSV; the spec is stored as JSON in the Markdown, with optional linking to a CSV kept in a tidy data/ directory. Optional min/max draw reference lines (bar/line/area/combo/waterfall) or fix the scale (radar); legend hover highlights a series, and line tooltips pick the dot under the cursor.
  • Question slides (interactive quiz) — multiple choice, true/false, multiple-correct, or ordering questions (tap the shuffled options into the right order), with the kind chosen in the editor. The answer pool is unlimited; a configurable number of options (default 4) is drawn at random each run. Optional answer-time countdown, an on-wrong policy (retry until correct, or reveal-and-continue), and an optional image with a pan-and-zoom detail view. Presenting blocks advancing until answered correctly; the audience window is interactive and stays in sync. The spec round-trips as a JSON block; answer state is session-only.
  • Timeline slides — turn a list of dated events into an animated timeline: a glowing accent spine with nodes and cards that alternate above/below (horizontal) or left/right (vertical), styled from the active profile. Layout is automatic (horizontal for short timelines, vertical for longer ones) or forced; animation is draw-in-on-open, step-by-step (one event per click, synced to the audience window), or none. Events are an ordinary Markdown list (marker :: title :: description), so the .md stays readable; layout and animation round-trip as _class tokens.
  • Live preview — see each slide rendered as you edit, with inline Markdown, footers, and TLP (Traffic Light Protocol) marking. Free-Markdown slides render fenced code with syntax highlighting, $…$ / $$…$$ LaTeX math, and Mermaid diagrams.
  • Traffic Light Protocol — a deck-wide classification plus an optional per-slide TLP level; slides classified stricter than the level the deck is shown at are automatically withheld, both when presenting and exporting. Visual marking (banner, badge, optional diagonal watermark) follows FIRST TLP 2.0 colours and is WYSIWYG through preview, presenter, and PDF/PPTX export. Optional classification enforcement (release ceiling, required minimum, mandatory classification, watermark toggle) blocks export fail-closed when policy fails; export metadata embeds TLP in PDF/PPTX/HTML.
  • Privacy check & redaction (OciWacht) — OciDeck reads every slide for data that may be privacy-sensitive and reports it in the quality panel, right next to the contrast and readability checks: identification numbers (the Dutch BSN plus national ID numbers across the EU, each validated by its own checksum), IBANs and payment cards, e-mail addresses, phone numbers (E.164 against the assigned ITU calling codes), API keys, tokens, private keys and plain-text passwords, GDPR art. 9/10 special categories (health, criminal, religion, trade-union, biometric, genetic notation), bulk personal data in tables, and the structural leaks generic scanners miss — user paths that reveal a name, tokens in URLs, share links with built-in access. False positives are the real enemy here (a scanner that cries wolf gets switched off, and then it detects nothing at all), so checksums, context gates, and an allow-list of values that belong to nobody — test IBANs, the official test-BSN range, example.com, the reserved "drama" phone ranges — do the filtering, and a co-occurrence escalator keeps a slide about privacy law quiet while a slide carrying a name, a BSN and a diagnosis speaks up. It all runs on this device, and a finding never shows you the value it found.
  • What you do with a finding is yours to decide — per slide, or deck-wide: accept (a police briefing contains personal data by definition), accept + warn (the slide gets a personal data badge beside its TLP marking, so whoever receives the deck knows what they are holding), or leave out of display and export. That last one means what it says: not a black bar painted over text that is still sitting in the PDF, but data removed at a single projection boundary that the type system enforces, so no render surface can forget it — screen, presenter, audience window, PDF, PPTX, HTML, speaker notes, document metadata. Your Markdown keeps the original. And because a pentest report has to be verifiable, export gives you two versions from one source: full for the client who must be able to check the finding, redacted for the wider circle — with a salted-commitment redaction manifest so a third party can still prove nothing else was altered. The check does not guarantee that everything is found; it reduces the chance that personal data leaks out unintentionally.
  • Information-security reporting (MIAUW pentest module) — an optional module, off by default, for authoring MIAUW-conforming penetration-test reports. It adds dedicated finding / findings-summary / checklist / scope-matrix / sign-off slide types, a native CVSS 4.0 builder with an offline CWE picker and CVE fields, a finding wizard, and a compliance overview that scores the deck against the MIAUW requirements (EIS). Mechanical bookkeeping is automated: auto-numbering of findings, evidence SHA1 + SHA-256 hashing, scope-coverage checks, and a derived management summary. A report can be finalised and sealed (SHA-512 over the canonicalised content, detecting any later change) with an optional RFC 3161 timestamp token (OciDeck checks that the token's imprint matches the seal; it does not validate the authority's signature or certificate), exported as a MIAUW report template, and bundled into a one-click, AES-256-encrypted audit dossier (report + evidence + hash tables). Everything is offline by default; the optional AI helpers below are privacy-gated.
  • Optional, privacy-first AI assistance — an entirely optional, off-by-default backend (a local model, or a consented outbound endpoint) that drafts free-text finding fields grounded only on the tester's own facts (it strips any CWE/CVE/CVSS identifier it invents) and suggests image alt-text / tags for accessibility. AI-drafted content is marked and blocks sealing until a human reviews it, so provenance is always explicit.
  • Fullscreen presenter — keyboard-driven navigation, presenter view, blank screen, auto-advance, and a slide-grid overview.
  • Presentation timer / rehearsal mode — the presenter view doubles as a rehearsal clock: a countdown against a target time (set in Settings or live with K), the time spent on the current slide, and an end-of-run summary (total vs. target and per-slide times, copyable). It measures only — no pacing coaching — and is session-only, never written to disk.
  • Dual-screen presenter — when a second display is connected, the beamer shows the slide while the laptop shows the presenter view (current/next slide, notes, timer), kept in sync.
  • Annotation layer — draw on slides while presenting (pen, highlighter, eraser, laser pointer). Kept as a separate layer that never touches the Marp Markdown, mirrored live to the beamer, and saved in a .ink.json sidecar.
  • Live table editing — opt a table into being editable while presenting (off by default) and change it in front of the audience: a subtle pencil toggle, arrow keys moving the cursor inside a cell, Tab between cells, mirrored to the beamer.
  • Media handling — drag-and-drop images, an image carousel picker, captions, and descriptions stored as sidecar metadata. The library can filter images without tags and clean up md5-identical duplicates (merging their tags/captions and repointing every deck — open or on disk — to the kept file).
  • Import / export — round-trips Marp Markdown, imports existing slides, and exports to PDF, PPTX (with speaker notes), and an offline HTML deck: the JavaScript, CSS, font and charts are inlined, so code highlighting, math, charts and mermaid diagrams render in a browser with no network fetch. Slide images are not inlined — they stay relative <img> references, so the .html needs its images/ folder beside it. Decks are saved as a self-contained package with copied assets; that one really is a single file.
  • Nextcloud (WebDAV) source — browse a folder on your Nextcloud, open .ocideck packages or Marp .md decks from it, and save a deck back (as an .ocideck package or as a flat .md plus assets). The app password is stored encrypted in the OS keychain; a private/LAN server is only reachable after explicitly marking it as trusted, and downloads pass through the same safety gate and size limits as every other import.
  • Productivity — find & replace, slide finder, undo/redo, skip-slide state, multi-select with bulk copy-to-another-deck / delete / skip, and tabbed multi-deck editing. Paste a spreadsheet selection (or CSV / a markdown table) into a table cell to fill the whole grid. Markdown mode edits the whole deck as raw Marp text, with an in-editor find bar (Ctrl/Cmd+F, replace via Ctrl/Cmd+H) over the live buffer, plus a structural syntax check (line highlights, optional Apply anyway) before switching back. Ctrl/Cmd+O opens, Ctrl/Cmd+S saves.
  • Accessibility — in the editor: interface text scaling up to 200% (WCAG 1.4.4), a keyboard-operable panel divider and add-slide dialog, screen-reader labels for slides and charts (a chart reads out its data), slide-change announcements while presenting, and a test that fails the build when a button has no accessible name (WCAG 4.1.2). The exports are a different story: PDF and PPTX are one bitmap per slide — no text layer, no alt-text, no structure — so hand over the Markdown or the HTML when the recipient needs to read rather than look. No WCAG conformance is claimed and nothing has been tested with a real screen reader. The full picture, limitations first, is in ACCESSIBILITY.md.
  • Crash recovery — automatic snapshots so work survives an unexpected exit.
  • Theming — customizable deck style profiles (deck and source-code colours via presets or custom hex, fonts, logo, footer) and app appearance (including a dark interface), a bundled Marp CSS theme (assets/themes/ocideck.css), and a bundled EB Garamond font (no network fetch). A style profile can be exported to and imported from a standalone .ocideckstyle file — logo included — so a house style travels between installs without a deck around it.
  • Localized — the interface runs in 32 languages: Dutch, English, German, French, Italian, Spanish, Portuguese, Polish, Czech, Slovak, Slovenian, Croatian, Bulgarian, Romanian, Hungarian, Greek, Danish, Swedish, Finnish, Estonian, Latvian, Lithuanian, Maltese, Irish, Ukrainian, Turkish, Indonesian, Frisian, Swiss German, Latin, Papiamento, and Klingon. Every string that is localised in the source is translated into all 32, and a test fails the build if one is missing. That gate scans literal d('…') calls, which is not the same as "every string you can see": about fifty field labels and hints in the slide editors are handed to a widget that localises them indirectly, so the gate never sees them and they still show their Dutch source text — 'Titel (H1)' and 'Aanbeveling' among them. A handful of blocking messages (classification-policy refusals, the export-failure text) are rendered in Dutch regardless of your language setting. Repairing that is separate work; until it lands the claim reads as it does here. (Corrected 2026-07-21: this line used to say "every interface string is translated in all of them, enforced by tests".)

Requirements

  • Flutter 3.44.6 (stable) — the pinned toolchain (see .tool-versions), bundling Dart 3.12.2. The pubspec.yaml Dart SDK constraint is ^3.12.0; building tolerates Flutter 3.44+, but make format-check is version-sensitive (see BUILD.md).
  • A desktop target enabled: macOS, Windows, or Linux — or run in a browser via Flutter web

Getting started

make setup      # flutter pub get
flutter run -d macos   # or -d windows / -d linux / -d chrome

Development

The Makefile is the entry point for all quality checks. Run make help for the full list.

make check        # format + analysis + conventions + method length + dead code + tests & coverage
make check-full   # check + licences + SBOM freshness + web hardening + dependency report
make format       # auto-format all Dart code
make analyze      # flutter analyze only
make test         # full test suite only

Targeted test groups speed up focused work:

Target Covers
make test-contracts Markdown generation/parsing, save-load round-trips, field migration
make test-preview Slide rendering, footers, TLP, inline Markdown, text styles
make test-export PDF/PPTX export and project file-save behavior
make test-state Providers, undo/redo, search/replace, settings, recovery
make test-services Image, caption, and description sidecar services
make test-presenter Fullscreen presenter navigation and keyboard shortcuts

Run make check before pushing — it is the same quality gate (format check, static analysis, full test suite) you would wire into CI. make check-full adds licence compliance (make licenses), the vendored-JS security check (make deps-check), and the SBOM freshness gate (make sbom-verify). For the full reference — every check, what it covers, and how it is enforced — see docs/CHECKS.md.

OciDeck ships a machine-readable Software Bill of Materials (CycloneDX 1.6 + SPDX 2.3) covering every component, as required by the EU Cyber Resilience Act — see docs/SBOM.md.

Project layout

lib/
  models/     # Deck, Slide, Settings data models
  services/   # Markdown, export, file, image, caption, recovery, rasterizer
  state/      # Riverpod providers (deck, editor, settings, tabs, clipboard)
  widgets/    # UI: app shell, panels, dialogs, per-type editors, presenter
  l10n/       # AppLocalizations (32 languages)
  theme/      # App theming
  utils/      # Small shared helpers (clipboard table parsing, URL launching)

State is managed with Riverpod.

File format

Presentations are saved as standard, Marp-compatible Markdown (.md) with a defined project folder layout and an optional portable .ocideck package. Anything that isn't plain Marp is kept in side files so the .md stays pure and portable: image captions, the annotation layer (.ink.json), and linked chart data (data/*.csv). A style profile also travels on its own as a .ocideckstyle file (JSON, with any custom logo embedded). The full specification — front matter, per-slide markup, style profile, sidecars, and the package format — is documented in docs/FILE_FORMAT.md.

Documentation

Document What it covers
Architecture How the code fits together (for contributors)
Build & release Building from source and producing distributables
Changelog Notable changes (nothing is released yet)
Checks & CI Every automated check, what it covers, and how each is enforced
Contributing Setup, the quality gate, and how to propose changes
File format The Marp Markdown, front matter, sidecars, the .ocideck package, and the .ocideckstyle style profile
Keyboard shortcuts Editor and presenter shortcuts
Licence compliance Open-source policy and the make licenses check
SBOM The machine-readable Software Bill of Materials and its CRA mapping
Security policy How to report a vulnerability
Source map An index of the files under lib/, grouped per directory
Third-party notices Bundled components and their licences
User Guide Using the app: slide types, charts, presenting, exporting, theming, the privacy check and redaction, and the information-security (pentest) module

Design documents

The design rationale and chosen architecture, kept separate from the current-state docs above. The OciWacht, pentest-reporting (MIAUW) AI-assistance and Git storage designs are now implemented (see the User Guide and the Features list); Collaboration remains a forward-looking proposal.

Document Status What it covers
OciWacht Implemented Detecting privacy-sensitive data in a deck, and — where you ask for it — removing it from everything you share, at one enforced boundary
Pentest reporting (MIAUW) Implemented Penetration-test reports with audit value, per the MIAUW methodology
AI assistance Implemented Optional, privacy-first AI assistance (finding text drafting and image tagging)
Agentic build plan Executed How the pentest and AI feature set was built with autonomous agents
Collaboration Proposal Real-time co-authoring, presenting, and calls
Git storage Implemented (merge open) Decks in a git repository: history, releases as tags, review as pull requests

Contributing

Contributions are welcome! Please read CONTRIBUTING.md and our CODE_OF_CONDUCT.md. In short: make check must pass, new UI strings must be translated in all languages, and file-format changes must be reflected in docs/FILE_FORMAT.md. For security issues, see SECURITY.md.

License

Copyright © 2026 Stichting LibreKAT.

OciDeck was initiated and conceived by Brenno de Winter and is developed as an open-source project.

OciDeck is licensed under the European Union Public Licence v. 1.2 (EUPL-1.2). You may use, study, share, and modify the software under the terms of that licence. The full text is in LICENSE.md; the official versions in all EU languages are available from the EUPL collection.

SPDX-License-Identifier: EUPL-1.2