@akinon/pz-theme 2.0.91-beta.0 → 2.0.91

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/CHANGELOG.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # @akinon/pz-theme
2
2
 
3
- ## 2.0.91-beta.0
3
+ ## 2.0.91
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Updated dependencies [cbbbfd757]
8
- - @akinon/next@2.0.91-beta.0
9
- - @akinon/theme-contract@2.0.91-beta.0
7
+ - 340440f3: ZERO-4983: Document that a region seed is a one-way door
8
+ - @akinon/next@2.0.91
9
+ - @akinon/theme-contract@2.0.91
10
10
 
11
11
  ## 2.0.90
12
12
 
package/package.json CHANGED
@@ -1,26 +1,26 @@
1
1
  {
2
2
  "name": "@akinon/pz-theme",
3
3
  "description": "Theme package for Project Zero Next — ThemePlaceholder system and theme editor infrastructure",
4
- "version": "2.0.91-beta.0",
4
+ "version": "2.0.91",
5
5
  "license": "MIT",
6
6
  "main": "src/index.ts",
7
7
  "scripts": {
8
8
  "test": "jest"
9
9
  },
10
10
  "peerDependencies": {
11
- "@akinon/next": "2.0.91-beta.0",
11
+ "@akinon/next": "2.0.91",
12
12
  "react": "^18.0.0 || ^19.0.0",
13
13
  "react-dom": "^18.0.0 || ^19.0.0"
14
14
  },
15
15
  "dependencies": {
16
- "@akinon/theme-contract": "2.0.91-beta.0",
16
+ "@akinon/theme-contract": "2.0.91",
17
17
  "clsx": "2.1.1",
18
18
  "lottie-web": "^5.13.0",
19
19
  "tailwind-merge": "^2.5.4",
20
20
  "zod": "4.4.1"
21
21
  },
22
22
  "devDependencies": {
23
- "@akinon/next": "2.0.91-beta.0",
23
+ "@akinon/next": "2.0.91",
24
24
  "@testing-library/jest-dom": "5.16.4",
25
25
  "@testing-library/react": "16.3.2",
26
26
  "@testing-library/user-event": "14.1.1",
@@ -95,8 +95,30 @@ reorder it or insert beside it — your JSX owns the layout, and no drag in the
95
95
  layer tree changes where it renders.
96
96
 
97
97
  `properties`, `styles` and `value` on a region are a **seed**: what it looks
98
- like the moment the section is added. They are written once, at insert, and
99
- nothing re-applies them, so a merchant's edit is never quietly reverted.
98
+ like the moment the section is added. They are copied into the merchant's
99
+ composition once, at insert, and nothing re-applies them — not a later load,
100
+ not a publish, not a deploy.
101
+
102
+ That cuts both ways and only one half of it is usually noticed. A merchant's
103
+ edit is never quietly reverted; a DEVELOPER's later edit is never delivered.
104
+ Change a seeded colour and every store that already placed the section keeps
105
+ the old one, and nothing on either side says so — no load compares a placed
106
+ region against the descriptor it was seeded from, so the build has nothing to
107
+ report and the editor draws no badge. The two disagree in silence.
108
+
109
+ There IS a way back, but only one a person can take. Selecting the row (or the
110
+ section itself) puts **Reset to code** in the right panel's Danger zone, which
111
+ rewrites that one row from today's descriptor in place — keeping the blocks
112
+ inside it, its id, its scoped custom CSS and its place in the list — after
113
+ showing exactly which settings and styles it will overwrite. It is a merchant's
114
+ deliberate click, never automatic, because the editor cannot tell a value the
115
+ merchant chose from one the code seeded: both live in the same map with no
116
+ marker. So a seed is still a ONE-WAY DOOR as far as shipping goes — a seed
117
+ change reaches nobody you do not tell. Say it in the release note, name the
118
+ stores, and give the merchant those steps.
119
+ [Evolving a section that is already placed](#evolving-a-section-that-is-already-placed)
120
+ is the rest of the rule.
121
+
100
122
  `value` is there for the block types that keep their content there rather than
101
123
  in `properties` — an `image` reads `block.value` for its src.
102
124
 
@@ -121,6 +143,94 @@ of them.
121
143
  | Merchant can reorder | no | yes, inside the slot |
122
144
  | Merchant can delete | yes, and it stays deleted (Add back restores it) | the row itself, no |
123
145
 
146
+ ## Evolving a section that is already placed
147
+
148
+ A placed section is a composition sitting in someone's store, and your next
149
+ commit reaches only part of it. Some changes arrive on the next deploy with
150
+ nobody doing anything; one costs the merchant a click per placement; some
151
+ arrive nowhere at all. Nothing in the JSX tells the three apart, which is why
152
+ this has to be a habit rather than a check you run.
153
+
154
+ **The test: if you can imagine changing this value in six months, it must not
155
+ be a seed.** Put it in `ThemeEditable` or `useThemeSetting` on the section's own
156
+ JSX instead. A section is inserted with no stored settings whatsoever — its
157
+ insert template carries the region blocks and the slot nodes and nothing else —
158
+ so a `useThemeSetting` default is read at RENDER, on every render, in every
159
+ store, and changing it changes what every placement the merchant never touched
160
+ shows on the next deploy. The authored JSX inside a `ThemeEditable` is live for
161
+ the same reason. A seed is the one thing in this file that is copied into the
162
+ merchant's data and then left there.
163
+
164
+ **And the counterweight, because that rule over-applies easily.** A region's
165
+ `styles` and the section root's `styles` have no live substitute and are seeds
166
+ deliberately. A section's parts cannot reach inside a region — part rules carry
167
+ an ownership guard that excludes anything under a descendant `[data-block-id]`
168
+ and every region renders inside one, which is what "Parts must live on YOUR
169
+ markup" says below. And the section root is the wrapper your component renders
170
+ INSIDE, so nothing the component draws can style the box it sits in. Seed both,
171
+ and treat the first paint as the only one you get.
172
+
173
+ ### Identity strings are never renamed
174
+
175
+ A region name, a slot name, a setting key, the record key in
176
+ `registerCustomSections` and the vendor are all identity: each is matched
177
+ LITERALLY against something already saved in a merchant's composition. A rename
178
+ is therefore not a rename. It is a delete plus an unrelated insert, performed
179
+ silently, on data you cannot see:
180
+
181
+ | Renamed | What it costs the merchant |
182
+ |---|---|
183
+ | a region name | the seeded block keeps the old name and no `<ThemeRegion>` asks for it any more, so it renders nowhere; the new name was never seeded, so the editor lists it under **Not placed** and **Add back** builds a FRESH block — without the original's styling, id or scoped custom CSS |
184
+ | a slot name | the load pass creates a node for the new name and adopts nothing, so everything the merchant put in the slot stays under the old node, drawn nowhere — and that node cannot be deleted either, because delete refuses any `custom-slot` node whether or not the type still declares it |
185
+ | a setting key | the stored value stays under the old key forever and the new key reads the declared default, so the control resets to code in every store at once |
186
+ | the record key or the vendor | the type string changes, so every composition holding the old one names a type this build does not register: nothing live, a labelled band in the designer |
187
+
188
+ If a rename is genuinely unavoidable, register the SAME component under both
189
+ keys and wrap the retiring one in its own `defineCustomSection` whose options
190
+ carry `placeholders: []`. It keeps drawing every composition that already names
191
+ it and stops being offered for new ones. It costs one of the sixteen section
192
+ types this build may declare, which is the honest price of the old name.
193
+
194
+ **Never change a region's `block` type either.** A region resolves by NAME, so
195
+ `<ThemeRegion name="basket" />` goes on rendering whatever type the node was
196
+ born as, and every surface agrees that it is correct. **Reset to code** does not
197
+ help here and deliberately hides itself when the types no longer match: it
198
+ rewrites a node's settings and styles, and re-typing one in place would leave a
199
+ row the editor can never delete again — delete refuses any node that hosts a
200
+ slot, without checking whether the new type still declares one. So the
201
+ merchant's only route to the new type is deleting the region and adding it back,
202
+ which takes the block's own subtree with it.
203
+
204
+ **Never re-use a setting key you removed.** Nothing in the editor or the
205
+ renderer deletes a stored property key, so the merchant's old value is still
206
+ sitting in the composition and it shadows the new default permanently — the
207
+ setting comes up holding whatever was typed into a control that no longer
208
+ exists.
209
+
210
+ **Ship a setting `type` change as a NEW key, not an edit.** The panel builds the
211
+ control from today's descriptor and reads the value stored under that key with
212
+ no conversion of any kind, so a `text` that becomes a `select` draws a select
213
+ with nothing matching the string the merchant free-typed, and the storefront is
214
+ handed that same string. Add the new key beside the old one and stop reading
215
+ the old one.
216
+
217
+ **Name every slot.** A slot declared without one is the slot you can never name
218
+ later: its contents are the section's own direct children rather than the
219
+ children of a node, and the pass that materializes slot nodes adopts nothing.
220
+ Name it the day you write it and there is nothing left to strand.
221
+
222
+ ### What reaches a section already on the page
223
+
224
+ | Change | Reaches placed instances |
225
+ |---|---|
226
+ | any JSX, CSS or component change | yes, on the next deploy |
227
+ | adding a setting with a default | yes — nothing is stored under the key until the merchant sets it |
228
+ | moving a `<ThemeRegion>` in the JSX | yes — the JSX decides where a region renders; the node's position in `section.blocks` decides nothing |
229
+ | adding a named slot | yes — slot nodes are re-materialized on every theme load, so the row is there the next time the editor opens |
230
+ | changing a slot's `accepts` or `max` | yes — both are read from today's descriptor at the moment a block is written into the slot, so a drag and a paste obey them too |
231
+ | adding a REGION | one click per placement — regions are seeded at insert and back-filled nowhere, so a new one arrives under **Not placed**, indistinguishable from one the merchant deleted, and stays empty on the live site until someone clicks **Add back** |
232
+ | changing a seed — a region's `properties`, `styles` or `value`, or the section's own `styles` | one click per placement, and only if someone knows to make it — nothing is re-applied and nothing announces the change, so the merchant has to open the section, select the row (or the section itself) and use **Reset to code** in the Danger zone. Every placement that nobody visits keeps what it was born with |
233
+
124
234
  ## Commerce data
125
235
 
126
236
  Two different things want data and only one of them is yours.