feat(live): section-aware live article pages + homepage entry point - #153
Open
RonanHevenor wants to merge 6 commits into
Open
RonanHevenor wants to merge 6 commits into
RonanHevenor wants to merge 6 commits into
Conversation
…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>
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>
Resolved. I merged |
Member
Author
|
@copilot base branch modified. fix this |
Checked and synced against |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Live blogs were effectively unreachable and visually inconsistent with normal articles. Two related fixes.
1. Live articles get a real section
live_articles.sectionlooked like the site taxonomy but is free text for the homepage strip ("Labor Department", "Election Night"). Added a separatesiteSectionselect (news/sports/features/opinion) sosectionkeeps its strip-label meaning.With a real section,
/live/[slug]now matchesStandardLayout:ArticleScrollBarshort header (same props the article route passes)ArticleRecommendationsonly ever readsection/id/opinionTypeoff its prop, so it's narrowed to an exportedRecommendationContext.Articlesatisfies it structurally, so article pages are untouched. Theidexclusion is conditional now, since a live article has noarticlesrow 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)scripts/run_deploy_sql_migrations.sh(production deploy)Column names verified against
20260420_000000_add_live_articles.ts(_live_articles_vuses theversion_prefix) and the articles enum convention. Both paths are idempotent — guardedCREATE TYPE,ADD COLUMN IF NOT EXISTS,CREATE INDEX IF NOT EXISTS, and the tracking insert ends inON CONFLICT DO NOTHING.Existing rows backfill to
newsvia a nullable columnDEFAULTplus an explicitUPDATE, so nothing breaks on read.required: trueis Payload write-time validation only.Checks
pnpm typecheckclean.pnpm lint0 errors (53 pre-existingmigrations/warnings, unchanged). Migration applied cleanly against a local Postgres.Note for after merge
Every existing live article will read as News until someone sets
siteSectionin the admin — worth a pass through the collection if any current live blog isn't news.🤖 Generated with Claude Code