Files
2026-09-14 11:57:22 +10:00

8.5 KiB
Raw Permalink Blame History

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

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.