Changelog
April 2026
Section titled “April 2026”Week 4 (29 apr) — Image block
Section titled “Week 4 (29 apr) — Image block”Solo afbeelding-block (image) met:
- Breedte: 4 presets —
small(col-span-6),normal(col-span-8, default),large(col-span-12 / 960px),fullscreen(viewport-bleed via 100vw + negatieve margin trick) - Hoogte: 4 presets —
landscape(16:9),portrait(3:4),square(1:1),original(vrije hoogte metobject-fit: contain, default) → matcht hetColumnImageHeightenum uitstyles.ts - Caption + alt zitten op het media-object zelf (
props.image), niet als aparte sidebar-velden.MediaObjectFieldheeft nuimagePickerShowAlt+imagePickerShowCaptionopt-in props die inline inputs onder de preview rendert. - Optionele link wrap rondom de figure
- Focal point wordt overgenomen via image picker (zelfde object-shape als background images)
- Astro renderer: 0 client-side JS, BlurImage met LQIP,
<figcaption>voor a11y,aboveFold→priorityeager-loading preloadBlockImagesextractor uitgebreid voorimage-blocks (kind:'inline')
Week 4 (21 apr) — Testimonials block-package
Section titled “Week 4 (21 apr) — Testimonials block-package”Drie nieuwe block types voor klantbeoordelingen, alle drie via één gedeelde
packages/shared/src/testimonials/ module
zodat card/quote/rating-styling exact identiek is tussen variants:
testimonials-grid— 2 of 3 kolommen, optionele mobile-carousel via Emblafeatured-testimonial— enkele gecentreerde quote (0 JS op publieke site)testimonials-carousel— Embla-gedreven slideshow met arrows + dots
Architectuur en infrastructuur:
- Embla Carousel als gedeelde carousel-engine (ADR-009).
Code-split via
React.lazy()+ dedicatedvendor-emblachunk; pagina’s zonder carousel betalen geen ~7 KB gzip kosten. - Shared rendering module met
types.ts,classes.ts(class-builders) enreview-card.css. React (editor + public) en Astro renderen via dezelfde classnames; geen drift tussen frameworks. - Pre-work — repeater compact mode in
content-sections.tsx:useEffect- synced refs +memo()opSortableRepeaterItemvoorkomen dat 11 sibling- items re-renderen bij elke keystroke. Compact-state per field-instance in localStorage. Layout-shift bij toggle = 0px (header op vaste Y-positie). - Pre-work —
preloadBlockImagesrefactor naar per-block-type extractor (columns + 3 testimonial blocks + group/columns recursie). Inline images preloaden single src tot 200px (geen volle 1920px srcSet voor 40px avatars).
Editor-UX-verbeteringen:
EditableText.keepEmptyprop — element blijft contentEditable bij lege value zodat user kan blijven typen na alles te wissen.FieldDefinition.visibleWhenaccepteert tweedeblockPropsargument → per-item velden kunnen gaten op block-level toggles (bv. Rating-veld alleen zichtbaar alsshowRatingaan).NumberStepper(alleen−/+-knoppen, step 0.5) — gebruikt voor rating.MediaObjectField(typeimage-picker) — slaat volledig media-object op{ url, media_id, blur_data_url, alt }i.p.v. enkel een URL-string.
Visuele consistentie:
- Card-stijl
cardaltijd witte surface + shadow + donkere tekst, ongeacht block-tekstmodus. Specificity (0,2,0) overschrijft Tailwindtext-whiteop light-blocks zodat de kaart-tekst leesbaar blijft. - Card-stijl
borderedborder-color =currentColor(volgt block-tekstkleur). .review-featuredoverschrijft padding niet; surface-eigenschappen komen van--card/--plain/--bordered. Author-rij is verticale stack: avatar/ logo, naam, rol/bedrijf gecentreerd.- Avatar + logo hebben dezelfde 40×40 footprint zodat meta-text op gelijke x-positie begint. Half-ster ratings (4.5) via twee-laags CSS overlay.
- Carousel pijltjes: position absolute buiten viewport (3.5rem in section- padding), z-index 10, witte solid surface met shadow.
- Grid + carousel + grid-overlay debug allemaal
1remkolom-gap. slidesToScroll: 1+containScroll: 'trimSnaps'→ per slide scrollen, dots = unieke views (geen dode dots).- Quote-mark
nonerendert echt niets (geen fallback decoratief teken). - Carousel viewport
overflow: visible(niethidden) zodat slides aan de rand niet hard worden afgekapt; section-overflow:hidden clipt op block-rand. - Cursor
grab/:active grabbingop carousel-viewport.
Quality gates:
- 4 kritieke + 11 belangrijke audit-bevindingen (van parallelle beam-reviewer, Performance Benchmarker en Senior Developer agents) opgelost vóór merge.
- 10 minor findings + UX-verfijningen verwerkt naar P3 backlog of doorgevoerd.
docs/block-types.md,docs/functionaliteit.md,docs/glossarium.md,docs/backlog.mdbijgewerkt. ADR-009 toegevoegd.
Week 1 (5 apr)
Section titled “Week 1 (5 apr)”Editor Code Quality & Accessibility Audit:
- Dead code verwijderd —
editor.tsx(ongebruikt, vervangen doorpage-editor.tsx) - History memory cap —
pushHistory()helper beperkt undo-stack tot 50 entries, voorkomt onbeperkt geheugengebruik - History dedup —
JSON.stringifyvergelijking vervangen doorupdatedAtcheck (O(1) ipv O(n)) - Store selectors —
ToolbarenCanvasgebruiken nu granulaire selectors (usePageMeta,useHistoryState,usePageBlocks) ipv volledige store destructuring - Code duplicatie —
useInlineEditor()hook (hover-to-edit),useEditableButtons()hook (button modal),resolvePatternBlocks()helper (canvas drop) geëxtraheerd - API validatie —
blocksveld in pages API:z.unknown()vervangen door typed schema met max 200 blocks;cascade-hrefsendpoint: Zod validatie toegevoegd - DOMPurify hardening —
url()waarden in style attributes worden gestript (tracking pixel preventie) - Accessibility — WAI-ARIA tablist op sidebar tabs,
aria-labelop toolbar knoppen,tabIndex+ keyboard handlers op blocks,<main>+role="application"landmarks, block picker<div>→<button>, toolbar zichtbaar bij keyboard focus
Week 1 (2 apr)
Section titled “Week 1 (2 apr)”Bunny Stream Video Upload (TUS Protocol):
- TUS resumable upload — Video’s worden direct naar Bunny CDN geüpload via TUS protocol. Beam API genereert SHA256 presigned headers via
crypto.subtle(Web Crypto API) - Client-side direct upload — Videobestand gaat niet via de API Worker, voorkomt payload-limieten en CPU-tijd
useVideoEncodingPoll()hook — Batch status polling met exponential backoff (2s → 30s), ref pattern voor stabiele callbacks, optimistic SWR cache update bij encoding completion- Encoding status — Polling via batch API endpoint (
PATCH /media/video/:id/status)
Button Kleur Systeem:
colorTokenvervangt hardcoded kleuren — Buttons gebruiken nulight,dark,auto(past aan op achtergrond), of brand tokens (primary,secondary,accent+ shades) in plaats van hex waarden- CSS injection preventie —
sanitizeColorToken()valideert tokens tegen een whitelist colorveld deprecated — Bewaard als fallback voor backwards compatibiliteit
Database migraties: 058 (backfill colorToken: 'primary' op buttons met oude hardcoded paarse kleur), 059 (idem, resterende gevallen)
Week 1 (3 apr)
Section titled “Week 1 (3 apr)”Rich Text Editor — Expand Modal & Performance:
- Single editor instance — Eén Tiptap editor die verplaatst wordt tussen canvas en modal (geen sync nodig)
- Expand modal — Sidebar-knop opent tekst in uitklapbare modal met
FixedToolbar+EditorContent - Static HTML tijdens animaties — Canvas en modal tonen statische HTML snapshot tijdens open/close animaties;
EditorContentmount/unmount gebeurt buiten animatieframes (~50ms ProseMirror overhead ontkoppeld van 60fps) - Deferred store writes — Bewerkingen in modal schrijven niet continu naar store; flush gebeurt pas bij sluiten
- Frozen height — Canvas behoudt hoogte wanneer editor naar modal verplaatst (synchrone DOM meting tijdens render)
Modal Component Verbeteringen:
- Focus trap (WCAG 2.1 §2.4.3) — Tab/Shift+Tab cyclet binnen modal, inclusief
[contenteditable]elementen - aria-labelledby via context —
Modalgenereert automatisch eentitleIden deelt deze via React context;ModalHeaderleest en koppelt het ID automatisch (werkt voor alle 17 modal consumers) - Escape stack — Module-level stack zodat alleen de bovenste modal reageert op Escape (gestapelde modals)
- Scroll lock counter — Reference counter voorkomt race conditions bij gestapelde modals
- Exit animatie —
onExitCompletecallback opAnimatePresencevoor deferred cleanup na exit transition - Event isolatie —
stopPropagation()op backdrop mousedown/click voorkomt sidebar clearing
Performance Optimalisaties (RichText):
- Zustand selectors — Individuele selectors i.p.v.
useEditorStore()destructuring; voorkomt re-renders bij ongerelateerde store updates - state.page bail-out — Zustand subscription skipt
getNestedValuetraversal alsstate.pagereferentie ongewijzigd (~80% reductie) - Echo prevention Map — Per-editor write timestamps i.p.v. single globals; veilig bij concurrent editors
- Cleanup op unmount — rAF cancellation,
_richTextActiveCountleak fix, orphanedexpandEditorPathopruiming
Performance Optimalisaties (Toolbar):
- shouldShow module-level — Stabiele functiereferentie voorkomt onnodige BubbleMenu recalculaties
- DOM leak fix —
useBubbleAnimationcleanup roeptpreviousRemove()aan bij unmount tijdens exit-animatie - Dead code —
handleLinkClickvereenvoudigd
Tekst Kleur Auto-detectie:
- Image/video achtergronden —
getTextColorClasses()default nu naar lichte tekst bij afbeelding/video achtergronden; alleen donkere tekst bij lichte overlay met >30% opacity
Overig:
useMemoinuseTextColorClasses— Voorkomt nieuwe object allocatie bij elke render- Lege gradient stops guard — Expliciete check voor lege
stopsarray in gradient detectie
Week 1 (1 apr)
Section titled “Week 1 (1 apr)”Rich Text Editor (Hero Block):
- Tiptap WYSIWYG editor — Hero block titel en subtitel gebruiken nu een Tiptap rich text editor met bubble menu in plaats van plain text
EditableText - Bubble menu — floating toolbar bij tekst selectie: vet, cursief, onderstrepen, doorhalen, link, heading levels (H1-H4), paragraaf, text style presets (Lead, Accent, Klein)
- Link bewerken —
LinkEditModalmet twee tabs: pagina selectie (PagePicker) of vrije URL invoer - Lazy loading — Tiptap dependencies in apart
vendor-editorchunk (~135KB gzipped), geladen viaReact.lazy() - Zustand history integratie — Tiptap’s built-in undo/redo uitgeschakeld, store history is single source of truth
- Backwards compatibel — bestaande plain text content wordt automatisch gewrapped in
<p>tags vianormalizeToHtml() - Sidebar vereenvoudigd — tag en size dropdowns verwijderd uit hero sidebar, heading keuze zit nu in bubble menu
- Vite chunk splitting —
manualChunksomgeschreven naar functie-form (fix voor pnpm hoisting)
Maart 2026
Section titled “Maart 2026”Week 5 (24 mrt)
Section titled “Week 5 (24 mrt)”Fix: Nested <a> tags in preview cards:
usePreviewMode()hook — Public block renderers (hero, buttons, cta, columns) detecteren of ze binnen eenPatternPreviewrenderen en tonen buttons als<span>in plaats van<a>, waardoor ongeldige geneste anchor tags voorkomen worden die React rendering konden crashen
Header Navigatie Configuratie:
- Twee navigatie layouts —
logo-menu-button(Logo—Menu—CTA) enmenu-logo-menu(Menu—Logo—Menu) - CTA Button — configureerbaar via
ButtonEditModaluit de editor, met brand kleuren en stijlen (solid/outline/transparent) - Header Design Configurator — achtergrond (brand kleuren + transparantie), border & schaduw, positie (sticky/static/fixed), hoogte (compact/default/tall), typografie per element
- Hide on scroll — header verdwijnt bij scrollen, verschijnt bij omhoog scrollen. CSS
transition: transform 240ms ease-out - Transparant bij hero — header wordt transparant als eerste block een achtergrondafbeelding/video heeft. Server-side gerenderd (geen JS flash). Tekstkleur past automatisch aan (licht/donker detectie)
- Live preview — sticky browser mockup met
BrowserMockupcomponent (CSSzoomvoor sticky header support). Morph animatie naar full-width via MotionlayoutId. Skeleton → content fade-in - Multi-logo upload — Brand → Elementen: meerdere logo varianten (Primary, Wit, Donker, Icoon). In header config selecteerbaar via
selectedLogoId - Brand kleuren in header —
ColorPresetRowmet shade picker (long-press/hover). Wit + transparant als extra opties - Sites query optimalisatie —
site_domainsjoin op de sites query: primary domain beschikbaar vanaf frame 1 (geen aparte fetch)
Database migraties: 049 (multi-logo), 050 (brand public read RLS), 055 (header nav type + design), 056 (brand logo), 057 (RLS header_menu_left_id)
Week 4 (20-23 mrt)
Section titled “Week 4 (20-23 mrt)”AI Content Generatie:
- Per-block AI generatie — ✨ sparkle knop per block in hover-toolbar, popover met intent input, smart merge (content wijzigen, styling behouden)
- Smart merge strategie — AI wijzigt alleen content (tekst, labels, items). Buttons behouden style/color/colorToken. Afbeeldingen en achtergronden onaangeroerd
- Verfijnen vs vervangen — AI bepaalt automatisch of content aangepast of volledig vervangen wordt op basis van de prompt
- Per-block style context — AI kent layout-instellingen (kolombreedte, achtergrond type, aantal items) per block type en past tekst lengte aan
- Bestaande content als context — Huidige teksten meegestuurd zodat AI kan verfijnen
- Brand Story tab — 5 card-secties op de Merk pagina: bedrijfsinfo, doelgroep, missie/waarden, tone of voice (3 sliders + aanspreekvorm), contactgegevens
- AI Block Schema — Machine-readable beschrijving van alle 7 block types met metadata, richtlijnen en voorbeelden (
packages/shared/src/ai/block-schema.ts) - AI Hero Test — 3 varianten van hero titel + subtitel via
POST /ai/generate-hero - Security hardening — Unicode newline sanitize, Anthropic client key rotation, tool-name check, brand context max 3000 chars, Zod output validatie, IDOR check, 25s timeout
- AI rate limit — 10 req/min per user
- Documentatie —
docs/ai-architectuur.md(dataflow, merge strategie, security),docs/ai-block-schema.md(per-block velden en verbeterpunten)
Settings Herstructurering:
- 4 subpagina’s — Settings opgesplitst in Mijn site (
/settings), SEO (/settings/seo), Domeinnamen (/settings/domains) en Gebruikers (/settings/users) - Mijn site — Site naam, homepage, site taal, afbeeldingen (favicon, touch icon, OG image), zoekmachines, gevarenzone
- SEO — Metadata, titel, social preview (read-only) — nieuw:
SettingsSeoPage.tsx - Domeinnamen — Subdomein + custom domains — nieuw:
SettingsDomainsPage.tsx - Gebruikers — Team management verplaatst van
/usersnaar/settings/users, site-scoped
Site-scoped Team Management (migraties 051-053):
owner_idverwijderd uitteam_membersenteam_invitations—site_idis de primaire scopeget_user_site_ids()vervangtget_team_owner_id()voor site-scoped queries- Bootstrap trigger
add_owner_to_site_teamvoegt owner automatisch toe als team member bij site creatie - Backfill migratie (053) — Alle bestaande sites krijgen owner in
team_membersals dat nog ontbrak
KV + ISR Cache Layer:
- KV namespace
BEAM_CACHEgebonden aan zowel API als public-site - Cache keys:
host:{hostname},site:{siteId},page:{siteId}:{slug},brand-css:{siteId} - Write-through: API schrijft naar KV bij publicatie, public site leest eerst uit KV
- Instant invalidatie bij publicatie (geen timer-based cache)
- ~1-5ms reads vs ~80-150ms DB reads
P0 Security Fixes:
- Unbounded queries —
.limit()toegevoegd aan 8 dashboard queries (domains, patterns, pages, menus, media, media_tags) - Pages PUT mass assignment — Zod validatie schema blokkeert
user_id,site_id,block_count,created_at site_settingsUNIQUE constraint — Voorkomt duplicate rijen per site (was oorzaak favicon bug)
Database fixes:
- UNIQUE constraint op
site_settings.site_id— voorkomt duplicaat rijen bij concurrent inserts
Database migraties: 047 (header nav type), 049-053 (RLS, site-scoped teams, backfill, site_settings UNIQUE)
Multi-site Support:
- Meerdere sites per owner — Max 10 actieve sites per owner, enforced via
check_site_quota()DB trigger - Site switcher — In user dropdown met favicon en sitenaam. Wisselen herlaadt alle site-scoped data
- Site aanmaken — Modal met naam invoer en auto-slug generatie. Bootstrap maakt automatisch settings en brand profile aan
- Soft delete —
DELETE /sites/:idzetdeleted_attimestamp. 14 dagen grace period.hardDeleteExpiredSites()cron ruimt R2, CF custom domains en DB op - Site naam bewerkbaar — In settings pagina, direct opslaan
- Site-scoped data — Alle SWR hooks geparametriseerd op
siteId. Menus en site_settings hebbensite_idkolom. Pages slug UNIQUE per site - Gecentraliseerde auth —
getSessionUserId()inlib/auth.tsvervangt 3 duplicate getUserId helpers - Service layer refactor —
siteIdparameter verplicht op settings, menus, pages service functies. Geen localStorage fallbacks meer
Header Navigation Types + Brand Logo:
- Header nav types —
HeaderNavTypeenHeaderDesigninterfaces in shared types.site_settingsuitgebreid metheader_nav_type,header_menu_left_id,header_button_text/url,header_designkolommen - Brand logo —
logo_urlkolom opbrand_profilestabel
Database migraties: 046 (multi-site), 047 (header nav type + menus/settings site scoping), 048 (brand logo), 049-050 (RLS policies)
Design Token Systeem Optimalisatie:
Design Token Systeem Optimalisatie:
- Variable fonts — Self-hosted statische fonts (62 bestanden, 3 weights per font) vervangen door 19 variable fonts + 3 statische fallbacks (Poppins, Lato, Proza Libre). Alle weights 100-900 correct gerenderd.
<link rel="preload">elimineert FOUT. CSP blijftfont-src 'self' - Shade preview — Kleurvarianten sectie in Colors tab met OKLCH shade scale (50-900) per brand kleur, click-to-copy
- BrandColorToken shades — Type uitgebreid met shade varianten (
primary-50t/maccent-900). Contrast vars voor alle 30 shades. Blocks kunnen shade varianten gebruiken als achtergrondkleur - Shade picker in editor — Expandable shade strip in block color settings na klik op brand kleur
- Performance fixes —
generateShadeScale()memoization met input validation, SSR brand CSS fetch timeout (3s), shade CSS vars in realtime sync
Brand Identity UI Uitbreiding:
- 7 brand kleuren — BrandColors uitgebreid met
neutralensurfacenaast primary, secondary, accent, textDark, textLight. Alle 6 mood presets bijgewerkt met unieke neutral/surface waarden - Vrije kleurkiezer — ColorInput component met native color picker + hex text input per kleur. Vervangt de oude preset-only selectie
- WCAG contrast badges — Client-side
checkWcagContrast()per kleurveld met inline pass/fail badge (ratio + level). Suggestie voor accessible alternatieven bij te laag contrast - Typography fijnafstelling — Heading weight/letter-spacing/line-height sliders, body weight/line-height sliders, type scale selector (compact/standaard/dramatisch)
- Export tab — 3 export kaarten (CSS Custom Properties, W3C Design Tokens JSON, Tailwind v4 @theme) met kopieer-naar-klembord en open-in-browser
- Versiegeschiedenis modal — Snapshot opslaan (label input), versie-lijst met relatieve timestamps, restore met audit logging
- Accessibility hardening — WAI-ARIA tabs pattern (keyboard nav, aria-selected, tabpanel), alle form controls met labels, aria-live regions voor toasts en status updates, aria-hidden op decoratieve iconen, ContrastBadge als role=“status”
- Security audit fixes —
safeFontNameregex uitgebreid met(, dubbele--brand-surfaceCSS var fix,generateDarkModeTokensdeduplicatie, version restore audit log
Eerdere features deze week:
- Semantic token laag — Automatisch afgeleide tokens:
--brand-surface,--brand-border,--brand-text-muted,--brand-on-primary,--brand-danger/success/warning/info. Semantic tokens in CSS + W3C JSON + dark mode overrides. - OKLCH shade generation —
generateShadeScale()genereert 50-900 kleurschaal per brand kleur via OKLCH kleurruimte. - Dark mode auto-generatie —
@media (prefers-color-scheme: dark)block in brand CSS met automatisch berekende dark mode tokens via OKLCH. - WCAG AA contrast checking —
checkWcagContrast(),getContrastRatio(),suggestAccessibleColor()in shared. API endpointGET /brand/:siteId/contrast. - W3C Design Token JSON export —
GET /brand/:siteId/tokensin W3C Design Tokens draft spec format. - Tailwind v4 @theme export —
GET /brand/:siteId/tokens/tailwind. - Token versioning —
token_versionstabel met auto-snapshot trigger.SECURITY DEFINERmetSET search_pathhardening. - Brand color tokens —
BrandColorTokentype voor achtergronden en buttons metvar(--brand-{token})CSS custom properties. - Self-hosted Google Fonts — 21 brand font families als self-hosted woff2 (19 variable + 3 static fallbacks), CSP
font-src 'self'only. - Brand realtime sync —
useBrandRealtimeSync()hook: BroadcastChannel + Supabase Realtime. - CF Image Resizing fix — Non-R2 URLs routeren door
media.builtwithbeam.com. - Font FOUT fix — Lazy font loading via IntersectionObserver, canvas opacity transitie.
- Vision docs alignment — 03-design-tokens.md consistent gemaakt met 01/02
Week 3 (17-19 mrt)
Section titled “Week 3 (17-19 mrt)”- Documentatie consolidatie — Alle docs samengevoegd in Starlight docs site met 7 secties + vision
- Router worker —
docs.builtwithbeam.comroutering naar docs Pages project - Tech debt opgelost — Alle openstaande tech debt items afgehandeld
- E2E test documentatie — CLAUDE.md en TECHNICAL_OVERVIEW.md bijgewerkt
Week 2 (10-16 mrt)
Section titled “Week 2 (10-16 mrt)”- Layered Context Architecture — 4-lagen systeem: hooks, rules, agents, context files
- Project agents — beam-reviewer, beam-block-builder, beam-migration, beam-api-builder, beam-preflight
- Scaffold script —
init-claudealias voor herbruikbare project setup
Week 1 (1-9 mrt)
Section titled “Week 1 (1-9 mrt)”- Fase 3 voltooid — Vite SPA dashboard op CF Pages
- ESLint cleanup — 172 warnings naar 0
- Sentry integratie — Error monitoring over alle 3 apps
- E2E tests — 21 Playwright tests (auth, pages, editor, media, smoke)
- Rate limiting — Sliding window per user (100/min general, 20/min uploads)
- Structured logging — JSON logs met request IDs
- Blur placeholders — LQIP via CF Image Resizing
Februari 2026
Section titled “Februari 2026”- Fase 2 voltooid — Hono API op CF Workers
- 8 route modules (pages, patterns, media, team, domains, stock-photos, cache, health)
- Bearer token auth + rate limiting
- R2 media pipeline met deduplicatie
- Team management met rollen en uitnodigingen
- Custom domains via CF Pages Custom Domains API
Januari 2026
Section titled “Januari 2026”-
Fase 1 voltooid — Astro publieke site op CF Pages
- 8 server-rendered block components
- Host-based routing voor custom domains
- CF Image Resizing voor responsive afbeeldingen
- CSP headers en XSS sanitisatie
- Performance: ~4KB JS, inline CSS, 103 Early Hints
-
Fase 0 voltooid — Security hardening
- RLS op alle tabellen
- SSRF preventie op externe media URLs
- XSS preventie via DOMPurify
- CSP headers op publieke site
- Bootstrap trigger voor nieuwe gebruikers