@digital-gravy/etch-connector 0.2.2 → 0.3.0

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.
package/README.md CHANGED
@@ -132,7 +132,7 @@ return groups.length;
132
132
  `etch-connector tabs` reports, per tab, which Etch runtime it is and which
133
133
  surfaces that runtime backs (read off `etch.environment`), then prints the
134
134
  authoring rules that follow from it: the dynamic-data model (data sources vs
135
- loops), the media lookup, the namespaces that throw `NOT_AVAILABLE`, and the
135
+ loops), the route tree, the content model, the media lookup, the namespaces that throw `NOT_AVAILABLE`, and the
136
136
  block types the runtime can construct. Not every runtime backs every surface, so
137
137
  read that before writing scripts — and branch on a capability, never on the
138
138
  product name. `eval` names the runtime it targets on stderr as a reminder, and
package/dist/cli.js CHANGED
@@ -522,7 +522,7 @@ hand-written eval.
522
522
 
523
523
  AGENT: \`etch-connector tabs\` prints, per connected tab, which runtime it is and
524
524
  which surfaces that runtime backs (from \`etch.environment\`), followed by the
525
- authoring rules for it — the dynamic-data model, the media lookup, the
525
+ authoring rules for it — the dynamic-data model, the route tree, the content model, the media lookup, the
526
526
  namespaces that throw, and the block types it can construct. Read that before
527
527
  writing scripts: not every runtime backs every surface, so the same script does
528
528
  not work everywhere. Branch on a capability, never on the product name.
@@ -602,12 +602,10 @@ render what you author):
602
602
  (specialized "image" is the URL-based picker — NOT for mediaId.) Instances
603
603
  then pass the numeric media ID, e.g. image: "10" — not the file URL.
604
604
 
605
- Classes. A block's classes ARE its read-only \`styles\` array, not an HTML
606
- attribute. Setting attributes.class in authored JSON is silently dropped, and
607
- \`styles\` is rejected on authoring. Assign classes with etch.blocks.addClass()
608
- — for component blocks, inside enterComponentEditMode() →
609
- saveComponentEditModeAsync() → exitComponentEditMode(). An existing class
610
- name re-links to its style record automatically (styling is preserved).
605
+ Classes. Set attributes.class in authored JSON (blocks.create(), replace(),
606
+ components.updateAsync()): each class links its style record, and a missing
607
+ one is created empty. A block's \`styles\` array is read-only; ids passed in
608
+ it are ignored. etch.blocks.addClass() works on existing blocks.
611
609
 
612
610
  Sections. When you create a top-level section, always tag it
613
611
  data-etch-element="section" and nest ONE container inside it as its inner —
@@ -630,8 +628,9 @@ render what you author):
630
628
  • Component instance props are attributes: set them at create()/replace()
631
629
  time, or update an existing instance in place with
632
630
  etch.blocks.update(instanceId, { attributes: { image: "10" } })
633
- • updateAsync({ blocks }) requires styles:[] on every element block
634
- (validation differs from blocks.create(), which omits styles entirely).
631
+ • components.updateAsync({ blocks }) takes the same block JSON as
632
+ blocks.create(). Older Etch builds reject it without \`styles\` on every
633
+ element block; there, pass the class style ids from etch.styles.list().
635
634
  • No etch.media API. Where the runtime backs the WordPress media library
636
635
  (capability \`wpMedia\` — see \`tabs\`), resolve a URL → media ID via the WP
637
636
  REST API:
package/dist/guidance.js CHANGED
@@ -16,7 +16,10 @@ import { hasCapability } from './protocol.js';
16
16
  /** Namespaces that are declared on every runtime but backed only on some. */
17
17
  const GATED_NAMESPACES = [
18
18
  { capability: 'loops', namespace: 'etch.loops.*' },
19
- { capability: 'fields', namespace: 'etch.fields.*' },
19
+ {
20
+ capability: 'fields',
21
+ namespace: 'etch.fields.* (content fields are on etch.content where backed)'
22
+ },
20
23
  { capability: 'templates', namespace: "etch.navigation.goTo('templates')" }
21
24
  ];
22
25
  const UNKNOWN_RUNTIME = [
@@ -59,6 +62,65 @@ function dynamicDataGuidance(environment) {
59
62
  ' sources — do not author etch/loop blocks against it.'
60
63
  ];
61
64
  }
65
+ function routesGuidance(environment) {
66
+ if (!hasCapability(environment, 'routes')) {
67
+ return [
68
+ 'Routes: etch.routes is absent here — the property does not exist, so',
69
+ ' feature-detect it rather than calling it and catching.'
70
+ ];
71
+ }
72
+ return [
73
+ 'Routes — the site URLs are a route tree, managed through etch.routes.*:',
74
+ ' listAsync(), createAsync({ segment, parentId?, source?, templateId? }),',
75
+ ' updateAsync(id, patch) and deleteAsync(id) persist immediately. A',
76
+ " segment is static ('about'), a group ('(marketing)') that adds nothing",
77
+ " to the URL, or dynamic ('{this.title.toSlug()}'), served once per",
78
+ ' record of its source: record ids in postIds, content type keys in',
79
+ ' postTypes. Templates are records listed by',
80
+ ' etch.navigation.listTemplatesAsync(); a route renders through its',
81
+ " templateId, or its nearest ancestor's (templateId: null in a patch",
82
+ ' inherits again). Read getPathsAsync() after a change — only',
83
+ ' `published` paths are built.',
84
+ ' Do not build links from .slug (this.slug, item.slug in a loop, or',
85
+ ' PostSummary.slug): only a record a static route serves has one. A',
86
+ ' record served by a dynamic route has none, and listPostsAsync() reports',
87
+ " '' for it, which reads like the homepage. Spell the link from the",
88
+ " route's own segment instead — /locations/{loc.title.toSlug()}/ under",
89
+ " a 'locations' route with segment '{this.title.toSlug()}' — and check it",
90
+ ' against getPathsAsync().',
91
+ ' Vocabulary: what users call a content type is a postType in the API',
92
+ ' (its key), and a record or page is a post (postId, listPostsAsync()).'
93
+ ];
94
+ }
95
+ function contentGuidance(environment) {
96
+ if (!hasCapability(environment, 'content')) {
97
+ return [
98
+ 'Content: etch.content is absent here — the property does not exist, so',
99
+ ' feature-detect it rather than calling it and catching.'
100
+ ];
101
+ }
102
+ return [
103
+ 'Content — content types, their fields and items, through etch.content.*',
104
+ ' (writes persist immediately): listTypesAsync(), getTypeAsync(key),',
105
+ ' createTypeAsync({ name, key?, isTemplate?, fields? }), updateTypeAsync(key,',
106
+ ' { name?, key? }), deleteTypeAsync(key); addFieldAsync(typeKey, field),',
107
+ ' updateFieldAsync(typeKey, fieldKey, patch) — a new key renames and keeps',
108
+ ' values — removeFieldAsync(typeKey, fieldKey); listItemsAsync(postType?),',
109
+ ' getItemAsync(id), createItemAsync({ postType, title, fields? }),',
110
+ ' updateItemAsync(id, { title?, fields? }) — fields merge per key, null',
111
+ ' clears — deleteItemAsync(id). Relationship values are item ids.',
112
+ ' Templates are items of a type created with isTemplate: true.',
113
+ ' Items carry title and field values only: build their blocks by opening',
114
+ ' them (etch.navigation.openPostAsync(id) / openTemplateAsync(id)) and',
115
+ ' using etch.blocks.',
116
+ " Refused: fields on a template type, changing isTemplate or an item's",
117
+ ' postType, deleting the page type, a template type as relation target,',
118
+ ' any write to a relationshipBacklink (READONLY), unknown field keys,',
119
+ ' and values their field rejects.',
120
+ ' Vocabulary: a content type is a postType (its key), an item is a post',
121
+ ' (its id is the postId).'
122
+ ];
123
+ }
62
124
  function mediaGuidance(environment) {
63
125
  const binding = ' Bind an etch/dynamic-image by a numeric `mediaId` attribute, never a URL.';
64
126
  if (hasCapability(environment, 'wpMedia')) {
@@ -128,6 +190,8 @@ export function environmentGuidance(environment) {
128
190
  `Runtime: ${environment.product} — backs ${backed.length > 0 ? backed.join(', ') : 'no optional surface'}.`
129
191
  ],
130
192
  dynamicDataGuidance(environment),
193
+ routesGuidance(environment),
194
+ contentGuidance(environment),
131
195
  mediaGuidance(environment),
132
196
  unavailableGuidance(environment),
133
197
  blockTypeGuidance(environment)
package/dist/protocol.js CHANGED
@@ -16,7 +16,9 @@ const CAPABILITY_NAMES = [
16
16
  'dataSources',
17
17
  'fields',
18
18
  'templates',
19
- 'wpMedia'
19
+ 'wpMedia',
20
+ 'routes',
21
+ 'content'
20
22
  ];
21
23
  /** Whether a runtime backs a capability. Absent or unknown counts as no. */
22
24
  export function hasCapability(environment, capability) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@digital-gravy/etch-connector",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },