first pass at the newspaper builder
Test / test (push) Has been cancelled

This commit is contained in:
2026-09-14 11:57:22 +10:00
commit bec1eaac87
497 changed files with 178953 additions and 0 deletions
@@ -0,0 +1,77 @@
# Architecture Decision Records — Wedding Newspaper Generator
> Durable ADR log for the significant design choices made in Domain Design.
## ADR-001: Cohesive Generator component over fine-grained splitting
- **Context** — The generator is a single-purpose, fully-local tool. The team
had to choose between one cohesive orchestrator with separated internal
responsibilities (Q1=A) and several fine-grained independent components
(ContentLoader, FunniesBuilder, LayoutEngine, Renderer).
- **Decision** — Adopt a single `Generator` component that owns content
parsing, the layout model, multi-page rendering, and emission, with its
funnies and AI-draft concerns delegated to two distinct components
(`Funnies`, `AiDraft`).
- **Consequences** — (+) Simpler for a local tool; fewer moving parts;
layout/print changes land in one place. (−) The Generator is larger; if the
tool grows into a larger system this may need splitting later.
- **Alternatives Rejected** — Option B (fine-grained components): over-fragmented
for this scope. Option C (minimal thin script): collapses concerns and loses
testability.
## ADR-002: Distinct optional AiDraft component (local Ollama only)
- **Context** — AI copy generation (US8/US9/FR8) uses local Ollama and must be
fully optional and stay on-machine (NFR9). The team chose whether to isolate it.
- **Decision** — Model `AiDraft` as a distinct, fully-optional component that
talks only to local Ollama and owns the draft + per-article review contract.
When `--draft` is absent it does nothing.
- **Consequences** — (+) No-draft runs never touch AI; the AI integration is
isolated and easier to test/disable; content stays local. (−) One more
component boundary to document.
- **Alternatives Rejected** — Folding Ollama calls into the generator: couples
the optional AI path into the core and makes "no AI" runs carry AI code.
## ADR-003: Funnies generation owned by a distinct component
- **Context** — The funnies (crossword, find-a-word, comics, xkcd cartoon) are
generated from the content itself (US3/FR4/Q5=X), which is non-trivial work.
- **Decision** — Give `Funnies` its own component that derives theme
words/context from content and produces the crossword/find-a-word grids and
the embedded cartoon. It emits A4-printable, self-contained blocks.
- **Consequences** — (+) Puzzle logic is isolated from layout so layout changes
don't destabilize generation; content-derived puzzles feel hand-crafted. (−)
The content→puzzle heuristic is genuinely complex and must handle a no-cartoon
fallback (mockups R-02).
- **Alternatives Rejected** — Folding funnies into the generator: couples puzzle
heuristics into layout; separate puzzle-gen and cartoon-fetch sub-components:
over-split for now.
## ADR-004: Draft bundle read via native file picker (no-server review handoff)
- **Context** — The per-article review page (US9/Q3=A) is a single `file://`
page, and the no-server/zero-network constraints (NFR3–NFR5) forbid `fetch()`
of sibling files and forbid running a server during review (Q1=A / Q4=B).
- **Decision** — The CLI writes the AI draft as a JSON `DraftBundle`; the review
page loads it through the **native file picker** (user-granted read, NFR5).
This resolves the mockups R-01 finding.
- **Consequences** — (+) Review works fully offline, no server, no CORS workaround
(§12 of the empirical file:// research). (−) The couple must pick the draft
bundle in the file dialog once per run.
- **Alternatives Rejected** — Serving a temporary localhost just during review
(violates the no-server decision and adds a process to the flow); CLI prompt
loop instead of an HTML review page (departs from confirmed Q3=A).
## ADR-005: Lightweight plain-data entities, filesystem as store
- **Context** — The project is a local generator with no deployment; the
entities are Issue, Article, MastheadConfig, Puzzle, DraftBundle.
- **Decision** — Model them as lightweight plain-data shapes owned by the
generator; the content `config.json` + `content/` filesystem is the store.
No database.
- **Consequences** — (+) No infra to run; entities are trivially serializable
(the DraftBundle JSON is one shape); fits the static-generator model. (−) No
built-in indexing/querying — not needed at this scale.
- **Alternatives Rejected** — A formalized strict-schema data layer: unnecessary
overhead for a static local tool. No explicit entity model: would make the
review handoff and traceability harder to test.