This file provides guidance to AI agents when working with code in this repository.
When making changes to the codebase, please also update this file as needed to reflect any new patterns, tools, or workflows that agents should be aware of. Especially when refactors or architectural changes are made, please update the "Architecture" and "Tech Stack" sections to reflect the new structure and technologies used in the project.
Static website for Processing Community Day (PCD) 2026 — a global map of events. Built with Astro 5 (static output) + Vue 3 + Leaflet. No backend, no database, no API calls at runtime.
The Astro project root is pcd-website/. All build commands run from there.
All from the pcd-website/ directory:
npm install
npm run dev # localhost:4321
npm run build # production build → dist/
npm run preview # preview production buildThere are currently no test or lint scripts configured.
- Astro (
src/pages/index.astro) is the single entry point — a static HTML shell with<head>, font/CSS links, and BASE_URL injection. No new Astro pages should be added. - Vue handles all interactive UI as
client:only="vue"island components. New UI features go in Vue components, not Astro pages.
Event data lives in src/content/events/<event-id>/:
metadata.json— event fields (id, name, location, dates, organizers, etc.)content.md— markdown body (frontmatter must includeid:)
src/lib/nodes.ts loads all events at Astro build time using import.meta.glob() + getCollection('events'), validates plus codes with OpenLocationCode, decodes lat/lng, and returns a sorted Node[] array passed as props to <MapView>.
If a plus_code is invalid or too short, the build fails with a clear error — this is intentional.
- Leaflet CSS is loaded via
<link>tags inindex.astro, NOT via JS imports — avoids SSR issues since MapView isclient:only="vue". open-location-codeexports{ OpenLocationCode }as a named export — usenew OpenLocationCode()(not static methods).leaflet.markerclustercauses a circular dependency warning, suppressed viarollupOptions.onwarninastro.config.mjs.- Deep linking:
?event=<id>query param auto-opens the event detail panel. - Map style preference persisted in
localStorage.
| File | Role |
|---|---|
src/components/MapView.vue |
Leaflet map, marker clustering, keyboard shortcuts, tile layer switching |
src/components/NodePanel.vue |
Slide-in event detail panel with minimap, calendar links, share button |
src/components/NodeList.vue |
Alphabetical event list overlay with map style switcher + dark mode toggle |
src/components/LanguageSwitcher.vue |
Language selector dropdown in the top bar |
src/lib/nodes.ts |
Node interface + loadNodes() |
src/lib/format.ts |
formatDate(), formatDateRange(), calendarLinks(), etc. |
src/lib/popup.ts |
Leaflet popup HTML generation (makePopupContent()) |
src/styles/global.css |
Design tokens (CSS custom properties), IBM Plex Sans, Leaflet overrides |
src/content.config.ts |
Astro content collection Zod schema for events |
src/config.ts |
Global static constants (contact email, etc.) |
src/i18n/index.ts |
Creates the vue-i18n instance and exports syncLocale() |
src/i18n/localeState.ts |
Reactive currentLocale ref, browser detection, localStorage persistence |
src/i18n/vuePlugin.ts |
Astro appEntrypoint — installs vue-i18n on every Vue island |
src/i18n/locales/en.json |
Source-of-truth translation file (all keys must exist here) |
src/i18n/locales/*.json |
Per-language translations (es, de, fr, pt, zh-TW, zh-CN, ja, ko) |
The site uses vue-i18n@11 with 9 supported locales: en, es, de, fr, pt, zh-TW, zh-CN, ja, ko.
vue-i18nis installed globally viaastro.config.mjs→vue({ appEntrypoint: '/src/i18n/vuePlugin' }).- Locale detection order: localStorage (
pcd-locale) →navigator.language→'en'. - The active locale is a reactive singleton (
currentLocaleref inlocaleState.ts) shared across all components.
- Always add the key to
en.jsonfirst. It is the source of truth and the fallback for all other locales. - Add the same key to every other locale file in
src/i18n/locales/. Missing keys fall back to English silently. - In Vue components, use
const { t, locale } = useI18n()and replace hardcoded text witht('key'). - In non-component TS files (e.g.
popup.ts), usei18n.global.t('key')imported fromsrc/i18n/index.ts. - Pass
locale(orlocale.valueas a string) toformatDateRange(),formatDate(), etc. for locale-aware date formatting.
Event data coming from content files — event_name, details_text, city, country, organization_name, organizer names, URLs — must never be wrapped in t(). Only static UI strings get translated.
Non-English locales use "Events" (not "Nodes") in list/dialog labels, since "Nodes" is a technical term that doesn't translate naturally.
Use src/config.ts for static, non-secret values that are referenced across multiple files or are likely to change. Import from it rather than hardcoding inline.
Store here:
- Contact emails (e.g.
PCD_EMAIL) - Stable URLs referenced in UI (e.g. a feedback form link)
- Project-wide constants (e.g. site name, org name)
Do not store here:
- Environment-specific or secret values — use
.envwithimport.meta.envfor those - Anything already defined in
astro.config.mjs(e.g. base path) - Component-local constants that aren't shared
- Always support both light and dark mode for any new or modified UI elements.
- Dark mode is toggled via
[data-theme="dark"]on<html>(set byNodeList.vue). - Dark mode uses CSS custom properties defined in
global.cssunder[data-theme="dark"]. - Vue scoped styles cannot target ancestor-based dark mode selectors — put those overrides in
global.css.
Must follow standard accessibility best practices (semantic HTML, ARIA attributes, keyboard navigation, focus management) for all interactive components (map, panels, buttons, etc.). WCAG 2.1 AA compliance is the goal.
New events are submitted via GitHub Issues using .github/ISSUE_TEMPLATE/new-event.yml. The workflow .github/workflows/new-event-intake.yml runs .github/scripts/process-new-event-issue.mjs to validate the issue and, if valid, opens a PR with generated metadata.json + content.md files.
GitHub Pages via .github/workflows/deploy.yml. Triggered on push to main (for paths under pcd-website/). The site base path is /pcd-website-mvp-2 (set in astro.config.mjs).