@akinon/pz-theme 2.0.88-rc.0 → 2.0.89-beta.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/CHANGELOG.md CHANGED
@@ -1,38 +1,20 @@
1
1
  # @akinon/pz-theme
2
2
 
3
- ## 2.0.88-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.88-rc.0
35
- - @akinon/theme-contract@2.0.88-rc.0
3
+ ## 2.0.89-beta.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [cbbbfd757]
8
+ - @akinon/next@2.0.89-beta.0
9
+ - @akinon/theme-contract@2.0.89-beta.0
10
+
11
+ ## 2.0.88
12
+
13
+ ### Patch Changes
14
+
15
+ - c871a38a: ZERO-4903: Add a brand search-term transform hook to the search box Merchandiser synonym/typo tables are brand data
16
+ - @akinon/next@2.0.88
17
+ - @akinon/theme-contract@2.0.88
36
18
 
37
19
  ## 2.0.87
38
20
 
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.88-rc.0",
4
+ "version": "2.0.89-beta.0",
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.88-rc.0",
11
+ "@akinon/next": "2.0.89-beta.0",
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.88-rc.0",
16
+ "@akinon/theme-contract": "2.0.89-beta.0",
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.88-rc.0",
23
+ "@akinon/next": "2.0.89-beta.0",
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",
@@ -4,6 +4,7 @@ import React, { useRef, useState } from 'react';
4
4
  import { useRouter } from 'next/navigation';
5
5
  import { getResponsiveValue, partAttrs, resolveForceOpen } from '../utils';
6
6
  import { emitCommerceEvent } from '../utils/analytics';
7
+ import { applySearchTermTransform } from '../utils/search-transform';
7
8
  import { useOverlay } from '../hooks/use-overlay';
8
9
  import ThemeBlock from '../theme-block';
9
10
  import { BlockRendererProps } from './block-renderer-registry';
@@ -183,7 +184,9 @@ const SearchBoxBlock = ({
183
184
 
184
185
  const submit = (event: React.FormEvent) => {
185
186
  event.preventDefault();
186
- const value = term.trim();
187
+ // The brand's synonym/typo transform (ZERO-4903) rewrites the query the
188
+ // way the merchandiser's table says before anything runs or reports it.
189
+ const value = applySearchTermTransform(term.trim());
187
190
  if (!value || isDesigner) return;
188
191
  emitCommerceEvent('search', { searchTerm: value });
189
192
  const separator = searchUrl.includes('?') ? '&' : '?';
@@ -0,0 +1,34 @@
1
+ import {
2
+ __resetSearchTermTransformForTests,
3
+ applySearchTermTransform,
4
+ registerSearchTermTransform
5
+ } from '../search-transform';
6
+
7
+ describe('search term transform registry (ZERO-4903)', () => {
8
+ afterEach(() => __resetSearchTermTransformForTests());
9
+
10
+ it('is the identity when no brand registered', () => {
11
+ expect(applySearchTermTransform('spka')).toBe('spka');
12
+ });
13
+
14
+ it('runs the registered transform and can unregister it', () => {
15
+ const detach = registerSearchTermTransform((text) =>
16
+ text.replace('spka', 'şapka')
17
+ );
18
+ expect(applySearchTermTransform('spka')).toBe('şapka');
19
+
20
+ detach();
21
+ expect(applySearchTermTransform('spka')).toBe('spka');
22
+ });
23
+
24
+ it('falls back to the input when the transform throws or empties', () => {
25
+ registerSearchTermTransform(() => {
26
+ throw new Error('brand bug');
27
+ });
28
+ expect(applySearchTermTransform('polo')).toBe('polo');
29
+
30
+ __resetSearchTermTransformForTests();
31
+ registerSearchTermTransform(() => '');
32
+ expect(applySearchTermTransform('polo')).toBe('polo');
33
+ });
34
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Brand hook for search-term rewriting (ZERO-4903).
3
+ *
4
+ * Merchandisers maintain synonym/typo tables ("spka" → "şapka") that the
5
+ * query must pass through BEFORE it is searched — but the table's source and
6
+ * matching rules are brand decisions, so the composed search box never learns
7
+ * them. A brand registers one transform from its own code (the same ownership
8
+ * pattern as the analytics bridge), and the search-box block runs every
9
+ * submitted query through it. No registration means identity: stores without
10
+ * a table keep exactly today's behavior.
11
+ */
12
+
13
+ type SearchTermTransform = (text: string) => string;
14
+
15
+ let transform: SearchTermTransform | null = null;
16
+
17
+ /** Register the brand's transform. Returns an unregister function. */
18
+ export const registerSearchTermTransform = (
19
+ fn: SearchTermTransform
20
+ ): (() => void) => {
21
+ transform = fn;
22
+ return () => {
23
+ if (transform === fn) transform = null;
24
+ };
25
+ };
26
+
27
+ /** The query the search should run — the input itself when no brand hooked in
28
+ * or the transform throws (a brand bug must never break search). */
29
+ export const applySearchTermTransform = (text: string): string => {
30
+ if (!transform) return text;
31
+ try {
32
+ const result = transform(text);
33
+ return typeof result === 'string' && result.length > 0 ? result : text;
34
+ } catch {
35
+ return text;
36
+ }
37
+ };
38
+
39
+ /** Test-only: reset the registry between cases. */
40
+ export const __resetSearchTermTransformForTests = (): void => {
41
+ transform = null;
42
+ };