# Domain Design — Component Catalogue > Components for the wedding newspaper generator, per confirmed decomposition > (Q1–Q5 all = A): a single `Generator` orchestrator, a distinct optional > `AiDraft` component (local Ollama only), a distinct `Funnies` component > (puzzles + cartoon), the review-page handoff via a draft JSON bundle read > through the native file picker, and lightweight plain-data entities with the > filesystem as the store. ## Part A — Machine-readable catalogue ```yaml components: - name: Generator summary: > The CLI entry point and orchestrator: reads config + content, coordinates layout and rendering, and emits the self-contained printable newspaper.html. behaviour: > Reads config.json (masthead metadata) and the content/ folder. Owns content parsing (markdown/txt), the classic broadsheet layout model, multi-page A4 pagination with clean page breaks, article/section placement, the optional per-article review handoff, and final zero-network HTML emission. Never sends content off-machine. Optional AI draft and funnies are delegated to AiDraft and Funnies respectively. responsibilities: - Read + validate config (masthead metadata) and content - Parse markdown/txt into Article entities - Build the newspaper layout (columns, pagination, masthead) - Assemble the funnies section output from Funnies - Emit self-contained newspaper.html (zero-network, file:// printable) - Write the AI-draft JSON bundle for the review page (review handoff) depends_on: - component: AiDraft interaction: calls optional local-Ollama draft when --draft is used; receives draft Article set style: sync - component: Funnies interaction: asks for crossword/find-a-word/cartoon built from content theme words + context style: sync dependents: [] external_dependencies: - name: Local filesystem (content/, config.json) kind: other purpose: the authoritative content store (Q5=A — filesystem is the store) entities: - name: Issue identifier: issueNumber attributes: [title, dateLine, issueNumber, volume, coupleNames] - name: Article identifier: articleId attributes: [type, headline, byline, body, section] - name: MastheadConfig identifier: configPath attributes: [title, coupleNames, dateLine, issueNumber, volume] references: - entity: Issue owned_by: Generator relationship: "each Issue carries the resolved masthead identity" - name: DraftBundle identifier: bundlePath attributes: [articles, generatedAt] references: - entity: Article owned_by: Generator relationship: "the DraftBundle holds the AI-drafted Article set for review" - name: AiDraft summary: > Optional component that drafts newspaper copy via the local Ollama model. behaviour: > Talks only to local Ollama. Given the content-derived context and brief, it drafts the lead story and filler articles. Produces a DraftBundle the review page reads. Fully optional — when --draft is absent it does nothing and the render uses only human-authored content. Never contacts any external/cloud service; content stays on-machine. responsibilities: - Accept the authoring brief (content-derived context + --draft flag) - Call local Ollama (granted model deepseek-v4-flash:cloud) to draft copy - Return a DraftBundle (article set) for per-article review depends_on: [] dependents: - component: Generator interaction: invoked by the generator when the couple requests an AI draft external_dependencies: - name: Local Ollama kind: third-party-api purpose: local model inference for copy drafting (NFR9, local-only) entities: [] - name: Funnies summary: > Component that builds the "fun" section — crossword, find-a-word, and comic/cartoon placement — generated from the content itself. behaviour: > Takes theme words and context drawn from the content (US3/FR4/Q5=X), produces the crossword grid + across/down clues, a find-a-word grid, and selects/embeds an xkcd-style cartoon using content-derived context for the search. A build-time network fetch to enrich content is allowed and encouraged (human ruling 2026-09-13: no server, not deployed, but internet data may enhance content) — so a genuine xkcd may be fetched at generation time and embedded locally. Emits print-ready, A4-bounded, self-contained outputs; the EMITTED page itself makes zero network requests. If no cartoon is found, it falls back to a tasteful content-derived decorative strip rather than blocking the run (mockups R-02 resolution). responsibilities: - Derive theme words and context from Content/article text - Build crossword grid + clues - Build find-a-word grid - Select/embed a cartoon (content-context search, local embed) - Emit A4-printable, self-contained puzzle/comic blocks depends_on: [] dependents: - component: Generator interaction: generator requests funnies output during the layout pass external_dependencies: - name: Local filesystem (xcomic assets / bundled art) kind: other purpose: source for comics and the embedded xkcd cartoon (no network at print) entities: - name: Puzzle identifier: puzzleId attributes: [type, grid, cluesAcross, cluesDown, solution, findWords] ``` ## Part B — Human-readable view ### Component Diagram ```mermaid graph LR Generator -->|"--draft drafts copy"| AiDraft Generator -->|"theme words and context"| Funnies AiDraft -->|"DraftBundle"| Generator Funnies -->|"puzzles and cartoon"| Generator ``` ### Component Summary | Component | Purpose | Depends On | Dependents | Entities Owned | |---|---|---|---|---| | Generator | CLI orchestrator: config/content → self-contained newspaper.html | AiDraft, Funnies | — | Issue, Article, MastheadConfig, DraftBundle | | AiDraft | Optional local-Ollama copy drafting | — | Generator | — | | Funnies | Content-derived crossword/find-a-word/cartoon | — | Generator | Puzzle | ### Entity Ownership | Entity | Owning Component | Identifier | Attributes | References | |---|---|---|---|---| | Issue | Generator | issueNumber | title, dateLine, issueNumber, volume, coupleNames | — | | Article | Generator | articleId | type, headline, byline, body, section | — | | MastheadConfig | Generator | configPath | title, coupleNames, dateLine, issueNumber, volume | Issue | | DraftBundle | Generator | bundlePath | articles, generatedAt | Article | | Puzzle | Funnies | puzzleId | type, grid, cluesAcross, cluesDown, solution, findWords | — | ### External Dependencies | Component | Dependency | Kind | Purpose | |---|---|---|---| | Generator | Local filesystem (content/, config.json) | other | authoritative content store (Q5=A) | | AiDraft | Local Ollama | third-party-api | local model inference (NFR9) | | Funnies | Local filesystem (comic/cartoon assets) | other | local comic + embedded cartoon source | ### Rationale | Component | Why a separate building block | |---|---| | Generator | Distinct concern (parsing → layout → emit) and distinct lifecycle; its own data ownership and change rate (layout/print changes land here) | | AiDraft | Distinct concern and change rate (AI integration is optional and isolated); keeping it separate means no-draft runs never touch it | | Funnies | Distinct concern (content→puzzle heuristics are non-trivial) and change rate; isolated so layout changes don't destabilize puzzle generation | ### Alternatives Rejected (decomposition) - **Option B (multiple fine-grained components: ContentLoader, FunniesBuilder, LayoutEngine, Renderer)** — rejected: over-fragmentation for a local single-purpose tool; Q1=A chose cohesion. Reconsider if the generator grows into a larger system. - **Option C (minimal thin script)** — rejected: would collapse puzzle/AI/layout responsibilities into un-testable ball of code; the three-component split keeps testability without over-splitting. ## Deliberate-cycles note None. The dependency graph is acyclic: Generator → { AiDraft, Funnies }, with the DraftBundle back-reference being a data artifact (written then read via the file picker), not a synchronous call, so it is not a dependency cycle.