ADR-010 — @tedos/core: build our own Refine-inspired app framework
:::note Contenido en inglés Esta página del wiki se sincroniza desde la base de conocimiento en inglés y todavía no está traducida. :::
canonical· tech-lead · updated 2026-06-27 · source
- Status: Accepted
- Date: 2026-06-27
- Owner: Tech Lead
- Related issue: #934 (effort) · scaffold sub #935 · provider subs #936–941 · runtime tenant config #922
Note: the intended slug was "ADR-009"; ADR-009 was already taken by
ADR-009-crm-via-twenty.md. This decision is recorded as ADR-010 (next free number — latest-number-wins).
Context
- Every Next.js app in the fleet (
apps/admin,apps/console,apps/clients/*) re-implements the same plumbing: auth gating, a data-fetch seam, routing/resource inference, i18n, and per-tenant config. ADR-008 (thin apps over@tedos/*packages) says this belongs in a package, not copied into each app. - Refine (
@refinedev/core) is the reference framework for exactly this pattern — a<Refine>shell that composes pluggable providers (authProvider,dataProvider,routerProvider,accessControlProvider,i18nProvider) over aresourcesregistry. Its concepts fit us; its runtime does not. - Forces: our stack is Next 16 / React 19, server-first RSC (ADR-008). Refine is a
client-runtime framework (its core is built around client hooks/context and TanStack Query); adopting
it would push a
"use client"boundary high in the tree and fight RSC. It is also a heavyweight third-party dependency to take on a load-bearing seam. - We need ONE
<TedosApp>shell every app mounts once, inheriting providers + a resource registry + the runtime per-tenant config that effort #922 produces (enabled modules/resources, branding tokens, locale, auth org).
Decision
Build our own RSC-first, Refine-inspired core (@tedos/core) — do NOT adopt @refinedev.
-
A new package
@tedos/coreowns: the typed provider contracts (the seam), the<TedosApp>shell (a server-safe entry + a single"use client"provider island holding the React context), and the consumer hooks (useTedos()+ per-provider hooks). -
Refine's concepts map onto our existing packages — we don't reinvent the implementations, we wrap what we already have:
Refine concept @tedos/core seam Backed by (impl, subs #936–941) dataProviderDataProvider@tedos/api-client(the ONE typed transport, ADR-008 BD)authProviderAuthProvider@tedos/auth/ Clerk (auth stays app-owned, ADR-008 A1)accessControlProviderAccessControlProviderreads AuthProvider.getPermissionsrouterProviderRouterProviderNext.js App Router ( next/navigation+ route params)i18nProviderI18nProvidernext-intl (added by the i18n sub, not the scaffold) resourcesResource[]registryapp-owned hrefs/segments + display meta (ADR-008 A3) -
Resource concept: a
Resourcemaps a domain entity (name) to its app-owned route templates (list/show/create/edit) + displaymeta(label, icon, parent). The router infers the active resource/action/id from the URL; the data layer keys calls byname; nav readsmeta. Routing stays app-owned (no hardcoded paths in the package). -
Runtime tenant config (#922):
<TedosApp>accepts aTenantConfig—enabledModules,resources,brandingTokens(white-label CSS var overrides,color-scope.md),locale,authOrgId. It is sourced at runtime from effort #922;@tedos/coreonly types it and threads it through context. -
RSC discipline (ADR-008 B1): the
<TedosApp>server entry is"use client"-free and can mount in a Server Component root layout; the only client boundary is the leaf provider island that holds the React context. -
Scope boundaries: this decision (and the scaffold sub #935) cover the package + the shell + the typed contracts + this ADR ONLY. The concrete provider implementations are sibling subs (#936–941) built against these contracts. No state-management opinion is added here — ADR-003 (Zustand + TanStack Query) is unchanged; client data hooks, if any, are a provider-impl concern.
Consequences
Positive
- Server-first by construction — no client framework runtime fighting RSC; the client boundary is a single leaf.
- No heavyweight third-party dependency on a load-bearing seam; we own the surface and can evolve it with our stack.
- Apps converge on ONE shell + ONE set of provider contracts — the thin-app architecture (ADR-008) realized for cross-cutting app concerns, not just UI.
- Providers wrap packages we already maintain (
@tedos/api-client,@tedos/auth), so there is one implementation per concern (no duplication).
Negative / trade-offs
- We maintain the framework ourselves — Refine's ecosystem (its data-provider adapters, hooks, inferencer, devtools) is not available to us.
- The provider contracts are ours to keep stable; a breaking change ripples to every consuming app.
- Passing provider objects (which hold methods) across the server→client boundary needs care; the exact construction strategy per provider is finalized in the impl subs against these contracts.
Follow-ups required
- Subs #936–941: implement
AuthProvider(@tedos/auth/Clerk),AccessControlProvider,RouterProvider(Next App Router),DataProvider(@tedos/api-client),I18nProvider(next-intl) against the@tedos/core/providerscontracts. - Effort #922: produce the runtime
TenantConfigand wire it into each app's<TedosApp>mount. - Apps adopt
<TedosApp>in their root layout once the providers land (separate effort).
Alternatives considered
| Option | Why not |
|---|---|
Adopt @refinedev/core | Client-runtime framework (client hooks + TanStack Query core); fights RSC (ADR-008), heavyweight dependency on a load-bearing seam. |
| No framework — each app wires its own | Violates ADR-008 (thin apps); duplicates auth/data/router/i18n plumbing across apps; the twins we just deleted. |
A loose set of hooks, no <TedosApp> | No single mount point for providers + tenant config; every app re-composes the same boilerplate. |
References
- ADR-008 — React style guidelines + thin-app architecture
- ADR-003 — frontend state: Zustand + TanStack Query
.claude/rules/react-style-guidelines.md(A1–A5 thin apps · BD typed data seam · B RSC · D1 bundle).claude/rules/color-scope.md(white-label / branding tokens)- Refine: https://refine.dev — concept reference, not adopted
- Code:
packages/core/(src/app/tedos-app.tsx,src/providers/*,src/tenant-config.ts)