From e601d7ee7819b4a9b9c729b5a26c13102cf5ad26 Mon Sep 17 00:00:00 2001 From: Brenno de Winter Date: Tue, 16 Jun 2026 10:52:19 +0200 Subject: [PATCH] Document TLP classification enforcement, marking, and export metadata. Co-authored-by: Cursor --- CHANGELOG.md | 10 +++++++ README.md | 2 +- docs/ARCHITECTURE.md | 54 +++++++++++++++++++++++++++++------ docs/FILE_FORMAT.md | 35 +++++++++++++++++++++++ docs/USER_GUIDE.md | 68 ++++++++++++++++++++++++++++++++++++++------ 5 files changed, 150 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1498d86..5be2062 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). Classifying a deck stays optional — the ceiling only stops decks that exceed 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 `` / ``, plus a fixed HTML banner). - **Dual-screen presenter** — on a second display the beamer shows the slide while the laptop shows the presenter view (current/next slide, notes, timer), kept in sync over method channels. diff --git a/README.md b/README.md index 5a1b115..9cad5af 100644 --- a/README.md +++ b/README.md @@ -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. - **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. -- **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. - **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. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a24f0a5..56178ca 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -15,8 +15,9 @@ are stored on disk, see [`FILE_FORMAT.md`](FILE_FORMAT.md). ``` lib/ models/ # Deck, Slide, Settings/ThemeProfile, Chart, Annotation - services/ # markdown, markdown_validator, file, export, classification_policy, - # image, caption, + services/ # markdown, markdown_validator, file, export, + # classification_policy, classification_enforcement_policy, + # export_metadata, image, caption, # description, image_dedup (md5 duplicates), # image_reference (.md rewrites), recovery, rasterizer, # marp_html, annotation_codec, rehearsal_controller @@ -75,13 +76,48 @@ the key thing to understand before touching rendering: renderer by design. Both worlds converge at one chokepoint: `services/export_service.dart` -(`ExportService.export()`) is the only place that writes an export, so the -**classification gate** lives there rather than in the export dialog. A -`ClassificationPolicy` enforces an optional *release ceiling* and refuses, -**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); -the dialog also runs the same check up front so a blocked export is explained -before any work starts. +(`ExportService.export()`) is the only place that writes an export. + +### Classification enforcement + +Export blocking is decided by `ClassificationEnforcementPolicy` +(`services/classification_enforcement_policy.dart`), evaluated inside +`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 `` 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 diff --git a/docs/FILE_FORMAT.md b/docs/FILE_FORMAT.md index 1913b30..52b1a56 100644 --- a/docs/FILE_FORMAT.md +++ b/docs/FILE_FORMAT.md @@ -118,6 +118,18 @@ Opgeslagen onder de sleutel `tlp` met deze stabiele waarden: | `amber+strict` | `TLP:AMBER+STRICT` | | `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 (``). 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) 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: `test/markdown_validator_test.dart`. Zie ook [`USER_GUIDE.md`](USER_GUIDE.md) (§ 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` | `` | +| `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). diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 8143c6a..5e14da5 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -108,15 +108,38 @@ Below each editor you can set: ## Traffic Light Protocol (TLP) -A deck has an overall TLP level (shown as a marking on the slides). Each slide can -*also* carry its own level. When you present or export, slides whose level is +A deck has an overall TLP level (set from the **TLP** chip in the title bar, or +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 deck can be shown safely to audiences with different clearances. Order, least to most restrictive: none < CLEAR < GREEN < AMBER < AMBER+STRICT < RED. -Classifying a deck is **optional**. As an extra guardrail, an organisation can -set a **release ceiling** — a maximum level that may leave the machine; see -*Exporting* below. +Classifying a deck is **optional** by default. An organisation can tighten that +with the **classification enforcement** settings under *Settings → General → +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 @@ -174,10 +197,37 @@ Export to: - **Portable package** (`.ocideck`) — a single zip with the Markdown and all assets, to hand the whole deck to someone else. -**Release ceiling (optional).** When a maximum TLP level is configured, exporting -a deck classified *above* it is blocked for every format, and the export dialog -explains why. The ceiling is off by default and classifying a deck stays -optional — it only stops decks that exceed the configured level. +**Classification enforcement (optional).** Under *Settings → General → +Accessibility → Classification enforcement* an organisation can configure up to +four independent rules. All are off by default; together they form a single +**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