From 2e920a96e76a27211ed2c6f15aa92d74af481bc4 Mon Sep 17 00:00:00 2001 From: Hisham Aburaqibah Date: Sun, 20 Sep 2026 14:16:53 +0200 Subject: [PATCH] Land the third design drop, seed the CMS, and retire the figures band Three pieces of one session's work. They are in one commit because home.tsx and _page-kit/copy.ts carry hunks from all three and splitting them by hand would have meant committing intermediate states of those two files that were never built or served. THE THIRD DESIGN DROP Only Home.dc.html and SiteFooter.dc.html differ from the second drop; the other nine page drafts are byte-identical to what already ships. The home page stops opening on a launch announcement dated to one day in June. New headline, lead and primary call to action ("See open calls"), and the launch band becomes "A nationwide project, delivered locally" - launchEyebrow/Heading/Lead renamed approach*, LaunchShots renamed GalleryShots, since the band is no longer about an event. Its Arabic reuses the About page's already-approved wording for the coverage line and the body heading; the new lead and headline are ar-draft like the rest of the Arabic home copy and still want client sign-off. Five motion classes in _page-kit/motion.css, tokens only, each with its RTL form where the property has no logical one: sh-wipe (the headline uncovered from the inline start), sh-pan (the hero picture on a view timeline), sh-zoom (a picture closing in inside its frame), sh-rule-top (an accent rule drawn across a hovered tile) and sh-marquee (the logo wall). sh-wipe fills `backwards`, not `both`, so the clip is gone at rest - inset(0) would shave a descender or an Arabic mark off a tight heading line - and it is removed outright under prefers-reduced-motion, because a clip does not fade a headline, it hides it. The marquee carries each logo twice and slides exactly half the track, so its spacing is a margin on the tile rather than a gap on the row: with a row gap the seam lands half a gap short. The second half is aria-hidden with no link, so the wall is neither read out nor tabbed through twice; it pauses on hover and focus (WCAG 2.2.2) and unwraps to a plain row under reduced motion. Below eight partners the wall stays a grid, because a half-track narrower than its frame drifts a short row across empty space instead of looping. SEEDING THE CMS `pnpm seed` writes content through the document service. It opens no port, so it runs happily beside `pnpm dev:cms`, and every write is matched on a slug and written over the same document, so a second run changes nothing but the fields the seed owns. src/seed/approved.ts is transcribed from docs/ in both languages and is safe to publish: site settings, the three consortium members, the topic taxonomy, the five activity streams with their implementing partners, the launch article in full, and the four pages. What docs/ withholds is not written - no statistics, no project email, no Facebook URL, no consortium addresses, all of which the source writes as bracketed placeholders. Two fields are judgement calls and say so at the line: activityStatus, which the schema requires and the source never states, and the launch article's year, which is derived - the source says "Tuesday, June 30" and within December 2025 to December 2027 that is 2026 and no other year. src/seed/demo.ts is invented and kept apart three ways: opt-in behind --demo, refused outright when NODE_ENV=production, and tagged in the data itself with a demo- slug prefix that shows in every URL and is how --demo --clean finds them again, with nothing remembered between runs. Two things the Strapi 5 document service does not do by itself, both of which leave Arabic rows behind that the next run then collides with on the unique slug: delete({ documentId }) with no locale removes the DEFAULT locale only, not the document, and findMany({ locale }) answers for one locale, so a document whose English half has gone is invisible to a search in English. The first --clean left six Arabic orphans and failed the next run partway. deleteMatching now pools documentIds across both locales and deletes with locale: '*'. Knex aborts its pending pool acquisitions on teardown, and each abort rejects a promise nothing awaits, so it arrives as an unhandled rejection rather than as an error a try/catch would see. scripts/seed.js absorbs exactly that, for the length of the shutdown only: a seed that worked must not report itself as a seed that failed. THE FIGURES BAND, AND THE BAND THAT REPLACED IT The third draft deletes the figures band, so shared.stat and blocks.stats-grid are deleted with it, home-page.stats is dropped, and blocks.stats-grid comes out of all six dynamic zones. On the web side that takes CmsStat, StatsRow, CountUpValue, StatStrip and Card.Stat - the last two were already dead, but they existed only to draw figures. No approved document carries a number, so there is now nowhere for one to be typed in, which is the point. The "nationwide project, delivered locally" band was half editable and half not: its pictures came from an image-gallery block down the Blocks dynamic zone, and its words came from bundled fallback copy with no CMS field at all. The new page.band-intro component (eyebrow, heading, lead, images) on home-page.approach puts the whole band in one place in the admin. The page still falls back to the bundled copy when the fields are empty, and still reads an image-gallery block after the component's own images, so a gallery built in Blocks keeps rendering. VERIFIED pnpm typecheck and both builds pass. Both rule greps are clean: no raw hex or tier-1 token in components/ or routes/, no physical-direction utility in apps/web/app. Against a seeded CMS on the built server: / redirects 302 with Vary: Accept-Language, and all ten sections plus the detail routes return 200 in both locales with lang/dir correct in the first bytes. The home page draws the marquee from twelve partners, the hero pan, the three hovered card rules and the status pill, and carries no figures band. The seed is idempotent - two identical --demo runs give identical counts, balanced across locales - and a clean/re-seed cycle leaves nothing behind. The approach band was proved editable end to end: heading, eyebrow, lead and images changed through the CMS all came through on /en while /ar stayed on its own Arabic, then the seeded copy was restored. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 3 + README.md | 72 +- apps/cms/package.json | 1 + apps/cms/scripts/seed.js | 75 ++ .../content-types/about-page/schema.json | 1 - .../content-types/activity/schema.json | 1 - .../content-types/home-page/schema.json | 7 +- .../content-types/knowledge-item/schema.json | 1 - .../content-types/news-article/schema.json | 1 - .../content-types/what-we-do-page/schema.json | 1 - .../cms/src/components/blocks/stats-grid.json | 19 - apps/cms/src/components/page/band-intro.json | 28 + apps/cms/src/components/shared/stat.json | 19 - apps/cms/src/seed/approved.ts | 706 +++++++++++++ apps/cms/src/seed/demo.ts | 925 ++++++++++++++++++ apps/cms/src/seed/index.ts | 441 +++++++++ apps/cms/src/seed/upsert.ts | 237 +++++ apps/cms/types/generated/components.d.ts | 44 +- apps/cms/types/generated/contentTypes.d.ts | 18 +- apps/web/app/components/ui/Card/CardStat.tsx | 106 -- apps/web/app/components/ui/Card/index.ts | 17 +- apps/web/app/components/ui/Footer/Footer.tsx | 4 +- apps/web/app/routes/_page-kit/accordion.tsx | 10 +- apps/web/app/routes/_page-kit/cms-types.ts | 19 +- apps/web/app/routes/_page-kit/copy.ts | 86 +- apps/web/app/routes/_page-kit/motion.css | 215 +++- apps/web/app/routes/_page-kit/sections.tsx | 70 +- apps/web/app/routes/home.tsx | 350 ++++--- .../Home page redesign with news and FAQs.zip | Bin 166046 -> 158965 bytes package.json | 1 + 30 files changed, 2994 insertions(+), 484 deletions(-) create mode 100644 apps/cms/scripts/seed.js delete mode 100644 apps/cms/src/components/blocks/stats-grid.json create mode 100644 apps/cms/src/components/page/band-intro.json delete mode 100644 apps/cms/src/components/shared/stat.json create mode 100644 apps/cms/src/seed/approved.ts create mode 100644 apps/cms/src/seed/demo.ts create mode 100644 apps/cms/src/seed/index.ts create mode 100644 apps/cms/src/seed/upsert.ts delete mode 100644 apps/web/app/components/ui/Card/CardStat.tsx diff --git a/CLAUDE.md b/CLAUDE.md index d406544..b28d21f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,6 +61,8 @@ unreachable — the site must never 500 because Strapi is down. `skillNames: "resource:figma-design-to-code"`. File key `yvKHp3Ha2rQmv9kt9tCC7j`. 5. **Never invent statistics, dates, names or quotes.** If a number is not in `docs/` or in the CMS, it does not go on the page. Leave the slot empty or omit the component. + The same rule governs the seed: anything invented belongs in `src/seed/demo.ts`, never + in `approved.ts`, and `pnpm seed` without `--demo` must stay publishable. 6. **`docs/` is read-only source material.** Never edit, move or "clean up" those files. 7. **Both locales, always.** New UI text goes into `app/i18n/messages/en.json` *and* `ar.json` with identical keys (the `Messages` type enforces it). A component that hard-codes @@ -86,6 +88,7 @@ unreachable — the site must never 500 because Strapi is down. | HTML shell, canonical, hreflang | `apps/web/app/root.tsx` | | Content types | `apps/cms/src/api//content-types//schema.json` | | Boot-time locale + permission setup | `apps/cms/src/bootstrap/` | +| CMS seed content (`pnpm seed`) | `apps/cms/src/seed/` — `approved.ts` is from `docs/`; `demo.ts` is invented, opt-in behind `--demo`, every slug prefixed `demo-` | | CORS, upload rules, i18n locales | `apps/cms/config/{middlewares.ts,plugins.ts}` | ## Conventions that bite diff --git a/README.md b/README.md index 3d821eb..4093664 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ publications — all of it in English and Arabic, with the Arabic side laid out - [Path A — manual setup](#path-a--manual-setup) - [Path B — Docker](#path-b--docker) - [Available scripts](#available-scripts) +- [Seeding content](#seeding-content) - [Design tokens](#design-tokens) - [Bilingual EN / AR](#bilingual-en--ar) - [Adding a content type, end to end](#adding-a-content-type-end-to-end) @@ -468,7 +469,8 @@ docker run --rm -v sharper_cms-uploads:/data -v "$PWD":/backup alpine \ | `pnpm dev:cms` | `strapi develop` (auto-reloads on schema changes) → `http://localhost:1337` | | `pnpm build` | builds the CMS, then the web app (that order: the web build has no CMS dependency, but a broken CMS build should fail first) | | `pnpm start` | runs both production builds | -| `pnpm typecheck` | the web app's `react-router typegen && tsc` (the CMS has no `typecheck` script — its TypeScript is checked by `strapi build`) | +| `pnpm seed` | writes the approved content into the CMS — see [Seeding content](#seeding-content) | +| `pnpm typecheck` | `tsc` in both applications, preceded in the web app by `react-router typegen` | **`apps/web`** — run with `pnpm --filter web