Skip to content

feat: Create / Refine the Summit data Astro components#170

Merged
Anca2022 merged 6 commits intostagingfrom
am/summit-components
Apr 1, 2026
Merged

feat: Create / Refine the Summit data Astro components#170
Anca2022 merged 6 commits intostagingfrom
am/summit-components

Conversation

@Anca2022
Copy link
Copy Markdown
Contributor

@Anca2022 Anca2022 commented Mar 31, 2026

Summary

This PR creates and refines all components required to render speaker and session data. It intentionally does not focus on CSS, prioritizing component structure instead, as per the requirement:

“Keep styling to an absolute bare minimum. Just make it readable—no responsiveness or additional styling.
Do focus on well-structured, semantic HTML.”

Translation work will be addressed in a follow-up PR.

Related Issue

Fixes INTORG-476 - Create the Summit data Astro components

Manual Test

Checks

  • pnpm run format
  • pnpm run lint

PR Checklist

  • PR title follows Conventional Commits (e.g. feat: ..., fix: ...)
  • Linked issue included
  • Scope is focused (target ~10-20 files when possible)
  • Screenshots for UI changes (if applicable)

@Anca2022 Anca2022 self-assigned this Mar 31, 2026
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 31, 2026

Deploy Preview for interledger-org-v5 ready!

Name Link
🔨 Latest commit 921a200
🔍 Latest deploy log https://app.netlify.com/projects/interledger-org-v5/deploys/69ccbf8f5a1abf000769eb51
😎 Deploy Preview https://deploy-preview-170--interledger-org-v5.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

<h2 class="text-[var(--color-primary)]">
<a href={href}>{talkPreview.title}</a>
</h2>
<SessionSubheader talk={talkPreview} showRecordingLabel={true} lang="en" />
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please ignore lang="en" for now, it will be addressed in a separate PR, when dealing with translations

Comment on lines 28 to 32
const title = lang == 'es' ? (entry.es?.title ?? entry.title) : entry.title
const description =
lang == 'es'
? (entry.es?.description ?? entry.description)
: entry.description
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, ignore all this logic please, will be refactored in a separate PR

@Anca2022 Anca2022 marked this pull request as ready for review April 1, 2026 06:52
@Anca2022 Anca2022 merged commit 6c675b7 into staging Apr 1, 2026
6 checks passed
@Anca2022 Anca2022 deleted the am/summit-components branch April 1, 2026 07:30
bosbaber added a commit that referenced this pull request Apr 9, 2026
…vironment (#195)

* fix(ci): remove stray page-for-ambassadors (#111)

remove stray page-for-ambassadors

* feat(sync-mdx): add Paragraph component handler for MDX import jm/intorg-457-paragraph-component (#102)

* add Paragraph to mdx files

* fix(cms): delete orphaned localizations before recreating to preserve document links

* fix(cms): use js-yaml v4 custom engine for single-quoted YAML frontmatter

* feat(cms): add Paragraph block handler for sync-mdx parser

* feat: render paragraph blocks as <Paragraph> JSX component

* pnpm format

* fix(sync-mdx): preserve dash bullet list marker on paragraph import

* fix(sync-mdx): fix bullet lists handling in component handlers and add new tests (#108)

* add Paragraph to mdx files

* feat(cms): add Paragraph block handler for sync-mdx parser

* pnpm format

* fix bullet list handling, refactor

* add and cleanup tests

* format

---------

Co-authored-by: Jonathan Matthey <mattheyj@gmail.com>

* fix(ci): refresh cms lockfile for js-yaml (#113)

* feat(404): add custom 404 page for static hosting jm/intorg-484-404page (#114)

* feat(404): add custom 404 page for static hosting

* pnpm format

* refactor(utils): convert create-excerpt to TypeScript - jon/intorg-445 (#115)

refactor(utils): convert create-excerpt to TypeScript

* ci(lint): add build job to lint workflow jon/intorg-405 (#117)

ci(lint): add build job to lint workflow

* feat(seo): add per-page meta title, description, image, and canonical… jon/intorg-446 (#116)

* feat(seo): add per-page meta title, description, image, and canonical URL

* pnpm format

* chore(cms): update Strapi dependencies [INTORG-334] (#112)

* feat: tailwind setup (#104)

* refactor: modular CSS architecture with Tailwind v4 theme tokens

- Decompose monolithic tailwind.css and pages.css into focused, layer-aware files
- Add src/styles/theme.css with @theme tokens (typography, spacing, colors, animations)
- Add src/styles/base/ for reset, typography (@font-face), and runtime CSS variables
- Add src/styles/components/ for prose variants (default, blog, foundation, summit) and navigation
- Move design tokens out of tailwind.config.mjs into @theme (removes duplication)
- Rename [data-category] to [data-pillar] theming system
- Standardise variable namespaces: --step-* → --text-step-*, --space-* → --spacing-space-*
- Scope Starlight docs variables to :where(.sl-container) to prevent global leakage
- Replace inline Tailwind arbitrary-variant classes with data-prose, data-prose-summit, data-nav-links attributes
- Delete src/styles/pages.css (content redistributed into new modular files)

* fix: update remaining --space-* references to --spacing-space-*

AmbassadorGrid, SummitHeader, and FoundationHeader were still using the
old --space-s variable name which no longer exists after the CSS variable
namespace rename.

* cleanup

* pillar

* fix schema

* cleanup

* review

* Address PR feedback and prepare for staging merge

* revert mdxTransformer

* Delete cms/scripts/sync-mdx/README.md

* ci: any change to the cms folder should trigger a rebuild (#118)

* chore: update Astro dependencies [INTORG-333] (#119)

* chore(cms): update @strapi/strapi 5.31.3 → 5.38.0, remove dead overrides [INTORG-334]

- Bump @strapi/strapi from 5.31.3 to 5.38.0 (latest stable)
- Remove inert pnpm.overrides block from cms/package.json — pnpm
  ignores overrides in workspace packages; only root overrides apply
- All three plugins (@_sh/strapi-plugin-ckeditor, @ckeditor/strapi-plugin-ckeditor,
  @notum-cz/strapi-plugin-record-locking) accept @strapi/strapi ^5.0.0

* foundation-page: sync (2 created)

* ambassador: create caroline-sinders

* fix(cms): bump @_sh/strapi-plugin-ckeditor to ^7.1.0

The v6 plugin bundles @strapi/design-system 2.0.0-rc.18 which conflicts
with Strapi 5.38.0's design-system 2.2.0, causing a styled-components
crash in the admin panel. v7.1.0 uses design-system 2.1.2 which is
compatible.

* foundation-page: update test-page

* remove test-page fixtures from content

* cleanup, auto generated types

* lockfile

* foundation-page: sync (2 created)

* ambassador: create caroline-sinders

* foundation-page: update test-page

* chore: upgrade to Astro 6, Node 22, and bump dependencies

INTORG-333

Upgrade the Astro site from v5 to v6 and move from Node 20 (lts/iron)
to Node 22 (lts/jod). Remove unused @astrojs/node adapter. Bump all
major dependencies: @astrojs/mdx 5, @astrojs/netlify 7,
@astrojs/starlight 0.38, Tailwind 4.2, Zod 4, marked 17, and
eslint/typescript-eslint tooling. Update CI workflows to Node 22.

Build, lint, all tests, and visual QA pass with no apparent regressions.

* cleanup

* cleanup

* chore(cms): upgrade Strapi to 5.39.0

* docs: document dual lockfile setup for root and cms

* ci(lint): fix build job to use Node 22 (required by Astro 6)

* chore: bump deps — starlight-links-validator, better-sqlite3, @types/node, vitest

* chore: pin pnpm to v10.27.0 across repo and CI workflows (#120)

* chore: pin pnpm to v10.27.0 across repo and CI workflows

* fix(ci): remove explicit pnpm version from workflows — read from packageManager field

* ci: better file change detection to trigger build (#121)

* ci: will now use change api to detect changes

* ci: make sure we use pnpm 10.27.0

* Update .github/workflows/staging-merge.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update .github/workflows/staging-merge.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* ci: fixed reference issues

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: align i18n routes and rename slug to pathSlug (#107)

* fix: align i18n routes and rename slug to pathSlug

* chore(deps): add better-sqlite3 to onlyBuiltDependencies and pin conflicting deps with overrides

* ci: pipeline should source basrc for nvm to work (#126)

* ci: explicit export of NVM_DIR (#127)

* ci: clean node modules before rebuild (#128)

* ci: force clean build (#129)

* ci: playground merge pipeline cleanup fix (#130)

* chore: store Sessionize data as JSON files (#131)

* feat: add Netlify-backed contact form (#87)

* feat(contact): add contact us page

* pnpm format

* chore: exclude preview pages from seo (#135)

exclude preview from seo

* feat(cms): cleanup strapi admin UI INTORG-481 (#106)

* fix(cms): disable draft and publish on all content types

* feat(cms): configure edit view layouts for ambassador and SEO

* feat(cms): customize admin panel styles and navigation

* pnpm format

* feat(cms): improve admin field labels and edit view layouts

* feat(cms): add labels and full-width layout for ambassador, blockquote, callout-text

* fix(cms): always apply field labels, not only when Strapi default

* fix page title too

* update labels

* hide preview button

* feat(cms): add help text for Directory Structure field

* fix - hide preview button

* simplify hiding open entity

* add mutable observable to doc

* stricter ID check in title

* refactor(404): reuse shared 404 page instead of inline not-found sect… (#137)

* refactor(404): reuse shared 404 page instead of inline not-found sections

Replace per-page inline 404 markup in [...page].astro and summit/[...page].astro
with redirects to /404, and disable Starlight's built-in 404 route so the
shared page is used consistently across the site.

* pnpm format

* chore: update buildBlogPayload to handle all fields in frontmatter (#105)

This PR updates the `sync:mdx` script to handle all frontmatter fields in blog post articles: `tags`, `articleBio`, `featureImage` + alt text, `thumbnailImage` +alt text

---------

Co-authored-by: Jonathan Matthey <mattheyj@gmail.com>

* fix: remove unused value from interface definition (#139)

* feat: PdfEmbed component (#132)

* pdf embed

* add download attribute

* fallback styling

* fix(lockfile): remove duplicate overrides keys from pnpm-lock.yaml

Merge introduced duplicate @strapi/design-system and @strapi/icons keys
in the overrides section, breaking frozen-lockfile installs.

* Delete src/content/foundation-pages/test-page.mdx

* Delete src/content/es/foundation-pages/test-page.mdx

* format

* revie and merge

* format

* fix(cms): target preview aside by nth-child instead of last-of-type (#142)

:last-of-type was hiding the wrong aside after a Strapi UI update moved
the publish panel. Switch to :nth-child(2) to reliably target only the
preview aside.

* fix(lockfile): update cms/pnpm-lock.yaml to include pnpm.overrides (#144)

The @strapi/design-system and @strapi/icons overrides added to
cms/package.json were missing from cms/pnpm-lock.yaml, causing
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH during the staging rebuild CI step.

* feat: add localization support for foundation-blog-posts (#143)

- add localization support for foundation-blog-posts
- remove `language` field from Strapi Admin ui for Blog post (redundant, we already use locale)- 
- add back `afterUpdate` hooks in lifecycles (after switching off drafts for content, the lifecycles have changed they behaviour)

* chore: Create list pages for Summit talks and speakers (#136)

* chore: add base logic for summit talks list page

* chore: add base HTML structure to summit talks page

* chore: create Summit Speakers list page

* add Spanish summit list pages

* add comments

* refactor list pages

* style(summit): remove redundant .ts extensions from imports

* apply changes as per review request

---------

Co-authored-by: Jonathan Matthey <mattheyj@gmail.com>

* ci: trigger sync on navigation changes and run sync:all jm/intorg-493-syncall (#138)

* ci: trigger sync on navigation changes and run sync:all

Navigation changes now trigger the sync job, same as MDX content.
Switched to sync:all to run both syncs together, and renamed the job to match.

* ci: set navigation_changed in all fallback sync paths

* feat: added locale awareness to content collections (#134)

* foundation-page: sync

* foundation-page: sync

* chore: added nested grant pages (#153)

chore: added nested grant pages

* fix: alt text not syncing from MDX frontmatter to Strapi, and inline JSX images breaking in CKEditor. (#141)

* image normalisation, alt text fixes

* handle conflicts

* format

* fix set usage and revert to POST

* use correct image

* feat: add LanguageSwitcher component (#154)

* feat: add LanguageSwitcher component

* chore: refactor basePath logic

* refactor(i18n): centralize home slug usage in route helpers

* refactor(i18n): memoize translation map with top-level data module

* refactor(i18n): move route context into Astro.locals via middleware

Introduces routeContextFromPathname utility and wires it through middleware
so layouts and components read routeLocale/currentSlug/currentBasePath from
Astro.locals instead of receiving them as explicit props.

* chore: formatting

---------

Co-authored-by: Jonathan Matthey <mattheyj@gmail.com>

* fix(cms): ensure uploads folder exists before Strapi registers (#155)

The local upload provider needs public/uploads to exist before the
upload plugin registers. Create it eagerly in server config to avoid
startup failures on fresh clones.

* feat(parser): implement paragraph merging in parseMdxToBlocks (#156)

INTORG-514

Consecutive markdown nodes (paragraphs, headings, lists, tables) are now
accumulated into a single blocks.paragraph, split only when a JSX component
interrupts or EOF is reached. A blog post with 80 markdown nodes now
produces 1 block instead of 80.

Also fixes missing `@` path alias in cms/vitest.config.ts that was causing
mdxTransformer.test.ts to fail (introduced in #133).

* chore(assets): add original upload images to public/uploads (#157)

* Feat: create summit detail pages (#146)

This PR creates detail pages for individual summit session and summit speaker pages - English and Spanish pages:
`/summit/[year]/talk/{talk-title-slug}`
`/es/summit/[year]/talk/{talk-title-slug}`
`/summit/[year]/speaker/{speaker-name-slug}`
`/es/summit/[year]/speaker/{speaker-name-slug}`

This PR focuses on the main pages logic only. It does NOT handle components, summit JSON data extraction, or translation logic; these will be addressed in follow-up PRs.

* feat(blocks): add VideoEmbed component and Strapi schema (#148)

* feat(blocks): add VideoEmbed component and Strapi schema

INTORG-513 INTORG-515

- Install @astro-community/astro-embed-youtube and astro-embed-vimeo
- Create VideoEmbed.astro (facade pattern: thumbnail on load, iframe on click)
- Create VideoEmbedBlock.astro for DynamicZone preview rendering
- Add shared detectVideoProvider utility (YouTube + Vimeo URL detection)
- Add blocks.video-embed Strapi component schema (url + title fields)
- Register in foundation-page content-type and pageLifecycle populate
- Add video-embed serializer for Strapi-to-MDX export
- Wire into DynamicZone.astro and [...page].astro components map

* feat(parser): register VideoEmbed handler in parseMdxToBlocks (#158)

INTORG-516

- Add VideoEmbedBlock type to types.blocks.ts and ParsedBlock union
- Create videoEmbedHandler.ts: extracts url + title props, emits blocks.video-embed
- Register handler via side-effect import in config.ts

* feat(blog): add components map to blog page rendering (#159)

INTORG-521

- Add VideoEmbed to FoundationContentPage.astro components map
  (replaces the old [...page].astro components map after staging refactor)
- Add Paragraph + VideoEmbed components map to FoundationBlogPostPage.astro
  (blog posts can now render <Paragraph> and <VideoEmbed> from MDX)

* feat(summit): add VideoEmbed to summit page components map

INTORG-521

Summit pages can now render <VideoEmbed> from MDX, matching
foundation pages and blog posts.

* format

* content(blog): replace EMBEDDED VIDEO links with VideoEmbed components (#160)

INTORG-517

Replace 6 broken [EMBEDDED VIDEO: ...](url) markdown links across 5 blog
posts with <VideoEmbed url="..." title="..." /> JSX components.

Affected posts:
- digital-financial-inclusion-igniting-sustainable-development-goals
- harnessing-future-insights-global-technology-retreat-digital-trust-and-inclusion
- exploring-community-based-identification-systems (2 videos)
- why-higher-education-institutions-worldwide-should-embrace-hackathons
- interledger-hackathon-intro-open-payments-live-qa-ilf-experts

* feat(cms): use route path to dictate page folder structure in lifecycle (#124)

* feat(cms): single full path slug for foundation and summit pages

- Drop path field; pathSlug is required string (multi-segment URLs)\n- Admin: Full Path Slug label, help text, 12-col layout\n- pageLifecycle: nested MDX paths from slug; legacy path delete; omit path in frontmatter\n- Sync payloads no longer send path

* fix(cms): walk nested files when removing localizes from locale MDX

* refactor(cms): always use recursive scan in sync-mdx

Single readdir pass from collection root; infer locale from path segments.

* chore(cms): add vitest alias @ -> src for CMS tests

* refactor(site): pathSlug-only page routes and frontmatter

- Drop optional path from page schemas; simplify static-paths\n- ES about page: sobre-nosotros slug + localizes link

* docs: full path slug and nested foundation/summit MDX layout

* fix(cms): handle nullable pathSlug in page lifecycle afterDelete

* docs(cms): clarify pathSlug field descriptions per content type

* pnpm format

* chore: Refactor localized page nesting

* feat(cms): add sync:delete-all script and fix Strapi 5 pagination

- Add sync-delete-all.ts utility to bulk delete Strapi entries
- Fix getAllEntries to use proper pagination (Strapi 5 ignores limit=-1)
- Normalize API URL construction to handle trailing slashes

* format

* fix(cms): delete MDX at old pathSlug per updated locale

* refactor(cms): drop legacy path prefix from page MDX resolution

* Apply suggestion from @JoblersTune

* Apply suggestion from @JoblersTune

* refactor(cms): centralize default locale as defaultLang

* refactor(cms): clarify Strapi params.where in page lifecycle

* refactor(cms): replace module-level Map with event.state in page lifecycle

beforeUpdate and afterUpdate now share oldPathSlug via event.state,
which is scoped to the lifecycle event pair. This eliminates concurrent
update races and potential leaks from the previous module-level Map.

* improve comment

* fix(cms): warn when MDX file is missing pathSlug in frontmatter

The file is always written with pathSlug by the export process, so its
absence indicates a corrupted or manually-edited file. Log a warning
rather than silently falling back to the Strapi value.

* pnpm format

* fix mdx

* add doc

* chore(cms): document locale resolution and log source in readLocaleFromUpdateEvent

* refactor(cms): trust Strapi pathSlug for beforeUpdate MDX cleanup

* feat(cms): enforce unique pathSlug per locale on page types

* fix(cms): scope removeLocalizesFromLocaleFiles to each locale dir

* fix(cms): require --confirm for sync-delete-all destructive runs

* refactor(cms): derive sync-mdx locale dirs from LOCALES

* pnpm format

---------

Co-authored-by: JoblersTune <sarah@interledger.org>

* feat: create script to import summit data and speaker images from Sessionize (#163)

Create script to import summit data and speaker images from Sessionize

The script
- saves summit data as json files in `/src/data/sessionize/{year}-speakers.json` and `/src/data/sessionize/{year}-talks.json`
- imports speakers' profile images under `/public/img/sessionize-speakers/{year}`

* feat: Create utility functions for extracting Sessionize data (#161)

This PR creates utility functions for extracting Sessionize summit data from json files and making them available to components

* feat(cms): migrate blog content from CKEditor to structured blocks (#162)

* feat(cms): change blog content field from CKEditor to dynamiczone

INTORG-518

Change the blog post `content` field from `type: "customField"` /
`customField: "plugin::ckeditor5.CKEditor"` to `type: "dynamiczone"`
with the same component list as foundation pages. This allows Strapi
to accept block arrays instead of a plain string.

Requires Strapi restart to take effect.

* feat(sync): wire parseMdxToBlocks into buildBlogPayload

INTORG-519

- Add ParserContext parameter to buildBlogPayload
- When provided, parse MDX body into structured blocks via parseMdxToBlocks
  instead of sending raw markdown string
- Wire ParserContext in config.ts for foundation-blog-posts (same pattern
  as buildParsedPagePayload: resolveRelation + resolveMediaUpload)
- Parser errors include file pathSlug for context

* feat(cms): update blogLifecycle to serialise blocks on save (#164)

* feat(cms): change blog content field from CKEditor to dynamiczone

INTORG-518

Change the blog post `content` field from `type: "customField"` /
`customField: "plugin::ckeditor5.CKEditor"` to `type: "dynamiczone"`
with the same component list as foundation pages. This allows Strapi
to accept block arrays instead of a plain string.

Requires Strapi restart to take effect.

* feat(sync): wire parseMdxToBlocks into buildBlogPayload

INTORG-519

- Add ParserContext parameter to buildBlogPayload
- When provided, parse MDX body into structured blocks via parseMdxToBlocks
  instead of sending raw markdown string
- Wire ParserContext in config.ts for foundation-blog-posts (same pattern
  as buildParsedPagePayload: resolveRelation + resolveMediaUpload)
- Parser errors include file pathSlug for context

* feat(cms): update blogLifecycle to serialise blocks on save

INTORG-520

- Call serializeContent(post.content) instead of writing raw string
- Re-fetch blog post with populate params for dynamiczone content
  (lifecycle event.result doesn't auto-populate dynamiczone on params)
- Change BlogResult.content type from string to blocks array
- Extract shared StrapiDocumentAPI/StrapiGlobal types to strapiTypes.ts
  (was duplicated across pageLifecycle, navigationLifecycle, flatContentLifecycle)

* chore(cms): update generated content types after schema change

Strapi regenerated types on startup to reflect the blog content
field migration from CKEditor to dynamiczone.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cms): support all block types in blog lifecycle and share populate config

INTORG-520

- Extract CONTENT_BLOCK_POPULATE into shared contentPopulate.ts to avoid
  duplicating block populate config between page and blog lifecycles
- Add fetchWithPopulate to blogLifecycle.ts so dynamic zone content
  includes nested relations (ambassador photos, pdf files, etc.)
- Call serializeContent() for block arrays when generating blog MDX
- Blog lifecycle now handles all block types, not just paragraph + video-embed

* docs(decisions): add ADR-001 for content collection path handling (#165)

* docs(decisions): add ADR-001 for content collection path handling

Introduces Architecture Decision Records and documents the convention
for how MDX files are organised under Astro content collections,
including the EN/ES directory mirroring strategy.

Resolves INTORG-546

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* forma

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: add embedded video recordings on Summit Talk pages (#167)

* run syns:sessionize script to update recordingUrl links

* Add summit recording videos on pages using VideoEmbed component

* fix: add missing component renderers (#166)

* add missing component renderers

* remove unused comps

* refactor(cms): split populate config into page and blog variants

PAGE_CONTENT_POPULATE includes all block types (cards-grid, carousel, etc.).
BLOG_CONTENT_POPULATE includes only blog-relevant blocks (paragraph,
ambassador, blockquote, callout-text, pdf-embed, video-embed).

Keeps populate params in sync with the content-type schema component lists.

* remove unused compos

* fix: address PR #166 review comments

- Align summit-page dynamic-zone components with foundation-page
- Remove unused imports from FoundationBlogPostPage
- Simplify blog components map to Paragraph + VideoEmbed

INTORG-535

* add missing imports to SummitContentPage

---------

Co-authored-by: Anca Matei <98110730+Anca2022@users.noreply.github.com>

* feat: Create / Refine the Summit data Astro components (#170)

This PR creates and refines all components required to render speaker and session data. It intentionally does not focus on CSS, prioritizing component structure instead

* feat(blog): migrate all 92 blog posts to Paragraph-wrapped component … (#168)

* add missing component renderers

* feat(blog): migrate all 92 blog posts to Paragraph-wrapped component format

Parse each blog post's raw markdown body through parseMdxToBlocks →
serializeContent to produce <Paragraph>-wrapped MDX, matching the
foundation page format. 87 plain-markdown posts get a single
<Paragraph> wrapper; 5 VideoEmbed posts split into Paragraph +
VideoEmbed + Paragraph blocks. Synced to Strapi with 0 errors.

Includes the migration script (cms/scripts/migrate-blog-to-components.ts)
for reproducibility.

INTORG-524

* format

* refactor(cms): add index signature to StrapiBlockBase, remove cast

Add [key: string]: unknown to StrapiBlockBase so ParsedBlock[]
satisfies serializeContent's parameter type without a cast.

INTORG-524

* fix(cms): preserve content byte-for-byte in paragraph round-trip

The paragraph handler was re-serializing children via mdast-util-to-markdown,
which decoded HTML entities (&amp; → &) and escaped brackets ([ → \[).
Now passes sourceText through ParserContext so the handler can slice raw
content from AST positions instead of re-serializing from the AST.

INTORG-524

* feat(cms): hard-fail on nested JSX inside Paragraph blocks (#169)

* feat(blog): migrate all 92 blog posts to Paragraph-wrapped component format

Parse each blog post's raw markdown body through parseMdxToBlocks →
serializeContent to produce <Paragraph>-wrapped MDX, matching the
foundation page format. 87 plain-markdown posts get a single
<Paragraph> wrapper; 5 VideoEmbed posts split into Paragraph +
VideoEmbed + Paragraph blocks. Synced to Strapi with 0 errors.

Includes the migration script (cms/scripts/migrate-blog-to-components.ts)
for reproducibility.

INTORG-524

* format

* feat(cms): hard-fail on nested JSX inside Paragraph blocks

Import guard: paragraphHandler.ts walks <Paragraph> children before
childrenToMarkdown runs. If any custom JSX component (capital-letter
name) is found nested inside, throws MdxParserError with NESTED_JSX
code. HTML elements like <br> and <span> are allowed.

Export guard: Koa middleware in src/index.ts intercepts content-manager
PUT/POST requests and validates paragraph content via shared
contentValidation.ts. Returns a 400 with a descriptive error message
visible in the Strapi admin UI — authors can't save until they fix
the nesting. Covers blog posts, foundation pages, and summit pages.

INTORG-535

* lint, format

* fix: restore blog posts to staging state after rebase

The migration commit was re-applied during rebase on already-migrated
files, causing duplicate <Paragraph> wrappers. Restores all blog posts
to the correct state from staging.

* fix(cms): increase SEO meta description character limit (#174)

fix(cms): increase SEO meta description character limit to 300

The previous 160-character limit was too restrictive for content
editors. Bumped to 300 to allow longer descriptions while still
staying within commonly recommended SEO ranges.

Fixes INTORG-502

* feat: add /.well-known/security.txt (#175)

Adds a security.txt file per RFC 9116 with contact info for
responsible disclosure.

Fixes INTORG-525

* docs: add Summit Data (Sessionize Integration) section to README (#176)

* feat(analytics): add Umami event tracking to remaining components (#173)

* feat(analytics): add Umami event tracking to remaining components

Add data-umami-event attributes to CTA banners, Lander header/footer,
blog listing titles, pagination, cross-blog nav, and video embed
fallback links. Event names follow the established taxonomy from v4
and the production Umami dashboard.

Closes INTORG-360

* feat(analytics): add Umami tracking to CommunityLinks components

Add data-umami-event attributes to all links in
CommunityLinksFoundation and CommunityLinksDevelopers components.

* chore: add analytics for summit data pages (#179)

* chore: add analytics for summit data pages

* refactor: move Pagination component into shared directory

* feat: handle translations for summit data (#171)

This PR adds translation support for dynamic Sessionize content (talks and speakers):
- Populate translationMap with Sessionize data — ensures the LanguageSwitcher correctly navigates between en and es pages for all summit talks and speakers
- Fallback to En — when no Spanish content is found, the pages fall back to English content
- update README with info on Adding support for a new language

* feat(cms): update Strapi to v5.41.1 (#180)

* feat(cms): update Strapi to v5.41.1 and migrate entityService to Document API

INTORG-533

Migrate ensureLocales() from deprecated strapi.entityService to
strapi.documents() API (enforced in v5.40.0). Bump better-sqlite3
to 12.8.0. All tests pass, sync scripts verified.

* lockfiless

* sync lockfile

* comments

* chore: simplify the Strapi SEO component (#177)

This PR simplifies the Strapi SEO experience for editors by removing fields that were either unused, redundant, or likely to cause confusion.

* summit-page: create home

* summit-page: delete sessions

* summit-page: create hackathon

* foundation-page: create policy-and-advocacy

* foundation-page: create team

* foundation-page: create media

* foundation-page: create open-standards

* foundation-page: create interledger

* foundation-page: create open-payments

* foundation-page: create web-monetization

* foundation-page: create join-network

* foundation-page: create developers

* foundation-page: sync

* foundation-page: sync

* feat: add caching policy [INTORG-359] (#140)

feat: add caching policy with explicit Cache-Control headers [INTORG-359]

Define Cache-Control headers in netlify.toml for all asset categories,
add no-store response headers on SSR preview pages via applyPreviewNoStore(),
and disable runtime caching on fetchStrapi() calls.

* chore: added summit page handling (#184)

* chore: updated menu links (#185)

* chore: updated menu links

* formatting

* fix(styles): bump docs-design-system to fix h3 style leak (#178)

* fix(styles): bump docs-design-system to 0.11.1

Picks up the fix for unscoped h3 highlight selectors that were leaking
from Starlight pages into the main site (e.g. footer headings).

Resolves INTORG-529

* fix(styles): use GitHub ref for docs-design-system until npm publish

INTORG-529

* fix(styles): fix Starlight CSS layer ordering and form selector leaks

Pre-declare Starlight CSS layers before Tailwind layers so
starlight.reset no longer overrides Tailwind utility classes.

Update docs-design-system to include scoped form selectors
(label, input, ::placeholder) — prevents Starlight form styles
from leaking onto non-Starlight pages like /contact.

INTORG-529

* update docs-design-system

* ci: will now do dryrun as part of PR checks (#181)

* ci: strapi will now dry run against staging

* ci: will now use the strapi-staging environment

* ci: switch to RO token for dry run

* fix: dry run must use read-only key

* fix(cms): Astro HERO: background image + remove secondary CTA btns INTORG-572 INTORG-573 (#183)

* fix(cms): remove secondary CTA buttons from hero component

* fix(cms): clear deleted hero/seo fields from MDX frontmatter

* feat(hero): support custom background image on hero sections

* fix(styles): add list-style markers to prose content [INTORG-589] (#187)

list styles

* summit-page: create 2024

* summit-page: create 2023

* summit-page: create 2022

* summit-page: sync

* summit-page: sync

* summit-page: sync

* chore: moved Sessionize speakers images from public/img/sessionize-sp… (#193)

chore: moved Sessionize speakers images from public/img/sessionize-speakers to public/sessionize-speakers/img

* style(prose): add visible underline to content links (#191)

* style(prose): add visible underline to content links

Links in prose content now show an underline by default so users
can identify them as clickable. Underline fades out on hover.
Consistent across default, blog, and foundation prose variants.

Fixes INTORG-598

* style(prose): add link underlines to summit and developers-blog

- Summit prose: add explicit underline styling for links
- Developers blog: replace no-underline with visible underline,
  fixing the brief flash caused by conflicting hover rules

Fixes INTORG-598

* feat(cms): route uploads to img/original and disable image variants (#190)

* commit adding images

* feat(cms): route uploads to img/original and disable image variants

* pnpm format

* feat(cms): add helper text for blog post tags field [INTORG-570] (#189)

* feat(cms): add helper text for blog post tags field [INTORG-570]

Add description to the tags component field in the Strapi admin,
indicating that multiple tags can be selected via "+ Add an entry".

* format

* feat(cms): route uploads to img/original, seed media library from disk  INTORG-594 (#192)

* commit adding images

* feat(cms): route uploads to img/original and disable image variants

* pnpm format

* feat(cms): seed upload records from disk on bootstrap

* delete dup imags

* feat(cms): seed public/img/ images into Strapi media library

* pnpm format

* delete images

* delete img variants

* also delete images from /img folder

* rm uploads img

* fix iamge check

* fix sync all force

* sync all script

* feat(cms): brand pillar foundation pages updates INTORG-569 INTORG-568 (#186)

- replace  `pageType` ("Page Type") field with pillar ("Brand Pillar") field and update values

* ci: pr checks are now environment aware

* ci: merge was restarting wrong service

* ci: added debug info to the pipeline

* ci: swop out tokens with ENV prefix

* ci: fail fast if not set

* ci: ENV_STRAPI_URL is a var not a secret

---------

Co-authored-by: Ravi Soni <soni.ravi829@gmail.com>
Co-authored-by: Jonathan Matthey <mattheyj@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sarah Jones <sarah@interledger.org>
Co-authored-by: Anca Matei <98110730+Anca2022@users.noreply.github.com>
Co-authored-by: strapi <strapi@vm>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bosbaber added a commit that referenced this pull request Apr 9, 2026
…vironment (#195)

* fix(ci): remove stray page-for-ambassadors (#111)

remove stray page-for-ambassadors

* feat(sync-mdx): add Paragraph component handler for MDX import jm/intorg-457-paragraph-component (#102)

* add Paragraph to mdx files

* fix(cms): delete orphaned localizations before recreating to preserve document links

* fix(cms): use js-yaml v4 custom engine for single-quoted YAML frontmatter

* feat(cms): add Paragraph block handler for sync-mdx parser

* feat: render paragraph blocks as <Paragraph> JSX component

* pnpm format

* fix(sync-mdx): preserve dash bullet list marker on paragraph import

* fix(sync-mdx): fix bullet lists handling in component handlers and add new tests (#108)

* add Paragraph to mdx files

* feat(cms): add Paragraph block handler for sync-mdx parser

* pnpm format

* fix bullet list handling, refactor

* add and cleanup tests

* format

---------

Co-authored-by: Jonathan Matthey <mattheyj@gmail.com>

* fix(ci): refresh cms lockfile for js-yaml (#113)

* feat(404): add custom 404 page for static hosting jm/intorg-484-404page (#114)

* feat(404): add custom 404 page for static hosting

* pnpm format

* refactor(utils): convert create-excerpt to TypeScript - jon/intorg-445 (#115)

refactor(utils): convert create-excerpt to TypeScript

* ci(lint): add build job to lint workflow jon/intorg-405 (#117)

ci(lint): add build job to lint workflow

* feat(seo): add per-page meta title, description, image, and canonical… jon/intorg-446 (#116)

* feat(seo): add per-page meta title, description, image, and canonical URL

* pnpm format

* chore(cms): update Strapi dependencies [INTORG-334] (#112)

* feat: tailwind setup (#104)

* refactor: modular CSS architecture with Tailwind v4 theme tokens

- Decompose monolithic tailwind.css and pages.css into focused, layer-aware files
- Add src/styles/theme.css with @theme tokens (typography, spacing, colors, animations)
- Add src/styles/base/ for reset, typography (@font-face), and runtime CSS variables
- Add src/styles/components/ for prose variants (default, blog, foundation, summit) and navigation
- Move design tokens out of tailwind.config.mjs into @theme (removes duplication)
- Rename [data-category] to [data-pillar] theming system
- Standardise variable namespaces: --step-* → --text-step-*, --space-* → --spacing-space-*
- Scope Starlight docs variables to :where(.sl-container) to prevent global leakage
- Replace inline Tailwind arbitrary-variant classes with data-prose, data-prose-summit, data-nav-links attributes
- Delete src/styles/pages.css (content redistributed into new modular files)

* fix: update remaining --space-* references to --spacing-space-*

AmbassadorGrid, SummitHeader, and FoundationHeader were still using the
old --space-s variable name which no longer exists after the CSS variable
namespace rename.

* cleanup

* pillar

* fix schema

* cleanup

* review

* Address PR feedback and prepare for staging merge

* revert mdxTransformer

* Delete cms/scripts/sync-mdx/README.md

* ci: any change to the cms folder should trigger a rebuild (#118)

* chore: update Astro dependencies [INTORG-333] (#119)

* chore(cms): update @strapi/strapi 5.31.3 → 5.38.0, remove dead overrides [INTORG-334]

- Bump @strapi/strapi from 5.31.3 to 5.38.0 (latest stable)
- Remove inert pnpm.overrides block from cms/package.json — pnpm
  ignores overrides in workspace packages; only root overrides apply
- All three plugins (@_sh/strapi-plugin-ckeditor, @ckeditor/strapi-plugin-ckeditor,
  @notum-cz/strapi-plugin-record-locking) accept @strapi/strapi ^5.0.0

* foundation-page: sync (2 created)

* ambassador: create caroline-sinders

* fix(cms): bump @_sh/strapi-plugin-ckeditor to ^7.1.0

The v6 plugin bundles @strapi/design-system 2.0.0-rc.18 which conflicts
with Strapi 5.38.0's design-system 2.2.0, causing a styled-components
crash in the admin panel. v7.1.0 uses design-system 2.1.2 which is
compatible.

* foundation-page: update test-page

* remove test-page fixtures from content

* cleanup, auto generated types

* lockfile

* foundation-page: sync (2 created)

* ambassador: create caroline-sinders

* foundation-page: update test-page

* chore: upgrade to Astro 6, Node 22, and bump dependencies

INTORG-333

Upgrade the Astro site from v5 to v6 and move from Node 20 (lts/iron)
to Node 22 (lts/jod). Remove unused @astrojs/node adapter. Bump all
major dependencies: @astrojs/mdx 5, @astrojs/netlify 7,
@astrojs/starlight 0.38, Tailwind 4.2, Zod 4, marked 17, and
eslint/typescript-eslint tooling. Update CI workflows to Node 22.

Build, lint, all tests, and visual QA pass with no apparent regressions.

* cleanup

* cleanup

* chore(cms): upgrade Strapi to 5.39.0

* docs: document dual lockfile setup for root and cms

* ci(lint): fix build job to use Node 22 (required by Astro 6)

* chore: bump deps — starlight-links-validator, better-sqlite3, @types/node, vitest

* chore: pin pnpm to v10.27.0 across repo and CI workflows (#120)

* chore: pin pnpm to v10.27.0 across repo and CI workflows

* fix(ci): remove explicit pnpm version from workflows — read from packageManager field

* ci: better file change detection to trigger build (#121)

* ci: will now use change api to detect changes

* ci: make sure we use pnpm 10.27.0

* Update .github/workflows/staging-merge.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update .github/workflows/staging-merge.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* ci: fixed reference issues

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: align i18n routes and rename slug to pathSlug (#107)

* fix: align i18n routes and rename slug to pathSlug

* chore(deps): add better-sqlite3 to onlyBuiltDependencies and pin conflicting deps with overrides

* ci: pipeline should source basrc for nvm to work (#126)

* ci: explicit export of NVM_DIR (#127)

* ci: clean node modules before rebuild (#128)

* ci: force clean build (#129)

* ci: playground merge pipeline cleanup fix (#130)

* chore: store Sessionize data as JSON files (#131)

* feat: add Netlify-backed contact form (#87)

* feat(contact): add contact us page

* pnpm format

* chore: exclude preview pages from seo (#135)

exclude preview from seo

* feat(cms): cleanup strapi admin UI INTORG-481 (#106)

* fix(cms): disable draft and publish on all content types

* feat(cms): configure edit view layouts for ambassador and SEO

* feat(cms): customize admin panel styles and navigation

* pnpm format

* feat(cms): improve admin field labels and edit view layouts

* feat(cms): add labels and full-width layout for ambassador, blockquote, callout-text

* fix(cms): always apply field labels, not only when Strapi default

* fix page title too

* update labels

* hide preview button

* feat(cms): add help text for Directory Structure field

* fix - hide preview button

* simplify hiding open entity

* add mutable observable to doc

* stricter ID check in title

* refactor(404): reuse shared 404 page instead of inline not-found sect… (#137)

* refactor(404): reuse shared 404 page instead of inline not-found sections

Replace per-page inline 404 markup in [...page].astro and summit/[...page].astro
with redirects to /404, and disable Starlight's built-in 404 route so the
shared page is used consistently across the site.

* pnpm format

* chore: update buildBlogPayload to handle all fields in frontmatter (#105)

This PR updates the `sync:mdx` script to handle all frontmatter fields in blog post articles: `tags`, `articleBio`, `featureImage` + alt text, `thumbnailImage` +alt text

---------

Co-authored-by: Jonathan Matthey <mattheyj@gmail.com>

* fix: remove unused value from interface definition (#139)

* feat: PdfEmbed component (#132)

* pdf embed

* add download attribute

* fallback styling

* fix(lockfile): remove duplicate overrides keys from pnpm-lock.yaml

Merge introduced duplicate @strapi/design-system and @strapi/icons keys
in the overrides section, breaking frozen-lockfile installs.

* Delete src/content/foundation-pages/test-page.mdx

* Delete src/content/es/foundation-pages/test-page.mdx

* format

* revie and merge

* format

* fix(cms): target preview aside by nth-child instead of last-of-type (#142)

:last-of-type was hiding the wrong aside after a Strapi UI update moved
the publish panel. Switch to :nth-child(2) to reliably target only the
preview aside.

* fix(lockfile): update cms/pnpm-lock.yaml to include pnpm.overrides (#144)

The @strapi/design-system and @strapi/icons overrides added to
cms/package.json were missing from cms/pnpm-lock.yaml, causing
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH during the staging rebuild CI step.

* feat: add localization support for foundation-blog-posts (#143)

- add localization support for foundation-blog-posts
- remove `language` field from Strapi Admin ui for Blog post (redundant, we already use locale)-
- add back `afterUpdate` hooks in lifecycles (after switching off drafts for content, the lifecycles have changed they behaviour)

* chore: Create list pages for Summit talks and speakers (#136)

* chore: add base logic for summit talks list page

* chore: add base HTML structure to summit talks page

* chore: create Summit Speakers list page

* add Spanish summit list pages

* add comments

* refactor list pages

* style(summit): remove redundant .ts extensions from imports

* apply changes as per review request

---------

Co-authored-by: Jonathan Matthey <mattheyj@gmail.com>

* ci: trigger sync on navigation changes and run sync:all jm/intorg-493-syncall (#138)

* ci: trigger sync on navigation changes and run sync:all

Navigation changes now trigger the sync job, same as MDX content.
Switched to sync:all to run both syncs together, and renamed the job to match.

* ci: set navigation_changed in all fallback sync paths

* feat: added locale awareness to content collections (#134)

* foundation-page: sync

* foundation-page: sync

* chore: added nested grant pages (#153)

chore: added nested grant pages

* fix: alt text not syncing from MDX frontmatter to Strapi, and inline JSX images breaking in CKEditor. (#141)

* image normalisation, alt text fixes

* handle conflicts

* format

* fix set usage and revert to POST

* use correct image

* feat: add LanguageSwitcher component (#154)

* feat: add LanguageSwitcher component

* chore: refactor basePath logic

* refactor(i18n): centralize home slug usage in route helpers

* refactor(i18n): memoize translation map with top-level data module

* refactor(i18n): move route context into Astro.locals via middleware

Introduces routeContextFromPathname utility and wires it through middleware
so layouts and components read routeLocale/currentSlug/currentBasePath from
Astro.locals instead of receiving them as explicit props.

* chore: formatting

---------

Co-authored-by: Jonathan Matthey <mattheyj@gmail.com>

* fix(cms): ensure uploads folder exists before Strapi registers (#155)

The local upload provider needs public/uploads to exist before the
upload plugin registers. Create it eagerly in server config to avoid
startup failures on fresh clones.

* feat(parser): implement paragraph merging in parseMdxToBlocks (#156)

INTORG-514

Consecutive markdown nodes (paragraphs, headings, lists, tables) are now
accumulated into a single blocks.paragraph, split only when a JSX component
interrupts or EOF is reached. A blog post with 80 markdown nodes now
produces 1 block instead of 80.

Also fixes missing `@` path alias in cms/vitest.config.ts that was causing
mdxTransformer.test.ts to fail (introduced in #133).

* chore(assets): add original upload images to public/uploads (#157)

* Feat: create summit detail pages (#146)

This PR creates detail pages for individual summit session and summit speaker pages - English and Spanish pages:
`/summit/[year]/talk/{talk-title-slug}`
`/es/summit/[year]/talk/{talk-title-slug}`
`/summit/[year]/speaker/{speaker-name-slug}`
`/es/summit/[year]/speaker/{speaker-name-slug}`

This PR focuses on the main pages logic only. It does NOT handle components, summit JSON data extraction, or translation logic; these will be addressed in follow-up PRs.

* feat(blocks): add VideoEmbed component and Strapi schema (#148)

* feat(blocks): add VideoEmbed component and Strapi schema

INTORG-513 INTORG-515

- Install @astro-community/astro-embed-youtube and astro-embed-vimeo
- Create VideoEmbed.astro (facade pattern: thumbnail on load, iframe on click)
- Create VideoEmbedBlock.astro for DynamicZone preview rendering
- Add shared detectVideoProvider utility (YouTube + Vimeo URL detection)
- Add blocks.video-embed Strapi component schema (url + title fields)
- Register in foundation-page content-type and pageLifecycle populate
- Add video-embed serializer for Strapi-to-MDX export
- Wire into DynamicZone.astro and [...page].astro components map

* feat(parser): register VideoEmbed handler in parseMdxToBlocks (#158)

INTORG-516

- Add VideoEmbedBlock type to types.blocks.ts and ParsedBlock union
- Create videoEmbedHandler.ts: extracts url + title props, emits blocks.video-embed
- Register handler via side-effect import in config.ts

* feat(blog): add components map to blog page rendering (#159)

INTORG-521

- Add VideoEmbed to FoundationContentPage.astro components map
  (replaces the old [...page].astro components map after staging refactor)
- Add Paragraph + VideoEmbed components map to FoundationBlogPostPage.astro
  (blog posts can now render <Paragraph> and <VideoEmbed> from MDX)

* feat(summit): add VideoEmbed to summit page components map

INTORG-521

Summit pages can now render <VideoEmbed> from MDX, matching
foundation pages and blog posts.

* format

* content(blog): replace EMBEDDED VIDEO links with VideoEmbed components (#160)

INTORG-517

Replace 6 broken [EMBEDDED VIDEO: ...](url) markdown links across 5 blog
posts with <VideoEmbed url="..." title="..." /> JSX components.

Affected posts:
- digital-financial-inclusion-igniting-sustainable-development-goals
- harnessing-future-insights-global-technology-retreat-digital-trust-and-inclusion
- exploring-community-based-identification-systems (2 videos)
- why-higher-education-institutions-worldwide-should-embrace-hackathons
- interledger-hackathon-intro-open-payments-live-qa-ilf-experts

* feat(cms): use route path to dictate page folder structure in lifecycle (#124)

* feat(cms): single full path slug for foundation and summit pages

- Drop path field; pathSlug is required string (multi-segment URLs)\n- Admin: Full Path Slug label, help text, 12-col layout\n- pageLifecycle: nested MDX paths from slug; legacy path delete; omit path in frontmatter\n- Sync payloads no longer send path

* fix(cms): walk nested files when removing localizes from locale MDX

* refactor(cms): always use recursive scan in sync-mdx

Single readdir pass from collection root; infer locale from path segments.

* chore(cms): add vitest alias @ -> src for CMS tests

* refactor(site): pathSlug-only page routes and frontmatter

- Drop optional path from page schemas; simplify static-paths\n- ES about page: sobre-nosotros slug + localizes link

* docs: full path slug and nested foundation/summit MDX layout

* fix(cms): handle nullable pathSlug in page lifecycle afterDelete

* docs(cms): clarify pathSlug field descriptions per content type

* pnpm format

* chore: Refactor localized page nesting

* feat(cms): add sync:delete-all script and fix Strapi 5 pagination

- Add sync-delete-all.ts utility to bulk delete Strapi entries
- Fix getAllEntries to use proper pagination (Strapi 5 ignores limit=-1)
- Normalize API URL construction to handle trailing slashes

* format

* fix(cms): delete MDX at old pathSlug per updated locale

* refactor(cms): drop legacy path prefix from page MDX resolution

* Apply suggestion from @JoblersTune

* Apply suggestion from @JoblersTune

* refactor(cms): centralize default locale as defaultLang

* refactor(cms): clarify Strapi params.where in page lifecycle

* refactor(cms): replace module-level Map with event.state in page lifecycle

beforeUpdate and afterUpdate now share oldPathSlug via event.state,
which is scoped to the lifecycle event pair. This eliminates concurrent
update races and potential leaks from the previous module-level Map.

* improve comment

* fix(cms): warn when MDX file is missing pathSlug in frontmatter

The file is always written with pathSlug by the export process, so its
absence indicates a corrupted or manually-edited file. Log a warning
rather than silently falling back to the Strapi value.

* pnpm format

* fix mdx

* add doc

* chore(cms): document locale resolution and log source in readLocaleFromUpdateEvent

* refactor(cms): trust Strapi pathSlug for beforeUpdate MDX cleanup

* feat(cms): enforce unique pathSlug per locale on page types

* fix(cms): scope removeLocalizesFromLocaleFiles to each locale dir

* fix(cms): require --confirm for sync-delete-all destructive runs

* refactor(cms): derive sync-mdx locale dirs from LOCALES

* pnpm format

---------

Co-authored-by: JoblersTune <sarah@interledger.org>

* feat: create script to import summit data and speaker images from Sessionize (#163)

Create script to import summit data and speaker images from Sessionize

The script
- saves summit data as json files in `/src/data/sessionize/{year}-speakers.json` and `/src/data/sessionize/{year}-talks.json`
- imports speakers' profile images under `/public/img/sessionize-speakers/{year}`

* feat: Create utility functions for extracting Sessionize data (#161)

This PR creates utility functions for extracting Sessionize summit data from json files and making them available to components

* feat(cms): migrate blog content from CKEditor to structured blocks (#162)

* feat(cms): change blog content field from CKEditor to dynamiczone

INTORG-518

Change the blog post `content` field from `type: "customField"` /
`customField: "plugin::ckeditor5.CKEditor"` to `type: "dynamiczone"`
with the same component list as foundation pages. This allows Strapi
to accept block arrays instead of a plain string.

Requires Strapi restart to take effect.

* feat(sync): wire parseMdxToBlocks into buildBlogPayload

INTORG-519

- Add ParserContext parameter to buildBlogPayload
- When provided, parse MDX body into structured blocks via parseMdxToBlocks
  instead of sending raw markdown string
- Wire ParserContext in config.ts for foundation-blog-posts (same pattern
  as buildParsedPagePayload: resolveRelation + resolveMediaUpload)
- Parser errors include file pathSlug for context

* feat(cms): update blogLifecycle to serialise blocks on save (#164)

* feat(cms): change blog content field from CKEditor to dynamiczone

INTORG-518

Change the blog post `content` field from `type: "customField"` /
`customField: "plugin::ckeditor5.CKEditor"` to `type: "dynamiczone"`
with the same component list as foundation pages. This allows Strapi
to accept block arrays instead of a plain string.

Requires Strapi restart to take effect.

* feat(sync): wire parseMdxToBlocks into buildBlogPayload

INTORG-519

- Add ParserContext parameter to buildBlogPayload
- When provided, parse MDX body into structured blocks via parseMdxToBlocks
  instead of sending raw markdown string
- Wire ParserContext in config.ts for foundation-blog-posts (same pattern
  as buildParsedPagePayload: resolveRelation + resolveMediaUpload)
- Parser errors include file pathSlug for context

* feat(cms): update blogLifecycle to serialise blocks on save

INTORG-520

- Call serializeContent(post.content) instead of writing raw string
- Re-fetch blog post with populate params for dynamiczone content
  (lifecycle event.result doesn't auto-populate dynamiczone on params)
- Change BlogResult.content type from string to blocks array
- Extract shared StrapiDocumentAPI/StrapiGlobal types to strapiTypes.ts
  (was duplicated across pageLifecycle, navigationLifecycle, flatContentLifecycle)

* chore(cms): update generated content types after schema change

Strapi regenerated types on startup to reflect the blog content
field migration from CKEditor to dynamiczone.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(cms): support all block types in blog lifecycle and share populate config

INTORG-520

- Extract CONTENT_BLOCK_POPULATE into shared contentPopulate.ts to avoid
  duplicating block populate config between page and blog lifecycles
- Add fetchWithPopulate to blogLifecycle.ts so dynamic zone content
  includes nested relations (ambassador photos, pdf files, etc.)
- Call serializeContent() for block arrays when generating blog MDX
- Blog lifecycle now handles all block types, not just paragraph + video-embed

* docs(decisions): add ADR-001 for content collection path handling (#165)

* docs(decisions): add ADR-001 for content collection path handling

Introduces Architecture Decision Records and documents the convention
for how MDX files are organised under Astro content collections,
including the EN/ES directory mirroring strategy.

Resolves INTORG-546

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* forma

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: add embedded video recordings on Summit Talk pages (#167)

* run syns:sessionize script to update recordingUrl links

* Add summit recording videos on pages using VideoEmbed component

* fix: add missing component renderers (#166)

* add missing component renderers

* remove unused comps

* refactor(cms): split populate config into page and blog variants

PAGE_CONTENT_POPULATE includes all block types (cards-grid, carousel, etc.).
BLOG_CONTENT_POPULATE includes only blog-relevant blocks (paragraph,
ambassador, blockquote, callout-text, pdf-embed, video-embed).

Keeps populate params in sync with the content-type schema component lists.

* remove unused compos

* fix: address PR #166 review comments

- Align summit-page dynamic-zone components with foundation-page
- Remove unused imports from FoundationBlogPostPage
- Simplify blog components map to Paragraph + VideoEmbed

INTORG-535

* add missing imports to SummitContentPage

---------

Co-authored-by: Anca Matei <98110730+Anca2022@users.noreply.github.com>

* feat: Create / Refine the Summit data Astro components (#170)

This PR creates and refines all components required to render speaker and session data. It intentionally does not focus on CSS, prioritizing component structure instead

* feat(blog): migrate all 92 blog posts to Paragraph-wrapped component … (#168)

* add missing component renderers

* feat(blog): migrate all 92 blog posts to Paragraph-wrapped component format

Parse each blog post's raw markdown body through parseMdxToBlocks →
serializeContent to produce <Paragraph>-wrapped MDX, matching the
foundation page format. 87 plain-markdown posts get a single
<Paragraph> wrapper; 5 VideoEmbed posts split into Paragraph +
VideoEmbed + Paragraph blocks. Synced to Strapi with 0 errors.

Includes the migration script (cms/scripts/migrate-blog-to-components.ts)
for reproducibility.

INTORG-524

* format

* refactor(cms): add index signature to StrapiBlockBase, remove cast

Add [key: string]: unknown to StrapiBlockBase so ParsedBlock[]
satisfies serializeContent's parameter type without a cast.

INTORG-524

* fix(cms): preserve content byte-for-byte in paragraph round-trip

The paragraph handler was re-serializing children via mdast-util-to-markdown,
which decoded HTML entities (&amp; → &) and escaped brackets ([ → \[).
Now passes sourceText through ParserContext so the handler can slice raw
content from AST positions instead of re-serializing from the AST.

INTORG-524

* feat(cms): hard-fail on nested JSX inside Paragraph blocks (#169)

* feat(blog): migrate all 92 blog posts to Paragraph-wrapped component format

Parse each blog post's raw markdown body through parseMdxToBlocks →
serializeContent to produce <Paragraph>-wrapped MDX, matching the
foundation page format. 87 plain-markdown posts get a single
<Paragraph> wrapper; 5 VideoEmbed posts split into Paragraph +
VideoEmbed + Paragraph blocks. Synced to Strapi with 0 errors.

Includes the migration script (cms/scripts/migrate-blog-to-components.ts)
for reproducibility.

INTORG-524

* format

* feat(cms): hard-fail on nested JSX inside Paragraph blocks

Import guard: paragraphHandler.ts walks <Paragraph> children before
childrenToMarkdown runs. If any custom JSX component (capital-letter
name) is found nested inside, throws MdxParserError with NESTED_JSX
code. HTML elements like <br> and <span> are allowed.

Export guard: Koa middleware in src/index.ts intercepts content-manager
PUT/POST requests and validates paragraph content via shared
contentValidation.ts. Returns a 400 with a descriptive error message
visible in the Strapi admin UI — authors can't save until they fix
the nesting. Covers blog posts, foundation pages, and summit pages.

INTORG-535

* lint, format

* fix: restore blog posts to staging state after rebase

The migration commit was re-applied during rebase on already-migrated
files, causing duplicate <Paragraph> wrappers. Restores all blog posts
to the correct state from staging.

* fix(cms): increase SEO meta description character limit (#174)

fix(cms): increase SEO meta description character limit to 300

The previous 160-character limit was too restrictive for content
editors. Bumped to 300 to allow longer descriptions while still
staying within commonly recommended SEO ranges.

Fixes INTORG-502

* feat: add /.well-known/security.txt (#175)

Adds a security.txt file per RFC 9116 with contact info for
responsible disclosure.

Fixes INTORG-525

* docs: add Summit Data (Sessionize Integration) section to README (#176)

* feat(analytics): add Umami event tracking to remaining components (#173)

* feat(analytics): add Umami event tracking to remaining components

Add data-umami-event attributes to CTA banners, Lander header/footer,
blog listing titles, pagination, cross-blog nav, and video embed
fallback links. Event names follow the established taxonomy from v4
and the production Umami dashboard.

Closes INTORG-360

* feat(analytics): add Umami tracking to CommunityLinks components

Add data-umami-event attributes to all links in
CommunityLinksFoundation and CommunityLinksDevelopers components.

* chore: add analytics for summit data pages (#179)

* chore: add analytics for summit data pages

* refactor: move Pagination component into shared directory

* feat: handle translations for summit data (#171)

This PR adds translation support for dynamic Sessionize content (talks and speakers):
- Populate translationMap with Sessionize data — ensures the LanguageSwitcher correctly navigates between en and es pages for all summit talks and speakers
- Fallback to En — when no Spanish content is found, the pages fall back to English content
- update README with info on Adding support for a new language

* feat(cms): update Strapi to v5.41.1 (#180)

* feat(cms): update Strapi to v5.41.1 and migrate entityService to Document API

INTORG-533

Migrate ensureLocales() from deprecated strapi.entityService to
strapi.documents() API (enforced in v5.40.0). Bump better-sqlite3
to 12.8.0. All tests pass, sync scripts verified.

* lockfiless

* sync lockfile

* comments

* chore: simplify the Strapi SEO component (#177)

This PR simplifies the Strapi SEO experience for editors by removing fields that were either unused, redundant, or likely to cause confusion.

* summit-page: create home

* summit-page: delete sessions

* summit-page: create hackathon

* foundation-page: create policy-and-advocacy

* foundation-page: create team

* foundation-page: create media

* foundation-page: create open-standards

* foundation-page: create interledger

* foundation-page: create open-payments

* foundation-page: create web-monetization

* foundation-page: create join-network

* foundation-page: create developers

* foundation-page: sync

* foundation-page: sync

* feat: add caching policy [INTORG-359] (#140)

feat: add caching policy with explicit Cache-Control headers [INTORG-359]

Define Cache-Control headers in netlify.toml for all asset categories,
add no-store response headers on SSR preview pages via applyPreviewNoStore(),
and disable runtime caching on fetchStrapi() calls.

* chore: added summit page handling (#184)

* chore: updated menu links (#185)

* chore: updated menu links

* formatting

* fix(styles): bump docs-design-system to fix h3 style leak (#178)

* fix(styles): bump docs-design-system to 0.11.1

Picks up the fix for unscoped h3 highlight selectors that were leaking
from Starlight pages into the main site (e.g. footer headings).

Resolves INTORG-529

* fix(styles): use GitHub ref for docs-design-system until npm publish

INTORG-529

* fix(styles): fix Starlight CSS layer ordering and form selector leaks

Pre-declare Starlight CSS layers before Tailwind layers so
starlight.reset no longer overrides Tailwind utility classes.

Update docs-design-system to include scoped form selectors
(label, input, ::placeholder) — prevents Starlight form styles
from leaking onto non-Starlight pages like /contact.

INTORG-529

* update docs-design-system

* ci: will now do dryrun as part of PR checks (#181)

* ci: strapi will now dry run against staging

* ci: will now use the strapi-staging environment

* ci: switch to RO token for dry run

* fix: dry run must use read-only key

* fix(cms): Astro HERO: background image + remove secondary CTA btns INTORG-572 INTORG-573 (#183)

* fix(cms): remove secondary CTA buttons from hero component

* fix(cms): clear deleted hero/seo fields from MDX frontmatter

* feat(hero): support custom background image on hero sections

* fix(styles): add list-style markers to prose content [INTORG-589] (#187)

list styles

* summit-page: create 2024

* summit-page: create 2023

* summit-page: create 2022

* summit-page: sync

* summit-page: sync

* summit-page: sync

* chore: moved Sessionize speakers images from public/img/sessionize-sp… (#193)

chore: moved Sessionize speakers images from public/img/sessionize-speakers to public/sessionize-speakers/img

* style(prose): add visible underline to content links (#191)

* style(prose): add visible underline to content links

Links in prose content now show an underline by default so users
can identify them as clickable. Underline fades out on hover.
Consistent across default, blog, and foundation prose variants.

Fixes INTORG-598

* style(prose): add link underlines to summit and developers-blog

- Summit prose: add explicit underline styling for links
- Developers blog: replace no-underline with visible underline,
  fixing the brief flash caused by conflicting hover rules

Fixes INTORG-598

* feat(cms): route uploads to img/original and disable image variants (#190)

* commit adding images

* feat(cms): route uploads to img/original and disable image variants

* pnpm format

* feat(cms): add helper text for blog post tags field [INTORG-570] (#189)

* feat(cms): add helper text for blog post tags field [INTORG-570]

Add description to the tags component field in the Strapi admin,
indicating that multiple tags can be selected via "+ Add an entry".

* format

* feat(cms): route uploads to img/original, seed media library from disk  INTORG-594 (#192)

* commit adding images

* feat(cms): route uploads to img/original and disable image variants

* pnpm format

* feat(cms): seed upload records from disk on bootstrap

* delete dup imags

* feat(cms): seed public/img/ images into Strapi media library

* pnpm format

* delete images

* delete img variants

* also delete images from /img folder

* rm uploads img

* fix iamge check

* fix sync all force

* sync all script

* feat(cms): brand pillar foundation pages updates INTORG-569 INTORG-568 (#186)

- replace  `pageType` ("Page Type") field with pillar ("Brand Pillar") field and update values

* ci: pr checks are now environment aware

* ci: merge was restarting wrong service

* ci: added debug info to the pipeline

* ci: swop out tokens with ENV prefix

* ci: fail fast if not set

* ci: ENV_STRAPI_URL is a var not a secret

---------

Co-authored-by: Ravi Soni <soni.ravi829@gmail.com>
Co-authored-by: Jonathan Matthey <mattheyj@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sarah Jones <sarah@interledger.org>
Co-authored-by: Anca Matei <98110730+Anca2022@users.noreply.github.com>
Co-authored-by: strapi <strapi@vm>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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