Skip to content

feat(live): section-aware live article pages + homepage entry point - #153

Open
RonanHevenor wants to merge 6 commits into
mainfrom
fix/layout-grid-migration-drift
Open

RonanHevenor wants to merge 6 commits into
mainfrom
fix/layout-grid-migration-drift

Conversation

@RonanHevenor

Copy link
Copy Markdown
Member

What

Live blogs were effectively unreachable and visually inconsistent with normal articles. Two related fixes.

1. Live articles get a real section

live_articles.section looked like the site taxonomy but is free text for the homepage strip ("Labor Department", "Election Night"). Added a separate siteSection select (news/sports/features/opinion) so section keeps its strip-label meaning.

With a real section, /live/[slug] now matches StandardLayout:

  • ArticleScrollBar short header (same props the article route passes)
  • "Continue Reading / <Section>" recommendations block

ArticleRecommendations only ever read section/id/opinionType off its prop, so it's narrowed to an exported RecommendationContext. Article satisfies it structurally, so article pages are untouched. The id exclusion is conditional now, since a live article has no articles row to exclude.

2. Homepage always has an entry point

The strip is fed from layout.liveArticles, an editor-curated list that is often empty — and when empty, live blogs had no entry point anywhere on the site. Falls back to the 4 most recently updated published live articles; curation still wins when set. The strip also only rendered in the desktop header branch, so it's been added to the mobile header too.

Migration

Registered in both paths per docs/migrations.md:

  • migrations/20260906_010000_add_live_articles_site_section.ts + migrations/index.ts (CI)
  • Equivalent SQL + batch-30 tracking row in scripts/run_deploy_sql_migrations.sh (production deploy)

Column names verified against 20260420_000000_add_live_articles.ts (_live_articles_v uses the version_ prefix) and the articles enum convention. Both paths are idempotent — guarded CREATE TYPE, ADD COLUMN IF NOT EXISTS, CREATE INDEX IF NOT EXISTS, and the tracking insert ends in ON CONFLICT DO NOTHING.

Existing rows backfill to news via a nullable column DEFAULT plus an explicit UPDATE, so nothing breaks on read. required: true is Payload write-time validation only.

Checks

pnpm typecheck clean. pnpm lint 0 errors (53 pre-existing migrations/ warnings, unchanged). Migration applied cleanly against a local Postgres.

Note for after merge

Every existing live article will read as News until someone sets siteSection in the admin — worth a pass through the collection if any current live blog isn't news.

🤖 Generated with Claude Code

Ronan Hevenor and others added 5 commits September 6, 2026 15:35
…nbreak migrate:create

Fresh environments built purely from `migrations/` (new local checkouts, CI)
crashed on any query touching `layout`, `opinion_page_layout` or
`live_articles_updates`, because five columns present in the collection
definitions were never added by a migration. They reached existing databases
via dev-mode `db.push` before migrations became the source of truth:

  layout.grid                                   json
  opinion_page_layout.layout                    json
  live_articles_updates.author_id               -> users
  _live_articles_v_version_updates.author_id    version shadow
  payload_locked_documents_rels.opinion_page_layout_id

`updates.author` is a genuine mistake in 20260420_000000_add_live_articles,
which routed it through `live_articles_rels` under path "updates.author".
Payload stores single-target, non-hasMany relationships as a scalar
`<field>_id` column on the array's own table instead.

Every statement is idempotent (ADD COLUMN IF NOT EXISTS / duplicate_object
guards), and production predates the migration system and already has these
columns, so this is expected to be a no-op there rather than a schema change.
Added to both migration paths per docs/migrations.md.

Also removes four files in migrations/ that were never drizzle snapshots.
They contain `{id, name, batch}` — the shape of a payload_migrations row —
so `payload migrate:create` picked the newest as its previous-state snapshot
and died in Zod validation on missing `schemas` / `_meta` / `prevId`, which
made it impossible to generate any migration at all. Nothing referenced them:
migrations/index.ts does not import them and the deploy script carries its own
tracking INSERTs.

Finally, sets PAYLOAD_DISABLE_PUSH=1 in the generated .env. Without it a fresh
`pnpm dev` boots into Payload's dev-mode db.push, which blocks on an
interactive "DATA LOSS WARNING ... (y/N)" prompt (it wants to drop
`_layout_v.latest`, which exists because layout has drafts disabled) and
stalls every request behind it.

Verified with `pnpm db:migrate-test --seed`: both the TS and the production
SQL path apply cleanly to a fresh database and the seed completes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
….34.5

The twelve `pnpm.overrides` entries are minimum-version floors for known-
vulnerable transitive dependencies. pnpm stopped reading `pnpm.overrides`
from package.json and now warns that the keys are ignored, so they were
silently unenforced on any fresh resolution. Moved them verbatim to
`overrides:` in pnpm-workspace.yaml, which is where current pnpm reads them.

Verified: `pnpm install` with a full (non-frozen) resolution leaves
pnpm-lock.yaml byte-identical to the committed one, and `pnpm install
--frozen-lockfile` — what CI and the deploy both run — is unchanged. The
resolved dependency graph is therefore provably identical to what production
already builds from; this only restores enforcement for future resolutions.
`pnpm build` passes.

Also adds `packageManager: pnpm@10.34.5` to match the pnpm 10 that CI and the
deploy runner use. Without it a contributor on pnpm 11 silently rewrites
pnpm-lock.yaml — dropping the entire `overrides:` block from the lockfile
(729 insertions / 214 deletions locally) — and merging that would have carried
the downgraded transitive versions into a deploy. Pinning keeps every machine
resolving exactly what CI resolves.

Dropped the `version: 10` input from pnpm/action-setup in ci.yml and
android-build.yml. The action compares that input against the packageManager
field and hard-fails when they differ ("Multiple versions of pnpm specified
... Remove one of these versions"), so leaving both would have broken CI on
the first push. package.json is now the single source of truth, which also
makes CI use an exact pnpm rather than floating latest-10.x. deploy.yml does
not use action-setup (self-hosted runner) and is untouched.

No change to build-script approval: sharp ships prebuilt binaries and loads
fine with its postinstall skipped, so the ignored-build-scripts warning needs
no action and prod install behavior is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`previousSlug` was added to the Articles collection in 20260507_000000, but
payload-types.ts was never regenerated, so the committed types were missing
the field and its select entry. Pure `pnpm generate:types` output — no manual
edits, and regenerating again is a no-op.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…mmendations

Live article pages had no site chrome and no way back into the section
they belong to. `live_articles.section` looked like it could drive that,
but it is free text for the homepage strip ("Labor Department",
"Election Night") rather than the site taxonomy.

Add a separate `siteSection` select (news/sports/features/opinion) so
`section` keeps its strip-label meaning, then bring /live/[slug] in line
with StandardLayout: the ArticleScrollBar short header and the
"Continue Reading / <Section>" recommendations block.

ArticleRecommendations only ever read section/id/opinionType off its
prop, so narrow it to an exported RecommendationContext. Article still
satisfies it structurally, leaving article pages untouched. The id
exclusion is now conditional since a live article has no articles row to
exclude.

Migration is registered in both paths per docs/migrations.md: a TS
migration for CI, and the equivalent SQL plus a batch-30 tracking row in
run_deploy_sql_migrations.sh for the production deploy. Existing rows
backfill to 'news' via a nullable column DEFAULT plus an explicit
UPDATE, so no live row breaks on read before an editor sets the field.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The homepage live strip is fed from `layout.liveArticles`, an
editor-curated list that is frequently empty — and when it was empty,
live blogs had no entry point anywhere on the site. Fall back to the
four most recently updated published live-articles when nothing is
curated; curation still wins when it is set.

The strip also only rendered in the desktop (`hidden lg:block`) header
branch, so mobile had no live nav at all. Render it under the mobile
header's date/volume bar too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@RonanHevenor

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

…tion-drift

# Conflicts:
#	migrations/index.ts
#	scripts/run_deploy_sql_migrations.sh

Co-authored-by: RonanHevenor <101836957+RonanHevenor@users.noreply.github.com>

Copilot AI commented Sep 7, 2026

Copy link
Copy Markdown

@copilot resolve the merge conflicts in this pull request

Resolved. I merged origin/main into this branch and fixed the conflicts in the migration files in commit bae89d3.

@RonanHevenor

Copy link
Copy Markdown
Member Author

@copilot base branch modified. fix this

Copilot AI commented Sep 7, 2026

Copy link
Copy Markdown

@copilot base branch modified. fix this

Checked and synced against origin/main; this branch is already up to date after merge commit bae89d3, so no additional conflict-resolution commit was needed.

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.

2 participants