mythik 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +64 -16
  2. package/dist/actions/dispatcher.d.ts.map +1 -1
  3. package/dist/actions/dispatcher.js +21 -2
  4. package/dist/actions/dispatcher.js.map +1 -1
  5. package/dist/expressions/handlers/let.d.ts +6 -0
  6. package/dist/expressions/handlers/let.d.ts.map +1 -1
  7. package/dist/expressions/handlers/let.js +29 -4
  8. package/dist/expressions/handlers/let.js.map +1 -1
  9. package/dist/expressions/handlers/template.d.ts.map +1 -1
  10. package/dist/expressions/handlers/template.js +2 -1
  11. package/dist/expressions/handlers/template.js.map +1 -1
  12. package/dist/renderer/prop-schemas.js +1 -1
  13. package/dist/renderer/prop-schemas.js.map +1 -1
  14. package/dist/security/api-spec-validator.d.ts.map +1 -1
  15. package/dist/security/api-spec-validator.js +4 -0
  16. package/dist/security/api-spec-validator.js.map +1 -1
  17. package/dist/security/spec-validator.d.ts.map +1 -1
  18. package/dist/security/spec-validator.js +43 -31
  19. package/dist/security/spec-validator.js.map +1 -1
  20. package/dist/server.d.ts +7 -0
  21. package/dist/server.d.ts.map +1 -1
  22. package/dist/server.js +3 -0
  23. package/dist/server.js.map +1 -1
  24. package/dist/spec-stores/sql-versioned.d.ts +38 -0
  25. package/dist/spec-stores/sql-versioned.d.ts.map +1 -0
  26. package/dist/spec-stores/sql-versioned.js +186 -0
  27. package/dist/spec-stores/sql-versioned.js.map +1 -0
  28. package/dist/spec-stores/sql.d.ts +21 -0
  29. package/dist/spec-stores/sql.d.ts.map +1 -0
  30. package/dist/spec-stores/sql.js +65 -0
  31. package/dist/spec-stores/sql.js.map +1 -0
  32. package/dist/spec-stores/sqlserver-versioned.d.ts +5 -30
  33. package/dist/spec-stores/sqlserver-versioned.d.ts.map +1 -1
  34. package/dist/spec-stores/sqlserver-versioned.js +16 -245
  35. package/dist/spec-stores/sqlserver-versioned.js.map +1 -1
  36. package/dist/spec-stores/sqlserver.d.ts +4 -11
  37. package/dist/spec-stores/sqlserver.d.ts.map +1 -1
  38. package/dist/spec-stores/sqlserver.js +16 -66
  39. package/dist/spec-stores/sqlserver.js.map +1 -1
  40. package/dist/sql/ddl.d.ts +7 -0
  41. package/dist/sql/ddl.d.ts.map +1 -0
  42. package/dist/sql/ddl.js +134 -0
  43. package/dist/sql/ddl.js.map +1 -0
  44. package/dist/sql/drivers/mysql.d.ts +25 -0
  45. package/dist/sql/drivers/mysql.d.ts.map +1 -0
  46. package/dist/sql/drivers/mysql.js +329 -0
  47. package/dist/sql/drivers/mysql.js.map +1 -0
  48. package/dist/sql/drivers/postgres.d.ts +30 -0
  49. package/dist/sql/drivers/postgres.d.ts.map +1 -0
  50. package/dist/sql/drivers/postgres.js +321 -0
  51. package/dist/sql/drivers/postgres.js.map +1 -0
  52. package/dist/sql/drivers/sqlite.d.ts +28 -0
  53. package/dist/sql/drivers/sqlite.d.ts.map +1 -0
  54. package/dist/sql/drivers/sqlite.js +369 -0
  55. package/dist/sql/drivers/sqlite.js.map +1 -0
  56. package/dist/sql/drivers/sqlserver.d.ts +46 -0
  57. package/dist/sql/drivers/sqlserver.d.ts.map +1 -0
  58. package/dist/sql/drivers/sqlserver.js +405 -0
  59. package/dist/sql/drivers/sqlserver.js.map +1 -0
  60. package/dist/sql/errors.d.ts +22 -0
  61. package/dist/sql/errors.d.ts.map +1 -0
  62. package/dist/sql/errors.js +27 -0
  63. package/dist/sql/errors.js.map +1 -0
  64. package/dist/sql/factory.d.ts +3 -0
  65. package/dist/sql/factory.d.ts.map +1 -0
  66. package/dist/sql/factory.js +24 -0
  67. package/dist/sql/factory.js.map +1 -0
  68. package/dist/sql/index.d.ts +17 -0
  69. package/dist/sql/index.d.ts.map +1 -0
  70. package/dist/sql/index.js +9 -0
  71. package/dist/sql/index.js.map +1 -0
  72. package/dist/sql/named-params.d.ts +8 -0
  73. package/dist/sql/named-params.d.ts.map +1 -0
  74. package/dist/sql/named-params.js +182 -0
  75. package/dist/sql/named-params.js.map +1 -0
  76. package/dist/sql/types.d.ts +49 -0
  77. package/dist/sql/types.d.ts.map +1 -0
  78. package/dist/sql/types.js +2 -0
  79. package/dist/sql/types.js.map +1 -0
  80. package/dist/types.d.ts +1 -1
  81. package/dist/types.d.ts.map +1 -1
  82. package/docs/consumer/README.md +1 -1
  83. package/docs/consumer/WHERE-TO-LOOK.md +4 -4
  84. package/docs/consumer/ai-context-api.md +44 -0
  85. package/docs/consumer/ai-context-primitives.md +3 -0
  86. package/docs/consumer/ai-context-runtime-semantics.md +8 -3
  87. package/docs/consumer/ai-context.md +123 -39
  88. package/docs/consumer/reference-doc.md +30 -8
  89. package/docs/wiki/compiled/README.md +1 -1
  90. package/docs/wiki/compiled/_lint.md +13 -8
  91. package/docs/wiki/compiled/action-fetch.md +7 -2
  92. package/docs/wiki/compiled/concept-action-chains.md +62 -24
  93. package/docs/wiki/compiled/concept-package-layout.md +11 -9
  94. package/docs/wiki/compiled/concept-public-package-names.md +24 -13
  95. package/docs/wiki/compiled/concept-shape-animations.md +1 -1
  96. package/docs/wiki/compiled/concept-spec-stores-catalog.md +28 -16
  97. package/docs/wiki/compiled/concept-transactions.md +20 -12
  98. package/docs/wiki/compiled/expression-let-ref.md +36 -18
  99. package/docs/wiki/compiled/expression-template.md +28 -17
  100. package/docs/wiki/compiled/path-ui-loading-error.md +5 -0
  101. package/docs/wiki/compiled/pattern-fetch-vs-datasources.md +5 -0
  102. package/docs/wiki/compiled/pattern-loading-content-empty.md +3 -2
  103. package/docs/wiki/compiled/primitive-select.md +16 -2
  104. package/package.json +25 -2
@@ -14,7 +14,7 @@
14
14
  Public npm package names are unscoped:
15
15
 
16
16
  - Runtime/core: `mythik`
17
- - Browser-safe server helpers from core: `mythik/server`
17
+ - Node/server helpers from core: `mythik/server`
18
18
  - React host/runtime: `mythik-react`
19
19
  - CLI binary package: `mythik-cli` (binary command: `mythik`)
20
20
  - Programmatic CLI API: `mythik-cli/api`
@@ -22,6 +22,21 @@ Public npm package names are unscoped:
22
22
 
23
23
  Use `npm install mythik mythik-react` for a React app, `npm install -D mythik-cli` for CLI workflows, and add `mythik-server` only when building a Mythik-backed Node server. The `mythik` npm package bundles the AI documentation corpus under `node_modules/mythik/docs`; use `mythik docs path` to locate it after install or `mythik docs copy ./mythik-docs` to copy it into the current project.
24
24
 
25
+ Server-side SQL helpers, SQL drivers, and SQL-backed spec stores are imported from `mythik/server`, not from the browser-safe `mythik` entry. Supported SQL dialects are `sqlserver`, `postgres`, `mysql`, and `sqlite`.
26
+
27
+ Database runtime dependencies: SQL adapters (`mssql`, `pg`, `mysql2`, and `better-sqlite3`) are optional peer dependencies. Browser-only installs do not need them. SQL-backed stores and servers must install exactly the adapter for the selected database:
28
+
29
+ ```bash
30
+ npm install mssql # SQL Server
31
+ npm install pg # PostgreSQL
32
+ npm install mysql2 # MySQL
33
+ npm install better-sqlite3 # SQLite
34
+ ```
35
+
36
+ SQLite uses the native `better-sqlite3` adapter. npm warnings from that adapter's transitive native-build helpers are adapter-level install noise, not a Mythik runtime failure. Missing SQL adapter errors include the package name and exact install command for the selected dialect.
37
+
38
+ MySQL support targets MySQL 8.0.19+ for generated upsert SQL. Older MySQL deployments need an explicit custom SQL path or another supported dialect.
39
+
25
40
  ## Spec Structure
26
41
 
27
42
  Every screen is a flat tree: `root` ID + `elements` map + optional `initialActions`:
@@ -88,6 +103,9 @@ Every screen is a flat tree: `root` ID + `elements` map + optional `initialActio
88
103
  ```bash
89
104
  mythik docs path # Locate bundled AI documentation
90
105
  mythik docs copy ./mythik-docs # Copy docs for an AI agent / local review
106
+ mythik init-store --dialect sqlite --target ./mythik.db # Create local SQL store tables
107
+ mythik init-store --dialect postgres --dry-run # Print SQL store DDL for review/apply
108
+ mythik init-store --dialect sqlserver --server localhost --database Mythik --user "$DB_USER" --password "$DB_PASSWORD" --encrypt false --trust-server-certificate
91
109
  mythik manifest <screen> # See structural tree
92
110
  mythik elements <screen> <id1,id2> # Get element details
93
111
  mythik patch <screen> --from-file patch.json # Apply RFC 6902 patches
@@ -114,6 +132,36 @@ Do not edit database rows directly, do not call `SpecStore.save()` from app code
114
132
 
115
133
  All commands accept `--json`, `--table <name>`, `--store`, `--url`, `--key`. Never pass API keys inline — use `.mythikrc` + env vars.
116
134
 
135
+ ### CLI store configuration
136
+
137
+ The CLI can read and write specs from `memory`, `file`, `supabase`, `sqlserver`, `postgres`, `mysql`, and `sqlite` stores. SQL stores share the same commands and the same required edit loop: `manifest -> elements -> patch -> validate`.
138
+
139
+ ```bash
140
+ # SQLite: local development, tests, demos, lightweight deployments
141
+ mythik init-store --dialect sqlite --target ./mythik.db
142
+ mythik push floor-editor --from-file specs/floor-editor.json --store sqlite --filename ./mythik.db --author ai-agent
143
+ mythik manifest floor-editor --store sqlite --filename ./mythik.db
144
+ mythik elements floor-editor page,title --store sqlite --filename ./mythik.db
145
+ mythik patch floor-editor --from-file patch.json --store sqlite --filename ./mythik.db --author ai-agent
146
+ mythik validate floor-editor --store sqlite --filename ./mythik.db
147
+
148
+ # PostgreSQL
149
+ mythik init-store --dialect postgres --dry-run
150
+ mythik patch floor-editor --from-file patch.json --store postgres --url "$DATABASE_URL" --author ai-agent
151
+
152
+ # MySQL
153
+ mythik init-store --dialect mysql --dry-run
154
+ mythik patch floor-editor --from-file patch.json --store mysql --url "$DATABASE_URL" --author ai-agent
155
+
156
+ # SQL Server
157
+ mythik init-store --dialect sqlserver --server localhost --database Mythik --user "$DB_USER" --password "$DB_PASSWORD" --encrypt false --trust-server-certificate
158
+ mythik patch floor-editor --from-file patch.json --store sqlserver --server localhost --database Mythik --user "$DB_USER" --password "$DB_PASSWORD" --author ai-agent
159
+ ```
160
+
161
+ Environment equivalents: `MYTHIK_STORE`, `MYTHIK_DATABASE_URL`, `MYTHIK_SQLITE_FILE`, `MYTHIK_SQLSERVER_SERVER`, `MYTHIK_SQLSERVER_DATABASE`, `MYTHIK_SQLSERVER_USER`, `MYTHIK_SQLSERVER_PASSWORD`, `MYTHIK_SQLSERVER_PORT`, `MYTHIK_SQLSERVER_TRUSTED_CONNECTION`, `MYTHIK_TABLE`, `MYTHIK_VERSIONS_TABLE`, `MYTHIK_ENVIRONMENTS_TABLE`, and `MYTHIK_SNAPSHOT_INTERVAL`.
162
+
163
+ `init-store --dry-run` prints the canonical idempotent DDL for the dialect. `init-store` can initialize reachable SQL stores directly, including SQL Server through explicit flags, while production teams may still apply the dry-run DDL through their normal deployment process. Runtime reads/writes do not silently create missing tables on first use.
164
+
117
165
  ### CLI is the only approved path for spec writes
118
166
 
119
167
  Per reference-doc rule 248: three approved forms.
@@ -301,6 +349,11 @@ Extended: `locale`, `notation` (compact/scientific), `signDisplay` (always/excep
301
349
  ```json
302
350
  { "$let": { "total": { "$array": "count", "source": { "$state": "/items" } } }, "$in": { "$ref": "total" } }
303
351
  ```
352
+ `$ref` may also read nested values from an object binding with dot notation:
353
+ ```json
354
+ { "$let": { "user": { "$state": "/auth/user" } }, "$in": { "$ref": "user.name" } }
355
+ ```
356
+ Inside the same `$let`, `$template` placeholders can read the same dotted binding paths, for example `${user.name}`.
304
357
  **JSONB array format** (when stored in DB, order matters):
305
358
  ```json
306
359
  { "$let": [["filtered", { "$array": "filter", "source": { "$state": "/items" }, "where": { "field": "status", "eq": "active" } }], ["count", { "$array": "count", "source": { "$ref": "filtered" } }]], "$in": { "$ref": "count" } }
@@ -341,12 +394,14 @@ All primitives accept `style`, `visible`, and `permission`. Tokens are project-d
341
394
 
342
395
  Wire to events with `on`: `{ "on": { "press": { "action": "...", "params": {...} } } }`
343
396
 
344
- Arrays execute sequentially: `"press": [action1, action2, action3]`
397
+ Arrays execute sequentially: `"press": [action1, action2, action3]`. Arrays may mix normal action bindings and transaction bindings; each entry runs in order and transactions are awaited before the next entry.
345
398
 
346
399
  **Trap:** Action chains don't stop on failure — `validateForm` marks errors but does NOT halt the chain. Use `submitForm` with `formId` instead (validates + blocks if invalid). See [ai-context-patterns.md](ai-context-patterns.md).
347
400
 
348
401
  Add `"fireAndForget": true` to dispatch without waiting (background re-fetch after closing modal).
349
402
 
403
+ Any action binding may include `params.skipIf`. Mythik resolves `skipIf` at dispatch time before resolving the rest of the params; when truthy, that action is skipped and the surrounding action chain continues.
404
+
350
405
  ### Action Reference
351
406
 
352
407
  | Action | Params | Purpose |
@@ -386,6 +441,7 @@ Add `"fireAndForget": true` to dispatch without waiting (background re-fetch aft
386
441
  - Empty strings in body → `null` (prevents DB errors)
387
442
  - Sets `/ui/loading` while in flight
388
443
  - On error: sets `/ui/lastError` with status and message
444
+ - Optional `errorTarget` writes the same structured error to a screen-owned path and clears that path on success. Use it for critical screen-load fetches instead of relying only on global `/ui/lastError`
389
445
  - Auth headers auto-injected for `authDomains` URLs
390
446
 
391
447
  ### Transactions (Optimistic Updates)
@@ -539,14 +595,16 @@ Inside: `{ "$selection": "selected" }` (boolean), `{ "$selection": "count" }`. T
539
595
 
540
596
  **Rule:** Don't mix both for the same data target. Pick one pattern per data source.
541
597
 
598
+ For critical `initialActions` fetches, set `params.errorTarget` to a screen-owned path (for example `/ui/loadErrors/orderForm`) and render a visible error state from that path. `/ui/lastError` is global and can be overwritten by unrelated fetches.
599
+
542
600
  ### Loading/Content/Empty Pattern
543
601
 
544
- **With `initialActions` fetch** (uses `/ui/loading` and `/ui/lastError`):
602
+ **With `initialActions` fetch** (uses `/ui/loading`; critical loads should set `errorTarget`):
545
603
  ```json
546
604
  "loading": { "visible": { "$and": [{ "$state": "/ui/loading" }, { "$not": { "$array": "count", "source": { "$state": "/items" } } }] } },
547
605
  "content": { "visible": { "$array": "count", "source": { "$state": "/items" } } },
548
606
  "empty": { "visible": { "$and": [{ "$not": { "$state": "/ui/loading" } }, { "$not": { "$array": "count", "source": { "$state": "/items" } } }] } },
549
- "error": { "visible": { "$state": "/ui/lastError" } }
607
+ "error": { "visible": { "$state": "/ui/loadErrors/items" } }
550
608
  ```
551
609
 
552
610
  **With `dataSources`** (uses auto-generated `/{target}Loading` and `/{target}Error`):
@@ -1135,35 +1193,46 @@ Quick reference: `type: "api"` at root, with `auth`, `catalogs`, `endpoints` obj
1135
1193
 
1136
1194
  ## Storage Setup
1137
1195
 
1138
- Mythik stores specs in the consumer's database. Three tables are involved depending on which features the consumer opts into:
1196
+ Mythik stores specs in the consumer's selected store. Browser-safe stores (`MemorySpecStore`, `SupabaseSpecStore`, and their versioned/environment variants) are imported from `mythik`. Node-only SQL stores (`SqlSpecStore`, `SqlVersionedSpecStore`, `SqlEnvironmentStore`, `FileSpecStore`, and SQL Server compatibility classes) are imported from `mythik/server`.
1197
+
1198
+ SQL-backed stores support `sqlserver`, `postgres`, `mysql`, and `sqlite` through one `SqlDriver` boundary. The same three tables are involved across dialects:
1139
1199
 
1140
- - **`screens`** (base, **REQUIRED**) — current spec per `id`. Used by every adapter (`SqlServerSpecStore`, `SupabaseSpecStore`, and the versioned subclasses which extend the base).
1141
- - **`screen_versions`** (opt-in) — version history. Required only when using `SqlServerVersionedSpecStore` / `SupabaseVersionedSpecStore` (i.e., consumers calling `mythik push --author <name>` or `runPush({ author })`).
1142
- - **`screen_environments`** (opt-in) — environment promotions. Required only when using `SqlServerEnvironmentStore` / `SupabaseEnvironmentStore`.
1200
+ - **`screens`** (base, **REQUIRED**) — current spec per `id`. Used by every SQL/Supabase adapter.
1201
+ - **`screen_versions`** (opt-in but recommended) — version history. Required when the CLI runs with `--author`, when `runPush`/`runPatch` receives an `author`, or when code uses `SqlVersionedSpecStore` / `SupabaseVersionedSpecStore`.
1202
+ - **`screen_environments`** (opt-in but recommended) — environment promotions. Required for `history`, `diff`, `rollback`, `envs`, `promote`, and `SqlEnvironmentStore` / `SupabaseEnvironmentStore` workflows.
1143
1203
 
1144
- **The framework does NOT auto-create any of these tables.** Apply the schema below once during initial setup of the consumer's database, then the framework operates against the running tables.
1204
+ Use the CLI to bootstrap SQL store tables:
1145
1205
 
1146
- **Authoritative since v0.1.0.** The framework's INSERT/SELECT/UPDATE statements (in `packages/core/src/spec-stores/sqlserver.ts`, `supabase.ts`, `sqlserver-versioned.ts`, `supabase-versioned.ts`) require these exact columns by name + semantic type. AI applying this schema in the consumer's environment MUST preserve all columns, constraints, and indexes; the SQL dialect is free to vary (NVARCHAR/VARCHAR/TEXT, BIT/BOOLEAN, DATETIME2/TIMESTAMPTZ — pick what the target DB supports).
1206
+ ```bash
1207
+ mythik init-store --dialect sqlite --target ./mythik.db
1208
+ mythik init-store --dialect sqlserver --server localhost --database Mythik --user "$DB_USER" --password "$DB_PASSWORD" --encrypt false --trust-server-certificate
1209
+ mythik init-store --dialect postgres --dry-run
1210
+ mythik init-store --dialect mysql --dry-run
1211
+ ```
1212
+
1213
+ `init-store --dry-run` prints the canonical idempotent DDL. `init-store` can initialize reachable SQL stores directly, including SQL Server through explicit flags. Production teams may still apply the dry-run DDL through their normal database deployment path. Runtime store reads/writes do not silently create missing tables.
1147
1214
 
1148
- **Versioned stores extend the base store.** When `SqlServerVersionedSpecStore.saveVersion(id, doc, meta)` runs, it appends to `screen_versions` AND calls the inherited base `save(id, doc)` which writes to `screens` (`packages/core/src/spec-stores/sqlserver-versioned.ts:110-111`). This means `screens` MUST exist for every consumer there is no "versioning-only" mode that skips it.
1215
+ **Authoritative since v0.1.0.** The framework's SQL stores require the columns below by name and meaning. Use the generated DDL where possible; if translating manually, preserve all columns, constraints, and uniqueness rules.
1216
+
1217
+ **Versioned stores extend the base store.** When `SqlVersionedSpecStore.saveVersion(id, doc, meta)` or `SupabaseVersionedSpecStore.saveVersion(id, doc, meta)` runs, it appends to `screen_versions` and updates `screens`. This means `screens` MUST exist for every consumer — there is no "versioning-only" mode that skips it.
1149
1218
 
1150
1219
  ### Table 0 — `screens` (current spec — REQUIRED for all stores)
1151
1220
 
1152
1221
  | Column | Semantic type | Nullable | Default | Notes |
1153
1222
  |---|---|---|---|---|
1154
1223
  | `id` | short string (≤255) | NOT NULL | — | Spec identifier; PK. Framework reads/writes via `WHERE id = ?` |
1155
- | `name` | short string (≤255) | NULL | — | Display name. Framework's INSERT populates with the same value as `id` (`sqlserver.ts:76` MERGE: `INSERT (id, name, spec, version, is_active) VALUES (@id, @id, @spec, 1, 1)`) |
1156
- | `spec` | long string (≥1MB capacity) | NOT NULL | — | JSON-encoded current spec. SQL Server uses `NVARCHAR(MAX)` text + defensive parse (`sqlserver.ts:58`); Postgres / Supabase MUST be `jsonb` (see below) |
1157
- | `version` | integer | NOT NULL | `1` | Incremented on every UPDATE (SQL Server: app-level at `sqlserver.ts:74`; Postgres: via TRIGGER, see below) |
1158
- | `is_active` | boolean | NOT NULL | `true` | Framework's INSERT sets `1`/`true` (`sqlserver.ts:76`); UPDATE never touches it |
1224
+ | `name` | short string (≤255) | NULL | — | Display name. Framework inserts the same value as `id` by default. |
1225
+ | `spec` | JSON-capable long value | NOT NULL | — | Current spec. SQL Server and SQLite DDL store JSON as text; PostgreSQL uses `JSONB`; MySQL uses `JSON`. Generic SQL stores parse string values defensively. |
1226
+ | `version` | integer | NOT NULL | `1` | Incremented by the generic SQL store on every update. |
1227
+ | `is_active` | boolean | NOT NULL | `true` | Framework inserts active specs; current SQL store list operations do not filter by this column. |
1159
1228
  | `created_at` | UTC timestamp | — | — | OPTIONAL — framework does not read or write this column. If you add it for consumer hygiene (audit trail), make it `NOT NULL` with a NOW UTC default; otherwise omit entirely. |
1160
- | `updated_at` | UTC timestamp | NOT NULL | NOW UTC | Required. Updated on every UPDATE (SQL Server: app-level via `updated_at = GETUTCDATE()` at `sqlserver.ts:74`; Postgres: via TRIGGER, see below). Framework's INSERT at `sqlserver.ts:76` omits this column, so initial value comes from the column DEFAULT. |
1229
+ | `updated_at` | UTC timestamp/string | NOT NULL | NOW UTC | Required. The generic SQL store writes this value on insert and update; the database default covers manual/bootstrap inserts. |
1161
1230
 
1162
1231
  **Constraints**:
1163
1232
  - `PRIMARY KEY (id)` — required (every framework query filters by `id`)
1164
1233
 
1165
1234
  **Indexes** (all RECOMMENDED, none framework-required):
1166
- - `(is_active, id)` — useful for consumer queries filtering active screens. The framework's `list()` uses only `ORDER BY id` (`sqlserver.ts:83`, `supabase.ts:66`) and does not filter by `is_active`, so this index is consumer-hygiene only.
1235
+ - `(is_active, id)` — useful for consumer queries filtering active screens. The framework's `list()` uses only `ORDER BY id` and does not filter by `is_active`, so this index is consumer-hygiene only.
1167
1236
 
1168
1237
  ### Table 1 — `screen_versions` (version history — opt-in: VersionedSpecStore)
1169
1238
 
@@ -1201,20 +1270,22 @@ Mythik stores specs in the consumer's database. Three tables are involved depend
1201
1270
  **Constraints**:
1202
1271
  - `PRIMARY KEY (screen_id, environment)` — one promotion record per `(spec, env)` pair; re-promote upserts via MERGE/UPSERT
1203
1272
 
1204
- ### Postgres / Supabase: `jsonb` for `spec` and `patches`
1273
+ ### JSON column policy
1205
1274
 
1206
- On Postgres-flavored backends (Supabase, plain Postgres), the JSON columns MUST be `jsonb`, not `text`:
1275
+ Use the dialect-native JSON type when the dialect has one:
1207
1276
 
1208
- - `screens.spec` Supabase code path returns `rows[0].spec` directly (`packages/core/src/spec-stores/supabase.ts:41`) without `typeof === 'string' ? JSON.parse(...) : raw` defense. A `text` column would return a string and downstream consumers (e.g., `MythikInstance.getSpec`, `MythikRenderer`) would receive a stringified spec instead of an object.
1209
- - `screen_versions.spec` and `screen_versions.patches` — Supabase versioned path consumes these as already-parsed objects from PostgREST (`packages/core/src/spec-stores/supabase-versioned.ts:132`, `:146`, `:149`); a `text` column would cause `applyPatches` / `structuredClone` to receive a string instead of an object, silent corruption.
1277
+ - PostgreSQL / Supabase: `JSONB`
1278
+ - MySQL: `JSON`
1279
+ - SQL Server: `NVARCHAR(MAX)`
1280
+ - SQLite: `TEXT`
1210
1281
 
1211
- SQL Server stores parse `NVARCHAR(MAX)` defensively (`typeof === 'string' ? JSON.parse(...) : raw` at `sqlserver.ts:58`, `sqlserver-versioned.ts:133-135` and `:153`) and tolerate either form, but Postgres-flavored stores have no such defense.
1282
+ Generic SQL stores parse stored JSON defensively when the driver returns strings. Supabase's browser-safe REST stores consume PostgREST JSON as already-parsed objects, so Supabase tables should use `jsonb`, not `text`.
1212
1283
 
1213
- ### Postgres / Supabase: triggers for `screens.updated_at` and `screens.version`
1284
+ ### Supabase trigger note
1214
1285
 
1215
- On Postgres-flavored backends, `screens.updated_at` and `screens.version` MUST be maintained by `BEFORE UPDATE` triggers because the Supabase save path sends only the `spec` field (`packages/core/src/spec-stores/supabase.ts:55`: `body: JSON.stringify({ spec })`); it does NOT update `updated_at` or `version` itself. SQL Server stores set both columns app-level inside the MERGE (`sqlserver.ts:74`: `UPDATE SET spec = @spec, updated_at = GETUTCDATE(), version = version + 1`) and do not need triggers.
1286
+ Generic SQL stores write `screens.updated_at` and `screens.version` directly. Supabase's browser-safe REST store updates only the `spec` field, so Supabase projects should keep the `BEFORE UPDATE` triggers below if they want `updated_at` and `version` to advance on Supabase REST writes.
1216
1287
 
1217
- Recommended trigger pair on Postgres:
1288
+ Recommended trigger pair on Supabase/PostgreSQL when using `SupabaseSpecStore`:
1218
1289
 
1219
1290
  ```sql
1220
1291
  CREATE OR REPLACE FUNCTION screens_update_updated_at()
@@ -1232,16 +1303,17 @@ CREATE TRIGGER screens_version_trigger
1232
1303
  FOR EACH ROW EXECUTE FUNCTION screens_increment_version();
1233
1304
  ```
1234
1305
 
1235
- Trigger function names are arbitrary (use any unique name); the framework does not introspect them.
1306
+ Trigger function names are arbitrary (use any unique name); the framework does not introspect them. Plain PostgreSQL consumers using `SqlSpecStore` through `mythik/server` do not need these triggers for framework writes.
1236
1307
 
1237
- `screen_versions` and `screen_environments` do NOT need such triggers — those tables are append-only / upsert-on-PK and the framework writes all columns explicitly on each INSERT/UPSERT (`supabase-versioned.ts:61-69`, `:77-85`, `:89-97`, `:243-249`).
1308
+ `screen_versions` and `screen_environments` do NOT need such triggers — those tables are append-only / upsert-on-PK and the framework writes all columns explicitly on each INSERT/UPSERT.
1238
1309
 
1239
- ### Idempotency requirement
1310
+ ### Initialization and idempotency
1240
1311
 
1241
- The applied DDL MUST be idempotent re-running it on a database that already has the tables MUST NOT fail and MUST NOT recreate. Use the target dialect's "if not exists" form:
1242
- - SQL Server: `IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '...') CREATE TABLE ...`
1312
+ `mythik init-store` uses canonical idempotent DDL, so re-running it on a database that already has the tables does not fail and does not recreate existing tables. If translating the DDL manually into a deployment script, preserve the target dialect's "if not exists" guard:
1313
+ - SQL Server: `IF OBJECT_ID(N'...', N'U') IS NULL CREATE TABLE ...`
1243
1314
  - Postgres / Supabase: `CREATE TABLE IF NOT EXISTS ...`
1244
1315
  - MySQL / MariaDB: `CREATE TABLE IF NOT EXISTS ...`
1316
+ - SQLite: `CREATE TABLE IF NOT EXISTS ...`
1245
1317
 
1246
1318
  ### Verification (post-apply)
1247
1319
 
@@ -1255,29 +1327,33 @@ WHERE TABLE_NAME IN ('screens', 'screen_versions', 'screen_environments')
1255
1327
  ORDER BY TABLE_NAME, ORDINAL_POSITION;
1256
1328
  ```
1257
1329
 
1258
- Confirm: 7 columns for `screens` (`id`, `name`, `spec`, `version`, `is_active`, `created_at`, `updated_at`), 10 columns for `screen_versions` (`id` through `created_at`), 5 columns for `screen_environments` (`screen_id` through `promoted_by`). If any column is missing or has a wrong NULL/NOT NULL flag, abort and report — the framework's INSERT/SELECT/UPDATE will fail at runtime otherwise.
1330
+ Confirm: 6 required columns for `screens` (`id`, `name`, `spec`, `version`, `is_active`, `updated_at`), 10 columns for `screen_versions` (`id` through `created_at`), and 5 columns for `screen_environments` (`screen_id` through `promoted_by`). `created_at` on `screens` is optional. If any required column is missing or has a wrong NULL/NOT NULL flag, abort and report — the framework's INSERT/SELECT/UPDATE will fail at runtime otherwise.
1259
1331
 
1260
- On Postgres / Supabase, additionally verify the two triggers on `screens` are present (`information_schema.triggers` WHERE `event_object_table = 'screens'`); without them, `updated_at` and `version` will silently stop advancing on save.
1332
+ On Supabase projects using `SupabaseSpecStore`, additionally verify the two triggers on `screens` are present (`information_schema.triggers` WHERE `event_object_table = 'screens'`); without them, `updated_at` and `version` will not advance on Supabase REST writes.
1261
1333
 
1262
1334
  ### Custom table names
1263
1335
 
1264
1336
  All three table names are independently configurable; overriding one does not require overriding the others:
1265
1337
 
1266
1338
  ```ts
1267
- new SqlServerSpecStore({ ..., table: 'my_screens' })
1268
- new SqlServerVersionedSpecStore({ ..., table: 'my_screens', versionsTable: 'my_versions' })
1269
- new SqlServerEnvironmentStore({ ..., table: 'my_envs' })
1339
+ import { createSqlDriver, SqlEnvironmentStore, SqlSpecStore, SqlVersionedSpecStore } from 'mythik/server';
1340
+
1341
+ const driver = createSqlDriver({ dialect: 'postgres', connection: process.env.DATABASE_URL });
1342
+
1343
+ new SqlSpecStore({ driver, table: 'my_screens' });
1344
+ new SqlVersionedSpecStore({ driver, table: 'my_screens', versionsTable: 'my_versions' });
1345
+ new SqlEnvironmentStore({ driver, table: 'my_envs' });
1270
1346
  ```
1271
1347
 
1272
- (`SqlServerVersionedSpecStore` accepts both `table` from the base config and `versionsTable` from its own config, since it extends `SqlServerSpecStore`.)
1348
+ `SqlVersionedSpecStore` accepts both `table` from the base config and `versionsTable` from its own config, since it extends `SqlSpecStore`.
1273
1349
 
1274
1350
  If a consumer overrides any name, AI applies the same schema under the consumer-chosen name.
1275
1351
 
1276
- **Identifier safety scope**: SQL Server stores enforce `assertValidIdentifier` on every configured name (regex `/^[a-zA-Z_][a-zA-Z0-9_.]*$/`, max 128 chars — see `packages/core/src/security/identifier-guard.ts`). The validator is invoked in the constructor of `SqlServerSpecStore` (`sqlserver.ts:26`), `SqlServerVersionedSpecStore` (`sqlserver-versioned.ts:25`), and `SqlServerEnvironmentStore`. This blocks SQL injection via table-name interpolation in `[${table}]` / `[${versionsTable}]` template literals. Supabase stores do NOT validate any configured name (it flows directly into the REST URL via `${this.tableName}`); consumer code passing user-controlled table names to a Supabase store must validate them upstream.
1352
+ **Identifier safety scope**: generic SQL stores enforce identifier validation on configured table names (regex `/^[a-zA-Z_][a-zA-Z0-9_.]*$/`, max 128 chars). This blocks SQL injection via table-name interpolation before dialect quoting runs. Supabase stores do not validate configured table names; consumer code passing user-controlled table names to a Supabase store must validate them upstream.
1277
1353
 
1278
- ### Canonical reference (SQL Server)
1354
+ ### Canonical reference
1279
1355
 
1280
- A working SQL Server bootstrap script for the **versioning tables only** (`screen_versions` + `screen_environments`) exists at `Demos/Mythik/demo-sqlserver/setup-versioning.ts` (in the demos workspace, outside the framework repo). It implements those two tables idempotently using `mssql` directly and is the canonical reference if the AI is uncertain about idempotent SQL Server syntax. There is no separate canonical script for the base `screens` table — AI applies this spec directly (the base table is short enough that translating the spec to a `CREATE TABLE` statement is unambiguous). For non-SQL-Server targets, AI translates both the spec and (if useful) the versioning script's structure to the target dialect.
1356
+ The canonical SQL store DDL ships in the installed package. Use `mythik init-store --dialect <sqlserver|postgres|mysql|sqlite> --dry-run` or `getSqlStoreDdl(dialect)` from `mythik/server` to inspect it. Do not copy DDL from unrelated local artifacts.
1281
1357
 
1282
1358
  ### Schema evolution
1283
1359
 
@@ -1377,3 +1453,11 @@ When the framework changes the schema in a future version, this section will gai
1377
1453
  87. DNA numeric seeds (`roundness`, `density`, `depth`, `formality`) are canonical `0–1` values. Generate `0.7`, not `70`. The runtime tolerates legacy `0–100` values by normalizing any numeric seed greater than `1` with `/100` during DNA derivation, including initial AppSpec load and runtime `updateTokens`.
1378
1454
  88. API query endpoints can combine `pagination: "offset"` with `scopeFilter`. For generated counts, Mythik applies the scope filter to the source query before `COUNT(*)`, so paginated totals remain tenant-scoped. Prefer generated counts. If a custom `endpoint.count` is truly needed with `scopeFilter`, include `{{scopeWhere[:alias]}}` or `{{scopeAnd[:alias]}}`; Mythik expands the macro to the correct scope predicate and removes it for bypass roles. Other custom count SQL is left verbatim. Use `:alias` for JOIN/subquery counts, and do not reference internal scope params directly.
1379
1455
  89. Transaction `confirm` failures from `fetch` preserve backend error details for `onError`. Read `/tx/error/message` for the best available message; when the backend returns `{ error: { code, message } }`, Mythik keeps that message, `code`, HTTP `status`, and raw `data` after rollback. Do not parse `/ui/lastError` from transaction specs.
1456
+ 90. For SQL-backed stores and servers, use the generic `mythik/server` SQL boundary. Initialize tables with `mythik init-store --dialect <sqlserver|postgres|mysql|sqlite>` or inspect DDL with `--dry-run`; configure the CLI with `--store`, `--url`/`--filename`/SQL Server flags, or `MYTHIK_*` environment variables; and keep spec edits on the required `manifest -> elements -> patch --from-file -> validate` loop. Write custom API SQL in the selected dialect with Mythik named params (`@name`); Mythik does not translate custom SQL between dialects.
1457
+ 91. Event arrays may mix normal actions and transaction bindings. Mythik executes them sequentially and awaits each transaction before continuing. Use this when a flow needs a small action before or after an optimistic transaction; do not wrap a transaction inside another transaction phase.
1458
+ 92. `$ref` and `$template` placeholders can read nested values from `$let` object bindings with dot notation, for example `{ "$ref": "user.name" }` or `${user.name}`. If a dotted `$ref` segment is missing, runtime throws an unknown `$ref` error rather than silently returning undefined.
1459
+ 93. Use `params.skipIf` for a dispatch-time action guard when an action should be skipped but the surrounding action chain should continue. `skipIf` is resolved before other params and is removed before the action handler runs. Do not use `skipIf` as a substitute for form validation or transaction rollback.
1460
+ 94. For critical direct `fetch` actions, especially `initialActions` screen loads, set `params.errorTarget` to a screen-owned `/ui/...` path and render that path visibly. The fetch action still writes global `/ui/lastError`, but `/ui/lastError` is shared and can be overwritten by unrelated fetches. On success, Mythik clears the provided `errorTarget`.
1461
+ 95. `select.options` accepts strings, `{ label, value }` objects, or catalog-shaped objects when `labelKey` and `valueKey` are provided. Example: `{ "options": { "$state": "/cat/services/data" }, "labelKey": "name", "valueKey": "id" }`. Values are emitted as strings from `on.change`; malformed option data renders disabled diagnostics instead of blank clickable rows or crashes.
1462
+ 96. SQL adapters are optional peer dependencies, not installed-by-default runtime payload. Browser-only apps install `mythik mythik-react` without database drivers. SQL-backed stores/servers must install exactly one selected adapter: `mssql`, `pg`, `mysql2`, or `better-sqlite3`. SQLite uses native `better-sqlite3`; native-build helper warnings from that adapter are not Mythik runtime failures.
1463
+ 97. Missing SQL adapter errors are actionable. The thrown `SqlDriverError` includes `packageName`, `installCommand`, and a message with the exact `npm install ...` command for the selected dialect.
@@ -311,7 +311,7 @@ Nested conditionals for multi-branch logic:
311
311
  ```
312
312
  Interpolates values into a string. Supports two reference types:
313
313
  - State paths: `${/user/name}` — reads from application state
314
- - `$let` bindings: `${age}` — reads from a named binding defined with `$let`
314
+ - `$let` bindings: `${age}` or `${user.name}` — reads from a named binding defined with `$let`
315
315
 
316
316
  ### `$computed` — Registered function
317
317
  ```json
@@ -328,6 +328,15 @@ Calls a registered function with resolved arguments. **Prefer `$math`, `$array`,
328
328
  ```
329
329
  Define a named value once, reference it multiple times. Avoids repetition.
330
330
 
331
+ `$ref` can read nested values from an object binding with dot notation:
332
+ ```json
333
+ {
334
+ "$let": { "user": { "$state": "/auth/user" } },
335
+ "$in": { "$ref": "user.name" }
336
+ }
337
+ ```
338
+ The same dotted binding lookup is available inside `$template` placeholders, for example `${user.name}`.
339
+
331
340
  **Later bindings can reference earlier ones:**
332
341
  ```json
333
342
  {
@@ -922,6 +931,7 @@ The `fetch` action supports:
922
931
  - **Empty string sanitization:** Empty strings in body are automatically converted to `null` (prevents database errors for typed columns)
923
932
  - **Loading state:** Sets `/ui/loading` to true while in flight, false when done
924
933
  - **Error handling:** On failure, sets `/ui/lastError` with status and message
934
+ - **Screen-owned errors:** Optional `errorTarget` receives the same structured error and is cleared on success; use it for critical `initialActions` loads
925
935
 
926
936
  ```json
927
937
  {
@@ -940,6 +950,10 @@ The `fetch` action supports:
940
950
  }
941
951
  ```
942
952
 
953
+ Action arrays may also mix normal action bindings and transaction bindings. Mythik runs each entry in order and waits for a transaction to finish before dispatching the next entry.
954
+
955
+ Any action binding can include `params.skipIf`. Mythik resolves `skipIf` at dispatch time before the rest of the params; if it is truthy, that action is skipped and the surrounding action chain continues.
956
+
943
957
  ### Transactions (Optimistic Updates)
944
958
 
945
959
  Use `transaction` for CRUD operations that need instant UI feedback. The framework takes a state snapshot, applies your changes instantly, then confirms with the server. On failure, it rolls back automatically.
@@ -1950,11 +1964,11 @@ Reference tokens or write custom values:
1950
1964
  64. **CLI warns on unknown prop names** — `mythik push` and `mythik validate` check prop names against known schemas for all 38 primitives. Unknown props generate warnings (not errors) with Levenshtein suggestions: `⚠ unknown prop "inputType" for type "input" — did you mean "type"?`. Warnings don't block saves
1951
1965
  65. **Use `audit` config for automatic change tracking** — endpoint-level config that auto-injects username and timestamp into CRUD INSERT and UPDATE operations. Column names are configurable per table. All fields are optional — only configured fields are injected. Audit values override client-sent values (prevents spoofing)
1952
1966
  66. **Audit `timezone` for correct local timestamps** — set `"timezone": "America/El_Salvador"` (IANA string) in audit config. Without timezone, timestamps are UTC. Use timezone when the database stores local time. The framework converts using `Intl.DateTimeFormat` — works regardless of server location
1953
- 67. **CRUD queries are trigger-safe** — INSERT uses `SCOPE_IDENTITY()` + SELECT instead of `OUTPUT INSERTED.*`. UPDATE uses SELECT after the update. Both patterns are compatible with SQL Server tables that have triggers enabled
1967
+ 67. **CRUD queries are dialect-aware** — Generated CRUD routes compile through the active SQL driver. SQL Server uses trigger-safe identity retrieval; PostgreSQL/SQLite use `RETURNING` where available; MySQL uses the driver insert id path and targets MySQL 8.0.19+ for generated upsert SQL. Custom SQL remains dialect-native and is not translated between databases.
1954
1968
  68. **CustomJWTProvider maps response paths explicitly** — `tokenPath`, `refreshTokenPath`, and `userPath` are dot paths against the full login/refresh response. `rolePath` and `rolesPath` are compat dual: plain keys (`"role"`, `"roles"`) resolve inside the extracted user object, dotted paths (`"user.role"`, `"data.user.role"`) resolve against the full response. If no role/roles are found, `defaultRole` is used and development logs a warning. HTTP errors read `response.error.message` first, then `response.message`
1955
1969
  69. **ApiSpec is pure declarative — no connection, no secrets** — `createServer` receives database connection and JWT secret in `MythikServerConfig`, not in the ApiSpec. The ApiSpec only contains endpoints, catalogs, auth model (provider, policies, scopeFilter), and audit config. This allows the ApiSpec to be stored safely in the database
1956
1970
  70. **Server loads api-spec from SpecStore** — `createServer({ spec: { store: myStore, id: 'my-api' }, database: {...}, auth: {...} })`. Supports three modes: file path (string), ApiSpec object (testing), or `{ store, id }` (database). The server never owns a SpecStore — the caller provides one
1957
- 71. **SpecStore table is configurable** — `new SqlServerSpecStore({ ..., table: 'api_specs' })` (imported from `mythik/server` Node-only) and `new SupabaseSpecStore({ ..., table: 'api_specs' })` (imported from `mythik` — browser-safe). Default: `'screens'`. Enables separate tables for frontend specs and backend specs with different DB permissions
1971
+ 71. **SpecStore table is configurable** — Generic SQL stores use `new SqlSpecStore({ driver, table: 'api_specs' })` from `mythik/server`; Supabase uses `new SupabaseSpecStore({ ..., table: 'api_specs' })` from `mythik`. Default: `'screens'`. Enables separate tables for frontend specs and backend specs with different DB permissions.
1958
1972
  72. **Api-specs are never served to the browser** — `GET /api/screens/:id` and `GET /api/app/:id` return 404 for documents with `type: "api"`. Prevents information disclosure of table names, SQL queries, and auth config
1959
1973
  73. **CLI operates on api-specs automatically** — `mythik validate my-api`, `mythik pull my-api`, `mythik push my-api` detect `type: "api"` and use the apiHandler. Manifest shows catalogs, endpoints, auth config. Elements support dot-notation: `mythik elements my-api endpoints.records-crud`
1960
1974
  74. **Use `mythik contract` for frontend↔backend validation** — `mythik contract --app app-demo --api records-api`. Cross-validates screen fetch URLs against api-spec endpoints. Supports multiple api-specs (comma-separated). Use `--api-table` when api-specs are in a separate table. Use `--base-url` to strip host from URLs. Use `--json` for CI/CD integration
@@ -1974,12 +1988,12 @@ Reference tokens or write custom values:
1974
1988
  88. **Rollback creates new version, never rewrites history** — `executeRollback(specId, toVersion)` creates vN+1 with the content of the target version. All intermediate versions are preserved. Impact analysis shows lost changes with author attribution and affected environments. Moving an environment pointer is a separate operation via `envs --set`
1975
1989
  89. **Lazy bootstrap for existing specs** — first versioned save on an existing spec with no history automatically creates v1 (snapshot of current spec), then saves the change as v2. No migration script needed. Specs without history continue working via base `SpecStore.load()`
1976
1990
  90. **Use `navigateScreen` and `goBackScreen` in specs** — `navigateScreen` and `goBackScreen` are registered by MythikApp and call the AppEngine directly. The built-in `navigate` and `goBack` only set state intents. Always use `navigateScreen`/`goBackScreen` in specs for navigation that works. Example: `{ "action": "goBackScreen" }` goes back to the previous screen in the navigation history, regardless of which screen navigated to the current one
1977
- 91. **CLI `--table` flag overrides store table** — all commands accept `--table <name>` to read/write from a different table. Use `--table api_specs` to operate on api-specs. The flag overrides `config.sqlserver.table` or `config.supabase.table`. Environment and version tables are NOT affected by `--table` they always use their fixed names (`screen_environments`, `screen_versions`)
1991
+ 91. **CLI `--table` flag overrides the current spec table** — all commands accept `--table <name>` to read/write from a different base table. Use `--table api_specs` to operate on api-specs. The flag overrides the base table for Supabase, SQL Server, PostgreSQL, MySQL, and SQLite stores. Version and environment tables are configured separately with `MYTHIK_VERSIONS_TABLE`, `MYTHIK_ENVIRONMENTS_TABLE`, or the equivalent `.mythikrc` SQL settings.
1978
1992
  92. **`push` and `patch` version automatically with `--author`** — when `--author` is provided, both commands use `VersionedSpecStore.saveVersion()` instead of `SpecStore.save()`. The version includes author, source type (`push`/`patch`), and optional `--description`. Without `--author`, commands work as before (no versioning). Example: `mythik patch screen-id --author alice --description "Fixed layout"`
1979
1993
  93. **`mythik history` shows inline diffs** — each version in the history output shows the actual changes (before/after values), not just a summary. Uses `computeStructuralDiff` between consecutive versions. Example output: `~ element "btn" prop content: "Send" → "Submit"`
1980
1994
  95. **`ai-context.md` is the AI-optimized spec reference** — compressed from this reference-doc (1145 lines vs 2658). Use ai-context.md for spec generation, reference-doc for full human reference. Validated via agent-based testing with progressive difficulty levels (L1-L4). Test scenarios in `../ai-context-test-scenarios.md`, results in `../ai-context-test-results.md` (framework-dev, not part of consumer publish surface)
1981
1995
 
1982
- 94. **SqlServerVersionedSpecStore available** — `resolveVersionedStore` supports `sqlserver` store type. Creates `SqlServerVersionedSpecStore` (specs + version history) + `SqlServerEnvironmentStore` (environment pointers). Requires `screen_versions` and `screen_environments` tables create with `setup-versioning.ts`
1996
+ 94. **Generic SQL versioned stores are available** — `resolveVersionedStore` supports `sqlserver`, `postgres`, `mysql`, and `sqlite` store types. It creates a driver-backed `SqlVersionedSpecStore` (specs + version history) plus `SqlEnvironmentStore` (environment pointers). Requires `screens`, `screen_versions`, and `screen_environments`; initialize with `mythik init-store` or apply the DDL from `mythik init-store --dry-run`.
1983
1997
  96. **SupabaseVersionedSpecStore available** — `resolveVersionedStore` supports `supabase` store type. Uses PostgREST REST API (no `@supabase/supabase-js` dependency). Same snapshot+patches pattern as SqlServer. Environment upsert uses `on_conflict=screen_id,environment` for PostgREST compatibility. Requires `screen_versions` and `screen_environments` tables created in Supabase dashboard
1984
1998
  97. **`variant` is a universal prop** — any primitive can use `variant` when component variants are defined in `tokens.components.{type}.{variant}`. The validator accepts `variant` on all primitives (via `COMMON_PROPS`). The render engine resolves variants before passing props to the primitive
1985
1999
  98. **Use DNA seeds for app identity** — define `tokens.dna` in AppSpec with 1-8 seed values. The framework derives all visual tokens (colors via OKLCH tonal palette, shape, typography, spacing, elevation, motion, opacity) plus auto dark mode. No manual color palette or radius scale needed — DNA generates it from `{ "primary": "#0D9488" }`
@@ -3043,7 +3057,7 @@ The current background and motion stack combines app-level `LayerBackground`, an
3043
3057
 
3044
3058
  211. **`useShapeAnimations(ref, animations, options)` — Layer 3 web runner** — Exported from `mythik-react` via `packages/react/src/animation/useShapeAnimations.ts`. React hook for SVG-child animations (`<path>`/`<circle>`/`<rect>`/`<g>`…). Consumes the same `ElementAnimations` contract as `useElementAnimations` but narrowed to the `ambient` trigger ONLY; shape children have no hover/focus/active contract and no distinct mount ceremony. Attaches CSS animations via `el.style.animation` (surgical, preserves other inline styles); keyframes register once through the CSSOM singleton (zero `dangerouslySetInnerHTML`) and dedupe by hash so multiple shape instances with the same recipe share one CSS rule. Dev mode warns when non-ambient triggers are passed. Production silently ignores them. `options.recipes` SHOULD be stable for useMemo performance.
3045
3059
 
3046
- 212. **`useShapeAnimations(animations, options)` — Layer 3 RN runner** — Repository-preview implementation in `packages/react-native/src/animation/useShapeAnimations.ts`; it is not part of the initial npm publish surface. Reanimated parity of rule 211: returns `{ animatedProps }` that the consumer spreads onto `Animated.createAnimatedComponent(Path)` from `react-native-svg`. Uses the `HARD_PER_TRIGGER` (=6) fixed-pool `useSharedValue` pattern (`useSharedValueArray` helper, also exported) so React Hook count stays stable. Reuses `composeRNStyle(contributions, interpolate, interpolateColor)` shared with `useElementAnimations` — single composition pipeline for View-style and animated SVG props. Relies on `react-native-svg` v13+ auto-translating the transform array into SVG `transform="..."` strings. Same dev-mode non-ambient-trigger warning as the web hook.
3060
+ 212. **`useShapeAnimations(animations, options)` — Layer 3 RN runner** — Repository-preview implementation in `packages/react-native/src/animation/useShapeAnimations.ts`; it is not part of the supported npm publish surface yet. Reanimated parity of rule 211: returns `{ animatedProps }` that the consumer spreads onto `Animated.createAnimatedComponent(Path)` from `react-native-svg`. Uses the `HARD_PER_TRIGGER` (=6) fixed-pool `useSharedValue` pattern (`useSharedValueArray` helper, also exported) so React Hook count stays stable. Reuses `composeRNStyle(contributions, interpolate, interpolateColor)` shared with `useElementAnimations` — single composition pipeline for View-style and animated SVG props. Relies on `react-native-svg` v13+ auto-translating the transform array into SVG `transform="..."` strings. Same dev-mode non-ambient-trigger warning as the web hook.
3047
3061
 
3048
3062
  213. **Cross-platform Layer 3 parity pins** — `buildCSSKeyframes` (web) and `buildReanimatedSpec` (RN) interpret the same resolved `AnimationSpec` identically on load-bearing invariants: duration agreement (ms count matches regardless of input form `'28s'`/`'28000'`), keyframe stop count (web `%` markers match RN `inputRange` length), direction semantic (`'alternate'` token ⇔ `timing.reverse=true`), iterations `'infinite'`/numeric count, animated-prop enumeration (`translateX/Y`/`rotateDeg`/`scale`).
3049
3063
 
@@ -3115,7 +3129,7 @@ The current background and motion stack combines app-level `LayerBackground`, an
3115
3129
  243. **CRUD endpoint generates 3 routes from 1 declaration** — an endpoint with `crud: { table, primaryKey, insertable, updatable }` generates `POST <path>`, `PUT <path>/:id`, `DELETE <path>/:id`. Do not declare 3 endpoints. See `ai-context-runtime-semantics.md § 3.1`.
3116
3130
  244. **`/api/auth/login` expects `{ username, password }`** — not `email`. Use `loginBody` template to map consumer email convention → framework username. See `ai-context-runtime-semantics.md § 3.2`.
3117
3131
  245. **Query endpoints envelope responses in `{ data: [...] }`** — shape is `{ data, total?, page?, pageSize?, totals? }`. Consumer specs read `response.data` via state; envelope is NOT auto-unwrapped. See `ai-context-runtime-semantics.md § 3.3`.
3118
- 246. **Browser vs Server entries — `mythik` vs `mythik/server`** (v0.1.0). The default `mythik` entry is browser-safe by construction: zero transitive Node-only imports. `FileSpecStore`, `SqlServerSpecStore`, `SqlServerVersionedSpecStore`, `SqlServerEnvironmentStore` and their config types live in `mythik/server`.
3132
+ 246. **Browser vs Server entries — `mythik` vs `mythik/server`** (v0.1.0). The default `mythik` entry is browser-safe by construction: zero transitive Node-only imports. `FileSpecStore`, generic SQL stores (`SqlSpecStore`, `SqlVersionedSpecStore`, `SqlEnvironmentStore`), SQL drivers (`createSqlDriver`, `getSqlStoreDdl`), and SQL Server compatibility stores live in `mythik/server`.
3119
3133
 
3120
3134
  247. **`derive` and `dataSources` are processed at runtime per spec mount** (v0.1.0). When `spec.derive` is present, the framework instantiates a `DeriveEngine`, evaluates all derive paths in topological order at mount, and re-evaluates dirty paths reactively on state changes. Derive paths are protected: setState targeting a derive path errors at validate time and runtime. When `spec.dataSources` is present, the framework instantiates a `DataSourcesEngine`, performs initial fetches (deferred to reactive resolution when URL template deps are undefined), and re-fetches reactively when dependencies change. The action `refreshDataSource` (params: `{ id: string }`) is automatically registered for any spec with dataSources. URL templating requires the explicit `{ $template: '...' }` form — plain strings with `${...}` are NOT substituted (validator catches at load). See `ai-context-runtime-semantics.md` for lifecycle, ordering, error degradation, and state protection details.
3121
3135
 
@@ -3144,7 +3158,7 @@ Exit codes: 0 = no errors, 1 = errors found, 2 = runtime error (e.g., unreadable
3144
3158
 
3145
3159
  See `ai-context.md` for spec-gen anti-patterns the AI must NOT generate.
3146
3160
 
3147
- 250. **Storage tables are declarative, not auto-created** (v0.1.0). The framework's spec stores (`SqlServerSpecStore`, `SupabaseSpecStore`, and the versioned/environment subclasses) operate against three tables the consumer's database MUST already have: `screens` (base, REQUIRED for every adapter), `screen_versions` (opt-in, only for `*VersionedSpecStore`), and `screen_environments` (opt-in, only for `*EnvironmentStore`). The framework does NOT ship a database migration command in v0.1.0. Instead, the canonical schema is declared declaratively in `ai-context.md § Storage Setup`: per-table column specs (semantic types, nullability, defaults), constraints, indexes, Postgres-specific `jsonb` requirements, Postgres trigger requirements for `screens.updated_at` + `screens.version`, idempotency requirement, post-apply verification, custom table names, identifier-safety scope, and schema-evolution policy. The AI bootstrapping the consumer's environment reads the schema spec, translates it to the target SQL dialect (SQL Server, Postgres/Supabase, MySQL, etc.), applies idempotently (`IF NOT EXISTS`-style), and verifies via `INFORMATION_SCHEMA` queries. Future schema changes will be documented as additive ALTER instructions in the same ai-context section.
3161
+ 250. **Storage tables are initialized explicitly, never silently at runtime** (v0.1.0). SQL-backed stores operate against three tables the consumer database must already have: `screens` (base, required), `screen_versions` (version history), and `screen_environments` (environment promotions). Use `mythik init-store --dialect <sqlserver|postgres|mysql|sqlite> --dry-run` to inspect canonical idempotent DDL, initialize a local SQLite file with `mythik init-store --dialect sqlite --target ./mythik.db`, or initialize a reachable SQL Server store with explicit `--server`, `--database`, `--user`, `--password`, `--encrypt`, and `--trust-server-certificate` flags. The same schema is described in `ai-context.md § Storage Setup`. Runtime reads/writes do not create missing tables. Production deployment scripts should verify required columns after apply.
3148
3162
  251. **`security.exposeErrors` controls render error detail** (v0.1.0). Default is `true`; set `createMythik({ security: { exposeErrors: false } })` for production-like hosts that must avoid leaking error messages/stacks. `_error` render nodes write diagnostics to `/ui/renderErrors` only when exposure is enabled. Primitive/component exceptions are caught by `MythikRenderer`'s error boundary: development + exposed mode shows an overlay with message and component stack; production or `exposeErrors: false` shows a neutral placeholder. The overlay resets when the spec changes so a corrected spec can recover without remounting the host.
3149
3163
  252. **Icon packs register through `plugins.setIconRenderer`** (v0.1.0). Mythik does not bundle Phosphor/Lucide/etc. Register one host-level renderer from `MythikApp.onPlugins`; the built-in `icon` primitive keeps identity behavior and calls the renderer with `{ name, size, weight, color, style }`. If the placeholder circle renders, verify `onPlugins` registered the renderer and the consumer is not validating against stale tarballs or source aliases.
3150
3164
  253. **`overridePrimitive('icon')` returns a RenderNode, not JSX** (v0.1.0). Use it only for full primitive replacement. The renderer function must return `{ type, props, children }` with `_component` in props when targeting React. Returning `<Icon />` directly is not the primitive renderer contract and bypasses the built-in icon identity wrapper.
@@ -3204,3 +3218,11 @@ See `ai-context.md` for spec-gen anti-patterns the AI must NOT generate.
3204
3218
  284. **DNA numeric seeds are canonical `0–1`, with legacy `0–100` normalization** - Generate `tokens.dna.roundness`, `density`, `depth`, and `formality` as `0–1` numbers (`0.7`, not `70`). The runtime normalizes numeric seed values greater than `1` by dividing by `100` inside DNA derivation, so initial AppSpec load and runtime `updateTokens` share the same backward-compatible behavior.
3205
3219
  285. **Scoped pagination counts filter before aggregation** - Query endpoints may combine `pagination: "offset"` with `scopeFilter`. For generated counts, the server applies the scope filter to the query source first and then counts the scoped source, so the response `total` matches the same tenant/role slice as `data`. Prefer generated counts. If custom `endpoint.count` is truly needed with `scopeFilter`, it must include `{{scopeWhere[:alias]}}` or `{{scopeAnd[:alias]}}`; Mythik expands the macro to the correct scope predicate and removes it for bypass roles. Other custom count SQL is left verbatim. Specs should use `:alias` for JOIN/subquery counts and should not reference internal scope parameter names directly.
3206
3220
  286. **Transaction fetch failures preserve backend error details** - When a transaction `confirm` uses `fetch` and the backend returns an HTTP error payload such as `{ error: { code, message } }`, `/tx/error` is written after rollback with the best backend message plus `code`, HTTP `status`, and raw `data`. `onError` should read `/tx/error/message`; transaction specs should not read global `/ui/lastError`.
3221
+ 287. **SQL-backed stores and servers use one dialect-aware `mythik/server` boundary** - Import `createSqlDriver`, `SqlSpecStore`, `SqlVersionedSpecStore`, `SqlEnvironmentStore`, and `getSqlStoreDdl` from `mythik/server` for Node-side SQL work. Supported dialects are `sqlserver`, `postgres`, `mysql`, and `sqlite`. Initialize store tables with `mythik init-store --dialect <dialect>` for reachable SQL stores, `--target` for SQLite, or `--dry-run` for review/apply through a deployment process. SQL Server `init-store` accepts explicit `--server`, `--database`, `--user`, `--password`, `--encrypt`, and `--trust-server-certificate` flags. CLI commands share the same store flags/env vars and must keep existing-spec edits on the `manifest -> elements -> patch --from-file -> validate` loop. ApiSpec `dialect` controls generated CRUD/catalog/pagination/scope SQL; custom SQL remains dialect-native with Mythik named params (`@name`) and is not translated between dialects.
3222
+ 288. **Event arrays may mix actions and transactions** - Event bindings can be a single action, a single transaction, or an array containing both normal action bindings and transaction bindings. Mythik executes the array sequentially and awaits each transaction before continuing. Transaction phases cannot contain nested transactions.
3223
+ 289. **`$let` dotted references read nested binding values** - A `$ref` may target an object binding path such as `{ "$ref": "user.name" }`, and `$template` placeholders may read the same path as `${user.name}`. Use this for object values produced by `$let`; missing dotted `$ref` segments are invalid references and should be fixed instead of treated as optional data.
3224
+ 290. **`params.skipIf` is a dispatch-time action guard** - Any action binding may include `params.skipIf`. Mythik resolves it before resolving the rest of the params; when truthy, the action is skipped and the action chain continues. The action handler never receives `skipIf`.
3225
+ 291. **Direct `fetch` supports `errorTarget` for visible screen-load failures** - `fetch.params.errorTarget` writes HTTP/network errors to a consumer-owned state path and clears it on success. Use it for critical `initialActions` loads so the screen can render a local banner/empty state; `/ui/lastError` remains global compatibility state and can be overwritten by unrelated fetches.
3226
+ 292. **`select` supports catalog keys and invalid-option diagnostics** - `select.options` may be strings, `{ label, value }`, or catalog-shaped objects when `labelKey`/`valueKey` are provided. Values emitted from the primitive are strings. Malformed option data renders as disabled diagnostics instead of blank clickable options or crashes, so AI-generated catalog bindings fail visibly.
3227
+ 293. **SQL adapters are optional peer dependencies** - `mythik` does not install SQL drivers by default. Browser-only apps install `mythik mythik-react`. SQL-backed stores/servers must install exactly the selected adapter (`mssql`, `pg`, `mysql2`, or `better-sqlite3`). SQLite uses native `better-sqlite3`; warnings from its transitive native-build helpers are adapter-level install warnings, not Mythik runtime failures.
3228
+ 294. **Missing SQL adapter errors are actionable** - If a SQL-backed store or server uses a dialect whose adapter package is not installed, Mythik throws `SqlDriverError` with `code: "SQL_DRIVER_DEPENDENCY_MISSING"`, `packageName`, `installCommand`, and a message containing the exact `npm install ...` command.
@@ -15,7 +15,7 @@ Compiled from `docs/consumer/` into **327 atomic articles**. The wiki is optimiz
15
15
  ## Publish notes
16
16
 
17
17
  - Public package names are unscoped: `mythik`, `mythik-react`, `mythik-cli`, `mythik-server`.
18
- - React Native work is a repository preview track, not part of the initial npm publish surface.
18
+ - React Native work is a repository preview track, not part of the supported npm publish surface yet.
19
19
  - The wiki metadata folder is not publish content.
20
20
  - `docs/consumer` remains the canonical source.
21
21
 
@@ -1,6 +1,6 @@
1
- # Lint Report - Publish Readiness Refresh
2
-
3
- Validation of cross-references, stale public references, and generated wiki shape after refreshing `docs/wiki/compiled` from `docs/consumer`.
1
+ # Lint Report - Event Binding Guard Refresh
2
+
3
+ Validation of cross-references, stale public references, and generated wiki shape after refreshing the compiled wiki delta from `docs/consumer` on 2026-05-09.
4
4
 
5
5
  ## Broken Cross-References
6
6
 
@@ -24,11 +24,16 @@ Validation of cross-references, stale public references, and generated wiki shap
24
24
  - Migration pages removed: **yes** (`migration-*.md` no longer publish)
25
25
  - Metadata folder publish status: **excluded**
26
26
 
27
- ## Key Refresh Additions
28
-
29
- - `concept-public-package-names`
30
- - `cli-existing-spec-edit-loop`
31
- - `primitive-spatial-map`
27
+ ## Key Refresh Additions
28
+
29
+ - Dotted `$let` binding reads for `$ref` and `$template`
30
+ - Mixed event arrays containing actions plus transactions
31
+ - `params.skipIf` dispatch-time action guards
32
+ - Optional SQL adapter dependency wording
33
+ - Generic SQL store / driver package-layout pages
34
+ - `concept-public-package-names`
35
+ - `cli-existing-spec-edit-loop`
36
+ - `primitive-spatial-map`
32
37
  - `concept-spatial-map-editor`
33
38
  - `concept-spatial-map-zones`
34
39
  - `concept-editor-sessions`
@@ -20,7 +20,8 @@ transactions, `GET` in `initialActions`). For reactive GETs, prefer
20
20
  "method"?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
21
21
  "headers"?: <object-or-$state>,
22
22
  "body"?: <object-with-expressions>,
23
- "target"?: "/state/path"
23
+ "target"?: "/state/path",
24
+ "errorTarget"?: "/ui/screen-owned-error-path"
24
25
  }}
25
26
  ```
26
27
 
@@ -30,6 +31,9 @@ transactions, `GET` in `initialActions`). For reactive GETs, prefer
30
31
  - **Empty strings in body → `null`** (prevents DB errors on typed columns).
31
32
  - Sets `/ui/loading` to `true` while in flight; `false` when done.
32
33
  - On error: writes `/ui/lastError` with status and message.
34
+ - If `errorTarget` is provided, writes the same structured error there and
35
+ clears it on success. Use this for critical `initialActions` screen loads
36
+ instead of relying only on global `/ui/lastError`.
33
37
  - **Auth headers auto-injected** for URLs whose hostname matches
34
38
  `auth.authDomains` — see [[@concept-auth-domains]].
35
39
 
@@ -50,7 +54,8 @@ POST with body:
50
54
  "title": { "$state": "/form/title" },
51
55
  "description": { "$state": "/form/description" }
52
56
  },
53
- "target": "/lastResult"
57
+ "target": "/lastResult",
58
+ "errorTarget": "/ui/loadErrors/taskCreate"
54
59
  }}
55
60
  ```
56
61