Skip to content

Backlog & Beslissingen

Formele beslissingen met context, overwegingen en gevolgen. Volledige documenten in docs/beslissingen/.

#BeslissingStatusDetail
ADR-001Vercel naar CloudflareBeslotenLees meer
ADR-002Next.js naar Vite SPABeslotenLees meer
ADR-003Server Actions naar Hono APIBeslotenLees meer
ADR-004CF for SaaS naar CF Pages Custom DomainsBeslotenLees meer
ADR-005ESLint 10 Flat ConfigBeslotenLees meer
ADR-006Layered Context ArchitectureBeslotenLees meer
ADR-007Text Styles Systeem (granulaire typografie per element-type)BeslotenLees meer
ADR-008KV Cache voor Publieke Site (ISR)BeslotenLees meer
ADR-009Embla Carousel als standaard carousel-engineVervangen door ADR-010Lees meer
ADR-010Swiper als standaard carousel-engineBeslotenLees meer
ADR-011Gedeeld carousel-optiemodel (customCursor · cardsPerView · dots)BeslotenLees meer
#VraagNodig voorImpact
OQ-1Onboarding app: aparte Vite app of route binnen dashboard?Vision Fase 1Architectuur + deploy
OQ-2Supabase: gedeeld of apart project voor onboarding?Vision Fase 1Database + auth
OQ-3AI model: Claude (voorkeur) of configureerbaar?Vision Fase 3API kosten + vendor lock
OQ-4Style Packs: hardcoded seed data of admin-interface?Vision Fase 4Data management
OQ-5Addon public site rendering: middleware of file-based routing?Vision Fase 4Astro architectuur
OQ-6Stockfoto’s in Asset Packs: API bij activatie of vooraf R2 cache?Vision Fase 7Storage kosten
OQ-7Achievements: beam_events tabel of CF Queue?Vision Fase 5Infra keuze
OQ-8Directory screenshots: Puppeteer of CF Browser Rendering API?Vision Fase 6Vendor + kosten
OQ-9Pricing: per addon, per vertical pack, of flat-rate per tier?Voor launchBusiness model
OQ-10GDPR: welke data is exporteerbaar/verwijderbaar?Voor launchJuridisch
#ItemStatusDetail
1Error monitoring (Sentry)Voltooid
2E2E tests (Playwright)Voltooid
3Staging environmentOpen
4Pages PUT mass assignment fixOpenZod schema toevoegen aan apps/api/src/routes/pages.ts — blokkeert overschrijven van user_id, site_id, created_at. Zie [Security Audit #3]
5Unbounded queries — .limit() toevoegenOpengetPages(), getMenus(), getMenusWithCounts(), updateLinkedButtonHrefs(), patterns usage endpoint. Max 500 rijen
#ItemStatus / Aanpak
6Database backup verificatieMaandelijkse restore-test naar staging
7Database backup documentatieOpen — Documenteer Supabase backup config, PITR status, R2 lifecycle rules in docs/
7bDisaster recovery proceduresOpen — Documenteer RTO/RPO, recovery stappen, rollback procedures
8Analytics (Plausible/PostHog)Privacy-first events: page views, editor sessions, media uploads
9Rate limitingVoltooid
10Structured loggingVoltooid
11Blur placeholdersVoltooid
12Env var fail-fast validatieOpen — Voeg check toe in apps/dashboard/src/lib/supabase.ts voor ontbrekende VITE_* vars
13Session expiry redirectOpen — Handle SIGNED_OUT event in auth-context.tsx met redirect naar /login
14FOUT (Flash of Unstyled Text)Open — Font preload werkt niet op subdomein-sites. Check <link rel="preload"> in public site [...slug].astro
14bCI db:advisors checkOpen — Voeg pnpm db:advisors toe aan CI workflow. Flag automatisch nieuwe USING (true) policies of niet-RPC anon SELECT-routes (zie .claude/rules/security.md#anon-select-pattern). Voorkomt herhaling van het info-lek dat in migraties 074-077 is gefixed
14cApiError.message leak in error-UIOpen — Op ~15-25 plekken in apps/dashboard/src/pages/*.tsx + components/*.tsx wordt err instanceof ApiError ? err.message : 'Algemene tekst' direct in een toast of inline UI getoond. ApiError.message is de raw error-string uit de API JSON response en kan PII bevatten (bv. “Log in met jan@example.com om deze uitnodiging te accepteren” lekt het uitnodigingsemail). Voorbeeld: apps/dashboard/src/pages/InviteAcceptPage.tsx:81 en :103. Fix: introduceer formatApiError(err): string helper met whitelist per HTTP-status (404 / 403 / 408 / 5xx → vooraf-vertaalde NL melding); voor onbekende codes generieke fallback. Of: API ApiError-payload uitbreiden met userMessage veld dat server expliciet zet.
14dDecline-status: 'revoked''declined'Open — apps/api/src/routes/team.ts:558 /invitations/decline schrijft status: 'revoked' i.p.v. een aparte 'declined' status. UI behandelt revoke (door owner ingetrokken) en decline (door uitgenodigde geweigerd) als hetzelfde — verkeerde feedback voor analytics én voor de owner (“Petra heeft je invite ingetrokken” vs “Petra heeft je invite afgewezen”). Pre-existing sinds migratie 010. Vereist: (1) check CHECK constraint op team_invitations.status of 'declined' toegelaten is, anders migratie; (2) endpoint update; (3) UI-tekst differentiatie; (4) optioneel backfill van bestaande “revoked” rijen die eigenlijk decline waren (verwaarloosbaar bij lage volumes).
#ItemAanpak
15Brand Export tabDesign tokens exporteren als CSS custom properties, Tailwind v4 theme, en W3C Design Tokens JSON
16Page version historypage_versions tabel, max 10 per pagina, restore UI
17Collaborative editing lockSupabase Realtime presence: “X bewerkt deze pagina”
18Billing (Stripe)Plan tiers, quota enforcement, webhooks
19GDPR complianceData export, account deletion cascade, privacy policy, cookie consent
20API documentatie@hono/zod-openapi of handmatige OpenAPI YAML
21Site restore (undo soft-delete)UI voor het herstellen van verwijderde sites binnen 14-dagen grace period
22getSession()getUser() migratieProject-breed: vervang getSession() door getUser() in security-kritieke flows (6+ bestanden). RLS vangt het server-side op, maar client-side is niet geverifieerd
23Patterns tab met presetsPatterns-tab in de block picker/editor uitbreiden met preset-varianten per block type (bv. Reviews Grid / Featured / Carousel, Pricing 3-tier / 2-tier highlight, Hero center / left / split). Presets hergebruiken de BlockPickerEntry.preset infrastructuur uit blocks.ts en tonen thumbnail + beschrijving per variant. Eén block-type, meerdere start-configuraties. Later uitbreidbaar met categorieën (Marketing / Social proof / Media / Conversion)
24Platform admin site management UIDB-laag is gedekt via migratie 080 — platform_admins hebben volledige CRUD bypass op alle site-scoped tabellen (sites, site_domains, site_settings, brand_profiles, menus, menu_items, pages, patterns, media). Wat nog moet: een expliciete /admin/sites dashboard view met alle sites, owner/team-stats, transfer-ownership en force-delete acties. Plus: site-switcher tonen onbeperkt voor platform_admins. Achter een platform_admin gate.
25Host-cache invalidate bij slug-renameapps/dashboard/src/lib/supabase/sites.ts heeft een updateSiteSlug(siteId, slug) functie die nu dead code is (nergens geïmporteerd). Wanneer ooit aangesloten op een UI: huidige api.cache.invalidate(siteId) ruimt site:* / brand-css:* / page:* op, maar NIET host:{old-slug}.builtwithbeam.com — die blijft tot zijn TTL (24u) staan. apps/api/src/lib/kv-cache.ts heeft al invalidateHostsForSite(kv, siteId, siteSlug). Vereist: (1) /cache/invalidate API-route uitbreiden met optionele oldSlug parameter die hosts mee invalideert; (2) updateSiteSlug past die aan; (3) of: updateSiteSlug helemaal verwijderen als slug-rename feature nooit user-facing wordt.

Roadmap voor het opschalen van Beam naar 500+ sites. Elke fase wordt getriggerd door groei, niet door planning.

FaseTriggerUpgradeEffortImpact
S1Nu (< 50 sites)KV voor site config + ISR pattern — CF KV als edge cache voor site config, brand tokens en gerenderde page data. Elimineert DB calls voor public site reads2-3 dagenTTFB ~100ms → ~1-5ms, DB load -95%
S2~50 sitesBilling (Stripe) — Plan tiers, quota enforcement via Stripe webhooks, metered billing2-3 wekenRevenue, quota enforcement
S3~100 sitesCF KV site-level cache tags — Purge per site i.p.v. globaal. Fijnmazige invalidatie1-2 dagenSnellere purge, minder cache misses
S4~200 sitesDurable Objects voor quota + rate limiting — Gedeelde state tussen Workers isolates. Exacte counters per site/user voor storage, pages, team size1-2 wekenExacte billing metering, geen race conditions
S5~200 sitesNeon Postgres migratie — Serverless Postgres met HTTP driver, read replica voor public site, database branching voor staging2-3 wekenConnection pooling, read replica latency, gratis staging
S6~300 sitesPartyKit realtime — Edge-native realtime (vervangt Supabase Realtime). Per-page rooms, presence, block-level locking2-3 wekenCollaborative editing, edge latency
S7~500 sitesCF Queues voor async taken — Media processing, bulk export, email campagnes, webhook delivery. Dead letter queue + retry1-2 wekenLagere API response times, betere reliability
S8~500 sitesImage pre-generation pipeline — Upload → Queue → genereer 8 srcSet variants (AVIF + WebP) → R2. Geen on-the-fly resize meer1-2 wekenLagere CF Image Resizing kosten, snellere delivery

Geschatte infra-kosten bij 500 sites: ~€210/mo (CF Workers $25, R2 $4, KV $5, DO $10, Queues $5, Neon $69, Supabase Auth $25, PartyKit $20, Resend $20, Sentry $26)

Principe: Upgrade pas wanneer de groei het rechtvaardigt. De huidige stack draagt 100+ sites zonder wijzigingen.

#Item
22bProactieve monitoring/alerting
23Multi-language dashboard (i18n)
24Analytics dashboard (bezoekerstatistieken per klant-site)
25React Native mobile app
26sessionStorage als auth storage adapter (i.p.v. localStorage, veiliger bij XSS)
27Background color upgrade — mesh/grain optie
28Visual regression tests voor blocks — Playwright screenshots op key block × variant combinaties (cardStyle × quoteMark × background type). Automatisch vergeleken in CI.
29Import van Trustpilot/Google reviews — automatisch testimonials importeren via externe review-API. Mapping naar TestimonialItem schema in API Worker.
30compactLabelField override voor repeater compact-labels — betere UX voor review-repeaters: toon auteursnaam als compact-label i.p.v. generiek “Item 1”.
31Testimonials-block — brand-token voor rating-kleurreview-card.css hardcodet #f59e0b voor sterren. Vervangen door var(--brand-accent, #f59e0b) zodra een dedicated brand-accent token bestaat of per user-preference instelbaar is.
32Testimonials-block — dark mode card styling.review-card--card.review-card--dark gebruikt #ffffff. Support voor prefers-color-scheme: dark toevoegen zodra dark-mode-first sites in scope komen.
33Testimonials-block — defaultTestimonial() helper hergebruikenapps/dashboard/src/lib/blocks.ts bevat bijna-duplicate inline defaults voor featured-testimonial en testimonials-grid. Consolideren naar één helper zodat nieuwe defaults één plek hebben.
34Testimonials-block — escape verbetering title-escape toolsescapeHtml() in packages/shared/src/testimonials/classes.ts inlinen in een algemene shared-helper als meer Astro-blocks dynamische heading tags krijgen.
35Testimonials-block — BlockRegistry interface driftapps/dashboard/src/lib/types.ts BlockRegistry mist benoemde keys voor testimonials-grid, featured-testimonial, testimonials-carousel, faq, columns. Index-signature vangt het, maar benoemde keys geven IDE-hints.
36Testimonials-block — E2E pageIds array opruimene2e/blocks-testimonials.spec.ts heeft een pageIds accumulator die niet gebruikt wordt in de cleanup. Verwijderen of koppelen aan ID-based delete.
37Testimonials-block — quote-mark font-family.review-quote-mark--classic gebruikt Georgia, serif. Overwegen om brand-font te gebruiken zodra een typography-token voor display-type bestaat.
38Testimonials-block — Swiper resize-transition — Grid-carousel herinit nu correct bij mobile→desktop. Bij desktop→mobile opnieuw wordt de observer opnieuw ge-armed maar dit kan strakker: één centrale media-query-watcher per block zodat we niet steeds IO opzetten. Nu acceptabel.
39Sidebar — addon-driven items layout-shift onderzoek — De Events-link in de sidebar verschijnt op sommige page-loads met visuele shift volgens gebruiker (Addons-positie wijzigt mee). Architectuur is theoretisch correct: useSyncExternalStore over localStorage cache in lib/hooks/use-events-active.ts, prefetch in preloadDashboardAfterSignIn, conditional rendering in sidebar. PerformanceObserver rapporteert CLS=0.0000 in alle drie geteste scenario’s (warm/cold/stale cache) op Playwright-test, maar gebruiker rapporteert nog steeds visuele beweging in eigen browser. Mogelijke oorzaken om verder te onderzoeken: (a) iets anders dan Events-row dat shift veroorzaakt (sidebar logo / site-switcher / avatar laadt op ander moment), (b) browser-cache verzet oude bundle, (c) specifieke browser/network setup. Volgende stap: gebruiker DevTools Performance recording laten maken en CLS-trigger element identificeren.