Purpose: Track architecture decisions and implementation progress for the unified public events experience in this branch.
- Branch in use:
calendar-of-events - Public canonical route:
/events - Legacy route behavior:
/calendarredirects to/events - Internal ops routes:
/internal/events(events registry + status/campaign checks)/admin/events(redirect alias to/internal/events)
- Event detail route:
/events/[slug](countdown + CTA + partner panel) - Event detail route also supports related training back-link when
programRef.skuis present. - Campaign short links:
/c/[id]mapped inweb/src/lib/data/campaigns.json - Events API route:
/api/events.json - Waitlist/capacity/sold-out implementation is intentionally deferred until Stripe-native checkout is live; while registrations remain in Luma, these are not MVP requirements.
- Calendar filter behavior:
- Keep top-level
All,Training,Events. - Show dynamic event-type chips only when present in current data.
- Hide duplicate/generic type chips (
training,training_session,event,other). - Webinar chip label includes emoji (
🎙️ Webinar).
- Keep top-level
- Known pre-existing
svelte-checkfailures (not introduced by this work):web/src/lib/components/ServiceCard.svelteweb/src/lib/components/training/CatalogCard.svelteweb/src/lib/components/training/ProgramPage.svelteweb/src/lib/components/techlab/ProgramPage.svelte
- Added featured images for events:
- Set
image+imageAltfor seeded events inweb/src/lib/data/events/events.json.
- Set
- Standardized the
/eventscard layout:- Always reserve space so
Learn more →aligns consistently on desktop. - Unified pill/meta ordering to match the “Happening now” card (pills first, then meta details).
- Always reserve space so
- Updated card link behavior:
Learn more →always goes to the event landing page (/events/[slug]) for event items.- For course-style events (
type=training_session+programRef.sku), the title links to the training program page (/training/[slug]). - For other events, title and
Learn more →both link to the event landing page.
- Improved registration button states:
- Closed events render a disabled, non-clickable button using the event’s
cta.label(ex: “Enrollment closed”).
- Closed events render a disabled, non-clickable button using the event’s
- Added compact chips for crowded cards:
- Added
📜 Certificatechip whencertificateTextis present (instead of a full row of text). - Moved trailer link into a compact
▶ Trailer ↗chip and placed it first in the pill row.
- Added
- Updated the AI Workshop event record (to reduce reliance on the training catalog for listing details):
- Updated
tagline,summary,date,time,timezone, andendAtUtcinweb/src/lib/data/events/events.json. - Updated
/eventsdate rendering to preferevent.datewhen present. - Updated event partner display on cards to use structured
partners[]entries resolved viaweb/src/lib/data/partners.ts(instead of pulling freeform partner strings from training sessions).
- Updated
- Build fix discovered while validating:
- Fixed an unescaped quote in
web/src/routes/forms/pre-training-survey/+page.sveltethat preventednpm --prefix web run buildfrom succeeding.
- Fixed an unescaped quote in
- Events data/model
web/src/lib/data/events/events.jsonweb/src/lib/data/events/events.schema.jsonweb/src/lib/data/events/types.tsweb/src/lib/data/events/index.tsweb/src/routes/api/events.json/+server.ts
- Events routes/UI
web/src/routes/events/+page.svelteweb/src/routes/events/[slug]/+page.tsweb/src/routes/events/[slug]/+page.svelteweb/src/lib/components/CalendarPage.svelteweb/src/routes/calendar/+page.tsweb/src/routes/calendar/+page.svelte
- Internal/admin
web/src/routes/internal/events/+page.tsweb/src/routes/internal/events/+page.svelteweb/src/routes/admin/events/+page.ts
- Campaigns/partners
web/src/lib/data/campaigns.jsonweb/src/lib/data/partners.ts
- Crawl/SEO metadata
web/src/lib/seo.tsweb/src/routes/sitemap.xml/+server.tsweb/static/robots.txt
- Assets
- Use one public surface at
/eventsfor the Calendar of Events. - De-emphasize
/calendaras a separate public destination. - Keep
/events/[slug]landing pages for each event (full or minimal mode). - Use one JSON structure/schema for all event types.
- Keep data file-driven and expose API output for automations.
- Store canonical event times as
startAtUtcandendAtUtc(ISO-8601 withZ). - Standardize official display timezone to
America/Los_Angeles(DST-aware). - Registration is external for now (Luma); plan for Stripe later.
- Keep events visible when scheduled but closed (
registrationStatus=closed). - List events by month sections (not a visual month-grid calendar).
- Each list item includes a one-line summary.
- Use campaigns for CTA tracking/shortlinks into event landing pages.
- Add partner support with 3-letter code conventions.
- Make
web/src/lib/data/events/events.jsonthe canonical source for all scheduled items shown on/events(including training sessions). - Use
programRef.sku(for exampleCM-TR-005) as the canonical training-to-event join key. - Use strict API split:
/api/events.jsonis canonical schedule data;/catalog.jsonremains non-schedule catalog/program content. - Remove
program.sessionsas a schedule source immediately (migrate all consumers to events registry). - Add a hard validation gate for
web/src/lib/data/events/events.jsonusing JSON Schema (fail on invalid data). - During current dev phase, keep
/eventslisting limited to two seeded entries: AI Workshop for Tech Writers and Vibe Coding Webinar.
- Public route:
/eventsis the single "Calendar of Events" surface. - Public route:
/events/[slug]for event details, countdown, and CTA. - Internal route:
/internal/eventsfor management workflows. - Add
/admin/eventsalias/redirect to/internal/events. - Optional redirect strategy for
/calendar(decide keep, alias, or canonicalize later).
- Create
web/src/lib/data/events/events.schema.json. - Document event
idstandard: 6-char base36 (^[a-z0-9]{6}$), immutable once created. - Rename canonical time fields to
startAtUtcandendAtUtc. - Add visibility fields:
visibility(public|unlisted|draft). - Add lifecycle fields:
lifecycleStatus(scheduled|postponed|canceled|completed). - Add registration fields:
registrationStatus(open|closed|external|none). - Add CTA fields (campaign-aware):
cta.label,cta.url,cta.campaignId. - Add location object:
mode,publicLabel,detailsVisibility. - Add partner link field(s):
partners[](multi-partner support with role labels). - Keep optional content blocks (
description,highlights,speakers,programRef,links).
- Add partner catalog data file (for code/name/logo/homepage).
- Define 3-letter partner code list and mapping to partner slug.
- Migrate legacy event IDs from
evt_...format to 6-char base36 standard. - Keep campaign IDs kebab-case to match existing
campaignsschema constraints. - For each promoted event, create a campaign with
landingPathto/events/[slug]. - Event landing pages support external CTA targets (Luma for now).
- Remove dev-only registry/documentation blocks from public
/eventspage. - Reuse
CalendarPagelayout as canonical/eventsUI (filters, visuals, countdowns). - Route event cards to
/events/[slug]for canonical detail pages. - Ensure closed events render non-click registration state in calendar cards.
- Build or update
/events/[slug]landing template for countdown + CTA. - Add event detail back-link to related catalog training program.
- Tighten calendar filter chips to avoid duplicate training labels.
- Add webinar emoji on webinar filter chip.
- Update SEO metadata entries for
/eventsand/calendarredirect behavior. - Update sitemap entries for event pages once public behavior is finalized.
- Parse countdown target from
startAtUtconly. - Never parse timezone-less datetime strings in new events route logic.
- Format official event date/time in
America/Los_Angeles. - Optionally display viewer-local time as secondary helper text.
- Use UTC timestamps for sorting and status transitions.
- Update
web/src/lib/data/events/types.tsto new schema fields. - Update
web/src/lib/data/events/index.tslist/filter/status helpers. - Update
web/src/lib/data/events/events.jsonto new schema. - Add/adjust
/apiendpoint for events payload consumed by automations. - Ensure campaigns endpoint integration remains compatible.
- Add a scriptable draft-event generator from training SKU + start date (
npm --prefix web run events:draft). - Add
events.jsonschema validation script (AJV) and wire it into npm scripts as a hard gate. - Remove legacy resolver compatibility fields in
web/src/lib/data/events/index.tsafter all consumers move to canonical fields.
- Update
README.mdroute notes (/eventspublic model and/calendarrole). - Update
llms.txtif public event model messaging changes materially. - Update
ai.txtonly if policy/usage language changes (no additional policy wording changes required). - If crawl behavior changes, update
web/static/robots.txt. - If route exposure changes, update
web/src/routes/sitemap.xml/+server.ts. - Sync root
ai.txtandllms.txttoweb/static/if either changes.
- Keep current AI workshop event with
registrationStatus=closed. - Add placeholder webinar event: "Vibe Coding Webinar" (next-week date).
- Add partner examples: TechLAB, Hufnagel Consulting, The Content Wrangler.
- Create matching campaign entries for seeded public events.
- Multi-partner events (host/sponsor arrays) with multiple logos.
- Waitlist + capacity + sold-out states (deferred: implement together with Stripe-native checkout so capacity can be source-of-truth and enforcement is reliable).
- "Happening now" section on
/events. - Past-events archive and recap/recording policy.
- Auto-hide/promote windows decision resolved as visibility/state filters: drafts auto-display only in
devand internal admin views, public events display whenvisibility=public, unlisted events are direct-link accessible (and visible indev), and in-progress training events switch to "Happening now" with enrollment closed state. - Stripe-native on-page checkout flow (dependency for first-class waitlist/capacity/sold-out enforcement; while registrations stay in Luma, treat these states as non-required for MVP).
- Remove legacy event resolver compatibility layer in
web/src/lib/data/events/index.tsonce consumers are migrated. - Complete migration of
/eventsand training surfaces to events-registry-only schedule reads (noprogram.sessionsreads). - Remove legacy calendar listing sources so events visibility is filter-driven (
devcan include drafts; production listings show only viewable events by visibility/status). - Decide whether the
/events/[slug]landing pages should display certificate/trailer chips: yes, display on event detail pages as well as/eventslisting cards. - Decide whether event cards should deep-link CTA through
/c/{campaignId}by default for attribution: yes, use campaign short links by default when a campaign ID is present. - Add editability workflow for ops (JSON editing guidance or lightweight tooling in
/internal/events). - Decide whether to expose partner homepage links for all partners: no external partner links on event surfaces; keep "In partnership with" attribution text while retaining users on Cambermast pages.
- Optional: add
/admin/eventsentry point in internal docs/navigation. - Optional cleanup: normalize old event IDs (
evt_...) to 6-char base36 after migration plan is approved. - Keep training program discovery registry-driven from
training.json(auto-includes new entries); include in-progress courses indevby default and hide drafts in production unlessincludeDraftsis explicitly enabled. - Optional UI polish: add explicit past/canceled state treatment on
/events/[slug]with a friendly notice and a clear path back to/events.
- Build/deploy cadence is daily at 12:30am PT.
- For now, "stop promoting" means the event is archived from public listings (
/events) on the next site build, not deleted; direct links (for example/events/[slug]) remain reachable. - Keep published event pages reachable (no accidental 404s for shared links).