Ocideck/docs/ARCHITECTURE.md
Brenno de Winter 2d8be6f0dd
Some checks failed
CI / Format · Analyze · Test (push) Has been cancelled
CI / Format · Analyze · Test (pull_request) Has been cancelled
Add project docs, EUPL licence, and open-source licence check
Documentation & licensing:
- Add the EUPL-1.2 licence (LICENSE.md) and set the project licence; refresh
  the README (name origin wink, updated feature list, documentation index).
- Add CONTRIBUTING, SECURITY, CODE_OF_CONDUCT, CHANGELOG, AUTHORS, and
  THIRD_PARTY_NOTICES, plus docs/ (ARCHITECTURE, BUILD, USER_GUIDE, SHORTCUTS,
  LICENSE_COMPLIANCE) and .github/ (CI workflow, issue/PR templates).
- Bring docs/FILE_FORMAT.md in line with current behaviour (code & chart
  slides, per-slide TLP comment, annotation .ink.json sidecar, chart data/ CSVs).

Open-source compliance:
- Add tool/check_licenses.dart and a `make licenses` target (wired into
  check-full and CI) that verifies every resolved dependency uses a recognised
  open-source licence. A scan of all 151 packages and bundled assets found only
  OSI-approved licences.

Charts (Fase 1.1):
- Replace the chart CSV textarea with an in-app editable data grid (editable
  series/labels/values, add/remove row & column, read-only when linked).
- Centralize the linked-CSV directory name (`data/`) in a shared constant.

Also normalize formatting repo-wide with `dart format` and fix one
curly-braces lint, so `make check` and CI are green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 12:19:56 +02:00

5.2 KiB

OciDeck — Architecture

A high-level map of how OciDeck is put together, for contributors. For how files are stored on disk, see FILE_FORMAT.md.

Stack

  • Flutter desktop app (macOS, Windows, Linux), Dart 3.12+.
  • State: Riverpod.
  • Storage: standard Marp Markdown (.md) as the single source of truth, with sidecars for anything that isn't plain Marp.

Module layout

lib/
  models/     # Deck, Slide, Settings/ThemeProfile, Chart, Annotation
  services/   # markdown, file, export, image, caption, description,
              # recovery, rasterizer, marp_html, annotation_codec
  state/      # Riverpod providers: deck, editor, settings, tabs, clipboard
  widgets/    # app shell, panels, dialogs, per-type editors, slides, presenter
  l10n/       # AppLocalizations (8 languages)
  theme/      # app theming

Data model

  • Deck holds metadata, a list of **Slide**s, the active ThemeProfile, the deck-wide TLP level, and an in-memory annotation layer (Map<slideId, List<InkStroke>>) that is never serialized into the Markdown.
  • Slide is a single immutable value with a SlideType and typed fields. A few types reuse customMarkdown for their payload: free-Markdown (raw), code (the source), and chart (the JSON spec).
  • Slide ids are regenerated on every parse, so they are stable only within a session. Anything persisted that must survive a reload (annotations) re-anchors by slide order + a content fingerprint rather than by id.

Markdown round-trip

MarkdownService is the contract:

  • generateDeck / generateSlide write Marp Markdown. OciDeck extras live in front-matter keys and <!-- … --> comments that Marp ignores.
  • parseDeck / _parseBlock read it back. code and chart slides are detected by their _class and parsed separately (their fenced block would otherwise confuse the generic line parser).

This service is heavily covered by the round-trip tests — treat it as the source-of-truth for the file format and keep FILE_FORMAT.md in sync.

The two rendering worlds

Charts, diagrams, and slides are rendered in two independent places, which is the key thing to understand before touching rendering:

  1. In-appwidgets/slides/slide_preview.dart (SlidePreviewWidget) renders a slide as Flutter widgets. The same widget is used for the editor preview, thumbnails, the fullscreen presenter, and — via services/slide_rasterizer.dart — the PDF and PPTX exports (rasterized to images). So anything that must appear in PDF/PPTX must render here. Charts use fl_chart.
  2. HTML exportservices/marp_html_service.dart produces a single self-contained .html that renders in a browser using inlined JavaScript (marked, highlight.js, mermaid, MathJax). Charts are pre-rendered to inline SVG in Dart here (no JS chart library). Fidelity differs from the in-app renderer by design.

Presenter

widgets/presentation/fullscreen_presenter.dart drives presenting:

  • Keyboard navigation, presenter view, blank screen, grid overview, auto-advance, and the annotation tools (pen/highlighter/eraser/laser).
  • Neighbour slide images are precached and gaplessPlayback is on, so slide changes never flash black (important for screen recording).

Dual-screen mode

When a second display is present (shouldUseDualScreen), the presenter runs in two OS windows:

  • The laptop window shows the presenter view.
  • A borderless audience window (audience_window.dart) fills the external screen with the slide.
  • They sync over method channels (ocideck/audience, ocideck/presenter): current index, blank state, ink strokes, and the laser pointer. Media plays only on the beamer to avoid double audio.

This needs a real second window, which window_manager (single-window) can't do, hence the vendored multi-window fork below.

Sidecars (separate layers)

To keep the .md pure Marp, three kinds of data live beside it (see FILE_FORMAT.md §6):

  • Captions.ocideck_captions.json (per image, in images/).
  • Annotations<name>.ink.json (services/annotation_codec.dart).
  • Linked chart datadata/*.csv (the living source for a chart).

Vendored forks

Two upstream plugins are forked into third_party/ and wired via pubspec.yaml (path dependency / dependency_overrides):

  • desktop_multi_window (MixinNetwork) — published 0.3.0 dropped the native window-geometry API. The fork adds macOS window_setFrame, window_coverScreen (borderless fill of a chosen screen), and window_close, exposed on WindowController. This is what makes the dual-screen audience window possible.
  • screen_retriever_macos (leanflutter) — a packaging fix for recent Xcode/CocoaPods.

If you bump either upstream, re-apply the local changes (they're small and documented in the diff) and re-test the dual-screen presenter.

Localization

l10n/app_localizations.dart holds Dutch source strings (d('…')) and translation maps for en/it/de/fr/es/fy/pap. A test enforces that every literal .d('…') has a translation in every language — add new strings to all maps.