Spec-driven framework

The future is not AI writing better code. It is AI not needing to write code.

Mythik is a spec-driven framework. The vast majority of your app is JSON the framework interprets at runtime — not source code that has to be regenerated, revalidated, and redeployed every time it changes. When code is the right answer, the framework provides the contracts that make it safe.

Help without limits. Freedom without chaos.

00 — Try it now

The demo below runs on Mythik. Edit the DNA, watch every primitive transform.

A live Mythik render — spec fetched from a Supabase row at page load, no rebuild, no deploy. Drag the sliders, change the surface, swap a preset; every control writes to state, every state path is read by primitives below.

01 — The fundamental shape

One JSON document.
One screen.
Zero component code.

Auth, fetch, loading state, error handling, and skeletons are driven by the spec. Wrong shape, invalid expressions, and broken cross-references are rejected with diagnostics — they never become runtime crashes.

02 — A screen as data

The screen itself is data, not a component file.

Agents edit a contract the framework can validate. Reviewers see a semantic change instead of a code rewrite that might touch ten unrelated things.

screens/task-manager.json
{
  "root": "screen",
  "elements": {
    "screen":  { "type": "screen", "children": ["heading", "list"] },
    "heading": { "type": "text",   "props": { "content": "Tasks" } },
    "list":    {
      "type": "list",
      "repeat": { "source": { "$state": "/tasks" } },
      "children": ["task-row"]
    }
  },
  "dataSources": {
    "tasks": { "url": "/api/tasks", "target": "/tasks" }
  }
}
03 — The whole app

The host file stays tiny because the product lives in the spec store.

A 5-screen app and a 500-screen app have the same shell. Adding features grows the spec store, not the host. The host file only changes when the project genuinely needs a cross-cutting integration — an icon pack, a custom auth provider, an action middleware, a storage adapter.

Client shell
import { MythikApp } from '@mythik/react';
import { createSupabaseSpecStore } from '@mythik/core';

const specStore = createSupabaseSpecStore({ url, anonKey });

export default function App() {
  return <MythikApp appSpec={appSpec} specStore={specStore} />;
}
Server shell
import { createServer } from '@mythik/server';

createServer({ spec: './api-spec.json' }).start(3010);

No router setup

No state-store wiring

No form library config

No JWT middleware glue

04 — One language, every layer

Screens, API endpoints, transactions, and patches all reduce to JSON the framework can prove.

One vocabulary. The same validation gate, the same lint rules, the same versioning, the same audit trail apply whether the change touches a screen, an API endpoint, a transaction, or a patch.

UI

Screen spec

Elements, props, style, transactions, derive, and dataSources live in a flat document the renderer interprets.

CRUD

Transaction block

Four declarative phases describe optimistic behavior. The engine owns snapshot, rollback, and commit.

Modification

RFC 6902 patch

JSON Pointer paths say exactly what changes. The framework validates the patch before it reaches the store.

navigation.auth forms derive translations ApiSpec version chain environment pointers

Think of it like a grammar.

Finite rules. Infinite sentences. The rules don't shrink what you can say — they make it possible to say anything and be understood.

05 — The validation gate

AI can change the app. Mythik proves the change is safe.

Every spec change passes through SpecEngine.patch() before reaching the store. Apply the patch, run document-handler validation, short-circuit on error, then persist. The previous valid version stays current when validation fails.

01 Propose

Agent emits an RFC 6902 patch against the current spec.

02 Apply

Patch is applied to a candidate spec in memory only.

03 Validate

Every rule, expression, ref and protected path is checked.

04 Diagnose

If invalid: typed ValidationError with ruleId. Store unchanged.

05 Persist

Only validated specs reach the store. Previous version stays live until then.

Specs describe intent in a validated shape

The agent edits a JSON contract, not implementation details. The validator knows the contract.

Diagnostics explain exactly what failed

Every ValidationError carries a ruleId that maps to a rule in the reference doc. No error-string parsing.

Derived paths are write-protected

StateGuard blocks setState to /auth/*, /tx/*, all derive paths, and framework-reserved paths — at validation time and runtime.

05.1 — The gate, live

Paste a spec. Validate it.

The card below runs the framework's validateSpec against any JSON you paste. Same engine that gates every spec store write — wrong shape, unknown primitives, broken references all surface here with typed paths.

validateSpec · same engine as the store
05.5 — Cross-cutting contract

Frontend and backend prove they agree, before either ships.

Most stacks check types between client and server. Mythik checks four dimensions at once — fetches, fields, query params, and role permissions — across every screen × every endpoint × every role, because both sides are JSON specs in the same vocabulary. Drift surfaces at push time, never in production.

$ mythik contract
# Cross-checking 14 screens against api-spec.json…

 screen "task-detail" reads field "assignee.avatar"
   from "GET /api/tasks/:id"
   → API returns assignee.avatarUrl (did you mean this?)

 screen "admin-panel" visible to role "user"
   contains action calling "DELETE /api/users/:id"
   → endpoint policy: "admin", role drift

 screen "task-list" sends query param "filter"
   → endpoint declares: ["status", "priority"], did you mean "status"?

3 contract violations. Push aborted.
01 — Fetches → endpoints

Every fetch in every screen spec must point at an endpoint the API actually exposes.

02 — Fields → response shape

Every $state binding for fetched data must match the field names the API spec declares it returns.

03 — Query params → declarations

Params sent in fetch URLs must be in the endpoint's params map. Typos surface with Levenshtein-suggested corrections.

04 — Roles → endpoint policy

If a screen is visible to role X and contains an action calling endpoint Y, role X must satisfy Y's policy.

The check is mechanical. No type-generation step, no contract test suite, no client/server schema sync. Both artifacts are JSON in a shared vocabulary — and the check runs as part of mythik push, before any pointer moves.

06 — Surgical edits

manifestelementspatch

The CLI lets a developer or AI agent modify a running app at the granularity of a single element. The patch declares exactly which JSON Pointer paths change; everything else is provably untouched.

~ /apps/task-manager — bash
01 $ mythik manifest task-manager
02 $ mythik elements task-manager submit-btn,priority-select --toon
03 $ mythik patch task-manager --from-file patch.json --author claude
04 $ mythik manifest task-manager
JSON Pointer addressability

Every element is reachable by absolute path, so the patch only touches what it names.

Validation before persistence

SpecEngine.patch() applies, validates, short-circuits on error, then saves.

Auditable with --author

Versioned stores record author attribution and semantic structural diffs.

Screens are isolated

Modifying one screen cannot affect another. They are separate documents in the spec store.

07 — Spec versioning

Every change is a queryable, reversible record.

Add --author to any push or patch, and the change is recorded in screen_versions as part of a snapshot-plus-patch chain. Inspect with mythik history; reverse with always-forward rollback; promote across environments by atomic pointer move.

$ mythik history task-manager
v3 (claude, 2026-04-25, patch)  "Reorder priority field"
   ~ element "priority-select" prop position: "after-title""before-status"
v2 (alice,  2026-04-24, push)   "Add priority field"
   + element "priority-select" added
   ~ element "submit-btn" prop content: "Send""Submit"
v1 (system, 2026-04-23, push)
Snapshot + patch chain

v1 stores the full spec. Subsequent versions store RFC 6902 patches. Every N versions a fresh snapshot bounds replay cost.

Semantic structural diffs

computeStructuralDiff produces typed records with actual values — "Send" → "Submit", not byte deltas.

Always-forward rollback

executeRollback creates a new forward version with the target version's content. History never gets rewritten.

Atomic promote gate

Promoting dev → staging → prod validates structure, cross-screen consistency, and the frontend↔backend contract before any pointer moves.

08 — No rebuild for spec work

A screen change is a row in the spec store, not a new bundle.

Spec changes — screens, AppSpec, ApiSpec, tokens, identity, presets, translations, validation rules, action chains, transactions — ship without a build queue, CDN bust, or deploy step. The next fetch picks up the validated document.

01

Patch in seconds

mythik patch writes the new spec to the store. Active screens pick up the next valid version on the next fetch — no host-app remount.

02

Promote by pointer

Dev, staging, and prod are environment pointers in screen_environments. Promotion validates atomically; rollback moves the pointer back.

03

Per-tenant variation

Different customers can receive different screen specs while the application binary remains identical. Multi-tenant variation is rows, not separate deploys.

04

AI loops at network speed

Every iteration of read manifest → propose patch → push → observe runs at network round-trip speed. Self-correction loops become real loops.

The boundary is honest. This applies to spec changes. The host file (App.tsx, server.ts) and custom plugin code still ship through the standard npm build pipeline — but those rarely change, because a 5-screen app and a 500-screen app have the same host file.

09 — The AI doesn't guess. It reads.

A retrieval system written for agents, before any spec is generated.

Two retrieval-optimized knowledge bases ship with the framework. Agents consult them before producing JSON, eliminating the hallucinated-primitive class of failures that plague codegen tools.

docs/wiki/compiled/

326 atomic concept articles

One concept per file (~30–80 lines each), cross-linked by [[@<id>]] wikilinks. _index.md lists every article in a flat searchable format the agent reads to decide which file to load.

  • 22 expressions · 19 actions · 37 primitives
  • 21 first-class anti-pattern articles
  • ~40 token / identity / animation articles
  • ~17 CLI · ~17 storage · ~14 ApiSpec

An agent answering "how do I write an UPDATE transaction?" loads pattern-tx-update.md (~40 lines) and follows wikilinks. No full-corpus scan.

docs/consumer/

6,754-line companion corpus

Module-by-module loadable for broader context. Frontend tasks load primitives + patterns. Server tasks load API + custom-elements. Debugging tasks load runtime semantics.

ai-context.md1,331
ai-context-runtime-semantics.md940
ai-context-primitives.md629
ai-context-patterns.md254
ai-context-custom-elements.md249
ai-context-api.md195
reference-doc.md3,156

The wiki is generated, linted, and gap-tracked by tooling. It is not documentation that happens to also be readable by AI — it is a retrieval system that happens to also be readable by humans.

Pattern credit: this approach follows the LLM wiki pattern Andrej Karpathy popularized — atomic markdown articles cross-linked for agent traversal, with a separate schema doc telling the LLM how to maintain it.

09.1 — The wiki, live

Sortable. Paginated. One primitive.

The table below is the framework's table primitive on real data fetched from this very framework checkout. sorting, pagination, and stickyHeader are flags on the same JSON spec — 5 declarative props on a single primitive. No table component, no row renderer, no pagination logic.

table primitive · real wiki data
10 — By the numbers

The framework, measured.

326 atomic wiki articles
256 numbered framework rules
6,754 AI-context lines
37primitives
22expressions
19actions
21anti-patterns
15animation recipes
8plugin extension points
11 — Bounded blast radius

A broken spec does not break the app.

A failure in one spec is contained to that spec. Per-screen storage, validation gate, lint defense-in-depth, StateGuard, screen-scoped re-mount, render error boundary, and per-instance custom element isolation all keep the blast radius bounded.

01Validation gate

Malformed patches are rejected before persistence. Store unchanged.

02StateGuard

Protected paths and derived state cannot be overwritten — at validation and runtime.

03Per-screen storage

Screens are separate documents. Patching one cannot affect another.

04Render boundary

Runtime errors write diagnostics to /ui/renderErrors. Sibling elements keep rendering.

[
  {
    "elementId": "priority-select",
    "message": "$state path /tasks/priority not found",
    "type": "select"
  }
]
12 — Closed-loop self-correction

The agent reads its own diagnosis from state.

Five stages, four of them framework work. Only the first costs LLM tokens — the rest are the framework catching mistakes, surfacing them, and resetting boundaries on the next valid spec.

  1. 01

    AI proposes a patch

    The only stage that costs LLM tokens. The framework owns everything that follows.

  2. 02

    Pre-persist diagnostics

    mythik lint + SpecEngine.patch() + StateGuard reject malformed patches synchronously. Most errors stop here.

  3. 03

    Render-time isolation

    If a validator-passing spec fails at render, the boundary scopes the failure to the offending element.

  4. 04

    Diagnostics as state

    Errors are written to /ui/renderErrors as a typed array. The agent reads them like any other state path.

  5. 05

    Corrective patch

    Agent generates the next patch. Boundary auto-resets on spec change. Loop closes.

The boundary is honest. The loop closes for technical spec errors — invalid expressions, unknown primitives, missing state paths, anti-pattern violations. It does not close for semantic errors (a spec where every expression resolves but the user-facing behavior is wrong). Semantic correctness still requires test suites, human reviewers, or verification hooks.

13 — Shipping behavior

What the framework owns, so the AI doesn't have to.

Mythik gives agents declarative power while the framework enforces contracts, security, rollback, design coherence, and cross-platform runtime behavior.

01

DNA seeds

A brand color and a handful of personality sliders derive colors, typography, spacing, elevation, dark mode, and motion timing.

02

Identity / Forge

Surface, corners, typography, gradients, color schemes, backgrounds, and motion live in one validated identity block.

03

Optimistic transactions

Snapshot state, apply optimistic UI, confirm over the network, and roll back atomically if the server rejects.

04

Reactive derive

Computed paths update in topological order, are write-protected by StateGuard, and never need manual memoization.

05

JSON expression language

22 handlers cover filtering, sorting, formatting, conditional logic, branching, i18n, auth-safe reads, and 16 array operations.

06

Auto-skeleton

Add a fetch and the renderer replaces data-dependent elements with correctly-shaped shimmer skeletons during loading.

07

Live retheme

updateTokens and applyPreset swap the brand, dark mode, gradients, or entire visual preset at runtime.

08

Cross-platform from one spec

$platform branches explicitly; cssToNative translates web idioms automatically.

09

i18n in the spec

$i18n with expression-valued args; setLocale at runtime; locale-aware $format and $date.

13.1 — Auto-skeleton, live

Click reload. Watch the skeletons.

The card below is a Mythik spec with initialActions firing a fetch. While /items is empty during loading, the renderer auto-substitutes shape-matching shimmer skeletons for each text and icon. Reload to see it again.

initialActions · fetch · auto-shimmer
13.2 — Optimistic updates, live

Click. The UI shifts. The framework handles the rollback.

Every interaction is a transaction with five named phases — before, snapshot, optimistic, confirm, resolve. The engine snapshots state before your optimistic update lands. When the API call fails, the snapshot wins back atomically. You never write rollback code — the contract lives in the spec.

5-phase transaction engine · auto-snapshot · auto-rollback
14 — Auth that ships ready

navigation.auth owns the lifecycle.

Authentication is part of the framework, not a problem to solve. A developer enables it with a JSON block — no auth context provider, no protected-route HOC, no session restoration logic, no token refresh loop, no cross-tab coordination, no rate limiting.

app-spec.json
"navigation": {
  "auth": {
    "provider": "supabase",
    "loginScreen": "login",
    "protectedScreens": ["*"],
    "roleAccess": {
      "admin": ["*"],
      "user":  ["dashboard", "profile"]
    },
    "persistence": "local",
    "tokenRefresh": true,
    "authDomains": ["api.example.com"]
  }
}
Three persistence modes, one field

local survives browser close. session ends with the tab. memory requires re-login on every refresh.

Cross-tab sync, zero config

Login, logout, refresh, and session expiry propagate across tabs via BroadcastChannel with localStorage fallback.

Architectural security

Access tokens never enter the state store. $auth whitelists block token reads. StateGuard blocks writes on /auth/*.

Auth-scoped headers

authDomains ensures Bearer tokens attach only to allowed hostnames. Tokens never leak to third-party APIs.

15 — Plugin system

Extensible at every layer.

When the framework doesn't ship what you need, you don't fork it — you register it. MythikApp.onPlugins exposes a single PluginLoader that lets you add capabilities at every layer of the engine. Every extension passes through the same dispatcher, validator, and error boundary as the built-ins.

registerAction

New action handlers callable from spec.

registerExpression

New $<name> handlers in any expression context.

registerPrimitive

New primitive types or replacements for built-ins.

registerElement

First-class Layer-3 custom elements with variants and animations.

registerValidator

New validator types for forms and inline checks.

registerSourceProvider

New data-source providers for dataSources.

registerPresets

DNA + Identity preset bundles for runtime theme switching.

setIconRenderer

Host-app icon renderer (Phosphor / Lucide / custom).

Structure that enables. Not structure that restricts.

Every extension point is a contract — a place where the AI can compose freely, with the framework guaranteeing safety, validation, and isolation around what gets composed. The framework owns the contracts; you and the AI own what fills them.

16 — The spec is the backend, too

@mythik/server turns an API spec into a REST server.

Auth, RBAC, row-level security, CRUD with field-injection defense, audit fields, and catalogs are declared in JSON. A real workload with 48,763 records, 35 endpoints, 170 tenant scopes, and 3 user roles was rebuilt in 192 total lines.

01
Spec-to-server runtime

Point Mythik at an API spec and it mounts typed REST endpoints from declarative JSON.

02
Auth, RBAC, and row scope

Policies and tenant boundaries are enforced uniformly on reads, updates, and deletes.

03
CRUD with field defense

Insertable and updatable allowlists drop injected fields before database writes.

04
Audit and verification

Server-side audit values are un-spoofable. Live-data tests prove the contract works across roles.

17 — One declaration, full CRUD

A single crud block generates three routes.

POST, PUT/:id, and DELETE/:id ship from one declaration — with field-injection defense and server-side audit built in. SCOPE_IDENTITY() + SELECT instead of OUTPUT INSERTED.* means CRUD is safe against tables with database triggers.

api-spec.json
"tasks": {
  "path": "/api/tasks",
  "policy": "authenticated",
  "crud": {
    "table": "tasks",
    "primaryKey": "id",
    "insertable": ["title", "priority"],
    "updatable":  ["title", "priority", "status"]
  }
}
POST /api/tasks

Insert. Body filtered to insertable fields. Injected fields like isAdmin are silently dropped.

PUT /api/tasks/:id

Update by primary key. Body filtered to updatable fields.

DELETE /api/tasks/:id

Delete by primary key. Scoped by scopeFilter if configured.

Field-injection defense

Fields not in insertable or updatable are silently dropped. A client cannot inject isAdmin: true.

Un-spoofable audit

Audit values are written server-side after field filtering. Clients cannot forge them.

Trigger-safe writes

INSERT uses SCOPE_IDENTITY() + SELECT. Compatible with tables that have triggers.

18 — Row-level security without code

Declare scopeFilter once. Every query, update, and delete is scoped.

One JSON block enforces per-user data boundaries across SELECT, UPDATE, and DELETE — uniformly. The same mechanism works for tenants, clinics, branches, departments, or any partitioning column.

api-spec.json
"scopeFilter": {
  "claim": "tenants",
  "column": "tenantId",
  "bypassRoles": ["admin"],
  "mode": "all"
}
mode: "all"

Multi-scope visibility

Injects WHERE column IN (:scope_array). The user sees data from every scope assigned to them.

mode: "select"

Single active scope

Injects WHERE column = @activeScope via header. The user picks one active scope at a time.

Applies uniformly

SELECT, UPDATE, and DELETE all enforce the filter. A user cannot modify rows outside their scope.

bypassRoles for admins

One config line lets specific roles skip the filter entirely.

Per-endpoint override

Custom SQL templates override the global config when needed.

Domain-agnostic naming

scopeFilter not institutionFilter — same pattern for any partitioning column.

19 — Server in one call

Auth, RBAC, CRUD, scope filters, and audit without glue code.

One createServer call wires it all up. The spec stays secret-free; secrets and connection strings come from the environment at boot.

server.ts
import { createServer } from '@mythik/server';

createServer({ spec: './api-spec.json' }).start(3010);
REST endpoints RBAC policy tenant scope CRUD allowlists audit fields live E2E checks
20 — Architectural ownership

The AI inherits the architecture. It never invents one.

Every framework decision shown above is made once, at the framework level — and every AI agent that touches the codebase inherits the same answer. Two agents working on the same codebase, six months apart, produce structurally identical decisions because the decisions are not theirs to make.

Concern AI task Framework role
Authentication Add a protected screen entry Owns auth runtime, refresh, cross-tab sync
Form validation Pick validators from the catalog Owns coordination, error paths, submit gating
Optimistic CRUD Fill four transaction phases Owns snapshot, rollback, commit semantics
Loading state Nothing — it's automatic Owns auto-skeleton matching element shape
Row-level security Set a scopeFilter on the endpoint Owns SQL injection across read/write/delete
Audit trail Pass --author on the patch Owns version chain, structural diffs, rollback
21 — AI-first by design

Every framework decision optimizes for agents generating it correctly the first time.

The framework was designed around one question: can an LLM generate this correctly without first breaking it? The answer shaped every surface — the wiki, the rule IDs, the JSON shape of extensions, the error boundaries, the build-free spec pipeline.

01

Addressable knowledge

256 numbered rules and 326 atomic wiki articles give agents small, retrievable units of framework behavior — hop-by-hop instead of full-corpus scans.

02

Context-window economy

6,754 lines of AI-context docs split into companion modules. Frontend tasks load primitives + patterns; debugging tasks load runtime semantics.

03

Self-correction loops

Validation errors carry ruleId for programmatic lookup. Render error boundaries reset on spec change so agents can repair their output.

04

Typed automation APIs

@mythik/cli/api exposes runPush, runPatch, and runLint with typed JSON output, so agents do not parse terminal text.

05

No build-speed bottleneck

Spec iterations run at network round-trip speed. read → patch → observe → correct is a real loop because there is no build pipeline blocking the cycle.

06

Bounded blast radius

The worst-case outcome of an AI mistake is contained to one spec — caught by validation gate or render boundary. Agents can iterate freely.

22 — How this started

I've been building with AI for a while. It's incredible how much it helps. Until it isn't — until you ask for one small change and it touches three things you didn't want it to touch.

I kept thinking there had to be a way to keep the help without the surprise. Some way to give the AI room to work without giving it permission to break things you weren't watching.

Eventually I tried something simple. I stopped letting the AI write code, and started letting it write JSON. Just data — a description of what the UI should be. A renderer I'd already built and tested would take care of the rest.

It's been working for me. It's not perfect, and it's not for every project. But if you've felt the same friction, maybe it'll help you too.

mythik.dev

Stop watching AI touch the wrong files. Give it Mythik, not a codebase.

Three packages. One spec. A renderer that proves the data is safe before it runs. Install it, point it at a JSON document, and watch an app render — validated, versioned, and isolated by default.

npm install @mythik/core @mythik/react @mythik/cli

Help without limits. Freedom without chaos.