@akinon/pz-theme 2.0.97-rc.0 → 2.0.97

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,38 +1,15 @@
1
1
  # @akinon/pz-theme
2
2
 
3
- ## 2.0.97-rc.0
4
-
5
- ### Patch Changes
6
-
7
- - Updated dependencies [86187b1c]
8
- - Updated dependencies [0cf9ea239]
9
- - Updated dependencies [324f97d55]
10
- - Updated dependencies [51ea06888]
11
- - Updated dependencies
12
- - Updated dependencies [2fea41430]
13
- - Updated dependencies [b55acb768]
14
- - Updated dependencies [760258c1c]
15
- - Updated dependencies [143be2b9d]
16
- - Updated dependencies [7889b08fe]
17
- - Updated dependencies [9f8cd3bc5]
18
- - Updated dependencies [bfafa3f49]
19
- - Updated dependencies [57d7eb305]
20
- - Updated dependencies [d99a6a7d5]
21
- - Updated dependencies [9db81a714]
22
- - Updated dependencies [591e345e1]
23
- - Updated dependencies [4de5303c5]
24
- - Updated dependencies [95b139dc1]
25
- - Updated dependencies [1d00f2d06]
26
- - Updated dependencies [4ac7b2a1e]
27
- - Updated dependencies [e9598c71d]
28
- - Updated dependencies [4998a9631]
29
- - Updated dependencies [804d2bd6c]
30
- - Updated dependencies [3909d3224]
31
- - Updated dependencies [6a3d8a631]
32
- - Updated dependencies [e18836b20]
33
- - Updated dependencies [b1111d7b]
34
- - @akinon/next@2.0.97-rc.0
35
- - @akinon/theme-contract@2.0.97-rc.0
3
+ ## 2.0.97
4
+
5
+ ### Patch Changes
6
+
7
+ - d4fe7b15: ZERO-5013: Hide the empty Group block placeholder from shoppers
8
+
9
+ An empty `group` rendered the literal string "Empty group block" on the live storefront with no designer guard. Every shipped section layout template seeds its columns with `blocks: []`, so a half-filled layout leaked grey debug text to real visitors. The chip now only draws on the canvas, matching `ContainerBlock`; the storefront keeps the group's own element (`display: contents`) so an `<a>` or `<form>` group survives being empty.
10
+
11
+ - @akinon/next@2.0.97
12
+ - @akinon/theme-contract@2.0.97
36
13
 
37
14
  ## 2.0.96
38
15
 
package/package.json CHANGED
@@ -1,19 +1,19 @@
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.97-rc.0",
4
+ "version": "2.0.97",
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.97-rc.0",
11
+ "@akinon/next": "2.0.97",
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.97-rc.0",
16
+ "@akinon/theme-contract": "2.0.97",
17
17
  "clsx": "2.1.1",
18
18
  "lottie-web": "^5.13.0",
19
19
  "react-multi-carousel": "2.8.4",
@@ -21,7 +21,7 @@
21
21
  "zod": "4.4.1"
22
22
  },
23
23
  "devDependencies": {
24
- "@akinon/next": "2.0.97-rc.0",
24
+ "@akinon/next": "2.0.97",
25
25
  "@testing-library/jest-dom": "5.16.4",
26
26
  "@testing-library/react": "16.3.2",
27
27
  "@testing-library/user-event": "14.1.1",
@@ -87,21 +87,60 @@ describe('GroupBlock — ordering', () => {
87
87
  });
88
88
 
89
89
  describe('GroupBlock — empty states', () => {
90
- it('renders the "Empty group block" placeholder when there are no children', () => {
90
+ it('shows the designer placeholder when there are no children', () => {
91
91
  const block = { ...props().block, blocks: [] };
92
- const { container } = render(<GroupBlock {...props({ block })} />);
93
- expect(container.textContent).toContain('Empty group block');
92
+ const { container } = render(
93
+ <GroupBlock {...props({ block, isDesigner: true })} />
94
+ );
95
+ expect(container.textContent).toContain('Group (drop blocks here)');
96
+ expect(
97
+ container.querySelector('[data-group-placeholder]')
98
+ ).toBeInTheDocument();
94
99
  expect(markers(container)).toHaveLength(0);
95
100
  });
96
101
 
102
+ /*
103
+ * Every shipped layout template seeds its columns with `blocks: []`, so a
104
+ * half-filled layout section put this branch straight in front of real
105
+ * shoppers. Nothing about it may be readable.
106
+ */
107
+ it('shows a shopper no debug text at all when there are no children', () => {
108
+ const block = { ...props().block, blocks: [] };
109
+ const { container } = render(
110
+ <GroupBlock {...props({ block, isDesigner: false })} />
111
+ );
112
+ expect(container.textContent).toBe('');
113
+ expect(
114
+ container.querySelector('[data-theme-block-placeholder]')
115
+ ).toBeNull();
116
+ });
117
+
118
+ it.each([
119
+ ['a', { tag: 'a', href: 'https://kargotakip.example.com' }, 'A'],
120
+ ['form', { tag: 'form' }, 'FORM']
121
+ ])(
122
+ 'keeps the group element on the storefront so an empty <%s> survives',
123
+ (_name, properties, expectedTag) => {
124
+ const block = { ...props().block, blocks: [], properties };
125
+ const { container } = render(
126
+ <GroupBlock {...props({ block, isDesigner: false })} />
127
+ );
128
+ const wrapper = container.querySelector('.contents') as HTMLElement;
129
+ expect(wrapper.tagName).toBe(expectedTag);
130
+ expect(wrapper).toBeEmptyDOMElement();
131
+ }
132
+ );
133
+
97
134
  it('renders an empty tag (no placeholder text) for visual-utility groups', () => {
98
135
  const block = {
99
136
  ...props().block,
100
137
  blocks: [],
101
138
  properties: { shippingRole: 'progress-fill' }
102
139
  };
103
- const { container } = render(<GroupBlock {...props({ block })} />);
104
- expect(container.textContent).not.toContain('Empty group block');
140
+ const { container } = render(
141
+ <GroupBlock {...props({ block, isDesigner: true })} />
142
+ );
143
+ expect(container.textContent).toBe('');
105
144
  expect(container.querySelector('.contents')).toBeInTheDocument();
106
145
  });
107
146
  });
@@ -7,7 +7,7 @@ import {
7
7
  readItemsCurrency
8
8
  } from '../utils/analytics';
9
9
  import { BlockRendererProps } from './block-renderer-registry';
10
- import { useThemeItemScope } from './product-block-utils';
10
+ import { DesignerPlaceholder, useThemeItemScope } from './product-block-utils';
11
11
  import { renderComposedChildren } from './render-composed-children';
12
12
 
13
13
  /**
@@ -83,8 +83,28 @@ const GroupBlock = ({
83
83
  return <Tag {...tagProps} />;
84
84
  }
85
85
 
86
+ /*
87
+ * Empty is a legitimate PUBLISHED state, not a mistake to shout about:
88
+ * every shipped layout template seeds its columns with `blocks: []`, so a
89
+ * half-filled layout used to leak grey debug text to real shoppers. The
90
+ * chip belongs behind the designer guard, the way ContainerBlock's does.
91
+ *
92
+ * The live branch still renders the tag rather than returning null. The
93
+ * tag is `display: contents`, so an empty one draws nothing either way,
94
+ * but it is the group's only element: `tag` can make it an `<a>` or a
95
+ * `<form>`, and a brand's CSS or script written against that element
96
+ * would lose it. Keeping it makes the empty case a true no-op instead of
97
+ * a structural change, and matches the visual-utility branch above.
98
+ */
86
99
  return (
87
- <div style={{ padding: '20px', color: '#6b7280' }}>Empty group block</div>
100
+ <Tag {...tagProps}>
101
+ {isDesigner ? (
102
+ <DesignerPlaceholder
103
+ label="Group (drop blocks here)"
104
+ attribute="data-group-placeholder"
105
+ />
106
+ ) : null}
107
+ </Tag>
88
108
  );
89
109
  }
90
110
 
@@ -103,10 +103,12 @@ const releaseSlot = (
103
103
  *
104
104
  * EMPTY BEHAVES DIFFERENTLY PER SIDE. The designer draws a dashed band so the
105
105
  * hole is visible and droppable; the live storefront renders nothing at all.
106
- * That distinction is the whole point of the split — the empty branch of
107
- * `GroupBlock` (blocks/group-block.tsx) still ships the literal string
108
- * "Empty group block" to shoppers, which is exactly the outcome this must
109
- * not repeat.
106
+ * That distinction is the whole point of the split, and it is the rule across
107
+ * the package rather than a local habit: the empty branches of `GroupBlock`
108
+ * and `ContainerBlock` (blocks/) put their chip behind the same guard, after
109
+ * `GroupBlock` was found shipping a literal "Empty group block" string to
110
+ * shoppers (ZERO-5013). A placeholder that is not guarded is a live defect,
111
+ * not a rough edge.
110
112
  */
111
113
  export function ThemeSlot({ label, name }: ThemeSlotProps) {
112
114
  const context = useCustomBlockContext();