Formele beslissingen met context, overwegingen en gevolgen. Volledige documenten in docs/beslissingen/.
| # | Beslissing | Status | Detail |
|---|
| ADR-001 | Vercel naar Cloudflare | Besloten | Lees meer |
| ADR-002 | Next.js naar Vite SPA | Besloten | Lees meer |
| ADR-003 | Server Actions naar Hono API | Besloten | Lees meer |
| ADR-004 | CF for SaaS naar CF Pages Custom Domains | Besloten | Lees meer |
| ADR-005 | ESLint 10 Flat Config | Besloten | Lees meer |
| ADR-006 | Layered Context Architecture | Besloten | Lees meer |
| ADR-007 | Text Styles Systeem (granulaire typografie per element-type) | Besloten | Lees meer |
| ADR-008 | KV Cache voor Publieke Site (ISR) | Besloten | Lees meer |
| ADR-009 | Embla Carousel als standaard carousel-engine | Vervangen door ADR-010 | Lees meer |
| ADR-010 | Swiper als standaard carousel-engine | Besloten | Lees meer |
| ADR-011 | Gedeeld carousel-optiemodel (customCursor · cardsPerView · dots) | Besloten | Lees meer |
| # | Vraag | Nodig voor | Impact |
|---|
| OQ-1 | Onboarding app: aparte Vite app of route binnen dashboard? | Vision Fase 1 | Architectuur + deploy |
| OQ-2 | Supabase: gedeeld of apart project voor onboarding? | Vision Fase 1 | Database + auth |
| OQ-3 | AI model: Claude (voorkeur) of configureerbaar? | Vision Fase 3 | API kosten + vendor lock |
| OQ-4 | Style Packs: hardcoded seed data of admin-interface? | Vision Fase 4 | Data management |
| OQ-5 | Addon public site rendering: middleware of file-based routing? | Vision Fase 4 | Astro architectuur |
| OQ-6 | Stockfoto’s in Asset Packs: API bij activatie of vooraf R2 cache? | Vision Fase 7 | Storage kosten |
| OQ-7 | Achievements: beam_events tabel of CF Queue? | Vision Fase 5 | Infra keuze |
| OQ-8 | Directory screenshots: Puppeteer of CF Browser Rendering API? | Vision Fase 6 | Vendor + kosten |
| OQ-9 | Pricing: per addon, per vertical pack, of flat-rate per tier? | Voor launch | Business model |
| OQ-10 | GDPR: welke data is exporteerbaar/verwijderbaar? | Voor launch | Juridisch |
| # | Item | Status | Detail |
|---|
| 1 | Error monitoring (Sentry) | Voltooid | |
| 2 | E2E tests (Playwright) | Voltooid | |
| 3 | Staging environment | Open | |
| 4 | Pages PUT mass assignment fix | Open | Zod schema toevoegen aan apps/api/src/routes/pages.ts — blokkeert overschrijven van user_id, site_id, created_at. Zie [Security Audit #3] |
| 5 | Unbounded queries — .limit() toevoegen | Open | getPages(), getMenus(), getMenusWithCounts(), updateLinkedButtonHrefs(), patterns usage endpoint. Max 500 rijen |
| # | Item | Status / Aanpak |
|---|
| 6 | Database backup verificatie | Maandelijkse restore-test naar staging |
| 7 | Database backup documentatie | Open — Documenteer Supabase backup config, PITR status, R2 lifecycle rules in docs/ |
| 7b | Disaster recovery procedures | Open — Documenteer RTO/RPO, recovery stappen, rollback procedures |
| 8 | Analytics (Plausible/PostHog) | Privacy-first events: page views, editor sessions, media uploads |
| 9 | Rate limiting | Voltooid |
| 10 | Structured logging | Voltooid |
| 11 | Blur placeholders | Voltooid |
| 12 | Env var fail-fast validatie | Open — Voeg check toe in apps/dashboard/src/lib/supabase.ts voor ontbrekende VITE_* vars |
| 13 | Session expiry redirect | Open — Handle SIGNED_OUT event in auth-context.tsx met redirect naar /login |
| 14 | FOUT (Flash of Unstyled Text) | Open — Font preload werkt niet op subdomein-sites. Check <link rel="preload"> in public site [...slug].astro |
| 14b | CI db:advisors check | Open — 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 |
| 14c | ApiError.message leak in error-UI | Open — 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. |
| 14d | Decline-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). |
| # | Item | Aanpak |
|---|
| 15 | Brand Export tab | Design tokens exporteren als CSS custom properties, Tailwind v4 theme, en W3C Design Tokens JSON |
| 16 | Page version history | page_versions tabel, max 10 per pagina, restore UI |
| 17 | Collaborative editing lock | Supabase Realtime presence: “X bewerkt deze pagina” |
| 18 | Billing (Stripe) | Plan tiers, quota enforcement, webhooks |
| 19 | GDPR compliance | Data export, account deletion cascade, privacy policy, cookie consent |
| 20 | API documentatie | @hono/zod-openapi of handmatige OpenAPI YAML |
| 21 | Site restore (undo soft-delete) | UI voor het herstellen van verwijderde sites binnen 14-dagen grace period |
| 22 | getSession() → getUser() migratie | Project-breed: vervang getSession() door getUser() in security-kritieke flows (6+ bestanden). RLS vangt het server-side op, maar client-side is niet geverifieerd |
| 23 | Patterns tab met presets | Patterns-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) |
| 24 | Platform admin site management UI | DB-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. |
| 25 | Host-cache invalidate bij slug-rename | apps/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.
| Fase | Trigger | Upgrade | Effort | Impact |
|---|
| S1 | Nu (< 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 reads | 2-3 dagen | TTFB ~100ms → ~1-5ms, DB load -95% |
| S2 | ~50 sites | Billing (Stripe) — Plan tiers, quota enforcement via Stripe webhooks, metered billing | 2-3 weken | Revenue, quota enforcement |
| S3 | ~100 sites | CF KV site-level cache tags — Purge per site i.p.v. globaal. Fijnmazige invalidatie | 1-2 dagen | Snellere purge, minder cache misses |
| S4 | ~200 sites | Durable Objects voor quota + rate limiting — Gedeelde state tussen Workers isolates. Exacte counters per site/user voor storage, pages, team size | 1-2 weken | Exacte billing metering, geen race conditions |
| S5 | ~200 sites | Neon Postgres migratie — Serverless Postgres met HTTP driver, read replica voor public site, database branching voor staging | 2-3 weken | Connection pooling, read replica latency, gratis staging |
| S6 | ~300 sites | PartyKit realtime — Edge-native realtime (vervangt Supabase Realtime). Per-page rooms, presence, block-level locking | 2-3 weken | Collaborative editing, edge latency |
| S7 | ~500 sites | CF Queues voor async taken — Media processing, bulk export, email campagnes, webhook delivery. Dead letter queue + retry | 1-2 weken | Lagere API response times, betere reliability |
| S8 | ~500 sites | Image pre-generation pipeline — Upload → Queue → genereer 8 srcSet variants (AVIF + WebP) → R2. Geen on-the-fly resize meer | 1-2 weken | Lagere 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 |
|---|
| 22b | Proactieve monitoring/alerting |
| 23 | Multi-language dashboard (i18n) |
| 24 | Analytics dashboard (bezoekerstatistieken per klant-site) |
| 25 | React Native mobile app |
| 26 | sessionStorage als auth storage adapter (i.p.v. localStorage, veiliger bij XSS) |
| 27 | Background color upgrade — mesh/grain optie |
| 28 | Visual regression tests voor blocks — Playwright screenshots op key block × variant combinaties (cardStyle × quoteMark × background type). Automatisch vergeleken in CI. |
| 29 | Import van Trustpilot/Google reviews — automatisch testimonials importeren via externe review-API. Mapping naar TestimonialItem schema in API Worker. |
| 30 | compactLabelField override voor repeater compact-labels — betere UX voor review-repeaters: toon auteursnaam als compact-label i.p.v. generiek “Item 1”. |
| 31 | Testimonials-block — brand-token voor rating-kleur — review-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. |
| 32 | Testimonials-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. |
| 33 | Testimonials-block — defaultTestimonial() helper hergebruiken — apps/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. |
| 34 | Testimonials-block — escape verbetering title-escape tools — escapeHtml() in packages/shared/src/testimonials/classes.ts inlinen in een algemene shared-helper als meer Astro-blocks dynamische heading tags krijgen. |
| 35 | Testimonials-block — BlockRegistry interface drift — apps/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. |
| 36 | Testimonials-block — E2E pageIds array opruimen — e2e/blocks-testimonials.spec.ts heeft een pageIds accumulator die niet gebruikt wordt in de cleanup. Verwijderen of koppelen aan ID-based delete. |
| 37 | Testimonials-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. |
| 38 | Testimonials-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. |
| 39 | Sidebar — 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. |