- Date: 2026-07-14
- Status: Accepted
- Feature: Tela viewport / responsive primitives
- Builds on: ADR 0065 (per-width primitive overrides) + its 2026-07-14 amendment (Adaptive removed)
Context
Adaptive was removed from Tela (see ADR 0065 amendment). Adaptive had played two
roles: the frameless full-canvas view, and the base-editing context — the
mode in which editing a primitive's gap/columns wrote the base value (applied
at every width) rather than a per-width propsAt override.
Removing it needed a replacement base-editing context. We chose a default device: exactly one Tela preset is marked Default; the Studio opens on it and editing at it writes the base. The open question was where the default lives. Session-only (module-load, resets to 1440×900 each reload) was rejected by product: the default is an intentional per-project choice — "create a project, set it, live with it" — so it must persist with the project, not the session.
Decision
The Tela default viewport is a persisted per-project setting.
- Storage. New column
project.default_viewport jsonb NOT NULL DEFAULT '{"width":1440,"height":900}'(packages/db/src/schema.ts, migration20260714140000_project_default_viewport). The DB default backfills existing rows with the 1440×900 desktop base — zero-impact on current projects. - Create.
POST /api/v1/projectsassignsDEFAULT_VIEWPORT(1440×900); the owner reassigns it later. - Read.
GET /api/v1/me/projectsreturnsdefaultViewport; it ridesProjectMetato the client and warms aviewportCache(useProjectViewport). - Write.
PATCH /api/v1/projects/:projectIdacceptsdefaultViewport(validated to integer200–4000, mirroring Tela'sMIN_DIM/MAX_DIM), alongside the existingname. Both fields are optional; at least one required. - Wiring. The workflow page seeds Tela (
setDefaultSize+setTelaSize) from the project's value on open, and persists the Default radio change back viaupdateProjectViewport. The seed's ownsubscribeDefaultSizeecho is guarded so opening a project does not issue a redundant PATCH. - Tela package stays generic.
@arno/telaowns only the in-memorydefaultSize+isDefaultSize; it never imports project persistence. The app is the sole persistence wirer (seed in, subscribe out) — same seam Canvas uses for the active size.
Sorgente (prj-arno-sorgente-seed) is a synthetic seed project with no
project row, so its default is not persisted — it stays 1440×900 in-memory.
Consequences
- The base-editing context is now a durable, user-owned per-project choice.
- Validation duplicates Tela's dimension bounds (200–4000) in
apps/api/viewport.tsrather than importing@arno/tela(a client"use client"package) into the worker bundle. Two constants, unit-tested (viewport.test.ts). - One more
projectcolumn + a one-line additive migration; no backfill script needed (DB default handles existing rows).
Alternatives rejected
- Session-only default. Simplest (no DB), but the default reset on every reload — rejected by product: it's a per-project decision, not a session one.
- Stash in
project.prop_overridesJSONB (no migration). Avoids a column but overloads a semantically-scoped blob (captured-component prop overrides). Rejected: a dedicated column is one trivial ALTER and keeps the meaning clean. - A separate
project_viewportrow (workflow_artboard-style). Overkill for a single{width,height}scalar — a column onprojectis lighter and loads with the existing/me/projectsfetch, no extra round-trip.