Skip to content

Render newsletter points as bullets and stop duplicating section titles - #1071

Merged
kevinhermawan merged 1 commit into
mainfrom
fix/content-generation-duplicate-heading
Jul 29, 2026
Merged

Render newsletter points as bullets and stop duplicating section titles#1071
kevinhermawan merged 1 commit into
mainfrom
fix/content-generation-duplicate-heading

Conversation

@kevinhermawan

Copy link
Copy Markdown
Contributor

Summary

The Content Generation Results table repeated every section name (Deals & Movements - Deals & Movements) and showed each entry as one paragraph. Both come from the same place: the step that flattens the newsletter document for persistence writes the canonical label into heading, and joins each article's points into a single string. The email has been rendering those points as a real list all along, so this makes the dashboard match it.

Related issues

Closes #1070

Important changes

  • points replaces summary on NewsletterSectionItem. The agent stops calling points.join(" "), the contract takes an array, and the Results table renders one row per point beneath the entry's headline. This is a breaking contract change: newsletterSectionItemSchema now requires points and rejects summary, so agent-data-api and content-generation need to deploy together.
  • The migration backfills before dropping, so nothing is lost: UPDATE ... SET points = ARRAY[summary] for rows that have none, then DROP COLUMN summary. Entries written earlier become a single point rather than N, which is honest, since the original points were destroyed at write time and cannot be reconstructed.
  • composeSectionHeaderLabel only appends the agent's heading when it adds information, comparing case-insensitively and collapsing repeated whitespace. The join is still worth keeping for older rows whose headings differed.
  • New bulletField on the sub-table column contract, a path to a boolean on the row. When truthy the renderer indents the cell under a bullet marker. The marker lives in the renderer, not in stored text, which is also what summarize-article.ts instructs the model to do.

Other changes

  • The Results column links the headline directly rather than carrying it as a description under the summary.
  • entryPoints trims and drops blank points.

Key files to review

  • packages/mediapulse/database/prisma/migrations/20260729040000_newsletter_section_item_points/migration.sql - hand-written, since no local Postgres was reachable. It is more involved than a plain drop because of the UPDATE backfill, and the ordering matters: add, backfill, drop. Worth a careful read.
  • apps/mediapulse/agents/content-generation/src/llm-generate-newsletter.ts - collectNewsletterSections, where the join was.
  • packages/shared/agent-data-api-contract/src/newsletter-section.ts - the breaking shape change.
  • apps/hermes/dashboard/components/detail-blocks/detail-block-sub-table.tsx - bulletField handling.

How to test

  1. pnpm code-quality from the repo root. 108/108 tasks pass.
  2. Open a newsletter detail page and go to the Content Generation stage.
  3. Section headers read Deals & Movements, not Deals & Movements - Deals & Movements.
  4. Each entry shows its headline as a link, with one bullet per written point beneath it, matching what the same newsletter looks like in the Email Preview tab.
  5. Generate a newsletter end to end and confirm sections persist with points populated and no summary column in play.
  6. Against a database with existing rows, apply the migration and confirm each old entry now carries exactly one point holding its former summary text.

Note on the email

No changes there. It already renders article.points as a <ul> and takes its section heading from SECTION_COPY, not from the persisted heading, so it never had either bug.

… titles

The Results table showed "Deals & Movements - Deals & Movements" for every
section, because collectNewsletterSections writes the canonical label into
`heading` and the builder joined label and heading unconditionally.

It also showed each entry as one paragraph. The email renders an article's
points as a real list, but the persister flattened them with points.join(" ")
before they reached the dashboard, so the structure was gone by the time the
table saw it. Carry `points` through instead, replacing the joined `summary`
column, and render one row per point behind a `bulletField` marker so the
glyph stays in the renderer rather than the data.

The migration backfills points from summary before dropping it, so entries
written earlier keep their text as a single point.
@kevinhermawan
kevinhermawan merged commit af316b9 into main Jul 29, 2026
24 checks passed
@kevinhermawan
kevinhermawan deleted the fix/content-generation-duplicate-heading branch July 29, 2026 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix duplicated section titles and flattened points in the Content Generation stage

2 participants