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
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
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.