@tanqory/dev-mcp 0.0.0-stage → 0.1.0-alpha.1

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -2
  3. package/bin/tanqory-dev-mcp.mjs +16 -0
  4. package/bundled/examples/blank/config/settings.json +3 -0
  5. package/bundled/examples/blank/locales/en.json +22 -0
  6. package/bundled/examples/blank/locales/th.json +22 -0
  7. package/bundled/examples/blank/regions/footer.json +35 -0
  8. package/bundled/examples/blank/regions/header.json +13 -0
  9. package/bundled/examples/blank/src/bundle.ts +15 -0
  10. package/bundled/examples/blank/src/components/parts.tsx +32 -0
  11. package/bundled/examples/blank/src/entry.client.tsx +6 -0
  12. package/bundled/examples/blank/src/entry.server.tsx +13 -0
  13. package/bundled/examples/blank/src/sections/chrome.tsx +132 -0
  14. package/bundled/examples/blank/src/sections/commerce.tsx +242 -0
  15. package/bundled/examples/blank/src/sections/content.tsx +111 -0
  16. package/bundled/examples/blank/src/sections/editorial.tsx +107 -0
  17. package/bundled/examples/blank/src/settings.ts +15 -0
  18. package/bundled/examples/blank/src/styles/theme.css +195 -0
  19. package/bundled/examples/blank/src/templates.ts +14 -0
  20. package/bundled/examples/blank/src/theme.tsx +76 -0
  21. package/bundled/examples/blank/template.json +24 -0
  22. package/bundled/examples/blank/templates/404.json +14 -0
  23. package/bundled/examples/blank/templates/account.json +14 -0
  24. package/bundled/examples/blank/templates/article.json +14 -0
  25. package/bundled/examples/blank/templates/blog.json +14 -0
  26. package/bundled/examples/blank/templates/cart.json +14 -0
  27. package/bundled/examples/blank/templates/collection.json +14 -0
  28. package/bundled/examples/blank/templates/index.json +36 -0
  29. package/bundled/examples/blank/templates/page.contact.json +18 -0
  30. package/bundled/examples/blank/templates/page.json +14 -0
  31. package/bundled/examples/blank/templates/product.json +18 -0
  32. package/bundled/examples/blank/templates/search.json +14 -0
  33. package/bundled/examples/blank/tests/theme.test.ts +49 -0
  34. package/bundled/theme-abi/contracts/render-context.v2.d.ts +849 -0
  35. package/bundled/theme-abi/contracts/theme-abi-v2.md +1833 -0
  36. package/bundled/theme-abi/generated/capabilities.json +80 -0
  37. package/bundled/theme-abi/generated/field-types.json +869 -0
  38. package/bundled/theme-abi/generated/limits.json +173 -0
  39. package/bundled/theme-abi/schemas/conformance-report.v2.schema.json +482 -0
  40. package/bundled/theme-abi/schemas/theme-artifact.v1.schema.json +230 -0
  41. package/bundled/theme-abi/schemas/theme-manifest.v2.schema.json +254 -0
  42. package/bundled/theme-conformance/docs/storefront-tier.md +221 -0
  43. package/bundled/theme-conformance/schema/conformance-report.v2.schema.json +482 -0
  44. package/bundled/theme-conformance/schema/storefront-report.v1.schema.json +110 -0
  45. package/bundled/theme-conformance/schema/x-tanqory-ai.v0.schema.json +70 -0
  46. package/bundled/theme-kit/docs/headless-api.md +220 -0
  47. package/bundled/theme-kit/generated/kit-strings.json +180 -0
  48. package/bundled/theme-kit/types/headless/index.d.ts +36 -0
  49. package/bundled/theme-kit/types/headless/theme.d.ts +99 -0
  50. package/bundled/theme-kit/types/sdk/schema.d.ts +197 -0
  51. package/bundled/versions.json +8 -0
  52. package/package.json +53 -3
  53. package/src/bm25.mjs +69 -0
  54. package/src/browser.mjs +123 -0
  55. package/src/cli.mjs +127 -0
  56. package/src/devserver.mjs +82 -0
  57. package/src/docs.mjs +329 -0
  58. package/src/index.mjs +33 -0
  59. package/src/kit.mjs +150 -0
  60. package/src/manifest.mjs +82 -0
  61. package/src/prompts.mjs +97 -0
  62. package/src/scaffold.mjs +471 -0
  63. package/src/schemas.mjs +127 -0
  64. package/src/server.mjs +595 -0
  65. package/src/theme.mjs +91 -0
@@ -0,0 +1,230 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.tanqory.com/theme-artifact/v1/artifact.schema.json",
4
+ "title": "ThemeArtifact descriptor (artifact.json) v1",
5
+ "description": "Root descriptor of an immutable, content-addressed theme artifact. Deterministic: two builders with the same `inputs` produce byte-identical artifact.json (no measurements, no timestamps). artifact.json itself is NOT in `files` and is NOT covered by artifactDigest; its digest is bound inside the signed in-toto statement (theme-artifact-v1.md §6). Measured numbers live in b/<buildId>/conformance-report.json (B5, Build-1).",
6
+ "type": "object",
7
+ "required": [
8
+ "format",
9
+ "abiVersion",
10
+ "artifactDigest",
11
+ "files",
12
+ "inputDigest",
13
+ "inputs",
14
+ "manifestDigest",
15
+ "limits",
16
+ "capabilities",
17
+ "inlineScriptHashes"
18
+ ],
19
+ "additionalProperties": false,
20
+ "properties": {
21
+ "format": {
22
+ "const": "tanqory-theme-artifact/1"
23
+ },
24
+ "abiVersion": {
25
+ "type": "string",
26
+ "pattern": "^2\\.[0-9]+$",
27
+ "description": "Equals manifest.json `abi`."
28
+ },
29
+ "artifactDigest": {
30
+ "$ref": "#/$defs/sha256",
31
+ "description": "Merkle-list root over `files` (theme-artifact-v1.md §4.2). Also the storage key prefix `a/<hex>/`."
32
+ },
33
+ "files": {
34
+ "type": "array",
35
+ "minItems": 2,
36
+ "maxItems": 4096,
37
+ "items": {
38
+ "type": "object",
39
+ "required": [
40
+ "path",
41
+ "sha256",
42
+ "size"
43
+ ],
44
+ "additionalProperties": false,
45
+ "properties": {
46
+ "path": {
47
+ "type": "string",
48
+ "pattern": "^(manifest\\.json|server/entry\\.mjs|preview/entry\\.mjs|client/client-manifest\\.json|client/assets/[A-Za-z0-9._-]+|defaults/(settings\\.json|templates/[a-z0-9][a-z0-9.-]*\\.json|regions/[a-z][a-z0-9-]*\\.json)|locales/[a-zA-Z]{2,3}(-[A-Za-z0-9]{2,8})*\\.json|sbom\\.cdx\\.json|README\\.md|LICENSE)$",
49
+ "maxLength": 200
50
+ },
51
+ "sha256": {
52
+ "type": "string",
53
+ "pattern": "^[0-9a-f]{64}$"
54
+ },
55
+ "size": {
56
+ "type": "integer",
57
+ "minimum": 0,
58
+ "maximum": 67108864
59
+ },
60
+ "contentType": {
61
+ "type": "string",
62
+ "maxLength": 100
63
+ }
64
+ }
65
+ },
66
+ "description": "Sorted by `path` as UTF-8 byte order, ascending, no duplicates. Must contain manifest.json, server/entry.mjs, client/client-manifest.json and sbom.cdx.json. The signed statement repeats these as `predicate.files` — the loader verifies leaves from the STATEMENT, this list is for builders/tools."
67
+ },
68
+ "manifestDigest": {
69
+ "$ref": "#/$defs/sha256",
70
+ "description": "sha256 of manifest.json bytes."
71
+ },
72
+ "inputDigest": {
73
+ "$ref": "#/$defs/sha256",
74
+ "description": "sha256 over RFC 8785 (JCS) canonical JSON of `inputs`, which holds exactly the six identity fields."
75
+ },
76
+ "inputs": {
77
+ "type": "object",
78
+ "required": [
79
+ "sourceDigest",
80
+ "lockfileDigest",
81
+ "builderImageDigest",
82
+ "abiVersion",
83
+ "buildFlags",
84
+ "mirrorSnapshotId"
85
+ ],
86
+ "additionalProperties": false,
87
+ "properties": {
88
+ "sourceDigest": {
89
+ "$ref": "#/$defs/sha256",
90
+ "description": "Merkle-list root (§4.2 algorithm, header tanqory-theme-source/1) over the ingested ThemeSource tree after the ingest gate filter."
91
+ },
92
+ "lockfileDigest": {
93
+ "$ref": "#/$defs/sha256",
94
+ "description": "sha256 of pnpm-lock.yaml bytes."
95
+ },
96
+ "builderImageDigest": {
97
+ "type": "string",
98
+ "pattern": "^sha256:[0-9a-f]{64}$",
99
+ "description": "OCI digest of the theme-builder image."
100
+ },
101
+ "abiVersion": {
102
+ "type": "string",
103
+ "pattern": "^2\\.[0-9]+$"
104
+ },
105
+ "mirrorSnapshotId": {
106
+ "type": "string",
107
+ "pattern": "^[A-Za-z0-9._-]{1,128}$",
108
+ "description": "Immutable snapshot id of the registry mirror the lockfile was resolved against (security §5.5)."
109
+ },
110
+ "buildFlags": {
111
+ "type": "object",
112
+ "additionalProperties": false,
113
+ "required": [
114
+ "profile",
115
+ "minify",
116
+ "sourcemaps",
117
+ "target",
118
+ "preview"
119
+ ],
120
+ "properties": {
121
+ "profile": {
122
+ "enum": [
123
+ "production"
124
+ ]
125
+ },
126
+ "minify": {
127
+ "type": "boolean"
128
+ },
129
+ "sourcemaps": {
130
+ "enum": [
131
+ "none",
132
+ "external-private"
133
+ ]
134
+ },
135
+ "target": {
136
+ "type": "string",
137
+ "pattern": "^es20[0-9]{2}$"
138
+ },
139
+ "preview": {
140
+ "type": "boolean",
141
+ "description": "Whether preview/entry.mjs was built."
142
+ },
143
+ "defines": {
144
+ "type": "object",
145
+ "additionalProperties": {
146
+ "anyOf": [
147
+ {
148
+ "type": "string"
149
+ },
150
+ {
151
+ "type": "number"
152
+ },
153
+ {
154
+ "type": "boolean"
155
+ }
156
+ ]
157
+ },
158
+ "description": "Build-time constants inlined into the bundle; part of identity."
159
+ }
160
+ }
161
+ }
162
+ },
163
+ "description": "Exactly the six identity fields of §4.3, and nothing else. `sourceRef` was removed on 2026-10-09 (Vec-3): the descriptor must be byte-identical for a given artifactDigest, and where the source was fetched from varies per build. A descriptor that carries `sourceRef`, or any other member here, is invalid. The source reference is recorded in the signed provenance of each build."
164
+ },
165
+ "limits": {
166
+ "type": "object",
167
+ "required": [
168
+ "limitsVersion",
169
+ "passed"
170
+ ],
171
+ "additionalProperties": false,
172
+ "properties": {
173
+ "limitsVersion": {
174
+ "type": "string",
175
+ "pattern": "^2\\.[0-9]+$",
176
+ "description": "Limits table (ABI minor) the builder checked against."
177
+ },
178
+ "passed": {
179
+ "const": true,
180
+ "description": "Only a passing artifact is ever written to storage; a failed build produces no artifact. Measurements are in conformance-report.json."
181
+ }
182
+ }
183
+ },
184
+ "capabilities": {
185
+ "type": "object",
186
+ "required": [
187
+ "server"
188
+ ],
189
+ "additionalProperties": false,
190
+ "properties": {
191
+ "server": {
192
+ "type": "array",
193
+ "uniqueItems": true,
194
+ "items": {
195
+ "type": "string"
196
+ }
197
+ },
198
+ "browser": {
199
+ "type": "array",
200
+ "uniqueItems": true,
201
+ "items": {
202
+ "type": "string"
203
+ }
204
+ }
205
+ },
206
+ "description": "Byte-equal copy of manifest.capabilities (browser defaulted explicitly). The signed statement carries the same object; the loader trusts the STATEMENT."
207
+ },
208
+ "inlineScriptHashes": {
209
+ "type": "array",
210
+ "maxItems": 32,
211
+ "uniqueItems": true,
212
+ "items": {
213
+ "type": "string",
214
+ "pattern": "^'sha256-[A-Za-z0-9+/]{43}='$"
215
+ },
216
+ "description": "Sorted CSP source expressions ('sha256-<base64>', single quotes included) of every executable inline script the theme may emit, computed at build time from src/inline/*.js (theme-artifact-v1 §1.2). Copied verbatim into the signed statement; a per-render x-tq-csp-script-hash is accepted only if listed there (ABI §5). [] when the theme has none."
217
+ },
218
+ "sbomRef": {
219
+ "type": "string",
220
+ "pattern": "^a/[0-9a-f]{64}/sbom\\.cdx\\.json$",
221
+ "description": "CycloneDX 1.6 SBOM (also listed in `files`). Build-scoped objects (statement, provenance, conformance report under b/<buildId>/) are never referenced from this deterministic descriptor."
222
+ }
223
+ },
224
+ "$defs": {
225
+ "sha256": {
226
+ "type": "string",
227
+ "pattern": "^sha256:[0-9a-f]{64}$"
228
+ }
229
+ }
230
+ }
@@ -0,0 +1,254 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.tanqory.com/theme-abi/v2/theme-manifest.schema.json",
4
+ "title": "ThemeManifest v2",
5
+ "description": "Static description of a theme for the platform (routing, regions, components, settings, capabilities). Produced by the builder into manifest.json and exported verbatim as `manifest` from server/entry.mjs. Keys starting with `x-` are vendor namespaces the platform ignores and never validates (Nova uses `x-nova`).",
6
+ "type": "object",
7
+ "required": ["abi", "name", "version", "engines", "capabilities", "client", "contentVersion", "routes", "templates", "regions", "components", "settingsSchema"],
8
+ "additionalProperties": false,
9
+ "patternProperties": {
10
+ "^x-[a-z0-9][a-z0-9-]*$": { "description": "Vendor namespace. Ignored by the platform. Counted toward manifest size." }
11
+ },
12
+ "properties": {
13
+ "abi": { "type": "string", "pattern": "^2\\.[0-9]+$", "description": "ABI the theme was built against (major.minor)." },
14
+ "name": { "type": "string", "pattern": "^(@[a-z0-9][a-z0-9-]*/)?[a-z0-9][a-z0-9._-]*$", "maxLength": 128 },
15
+ "version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$", "description": "Theme semver (marketplace version)." },
16
+ "engines": {
17
+ "type": "object",
18
+ "required": ["tanqory-abi", "tanqory-field-types"],
19
+ "additionalProperties": false,
20
+ "properties": {
21
+ "tanqory-abi": { "type": "string", "description": "Semver range of hosts this theme accepts, e.g. \"^2.0\"." },
22
+ "tanqory-field-types": { "type": "string", "pattern": "^[0-9]+$", "description": "Field-type registry version every `type` below is validated against." }
23
+ }
24
+ },
25
+ "capabilities": {
26
+ "type": "object",
27
+ "required": ["server", "browser"],
28
+ "additionalProperties": false,
29
+ "description": "Security B-6: both arrays required and explicit; browser ⊆ server. The builder copies both verbatim into the signed statement; the runtime uses the statement's copy, never this file.",
30
+ "properties": {
31
+ "server": {
32
+ "type": "array",
33
+ "uniqueItems": true,
34
+ "maxItems": 32,
35
+ "items": { "$ref": "#/$defs/capability" },
36
+ "description": "What render() may use. `ctx.data`/`ctx.actions` outside this set throw CapabilityError. `network:<origin>` is the ONLY egress form (proxied fetch to that origin, counted as a sub-request, store/marketplace-approved)."
37
+ },
38
+ "browser": {
39
+ "type": "array",
40
+ "uniqueItems": true,
41
+ "maxItems": 16,
42
+ "items": {
43
+ "type": "string",
44
+ "pattern": "^(data:(shop|localization|catalog|content|search|cart|products\\.recommendations|inventory)|actions:(cart|consent))$"
45
+ },
46
+ "description": "What hydrated theme JS may call via /_tq/data/* and /_tq/actions/*. Ceiling per capability-security-v1.md §2.6 / B-6 / A10: data:customer, data:metaobjects, actions:customer, actions:form.submit are NEVER browser-mintable (the pattern excludes them, so validation fails on them); data:products.recommendations and data:inventory ARE allowed. Must be ⊆ server (checked by `theme check`, not expressible here). `tanqory theme init` scaffolds {data:shop, data:localization, data:catalog, data:content, data:search, data:cart, actions:cart, actions:consent}; an empty array is valid for zero-JS themes."
47
+ }
48
+ }
49
+ },
50
+ "forms": {
51
+ "type": "object",
52
+ "maxProperties": 16,
53
+ "propertyNames": { "pattern": "^[a-z][a-z0-9-]*$" },
54
+ "additionalProperties": {
55
+ "type": "object",
56
+ "required": ["fields"],
57
+ "additionalProperties": false,
58
+ "properties": {
59
+ "title": { "type": "string", "maxLength": 64 },
60
+ "fields": { "type": "array", "minItems": 1, "maxItems": 32, "uniqueItems": true, "items": { "type": "string", "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$" } }
61
+ }
62
+ },
63
+ "description": "Forms submittable through actions:form.submit (server-side POST only). Native HTML: the theme writes only `<form data-tq-form=\"<id>\" method=\"post\">`; the form posts to the same path it was rendered on (no `action`, or `action` = that path); the gateway injects both hidden fields `_tq_form` and `_tq_csrf` after its cache (theme must not render them), strips both on POST, accepts application/x-www-form-urlencoded only, caps the body at 8 KiB (8192 bytes) before the isolate, filters to `fields`, and exposes ctx.request.form (owner approval 2026-10-07). Field names are an allowlist and must not start with `_tq_`; destination is the merchant inbox, never a theme URL."
64
+ },
65
+ "client": {
66
+ "type": "object",
67
+ "required": ["hydration"],
68
+ "additionalProperties": false,
69
+ "properties": {
70
+ "hydration": { "enum": ["none", "full", "partial"], "description": "none = zero-JS/HTMX theme: client-manifest has no isEntry script and the hydration-parity suite is skipped (per-content-type assertions still run)." }
71
+ }
72
+ },
73
+ "contentVersion": { "type": "integer", "minimum": 1, "description": "Version of the content-document shape this theme reads. Docs at a lower version are migrated by the platform via declared `migrations` before render." },
74
+ "minContentVersion": { "type": "integer", "minimum": 1, "description": "Oldest version this artifact's migrate() accepts. Absent ⇒ equals contentVersion (no chain required); > contentVersion is `migration_range_invalid` (validateManifest semantic check). Docs older than this are first brought up by the previously installed artifact's chain (platform-chained migration)." },
75
+ "migrations": {
76
+ "type": "array",
77
+ "items": {
78
+ "type": "object",
79
+ "required": ["from", "to"],
80
+ "additionalProperties": false,
81
+ "properties": {
82
+ "from": { "type": "integer", "minimum": 1 },
83
+ "to": { "type": "integer", "minimum": 2 },
84
+ "note": { "type": "string", "maxLength": 512 }
85
+ }
86
+ },
87
+ "description": "Each step must be implemented by the entry's `migrate(doc, from, to)`. Steps must chain gap-free from `minContentVersion` (default: 1) to `contentVersion`. Docs below `minContentVersion` are migrated by the platform through the previously installed artifact's chain first (S5)."
88
+ },
89
+ "routes": {
90
+ "type": "object",
91
+ "description": "Default template per platform route kind. `home` and `not_found` are required. A missing kind means the platform serves its own minimal page for that kind.",
92
+ "required": ["home", "not_found"],
93
+ "properties": {
94
+ "home": { "type": "string", "description": "Template name (key of `templates`)." },
95
+ "not_found": { "type": "string", "description": "Template name (key of `templates`)." }
96
+ },
97
+ "propertyNames": { "pattern": "^(home|product|collection|collections|page|blog|article|search|cart|customer|policy|not_found|custom:[a-z][a-z0-9-]*)$" },
98
+ "additionalProperties": { "type": "string", "description": "Template name (key of `templates`)." }
99
+ },
100
+ "customRoutes": {
101
+ "type": "object",
102
+ "maxProperties": 16,
103
+ "propertyNames": { "pattern": "^[a-z][a-z0-9-]*$" },
104
+ "additionalProperties": {
105
+ "type": "object",
106
+ "required": ["path"],
107
+ "additionalProperties": false,
108
+ "properties": {
109
+ "path": { "type": "string", "pattern": "^/[A-Za-z0-9._~/-]*(:[a-z][A-Za-z0-9]*(/[A-Za-z0-9._~/-]*)?)*$", "description": "URL pattern with `:param` captures, e.g. \"/lookbook/:season\". Must not start with /collections, /products, /pages, /blogs, /policies, /cart, /search, /account (the built-in `customer` kind), /checkout, /orders, /api, /_tq (the reserved platform prefix is `/_tq/`)." }
110
+ }
111
+ },
112
+ "description": "Theme-defined route kinds. Route kind is `custom:<key>`; captures appear in ctx.route.params."
113
+ },
114
+ "templates": {
115
+ "type": "object",
116
+ "minProperties": 2,
117
+ "maxProperties": 128,
118
+ "propertyNames": { "pattern": "^[a-z0-9][a-z0-9.-]*$" },
119
+ "additionalProperties": {
120
+ "type": "object",
121
+ "required": ["route", "regions"],
122
+ "additionalProperties": false,
123
+ "properties": {
124
+ "route": { "type": "string", "pattern": "^(home|product|collection|collections|page|blog|article|search|cart|customer|policy|not_found|custom:[a-z][a-z0-9-]*)$" },
125
+ "title": { "type": "string", "maxLength": 128 },
126
+ "regions": { "type": "array", "minItems": 1, "maxItems": 16, "uniqueItems": true, "items": { "type": "string" }, "description": "Ordered region ids this template renders (keys of `regions`)." }
127
+ }
128
+ },
129
+ "description": "Template name → route kind. A resource's templateSuffix selects `<route>.<suffix>` when it exists, else `routes[kind]`."
130
+ },
131
+ "regions": {
132
+ "type": "object",
133
+ "minProperties": 1,
134
+ "maxProperties": 32,
135
+ "propertyNames": { "pattern": "^[a-z][a-z0-9-]*$" },
136
+ "additionalProperties": {
137
+ "type": "object",
138
+ "required": ["title", "scope"],
139
+ "additionalProperties": false,
140
+ "properties": {
141
+ "title": { "type": "string", "maxLength": 64 },
142
+ "scope": { "enum": ["shared", "template"], "description": "`shared`: one doc per region for the whole store (header/footer pattern), editable in place from any template; `template`: one doc per template." },
143
+ "accepts": { "type": "array", "uniqueItems": true, "items": { "type": "string" }, "description": "Component ids (role section|layout) allowed here. Absent = every section whose `regions` lists this id." },
144
+ "min": { "type": "integer", "minimum": 0 },
145
+ "max": { "type": "integer", "minimum": 1, "maximum": 64 }
146
+ }
147
+ },
148
+ "description": "Theme-declared regions. The platform imposes none; today's header/template/footer is just one possible declaration."
149
+ },
150
+ "components": {
151
+ "type": "object",
152
+ "minProperties": 1,
153
+ "maxProperties": 256,
154
+ "propertyNames": { "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)?$" },
155
+ "additionalProperties": { "$ref": "#/$defs/component" }
156
+ },
157
+ "settingsSchema": {
158
+ "type": "array",
159
+ "maxItems": 32,
160
+ "items": {
161
+ "type": "object",
162
+ "required": ["group", "items"],
163
+ "additionalProperties": false,
164
+ "properties": {
165
+ "group": { "type": "string", "maxLength": 64 },
166
+ "items": {
167
+ "type": "array",
168
+ "maxItems": 64,
169
+ "items": { "allOf": [ { "$ref": "#/$defs/field" }, { "type": "object", "required": ["key"], "properties": { "key": { "type": "string", "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$" } } } ] }
170
+ }
171
+ }
172
+ }
173
+ }
174
+ },
175
+ "$defs": {
176
+ "capability": {
177
+ "type": "string",
178
+ "pattern": "^(data:(shop|localization|catalog|content|search|cart|customer|metaobjects|products\\.recommendations|inventory)|actions:(cart|consent|customer|form\\.submit)|preview:renderSection|network:https://.+)$",
179
+ "description": "Normative set = `Capability` in render-context.v2.d.ts (`network:https://${string}` — origin well-formedness is a validateManifest() semantic check). Security (capability-security-v1.md §2) cites this type."
180
+ },
181
+ "field": {
182
+ "type": "object",
183
+ "required": ["type"],
184
+ "additionalProperties": false,
185
+ "properties": {
186
+ "key": { "type": "string" },
187
+ "type": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$", "description": "Id in the field-type registry version named by engines[\"tanqory-field-types\"]. Unknown ids fail `theme check`." },
188
+ "label": { "type": "string", "maxLength": 128 },
189
+ "info": { "type": "string", "maxLength": 512 },
190
+ "default": {},
191
+ "placeholder": { "type": "string", "maxLength": 128 },
192
+ "group": { "type": "string", "maxLength": 64 },
193
+ "min": { "type": "number" },
194
+ "max": { "type": "number" },
195
+ "step": { "type": "number", "exclusiveMinimum": 0 },
196
+ "unit": { "type": "string", "maxLength": 8 },
197
+ "options": { "type": "array", "maxItems": 64, "items": { "type": "object", "required": ["value", "label"], "additionalProperties": false, "properties": { "value": { "type": "string" }, "label": { "type": "string" } } } },
198
+ "visible_if": { "oneOf": [ { "type": "string", "maxLength": 256 }, { "type": "object" } ] },
199
+ "dynamic": { "type": "boolean" },
200
+ "resolve": { "type": "boolean", "description": "Resource-typed fields only: platform resolves the referenced resource into snapshot.refs." },
201
+ "items": { "$ref": "#/$defs/field", "description": "`list` type only: the element field spec (depth ≤ 2)." },
202
+ "schema": { "type": "object", "description": "`json` type only: optional JSON Schema (draft 2020-12, ≤ 8 KB serialised) the editor validates the value against." }
203
+ }
204
+ },
205
+ "contentNode": {
206
+ "type": "object",
207
+ "required": ["type", "id"],
208
+ "additionalProperties": false,
209
+ "properties": {
210
+ "type": { "type": "string" },
211
+ "id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$", "maxLength": 64 },
212
+ "settings": { "type": "object" },
213
+ "blocks": { "type": "array", "maxItems": 64, "items": { "$ref": "#/$defs/contentNode" } },
214
+ "disabled": { "type": "boolean" }
215
+ }
216
+ },
217
+ "component": {
218
+ "type": "object",
219
+ "required": ["role", "title", "attributes"],
220
+ "additionalProperties": false,
221
+ "properties": {
222
+ "role": { "enum": ["section", "block", "layout"], "description": "section: merchant-placed in a region · block: nested in a section · layout: a region-level unit (e.g. header bar) the theme pins." },
223
+ "title": { "type": "string", "maxLength": 128 },
224
+ "description": { "type": "string", "maxLength": 512 },
225
+ "category": { "type": "string", "maxLength": 64 },
226
+ "icon": { "type": "string", "maxLength": 64 },
227
+ "regions": { "type": "array", "uniqueItems": true, "items": { "type": "string" }, "description": "Sections/layout: regions that accept this component." },
228
+ "parents": { "type": "array", "uniqueItems": true, "items": { "type": "string" }, "description": "Blocks: component ids that may contain this block." },
229
+ "children": { "type": "array", "uniqueItems": true, "maxItems": 64, "items": { "type": "string" }, "description": "Sections: block ids accepted." },
230
+ "requires": { "type": "array", "uniqueItems": true, "items": { "type": "string", "pattern": "^(home|product|collection|collections|page|blog|article|search|cart|customer|policy|not_found|custom:[a-z][a-z0-9-]*)$" }, "description": "Route kinds (any RouteKind, aligned with the .d.ts) whose resource this component needs. The editor refuses to place it on other templates." },
231
+ "attributes": {
232
+ "type": "object",
233
+ "maxProperties": 64,
234
+ "propertyNames": { "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$" },
235
+ "additionalProperties": { "$ref": "#/$defs/field" }
236
+ },
237
+ "presets": {
238
+ "type": "array",
239
+ "maxItems": 16,
240
+ "items": {
241
+ "type": "object",
242
+ "additionalProperties": false,
243
+ "properties": {
244
+ "name": { "type": "string", "maxLength": 128 },
245
+ "settings": { "type": "object" },
246
+ "blocks": { "type": "array", "maxItems": 64, "items": { "$ref": "#/$defs/contentNode" } }
247
+ }
248
+ }
249
+ },
250
+ "limits": { "type": "object", "additionalProperties": false, "properties": { "maxBlocks": { "type": "integer", "minimum": 0, "maximum": 64 } } }
251
+ }
252
+ }
253
+ }
254
+ }