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

120 lines
5.2 KiB
Markdown

# 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`](FILE_FORMAT.md).
## Stack
- **Flutter** desktop app (macOS, Windows, Linux), Dart 3.12+.
- **State**: [Riverpod](https://riverpod.dev/).
- **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-app**`widgets/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 export**`services/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 data** — `data/*.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.