System technical documentation

An overview document for audits and code review. It describes how the system is built, which patterns apply, and where the boundaries of responsibility lie. The conceptual specification ("what and why") is in System Architecture, operational instructions are in the Knowledge Base, and call examples are in the API Cookbook.

The source of truth for the state of the code is always the repository — this document links to files rather than repeating their content.

---

1. Guiding principle

> The schema definition is the single source of truth. Persistence, validation, the panel, REST, JSON:API, GraphQL, OpenAPI, permissions, events, and the SDK are all derived from it, never written by hand.

Consequence for review: if a feature requires a change in two places (e.g. adding a field in the database *and* in the API contract), that's an architectural bug — a derivation is missing.

---

2. Technology stack

LayerTechnologyNotes
FrameworkTanStack Start v1 (SSR + server functions)src/router.tsx, src/routes/__root.tsx, src/start.ts
RoutingTanStack Router, file-basedsrc/routes/, tree generated into src/routeTree.gen.ts (not edited)
Server runtimeCloudflare Workers (workerd) via Nitrono Node host — see §9
UIReact 19 + TypeScript 5.8strict mode, 0 tsc/tsgo errors
StylingTailwind CSS v4 (src/styles.css, @theme)semantic tokens, no hardcoded colors
Componentsshadcn/ui on Radix UIsrc/components/ui/
Server stateTanStack Query v5loader ensureQueryData + useSuspenseQuery
Formsreact-hook-form + zodschemas generated from the model definition
Database / auth / storagePostgres (Lovable Cloud)RLS, public and cms schemas
GraphQLgraphql 17 (executable schema built at runtime)src/platform/api/graphql.ts
Maps / chartsLeaflet, Recharts (lazy)loaded after hydration
BuildVite 8 + @lovable.dev/vite-tanstack-configPWA via vite-plugin-pwa
TestsVitest 445 files / 413 tests

---

3. Repository map

src/
  routes/                 # file-based routing (URL = file name)
    index.tsx, blog.*, docs.*, use-cases.*   # marketing + portal (public, SSR/SEO)
    _authenticated/       # /admin/* panel behind a session gate (route.tsx)
    api/public/           # public HTTP: REST v1, JSON:API, GraphQL, webhooks, cron
    mcp/                  # MCP servers (content + management), OAuth 2.1
  platform/               # DOMAIN — pure functions, no React and no I/O
    schema/               # field definitions, DDL compiler, validation, conditions, graph
    api/                  # surface, OpenAPI, GraphQL SDL, scopes, tokens
    access/, permissions.ts, policy.ts       # RBAC + permission sets + ABAC/FLS
    tenancy.ts, tenant-scope.ts              # multi-tenancy and isolation
    publishing/, channels/, view/, i18n/     # lifecycle, channels, layouts, text
    server/               # database access lives EXCLUSIVELY here (*.server.ts)
    __tests__/            # domain tests (fast, no DOM)
  lib/                    # *.functions.ts — typed RPC (createServerFn)
  components/admin/       # heavy panel views
  components/ui/          # shadcn primitives
  integrations/supabase/  # generated clients — NOT edited
supabase/migrations/      # the only way to change the database schema
docs/                     # documentation = source of the /docs portal

Directory rule (to verify during review): src/platform/** (outside server/) may not import anything from server/, from @/integrations/supabase/*, or from React. This is the foundation for fast tests and for keeping secrets out of the client bundle.

---

4. Frontend

4.1 Routing patterns

  • Every content section has its own route (SEO, SSR, og:*), not a hash anchor.
  • The panel sits under the pathless _authenticated/ layout — the gate in src/routes/_authenticated/route.tsx redirects unauthenticated users to /auth before loaders run. That's why loaders under this tree can call protected functions.
  • A public route never calls a protected server function in its loader (prerendering has no session) — such reads go through useServerFn + useQuery in the component.
  • head() per route: a unique title/description/og — no metadata inherited from the root for leaves.

4.2 Data

The default read shape:

loader: ({ context }) => context.queryClient.ensureQueryData(opts)
component: useSuspenseQuery(opts)

Forbidden: useEffect + fetch, manual isLoading for first-render data.

4.3 Performance and UX

  • pendingComponent + skeletons on every heavier route.
  • Lazy import for Leaflet, Recharts, editors, and import dialogs (Drive).
  • List virtualization (@tanstack/react-virtual) in content and media views.
  • Service worker / PWA (vite-plugin-pwa), precaching 128 entries.
  • Deterministic view cache (platform_view_cache + generations) — invalidated by bumping the generation, not TTL.

4.4 Design system

Colors, shadows, and gradients only as semantic tokens in src/styles.css. Components contain no hex values or classes like text-white. Direction: "Slate editorial" (IBM Plex, slate + amber).

---

5. Backend — boundaries

NeedMechanismLocation
Internal application logic (panel)createServerFn (typed RPC)src/lib/*.functions.ts
Public API for clients and integrationsserver routessrc/routes/api/public/**
Outgoing webhooks, cronserver routes + queueapi/public/hooks/*
Agents / LLMsMCP (content + management) with OAuth 2.1src/routes/mcp/**
Database accessonly *.server.tssrc/platform/server/

Rules:

  • Secrets (process.env) are read inside the handler, never at module scope.
  • Protected functions use the requireSupabaseAuth middleware; the token is attached by functionMiddleware in src/start.ts.
  • The /api/public/* prefix bypasses platform auth — every such handler verifies the caller itself (API token, HMAC signature for webhooks, scope).
  • createServerFn lives in thin wrappers: only imports, types, and function declarations at module scope. All logic lives in imported modules (a bundler-splitting requirement).

---

6. Data model

6.1 Two schemas

  • **public.platform_*** — platform metadata: schemas and their versions, accounts and memberships, groups, roles, permission sets, API tokens, webhooks, events, audit, revisions, environments, promotions, locales, channels, assets, cache, jobs.
  • `cms.<model_key>` — tables generated by the compiler from the model definition, plus join tables <model>__<relation> for manyToMany and cms.entry_translations for localized fields.

System columns on every content table: id, status, locale, account_id, created_at, updated_at.

6.2 Migrations

Database schema changes always go through a file in supabase/migrations/. Every create table in public requires, in the same migration: GRANT → enable row level security → policies. cms.* tables deliberately have no grants for anon/authenticated — they are not exposed via the Data API; all access goes through the server layer, which enforces scopes and FLS.

---

7. Security and multi-tenancy

Order of access resolution (a single place, src/platform/permissions.ts + access/permission-sets.ts):

role baseline → grant sets → restrict sets → schema policies   (deny always wins)
  • Field-Level Security exclusively via readableFieldsFor / writableFieldsFor. No handler returns a raw row.
  • Multi-tenancy: an account tree (org → company → brand → project), account_id on every row, the x-account header, rules in src/platform/tenant-scope.ts, query filtering and a row guard in src/platform/server/tenant-filter.server.ts.
  • A row belonging to another tenant returns 404, not 403 — no existence oracle. Rows with account_id = null are shared platform defaults, read-only for tenants.
  • A write with multiple scopes and no pinned accountId is rejected (no "orphaned" data).
  • Event fan-out (webhooks, SSE, GraphQL subscriptions) matches only events with an identical account_id.
  • Relation expansion (include=), revisions, and layouts pass through the same guard — history is projected through FLS too.
  • API tokens have a scope grammar narrowing models, operations, fields, and channels; MCP and REST use the same enforcement path.
  • A client with service-level privileges (supabaseAdmin) bypasses RLS — it may only be used after verifying the caller, inside the handler, via dynamic import. The role is never resolved with this client.

For review: see src/platform/__tests__/tenant-isolation.test.ts and runtime-isolation.test.ts — 11 cross-tenant scenarios plus an import guard.

---

8. Content lifecycle and delivery

  • Statuses and allowed transitions are defined only by src/platform/publishing/lifecycle.ts: draft → review → scheduled → published (+ archived). No component compares status strings on its own.
  • Publish scheduler: cron → api/public/hooks/publish-scheduled.
  • Versioning: immutable JSONB snapshots in platform_entry_revisions, rollback as a new write (history is never edited).
  • Environments: Dev → Staging → Prod with review gates and a promotion chain; Production serves only published, Preview also serves drafts.
  • Delivery channels: a field without tags is universal, a field with tags is delivered only to the listed channels. Projection lives in src/platform/channels/channels.ts, selection via ?channel=.
  • Events: platform_events outbox → webhook delivery with HMAC and retry → SSE for live clients.

---

9. Runtime constraints (Workers)

The server runs on workerd, not Node. Not allowed: child_process, sharp/canvas/puppeteer, fs.watch, os.cpus(), packages requiring native binaries or a real filesystem. Everything must be fully bundled at build time — there is no module resolution at runtime. Symptoms of incompatibility: [unenv] X is not implemented yet!, __dirname is not defined, "works in dev, fails in prod".

---

10. Code conventions

  • Domain = pure functions. I/O = *.server.ts. UI = components with no business logic.
  • No duplicating a rule in two places — one rule, one location (lifecycle, channels, scope, FLS, field conditions).
  • Panel text goes through the src/platform/i18n/catalog.ts catalog (EN), never strings in components. Documentation and communication are in Polish, identifiers in English.
  • A new field/type/interface = an entry in the registry (schema/field-types.ts, field-variants.ts, api/surface.ts) + a test + documentation.
  • Files above ~600 lines are candidates for splitting (current exceptions are listed in the audit).

---

11. Testing and quality

  • bunx vitest run — 45 files, 413 tests, ~4 s. They test domain rules, not I/O mocks.
  • Every new feature and every fixed bug gets a test (a regression test for the exact scenario).
  • Gates before a task counts as done: tsgo clean, bun run build passes, the whole suite green.
  • The interface registry (/admin/interfaces) has a regression guard — a new endpoint without a catalog entry fails a test.

Quality elements currently missing (a conscious backlog item): route E2E tests in CI, alerting on the webhook queue.

---

12. Build and deployment

  • bun run build → client + SSR worker + service worker; artifacts in dist/, worker configuration generated automatically.
  • Stable public URLs for cron jobs and integrations: project--<id>.lovable.app (prod) and project--<id>-dev.lovable.app (preview).
  • Pre-production-release checklist: DEMO_LOGIN_ENABLED disabled, storage policies for the asset bucket, narrowed SELECT on profiles and user_roles. Full list and priorities: docs/CODE-AUDIT-2026-08-11.md.

---

13. How to review this system

The order that most quickly surfaces real problems:

  1. src/platform/schema/compiler.ts + validate.ts — do DDL and validation really originate from a single definition.
  2. src/platform/permissions.ts, access/permission-sets.ts, tenant-scope.ts — is the deny/allow order and FLS exceptionless.
  3. src/platform/server/entries.server.ts — does every read and write pass through the guard and field projection (especially expand()).
  4. src/routes/api/public/** — does every handler verify the caller before doing anything.
  5. supabase/migrations/ — does every new table have GRANT + RLS + policies.
  6. src/platform/__tests__/ — do the tests cover the rules, not just the happy path.
  7. docs/CODE-AUDIT-2026-08-11.md — known gaps and priorities, so they aren't reported a second time.