Document TLP classification enforcement, marking, and export metadata.
Some checks are pending
CI / Format · Analyze · Test (push) Waiting to run
CI / Format · Analyze · Test (pull_request) Waiting to run

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Brenno de Winter 2026-06-16 10:52:19 +02:00
parent 173f1a3f26
commit e601d7ee78
5 changed files with 150 additions and 19 deletions

View file

@ -56,6 +56,16 @@ and the project aims to follow [Semantic Versioning](https://semver.org/).
(no file is written when blocked, and the export dialog explains why). (no file is written when blocked, and the export dialog explains why).
Classifying a deck stays optional — the ceiling only stops decks that exceed Classifying a deck stays optional — the ceiling only stops decks that exceed
it, and it is off by default. it, and it is off by default.
- **Classification enforcement** — extends the export gate with an optional
**required minimum TLP**, a **classification required** flag (reject decks
with no TLP level), and a **classification watermark** on every slide
(diagonal `TLP · organisation`, WYSIWYG in preview and raster exports).
Settings live under *Settings → General → Accessibility → Classification
enforcement*. The title-bar TLP chip highlights in orange when export is
blocked because the deck is unclassified.
- **Export metadata** — PDF, PPTX, and HTML exports embed title, author,
description, keywords, and TLP (Subject prefix, Keywords, HTML `<meta
name="classification">` / `<meta name="tlp">`, plus a fixed HTML banner).
- **Dual-screen presenter** — on a second display the beamer shows the slide - **Dual-screen presenter** — on a second display the beamer shows the slide
while the laptop shows the presenter view (current/next slide, notes, timer), while the laptop shows the presenter view (current/next slide, notes, timer),
kept in sync over method channels. kept in sync over method channels.

View file

@ -12,7 +12,7 @@ Built with Flutter for macOS, Windows, and Linux.
- **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. - **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, line, pie, and spider/radar 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) or fix the scale (radar); legend hover highlights a series, and line tooltips pick the dot under the cursor. - **Charts** — bar, line, pie, and spider/radar 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) or fix the scale (radar); legend hover highlights a series, and line tooltips pick the dot under the cursor.
- **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 and `$…$` / `$$…$$` LaTeX math. - **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 and `$…$` / `$$…$$` LaTeX math.
- **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. An optional **export release ceiling** can block exporting any deck classified above a chosen level — enforced for every format, off by default. - **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.
- **Fullscreen presenter** — keyboard-driven navigation, presenter view, blank screen, auto-advance, and a slide-grid overview. - **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. - **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. - **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.

View file

@ -15,8 +15,9 @@ are stored on disk, see [`FILE_FORMAT.md`](FILE_FORMAT.md).
``` ```
lib/ lib/
models/ # Deck, Slide, Settings/ThemeProfile, Chart, Annotation models/ # Deck, Slide, Settings/ThemeProfile, Chart, Annotation
services/ # markdown, markdown_validator, file, export, classification_policy, services/ # markdown, markdown_validator, file, export,
# image, caption, # classification_policy, classification_enforcement_policy,
# export_metadata, image, caption,
# description, image_dedup (md5 duplicates), # description, image_dedup (md5 duplicates),
# image_reference (.md rewrites), recovery, rasterizer, # image_reference (.md rewrites), recovery, rasterizer,
# marp_html, annotation_codec, rehearsal_controller # marp_html, annotation_codec, rehearsal_controller
@ -75,13 +76,48 @@ the key thing to understand before touching rendering:
renderer by design. renderer by design.
Both worlds converge at one chokepoint: `services/export_service.dart` Both worlds converge at one chokepoint: `services/export_service.dart`
(`ExportService.export()`) is the only place that writes an export, so the (`ExportService.export()`) is the only place that writes an export.
**classification gate** lives there rather than in the export dialog. A
`ClassificationPolicy` enforces an optional *release ceiling* and refuses, ### Classification enforcement
**fail-closed**, to export a deck classified above it — no format can bypass it.
The ceiling is stored in app settings (`maxReleaseExportTlpKey`, off by default); Export blocking is decided by `ClassificationEnforcementPolicy`
the dialog also runs the same check up front so a blocked export is explained (`services/classification_enforcement_policy.dart`), evaluated inside
before any work starts. `ExportService.export()` **before** any file bytes are built — fail-closed, so no
format (PDF/PPTX/HTML/package) can bypass the gate. The export dialog and status
bar run the same policy up front for UX (explain early, disable misleading work).
The policy combines three optional rules from `AppSettings`:
| Setting key | Rule |
| --- | --- |
| `maxReleaseExportTlpKey` | Release **ceiling** — deck TLP must not exceed this level. |
| `minRequiredExportTlpKey` | **Floor** — deck TLP must be at least this level. |
| `requireClassificationOnExport` | Deck must have a TLP level (`TlpLevel.none` is rejected). |
`ClassificationPolicy` remains as a thin wrapper around the ceiling only
(backward compatible); new code should use `ClassificationEnforcementPolicy`.
The gate evaluates **deck-wide** `Deck.tlp`, not per-slide levels (those still
control visibility via `slideVisibleAtTlp`).
`ExportDocumentMetadata` (`services/export_metadata.dart`) is built from deck
metadata and passed into PDF (`pw.Document` title/author/subject/keywords),
PPTX core properties, and HTML `<meta>` tags. HTML also gets a fixed
`.tlp-export-banner` when classified.
### Visual TLP marking
In-app slides (`SlidePreviewWidget`) compute `effectiveTlp(deckTlp, slideTlp)`
the stricter of deck and slide — and render FIRST TLP 2.0 markings from
`widgets/slides/previews/overlays.dart`:
- `_ClassificationBanner` — full-width top bar
- `_TlpOverlay` — bottom-right (or bottom-left) badge
- `_ClassificationWatermark` — optional diagonal watermark (`TLP · organisation`),
controlled by `AppSettings.classificationWatermarkEnabled`
The same widget tree is rasterized for PDF/PPTX (`slide_rasterizer.dart`), so
markings are WYSIWYG. The watermark setting is threaded through preview, presenter,
audience window, thumbnails, and export dialog.
## Presenter ## Presenter

View file

@ -118,6 +118,18 @@ Opgeslagen onder de sleutel `tlp` met deze stabiele waarden:
| `amber+strict` | `TLP:AMBER+STRICT` | | `amber+strict` | `TLP:AMBER+STRICT` |
| `red` | `TLP:RED` | | `red` | `TLP:RED` |
**Effectieve markering.** In de app wordt per slide het **strengste** niveau
getoond: het maximum van het deck-TLP (`tlp` in front matter) en het
per-slide TLP (`<!-- tlp: … -->`). Dat bepaalt banner, badge en optioneel
watermerk in preview, presenter en rasterexport — niet opgeslagen als extra
Markdown, maar berekend bij renderen (`effectiveTlp` in `lib/models/deck.dart`).
**Zichtbaarheid vs. export-gate.** Per-slide TLP bepaalt welke slides worden
achtergehouden bij presenteren/exporteren (`slideVisibleAtTlp`). De
**export-handhaving** (plafond, minimum, verplichte classificatie) kijkt
alleen naar het deck-brede `tlp`-veld in front matter, niet naar per-slide
niveaus.
### 3.2 `ocideck_style_profile` (stijlprofiel) ### 3.2 `ocideck_style_profile` (stijlprofiel)
Het complete visuele profiel wordt als JSON geserialiseerd, ge-UTF-8'd en Het complete visuele profiel wordt als JSON geserialiseerd, ge-UTF-8'd en
@ -552,3 +564,26 @@ niet modelleert wordt niet gerapporteerd.
Implementatie: `lib/services/markdown_validator.dart`; tests: Implementatie: `lib/services/markdown_validator.dart`; tests:
`test/markdown_validator_test.dart`. Zie ook [`USER_GUIDE.md`](USER_GUIDE.md) (§ `test/markdown_validator_test.dart`. Zie ook [`USER_GUIDE.md`](USER_GUIDE.md) (§
Markdown mode). Markdown mode).
---
## 11. Exportmetadata (niet in `.md`)
Bij PDF-, PPTX- en HTML-export schrijft OciDeck **documenteigenschappen** die
afgeleid zijn van front matter (`author`, `organization`, `description`,
`keywords`, `tlp`, titel). Deze metadata staat **niet** in het `.md`-bestand
en verandert het round-trip-formaat niet; ze worden alleen bij export gezet
(`ExportDocumentMetadata` in `lib/services/export_metadata.dart`).
| Bron (front matter) | PDF / PPTX | HTML |
| --- | --- | --- |
| Titel | `Title` | `<title>` |
| `author`, anders `organization` | `Author` / `dc:creator` | `<meta name="author">` |
| `description` | — | `<meta name="description">` |
| `keywords` + TLP + `OciDeck` | `Keywords` | `<meta name="keywords">` |
| `tlp` (wanneer ≠ none) | `Subject`: `TLP:… — titel` | `<meta name="classification">`, `<meta name="tlp">`, vaste `.tlp-export-banner` bovenaan |
Visuele TLP-markering (banner, badge, optioneel watermerk) wordt **gerasterd**
in PDF/PPTX-slides en staat los van deze documenteigenschappen. Zie
[`USER_GUIDE.md`](USER_GUIDE.md) (§ Traffic Light Protocol, § Exporting) en
[`ARCHITECTURE.md`](ARCHITECTURE.md) (§ Classification enforcement).

View file

@ -108,15 +108,38 @@ Below each editor you can set:
## Traffic Light Protocol (TLP) ## Traffic Light Protocol (TLP)
A deck has an overall TLP level (shown as a marking on the slides). Each slide can A deck has an overall TLP level (set from the **TLP** chip in the title bar, or
*also* carry its own level. When you present or export, slides whose level is under *Presentation properties*). Each slide can *also* carry its own level
(*Per-slide options*). When you present or export, slides whose level is
**stricter** than the level chosen for the deck are **withheld** — so the same **stricter** than the level chosen for the deck are **withheld** — so the same
deck can be shown safely to audiences with different clearances. Order, least to deck can be shown safely to audiences with different clearances. Order, least to
most restrictive: none < CLEAR < GREEN < AMBER < AMBER+STRICT < RED. most restrictive: none < CLEAR < GREEN < AMBER < AMBER+STRICT < RED.
Classifying a deck is **optional**. As an extra guardrail, an organisation can Classifying a deck is **optional** by default. An organisation can tighten that
set a **release ceiling** — a maximum level that may leave the machine; see with the **classification enforcement** settings under *Settings → General →
*Exporting* below. Accessibility* (see *Exporting* below).
### Visual marking (WYSIWYG)
When a slide is classified, OciDeck shows the official FIRST TLP 2.0 marking in
the preview, presenter, audience window, thumbnails, and raster exports (PDF/PPTX).
What you see on screen is what leaves the app — the marking is not a separate
overlay added only at export time.
For each visible slide, the **effective** level is the **stricter** of the deck
level and that slide's own level. On top of that:
- **Banner** — a full-width black bar at the top with the coloured TLP label
(e.g. `TLP:AMBER`).
- **Badge** — the same label in a compact box at the bottom-right (or bottom-left
when the logo sits bottom-right), so the footer text can step aside.
- **Watermark** (optional, off by default) — a faint diagonal repeat of the TLP
label and the deck's **organisation** field across the slide. Enable it under
*Settings → General → Accessibility → Classification watermark*.
Slides with no classification show none of the above. Per-slide TLP that is
stricter than the deck still contributes to the effective marking on slides that
are shown.
## Presenting ## Presenting
@ -174,10 +197,37 @@ Export to:
- **Portable package** (`.ocideck`) — a single zip with the Markdown and all - **Portable package** (`.ocideck`) — a single zip with the Markdown and all
assets, to hand the whole deck to someone else. assets, to hand the whole deck to someone else.
**Release ceiling (optional).** When a maximum TLP level is configured, exporting **Classification enforcement (optional).** Under *Settings → General →
a deck classified *above* it is blocked for every format, and the export dialog Accessibility → Classification enforcement* an organisation can configure up to
explains why. The ceiling is off by default and classifying a deck stays four independent rules. All are off by default; together they form a single
optional — it only stops decks that exceed the configured level. **export gate** that applies to PDF, PPTX, HTML, and the portable package.
When a rule blocks export, **no file is written** (fail-closed) and the export
dialog shows the reason. The status bar export button tooltip repeats that reason
when the deck is saved and clean.
| Setting | Effect |
| --- | --- |
| **Release ceiling** | Blocks export when the deck's TLP is **higher** than the chosen maximum (same as before — a deck at RED cannot export when the ceiling is AMBER). |
| **Required minimum level** | Blocks export when the deck's TLP is **lower** than the chosen minimum (e.g. minimum GREEN rejects CLEAR and unclassified decks). |
| **Classification required** | Blocks export when the deck has **no** TLP level set, even if no minimum is configured. |
| **Classification watermark** | Does not block export; adds the diagonal watermark described under *Traffic Light Protocol*. |
The gate evaluates the **deck-wide** TLP from front matter, not per-slide levels.
Per-slide stricter levels still affect which slides appear in the export (they are
withheld), but they do not satisfy "classification required" on their own — set
the deck level explicitly.
When export is blocked because the deck is unclassified, the **TLP** chip in the
title bar gets an orange border and its tooltip explains that a level is required.
**Export metadata.** PDF, PPTX, and HTML exports embed document properties derived
from the deck: title, author (falling back to organisation), description, keywords,
and TLP. When a TLP level is set, it is prefixed in the PDF/PPTX **Subject**
(`TLP:GREEN — My deck`), added to **Keywords** (`TLP`, the label, and the stable
key), and written to HTML `<meta name="classification">` and `<meta name="tlp">`.
HTML exports also show a fixed top banner with the TLP label when classified.
These properties are for discovery and handling downstream — they do not replace
the visible banner, badge, and optional watermark on the slides themselves.
## Accessibility ## Accessibility