This commit is contained in:
+179
@@ -0,0 +1,179 @@
|
||||
# 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.
|
||||
|
||||
<!-- domain-design-after-confirm -->
|
||||
+77
@@ -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.
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# Domain Design — Questions
|
||||
|
||||
> Fill in each `[Answer]:` tag. Options A-E plus X (Other). The file is the
|
||||
> authoritative record of your component-decomposition decisions.
|
||||
|
||||
## Q1: Generator monolith vs. separated components
|
||||
|
||||
How should the generator's build-time work be split as reusable components?
|
||||
|
||||
A) A single `Generator` component that reads content and orchestrates rendering, with clearly separated internal responsibilities (config, content, funnies, layout) — cohesive, simple for a local tool (recommended)
|
||||
B) Split into multiple independent components (ContentLoader, FunniesBuilder, LayoutEngine, Renderer) that the CLI calls in sequence
|
||||
C) Minimal: one thin script + the emitted static page only
|
||||
X) Other (please specify)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Q2: AI draft client boundary
|
||||
|
||||
The AI copy generation (US8/US9) uses local Ollama. Should it be its own component?
|
||||
|
||||
A) Yes — a distinct `AiDraft` component that talks only to local Ollama, owns the draft + per-article review contract, and is fully optional (recommended)
|
||||
B) No — fold the Ollama calls into the generator as an optional step
|
||||
X) Other (please specify)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Q3: Funnies generation ownership
|
||||
|
||||
The funnies (crossword, find-a-word, comics, xkcd cartoon) are generated from content (Q5=X). Which component should own this?
|
||||
|
||||
A) A distinct `Funnies` component that takes theme words/context from the content and produces the crossword/find-a-word grids + selects/embeds a cartoon (recommended)
|
||||
B) Fold funnies generation into the main generator as a sub-step
|
||||
C) Separate into puzzle-gen and cartoon-fetch sub-components
|
||||
X) Other (please specify)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Q4: Review page data handoff (R-01 from mockups)
|
||||
|
||||
Under the no-server constraint (Q1=A/Q4=B), how should the per-article review page receive the AI draft for a single file:// page to read it?
|
||||
|
||||
A) The CLI writes a draft bundle (JSON) the review-page loads through the native file picker (user-granted, NFR5) — recommended
|
||||
B) The review page is served briefly by a temporary localhost server just during review, then the final static HTML is produced
|
||||
C) Merge review into the CLI prompt loop (no HTML review page) — Q3=A in mockups says review page, so this departs from it
|
||||
X) Other (please specify)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Q5: Entity model for content & issue
|
||||
|
||||
For a purely local generator, how should we model the domain entities (an "issue", "article", "config", "puzzle")?
|
||||
|
||||
A) Lightweight plain data shapes (Issue, Article, MastheadConfig, Puzzle) owned by the generator, no database — the content filesystem is the store (recommended)
|
||||
B) A formalized issue/entity model with strict schemas up front
|
||||
C) No explicit entity model — keep it all file/template-driven
|
||||
X) Other (please specify)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
## Consolidated Summary Confirmation
|
||||
|
||||
> Summary of your five architecture answers before the component catalogue and ADRs are generated:
|
||||
>
|
||||
> - Generator decomposition: **single Generator component** with clearly separated internal responsibilities (config, content, funnies, layout) (Q1=A)
|
||||
> - AI draft client: **distinct, optional AiDraft component** that talks only to local Ollama and owns the draft + per-article review contract (Q2=A)
|
||||
> - Funnies ownership: **distinct Funnies component** that takes theme words/context from content and produces the crossword/find-a-word grids + selects/embeds a cartoon (Q3=A)
|
||||
> - Review-page data handoff: **CLI writes a draft JSON bundle** the review page loads through the native file picker (user-granted, NFR5) — resolving mockups R-01 (Q4=A)
|
||||
> - Entity model: **lightweight plain data shapes** (Issue, Article, MastheadConfig, Puzzle) owned by the generator; the content filesystem is the store (Q5=A)
|
||||
|
||||
Does this all look correct before I generate the component catalogue and ADRs?
|
||||
|
||||
- Looks correct
|
||||
- Request changes
|
||||
|
||||
[Answer]: Looks correct
|
||||
@@ -0,0 +1,17 @@
|
||||
<!-- INVARIANT: examples are single-line HTML comments so a fresh template parses to total=0 (MEMORY_EMPTY). Do NOT un-comment or split across lines. t100 guards this. -->
|
||||
> This file is kept up to date automatically while the stage runs. Add observations at the review step, not by editing here directly.
|
||||
|
||||
## Interpretations
|
||||
<!-- example: 2026-05-29T10:14:32Z — chose REST over GraphQL; the consuming team only needs CRUD, revisit if subscriptions land -->
|
||||
|
||||
## Deviations
|
||||
<!-- example: 2026-05-29T10:14:32Z — skipped the optional caching layer the stage prose suggested; the dataset is small enough that it adds risk -->
|
||||
|
||||
## Tradeoffs
|
||||
<!-- example: 2026-05-29T10:14:32Z — picked TDD over BDD this run; the team is unit-first and the domain is well-understood -->
|
||||
|
||||
## Open questions
|
||||
<!-- example: 2026-05-29T10:14:32Z — confirm the retention window with compliance before the next stage hardens the schema -->
|
||||
2026-09-13T12:00:00Z — Interpretation — Confirmed decomposition (all A): single Generator orchestrate, distinct optional AiDraft (local Ollama), distinct Funnies (content-derived puzzles + cartoon), draft bundle read via file picker, lightweight entities with filesystem store
|
||||
2026-09-13T12:00:00Z — Tradeoff — DraftBundle back-reference is a data artifact (written then read via file dialog), not a sync call, so no dependency cycle
|
||||
2026-09-13T12:05:00Z — Interpretation — Human ruling (learnings): the constraint is no server + not deployed, but pulling data from the internet to ENHANCE content is fine/encouraged (e.g. xkcd cartoons, comics). Resolves review R-01: build-time content enrichment may fetch from the internet; the EMITTED page stays self-contained/printable (fetch happens at generation time, assets embedded locally)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"stage": "domain-design",
|
||||
"upstream_ids": ["US1", "US2", "US3", "US4", "US5", "US6", "US7", "US8", "US9", "US10"],
|
||||
"coverage": [
|
||||
{ "id": "US1", "status": "OK", "target": "Generator" },
|
||||
{ "id": "US2", "status": "OK", "target": "Generator (MastheadConfig)" },
|
||||
{ "id": "US3", "status": "OK", "target": "Funnies" },
|
||||
{ "id": "US4", "status": "OK", "target": "Generator (Article)" },
|
||||
{ "id": "US5", "status": "OK", "target": "Generator" },
|
||||
{ "id": "US6", "status": "OK", "target": "Generator" },
|
||||
{ "id": "US7", "status": "OK", "target": "Generator (MastheadConfig/Issue)" },
|
||||
{ "id": "US8", "status": "OK", "target": "AiDraft" },
|
||||
{ "id": "US9", "status": "OK", "target": "AiDraft (DraftBundle), Generator (review handoff)" },
|
||||
{ "id": "US10", "status": "OK", "target": "Generator (empty state)" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user