Taken
Historisch werklog. Open werk staat in Linear, team Okhema. Begin bij die issue, niet bij het volgende open vinkje hier. Eén Linear-issue per sessie. Na afloop: Linear bijwerken (status + bewijs) en hier afvinken als het een genummerde fasetaak is. Regel: begin geen taak uit een volgende fase voordat de huidige fase af is.
Fase 0 — Fundament
Section titled “Fase 0 — Fundament”- 0.1 Monorepo-skelet: pnpm workspaces, Turborepo,
contract/db/blocksals lege packages, tsconfig-base, docker-compose voor Postgres. Astro- en Node-versie verifiëren via npm. - 0.2 Documentatie: PRD, Architectuur, Datamodel, Contract, Taken.
- 0.3 Agent-configuratie:
AGENTS.mdin de root,.cursor/rules/000-project.mdc(always apply), plus scoped rules voorapps/site-template/**enpackages/db/**.
Fase 1 — MVP: één klantsite met eigen CMS
Section titled “Fase 1 — MVP: één klantsite met eigen CMS”- 1.1 Astro-app scaffolden,
astro:envinrichten, Drizzle opzetten, tenant-datamodel bouwen, theming-fundament (design tokens → CSS-variabelen), seed-script. Bewijs: kleur in database wijzigen verandert de site zonder code-aanpassing. - 1.2 Blokkenbibliotheek v1: hero, tekst, afbeelding, CTA, kaartenrij, quote. Elk blok krijgt een Zod-schema, een renderer en een registry-entry.
BlockRendererdie optypede juiste component kiest. - 1.3 Publieke pagina’s: home, over ons, dienstenoverzicht, dienstdetail, nieuwsoverzicht, nieuwsdetail, referenties. Layout, header en footer op basis van
navigation. - 1.4 Contactpagina met formulier: Astro Actions, validatie via Zod, opslag in
form_submissions, e-mailnotificatie, spam-bescherming. - 1.5 SEO-basis: meta-tags uit
seoenseo_defaults, sitemap, robots, Open Graph, canonieke URL’s. - 1.6 Better Auth opzetten: inloggen, sessies, rollen beheerder/redacteur, wachtwoordherstel.
- 1.7 CMS-shell op het
admin-subdomein: routing, layout, navigatie, afscherming via middleware. - 1.8 CMS-CRUD: pagina’s, diensten, nieuws, referenties, navigatie, instellingen. Inclusief
audit_log-registratie bij elke mutatie. - 1.9 Media-upload naar R2 of B2, met verplichte alt-tekst en koppeling aan Astro’s beeldoptimalisatie.
- 1.10 Blok-editor in het CMS: blokken toevoegen, herordenen en bewerken via formulieren. Nog geen drag-and-drop.
- 1.11 Dockerfile voor
site-template, handmatig deployen op Coolify, domein koppelen, backups inrichten.- Keuze gemaakt:
TENANT_IDenSITE_DOMAINzijn runtime (access: "secret"), zodat één image meerdere tenants kan dienen. Astro valideert secrets bij import vanastro:env/servertijdens de build — Dockerfile heeft dus build-time placeholders nodig; echte waarden komen bij container-start. Raakt ook taak 2.9 (template-update naar alle klanten). - Code klaar:
.dockerignore,apps/site-template/Dockerfile(lokaal healthy smoketest),migrate.mjsals Coolify pre-deployment command, Deploy + Backups. - Live sinds 12 augustus 2026:
pilot.okhema.studioenadmin.pilot.okhema.studio, TLS bij Traefik (Cloudflare-proxy grijs). Postgres: twee Coolify-planningen oppostgres:18—0 3 * * *UTC dump-all als vangnet,30 3 * * *UTC per-database — naar R2platform-backups, 7 lokale kopieën en 35 dagen op S3 plus bucket-lifecycle. Media: host-cron15 3 * * *UTC,rclone syncR2 → B2 met--backup-dirper datum. Maandelijkse dump-probe0 4 1 * *UTC in een wegwerp-Postgres. Nog open: geen alarmering bij een falende cron. Hoort bij 4.4 (noemt mislukte backups letterlijk); 2.2 en 2.3 leveren de plumbing. De stamp-bestanden onder/var/lib/media-backupen/var/lib/dump-verifyzijn wat een statuspagina straks kan uitlezen.
- Keuze gemaakt:
- 1.12 Eerste echte klant opleveren en de restore-procedure één keer testen.
- Code klaar:
@platform/ops(bootstrap, create-user, backup, restore, verify),tenants/*.json, seed-guard, Operations. Lokale backup→drop→restore→verify tegentenant_demogeslaagd. - Repetitie gedaan (12 aug 2026): vernietig-en-restore op de pilot uit beide
bronnen — operator-backup en Coolify per-database
.dmp. RTO 24 s per stap, gemeten RPO 85 min (schema-worst-case 24 u). Bewijs met verify-output en de drie verrassingen in Restore-rehearsal 12 augustus 2026. - Klantvariant uitvoerbaar gemaakt (13 aug 2026): de veilige variant stond
alleen als proza in OPERATIONS.md en liep op drie punten vast. Nu:
scripts/scratch-verify-db.sh create|drop <id>maakt en dropttenant_<id>_verify(provision-tenant-db.shkon dat niet: de tenant-regex verbiedt_en de databasenaam is hardcoded, en droppen ontbrak);restoreweigert een*_verify-doel zonder--skip-media, want de manifest-keys zijn de live keys en een media-restore overschreef dus de klantbucket;backupleest de rijtellingen uit dezelfdepg_dump-snapshot, zodatcounts.matchniet meer valselijk faalt op een site met verkeer. Aangetoond onder gelijktijdige inserts: oude code291!=298, nieuwe code exact gelijk. - Eerste echte klant live (13 aug 2026):
rb-mediaoprbmedia.okhema.studio+admin.rbmedia.okhema.studio, geldige certificaten, container healthy.verify --httpvolledig groen zónder SKIPs: media bewezen viaHeadObject(content-length == size_bytes) en/_imagelevertwebp, dus de ingebakkenS3_PUBLIC_URLenremotePatternskloppen.grep -rl build-placeholder /app/dist= 0. - Klantvariant uitgevoerd op echte data (13 aug 2026): backup (mét media),
restore in
tenant_rb-media_verifymet--skip-media,verify --expect meta.jsonvolledig groen inclusiefcounts.matchensite.tenant_id, daarna scratch gedropt. De media-guard is onderweg één keer echt getriggerd: restore zónder--skip-mediawerd geweigerd. De live database is nooit aangeraakt. - Val gevonden bij de eerste deploy: Coolify slaat het pre-deployment
commando over als er nog geen container draait (
No running containers found. Skipping.) en meldt tóch “finished”. Schema bleef leeg, container werd healthy omdat/robots.txtde database niet raakt, enbootstrapviel om. Migratie één keer met de hand gedraaid; vastgelegd in Deploy en in stap 3 van de go-live-checklist.
- Code klaar:
Freeze-check (poort naar fase 2)
Section titled “Freeze-check (poort naar fase 2)”Korte go/no-go vóór 2.1. Geen nieuwe fase. Checklist: Freeze-check.
- Freeze-check uitgevoerd 17 augustus 2026. A + D groen; 2.1 mag.
Restpunten in Freeze-check § Uitgevoerd: zelf inloggen op
rb-media CMS, vannacht eerste
tenant_rb-media.dmp, optioneel www-DNS.
Review-fixes fase 1 (10 augustus 2026)
Section titled “Review-fixes fase 1 (10 augustus 2026)”Uit Review-fixes; geen taak afgevinkt, alleen bestaand werk hersteld.
- A1
CMS_SECTION_PREFIXESwordt nu afgeleid uitcmsNavItems, zodat sidebar en routing niet meer uit elkaar kunnen lopen. (/mediastond er al in sinds 1.10.) - A2
safeRedirectPath()inlib/safe-redirect.tsweert protocol-relatieve paden; server- en clientkant vanlogin.astrogebruiken dezelfde helper. Losse module en niethosts.ts, omdat dieastro:env/serverimporteert. - A3
deleteMediablokkeert nu met eenCONFLICTals een blok inpages/services/newsnog naar de media verwijst, en noemt waar. - A4 Statische assets slaan de sessielookup over;
/_actionsexpliciet niet. - A5 Postgres
23503(foreign key) geeftCONFLICTin plaats van 500.throwIfUniqueViolationheet nuthrowIfConstraintViolation. - A6
/diensten,/nieuwsen/referentiesstaan alleen in de sitemap als er minstens één item is. - B1
theme.logo.mediaIdrendert inHeader.astro, met de sitenaam als fallback en alsalt. Het dark-logoveld is uit het formulier gehaald; de opgeslagen waarde blijft behouden. - B2
--font-scalestuurt de root-fontsize inglobal.css,--spacing-baseis gemapt op Tailwinds--spacing. - B3
requireCmsAdminbeschermtupdateSiteSettingsenupdateFormRecipient; de sectie Instellingen is voor redacteuren verborgen en de pagina redirect. Content blijft voor beide rollen open. Nagekomen: het dashboard bouwde zijn kaarten uit een eigen lijst en toonde redacteuren daardoor alsnog een kaart naar Instellingen. De kaarten komen nu uitcmsNavItemsFor(), dezelfde bron als de sidebar. - C Niet gewijzigd, alleen genoteerd bij taak 1.11.
Fase 2 — Master dashboard en provisioning
Section titled “Fase 2 — Master dashboard en provisioning”Niet beginnen vóór de freeze-check hierboven groen is.
- 2.1 Nuxt-app scaffolden, master-datamodel, Better Auth voor beheerders.
- Plan: Taakplan 2.1 (vastgestelde versies, keuzes, stappen, bewijs).
- Versies vastgesteld (17 augustus 2026): Nuxt 4.5.2 (Nitro 2.13.4, Vite
8.2.1, Vue 3.5.41, vue-router 5.2.0), h3 1.15.11 dus
toWebRequestvoor de auth-handler. better-auth 1.6.26, gelijk aan@platform/db— 1.6.29 bestaat, pin bewust niet gebumpt. Tailwind 4.3.3 via@tailwindcss/vite(peer accepteert Vite 8).nuxt typecheckgroen met vue-tsc 3.3.10 + TypeScript 6.0.3, dezelfde TS-pin alssite-template. Node lokaal 22.23.2, rootenginesnu>=22.19.0(blijft geldig voor denode:22-images). - Master-datamodel:
@platform/db/masterals subpath-export, niet als nieuw package en niet in de app — het eerste account komt uit een ops-CLI, dus zowel de Nuxt-app als@platform/opsheeft het schema nodig. Negen tabellen indrizzle-master/0000_messy_lifeguard.sql, eigen config en eigenMASTER_DATABASE_URL. Aangetoond: tenant-migraties bevatten géén master-tabellen endrizzle-kit checkop de tenant-config blijft groen. - Afwijkingen van Datamodel (document bijgewerkt):
database_urlheetdatabase_url_encrypted, en er is géénadmin_users-tabel ofrole-kolom — de Better Authusers-tabel is de beheerderstabel. - Afwijking van het plan: de AES-256-GCM-helper staat in
apps/master-dashboard/server/utils/secrets.tsen niet inpackages/db, wantdatabase.mdcstaat daar geen niet-databasecode toe. Roundtrip, willekeurige iv, manipulatiedetectie en key-lengtecontrole handmatig geverifieerd. Nog niet in gebruik: 2.1 schrijft geen tenant-rijen. - Auth: Better Auth zonder admin-plugin en zonder rollen; publieke signup uit
(bewezen: 400
EMAIL_PASSWORD_SIGN_UP_DISABLED); geen wachtwoordherstel per mail, de CLI doet dat.create-adminweigert een tenant-database (site-tabel aanwezig) en een ongemigreerde database, beide messages geverifieerd. - Bewijs (17 augustus 2026): dev-server én productiebuild getest. Sign-in 200
met cookie,
/api/sessiongeeft de gebruiker,/302 →/loginzonder sessie,/login302 →/met sessie, fout wachtwoord 401. Sessie overleeft een herstart van de server, dus hij staat in de database. Geen secret uit.envterug te vinden in.output. Regressie: turbo typecheck 6/6, build 2/2,verifyoptenant_demogroen op de verwachte media-SKIP, en de drietenants/*.jsonvalideren nog na het verplaatsen vantenantSlugSchemanaarplatform.ts. - Nog open:
verify --httpop pilot en rb-media is niet gedraaid — dat vraagt de SSH-tunnel en productie-secrets. Master-deployment en de backuplijst staan bij de losse punten.
- 2.2 Platform-endpoints op de klantsite (
/health,/stats,/revalidate) plus webhook-verzending met HMAC.- Plan: Taakplan 2.2.
- Gebouwd:
packages/contract/src/events.ts(discriminated union optype;tenantIdviatenantSlugSchema). Drie routes onder/api/platform/metX-Platform-KeyenCache-Control: no-store./healthgebruikt een eenmaligepostgres()-client (max: 1,connect_timeout: 2), nietgetDb./statstelt alle rijen oppages/services/newsen neemtmax(greatest(created_at, updated_at))over die drie tabellen./revalidateroeptpurgeWholeSiteaan. Uitgaande webhooks viasendPlatformEvent+signEnvelope(HMAC-SHA256 overtimestamp + "." + body).PLATFORM_WEBHOOK_URLleeg is een no-op. Een mislukte fetch faalt de CMS-actie niet.site.errorstaat op het schema, heeft geen call site. - Login-emit: hook in
createAuth()(hooks.after+ctx.context.newSession). better-auth 1.6.26 types in@better-auth/coreexposen beide.[...all].tsblijft stuffing-limiter plus handler. - Bewust uitgesteld: webhook-ontvangst en idempotentie (2.3), retry-wachtrij,
per-tenant sleutels in de master (2.5),
site.error-verzending. - Afwijking van SKETCH.md:
userIdFromSignInResponseen de wrap van[...all].tsvervallen door de hook.astro:enven de contract-barrel insend-event.tszijn dynamische imports zodat de geïsoleerdesignEnvelope-test in Node kan draaien. HMAC blijft insend-event.ts, niet in@platform/contract(geen crypto in dat package).PlatformEventInputis een distributieveOmit, anders houdt TypeScript alleen de gemeenschappelijke keys van de union over.requirePlatformKeyreturned een 401-Response; een throw werd in Astro 7.2 een HTTP 500.
- 2.3 Webhook-ontvangst op de master, met idempotentie en retry-afhandeling.
- Plan: Taakplan 2.3.
- Gebouwd:
POST /api/webhooks/site(HMAC over de ruwe body, timestamp ±5 min / toekomst +60 s,platformEventEnvelopeSchema).webhook_receiptsin de master (event_id= envelope-UUID). Duplicate is 200 zonder tweedeactivity. Onbekende slug:tenant_idnull.NUXT_PLATFORM_WEBHOOK_SECRETtot 2.5. Site:webhook_outboxin de tenant-database, dezelfdeeventId, drain met exponentiële backoff, max 24 uur. 4xx stopt, 5xx/netwerk retriet. Fire-and-forget blijft. - Bewust uitgesteld: per-tenant secrets (2.5), activiteit-UI (2.4),
site.error-emit, Coolify-deploy van de master,/stats-pull. - Afwijking van het plan:
content.publishedna een geslaagde drain is bewezen viaenqueuePlatformEvent+drainWebhookOutbox(het pad vansendPlatformEvent), niet via een CMS-klik in de browser.
- 2.4 Klantenbeheer: overzicht, aanmaken, detailweergave met status en activiteit.
- Plan: Taakplan 2.4.
- Gebouwd: sessiebeveiligde tenant-API’s (lijst, ledger-insert, detail, statuswijziging), Nuxt-overzicht, aanmaakformulier en detail met activiteit, lege domein/deployment-staten en gedeelde contract-schema’s.
- Backfill: webhook-activiteit en receipts die vóór de tenant-rij
binnenkwamen worden bij aanmaken transactioneel gekoppeld; de nieuwste
eventtijd vult
site_stats.last_activity_at. - Statuskeuze:
quietis een afgeleid aandachtssignaal na 24 uur stilte, geen vijfde database-status. Echtedegradedvraagt ook healthcheck-data en blijft uitgesteld tot provisioning een hostname en platform-key levert. - Bewijs (3 september 2026): typecheck 6/6, build 2/2, 10 helper- en HMAC-tests groen. API: 401 zonder sessie, 400 validatie, 409 duplicate, orphan-backfill inclusief receipt, webhook-idempotentie en geen encrypted URL in responses. Browser: aanmaken → detail → actief → quiet-badge → overzicht en logout-redirect volledig groen.
- Bewust uitgesteld: database/Coolify/env en per-tenant secrets (2.5),
preview/custom domains (2.6/2.7), health-poll, delete en
/stats-pull.
- 2.5 Provisioning-service: tenant-database aanmaken, migraties draaien, Coolify-app aanmaken, env-variabelen zetten, deploy triggeren.
- Opgeleverd:
POST /api/tenants/:slug/provision(sessie) met idempotente stappen database → migrate → secrets → coolifyApp → coolifyEnv → coolifyStart. Per-tenant secrets in*_encrypted. Eerste migratie via tenant-DSN vóór Coolify. Zonder Coolify-token: stop na secrets,tenant.provision_partial, status blijftprovisioning. Coolify-startfout zettenant.provision_faileden statusfailed. Detail-UI: knop Provisioneren bijprovisioning/failed, deployments-lijst. - Lokaal pad:
NUXT_POSTGRES_ADMIN_URLmetCREATEROLE/CREATEDB(of superuser);NUXT_COOLIFY_TOKENleeg houdt de run bij secrets. - Bewust uitgesteld:
bootstrapTenant/ bootstrap-JSON, CMS-user (createCmsUser), Coolify per-database backup-lijst, master-dashboard Coolify-deploy, markeren alsactive. Preview-hostnames zijn 2.6.
- Opgeleverd:
- 2.6 Wildcard preview-URL’s inclusief DNS-01 voor wildcard-TLS.
- Gebouwd:
previewBinding+NUXT_PREVIEW_BASE_HOST(defaultokhema.studio). Eéndomains-rijtype: previewbij provision (ook zonder Coolify). Coolifydomainsop create en PATCH-retry. Detail-UI: publieke URL + afgeleide CMS-host, SSL “In behandeling”. - Operator: wildcard A
*.{previewBase}en Traefik DNS-01 in Deploy.admin.{slug}.{base}valt buiten een één-label-wildcard. - Infrastructuur uitgevoerd (7 september 2026): Cloudflare DNS + DNSSEC, wildcard A-record en wildcardcertificaat via een aparte Traefik DNS-01-resolver. Pilot-routing, publieke preview, CMS-login, publiceren en e-mail zijn gecontroleerd; zie Preview-domeinen.
- Gebouwd:
- 2.7 Custom domains via Cloudflare for SaaS: hostname registreren, DNS-instructies tonen, verificatie pollen, status weergeven.
- Gebouwd:
POST/DELETE /api/tenants/:slug/domains+POST …/verify. View-union opstate(unregistered/pending/active/failed; preview blijft 2.6).cf_verificationjsonb voor ownership-TXT. LeegNUXT_CF_API_TOKENslaatunregisteredop en toont de CNAME. CoolifydomainsdeeltdesiredCoolifyDomainsmet provision (custom zonderadmin.). GeenSITE_DOMAIN-cutover, geenadmin.{custom}bij Cloudflare. - Operator: CNAME naar
NUXT_CF_CNAME_TARGET; apex vraagt flattening. Actief = edge-TLS, CMS blijft op de preview-host. Recept in Deploy.
- Gebouwd:
- 2.8 Migratie-runner over alle tenant-databases, met statusrapportage per database.
- Plan: Taakplan 2.8 (8 september 2026).
- Gebouwd: runner in het master dashboard.
GET /api/migrations(schemastand per tenant, live uitdrizzle.__drizzle_migrationstegen de gebundelde migraties, plus laatste 10 runs),POST /api/migrations/runs({ slugs? }, sequentieel opcreated_at, advisory lock, 409 bij een lopende run, doorgaan na een fout),POST /api/tenants/:slug/database(bestaande database koppelen met naam-, bereikbaarheids- ensite.tenant_id-check; 409 bij overschrijven). Pagina/migrationsmet badges, “Bijwerken” per tenant, “Alles bijwerken” en run-historie; sectie Database op de klantpagina. Tabelmigration_runs(drizzle-master/0004_unusual_mephisto); activitytenant.migrated,tenant.migration_failed,tenant.database_attached. Contract:tenantSchemaStateSchema(no_database/unreachable/blocked/pending/up_to_date),platformMigrationRunSchema,platformTenantDetailSchema.hasDatabase. blockedwordt nooit gemigreerd:ahead(master-image ouder dan site-image),diverged(andere hash of gat in de boekhouding; Drizzle 0.45.2 leesthashzelf nooit terug),identity_mismatch(verkeerde DSN).applyTenantMigrationsheeft nulock_timeout10 s enstatement_timeout5 min als default, ook voor de Coolify-hook.- Bewijs (8 september 2026): typecheck en build 2/2 groen; 81
master-tests groen (8 state, 7 runner, 4 attach, rest regressie). Lokaal:
401 zonder sessie op alle drie routes.
tenant_demogekoppeld →up_to_date3/3, tweede keer 409. Legetenant_migr8gekoppeld →pending3 → canary-runappliedmet drie tags (103 ms) → alles-runup_to_date×2 plusskipped no_database;migrate.mjsdaarna no-op. Neprij met nieuwecreated_at→blocked/aheadenskippedin de run; gewijzigde hash →blocked/diverged; hersteld →up_to_date. Verkeerde databasenaam 422, ongeldige URL 400, onbekende slug 404, ongeldige body 400. Geen connectiestring in enige response. SSR van/migrationsen de klantpagina met sessie toont badges, koppelformulier en “Gekoppeld”. - Afwijkingen van het plan: de status per tenant nest de
state-union onderschemain plaats van een vijfvoudigeextend; het runresultaat kreeg eendetail-veld. Master-tests draaien metnode --import tsx --test --test-force-exitvanuitpackages/db(tsx is vanuit de app niet resolvbaar; zonder force-exit houdt de open pool het proces vast). De lokaleplatform_masterbleek weg en is opnieuw aangemaakt. Live isidentity_mismatchalleen via de unit-test bewezen; de naamcheck vuurt eerder. - Nog open: eerste productie-run wacht op het losse punt “Master
dashboard deployen”: pilot en rb-media koppelen,
up_to_date3/3, één no-op-run. Klik-door in een echte browser is niet gedaan; het SSR-bewijs via curl wel.
- 2.9 Uitrolmechanisme: template-update naar alle klanten met controleerbare volgorde en rollback.
Doel van deze fase: nieuwe klant van niets naar preview-URL in minder dan vijf minuten, zonder handwerk.
Fase 3 — Pagebuilder
Section titled “Fase 3 — Pagebuilder”- 3.1 Herevaluatie: zelfbouw voortzetten of overstappen op Payload CMS 3. Beslissen vóór 3.2.
- 3.2 Puck integreren in het CMS, gemapt op de bestaande blokken.
- 3.3 Blokkenbibliotheek uitbreiden en varianten per blok mogelijk maken.
- 3.4 Preview-modus: concept bekijken zonder te publiceren.
Fase 4 — Analytics en uitbreidingen
Section titled “Fase 4 — Analytics en uitbreidingen”- 4.1 Umami self-hosted, één website-ID per klantsite.
- 4.2 Aggregatie in het master dashboard via de Umami API.
- 4.3 Activiteitenweergave op basis van
audit_logen events. - 4.4 Statuspagina: achterlopende deployments, mislukte backups, verlopen certificaten.
Losse punten
Section titled “Losse punten”- Restore-procedure documenteren en aantoonbaar testen
- Backups + Operations beschrijven de procedure,
Restore-rehearsal 12 augustus 2026 is de uitgevoerde repetitie op de pilot met
gemeten RTO/RPO. De klantvariant (scratch-DB) is op 13 augustus 2026 op
rb-mediauitgevoerd, zie 1.12.
- Backups + Operations beschrijven de procedure,
Restore-rehearsal 12 augustus 2026 is de uitgevoerde repetitie op de pilot met
gemeten RTO/RPO. De klantvariant (scratch-DB) is op 13 augustus 2026 op
- Master dashboard deployen. Staat in geen enkele fase-2-taak, draait nu alleen lokaal. 2.5 (provisioning) kan een tijd lokaal, daarna niet meer.
-
platform_masterin de Coolify per-database-backuplijst zodra hij op de VPS staat. Dat handwerk is precies waar de freeze-check zelf in liep mettenant_rb-media; dump-all is het vangnet, de.dmpniet. - demo-seed herschrijven als bootstrap-config
- Rotatie van per-site secrets zonder downtime
- Font-strategie uitwerken (nu systeemstack met optionele webfont-URL)
- Beslissen of klanten zelf de huisstijl mogen aanpassen
- Dark-mode-logo:
theme.logo.mediaIdDarkblijft in het schema staan maar heeft geen formulierveld en geen renderer zolang er geen dark mode is. Zodra het veld terugkomt, moet het ook infindMediaUsage— nu bewust niet, zie het punt hieronder. - Media die als sitelogo of OG-afbeelding is ingesteld, wordt niet
meegenomen in de gebruikscheck van
deleteMedia. Opgelost:findBlockUsageisfindMediaUsagegeworden inlib/cms/media-usage.tsen dekt nu ooksite.theme.logo.mediaId,site.seoDefaults.ogImageIdenpages.seo.ogImageId(die laatste zat niet in de melding maar had dezelfde bug). De FK-velden (services.imageId,news.imageId,testimonials.logoId) zitten er ook in, zodat de melding het item bij naam noemt; de 23503-afhandeling blijft als vangnet voor de race met de delete./media/[id]toont de lijst vooraf en zet de verwijderknop uit. Keuze:theme.logo.mediaIdDarkwordt bewust niet gecheckt — er is geen formulierveld om die verwijzing weg te halen, dus blokkeren zou de media onverwijderbaar maken. -
pnpm turbo run buildfaalt, ook met alle env-variabelen gezet. Opgelost:TENANT_ID/SITE_DOMAINnaar runtime (access: "secret");turbo.jsonbuildheeftpassThroughEnvvoor secrets/tenant-vars enenvvoor overgebleven public geïnline’de vars. Zie taak 1.11.