@syntrologie/adapt-search 2.8.0-canary.568
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/dist/SearchSurfaceElement.d.ts +140 -0
- package/dist/SearchSurfaceElement.d.ts.map +1 -0
- package/dist/cdn.d.ts +52 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +33 -0
- package/dist/cdn.js.map +7 -0
- package/dist/chunk-3KPQ5AOG.js +1389 -0
- package/dist/chunk-3KPQ5AOG.js.map +7 -0
- package/dist/chunk-NKOTQPVZ.js +4071 -0
- package/dist/chunk-NKOTQPVZ.js.map +7 -0
- package/dist/hrefSafety.d.ts +32 -0
- package/dist/hrefSafety.d.ts.map +1 -0
- package/dist/regions.d.ts +93 -0
- package/dist/regions.d.ts.map +1 -0
- package/dist/runtime.d.ts +45 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +12 -0
- package/dist/runtime.js.map +7 -0
- package/dist/schema.d.ts +49 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +66 -0
- package/dist/schema.js.map +7 -0
- package/dist/variants.d.ts +52 -0
- package/dist/variants.d.ts.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adaptive-search — product URL scheme guard
|
|
3
|
+
*
|
|
4
|
+
* Same rule and same reasoning as `isSafeNavigationHref` in
|
|
5
|
+
* `packages/adaptives/adaptive-overlays/src/cta-navigation.ts`: parse with the
|
|
6
|
+
* URL parser rather than matching string prefixes, because browsers strip
|
|
7
|
+
* embedded control characters from a scheme before resolving it, so
|
|
8
|
+
* `"java\tscript:alert(1)"` slips straight past a
|
|
9
|
+
* `.trim().toLowerCase().startsWith('javascript:')` check.
|
|
10
|
+
*
|
|
11
|
+
* It is copied rather than imported because that helper is internal to
|
|
12
|
+
* adaptive-overlays (its package `exports` publish only `./runtime`,
|
|
13
|
+
* `./schema` and `./cdn`), and every adaptive in this repo keeps its own
|
|
14
|
+
* sanitizer for the same reason — see `adaptive-product/src/sanitizer.ts`,
|
|
15
|
+
* `adaptive-overlays/src/sanitizer.ts`, `adaptive-content/src/sanitizer.ts`.
|
|
16
|
+
*/
|
|
17
|
+
/** Where an unsafe or unparseable product URL points instead: nowhere. */
|
|
18
|
+
export declare const UNSAFE_HREF_FALLBACK = "#";
|
|
19
|
+
/** True when `href` resolves to a scheme it is safe to navigate to. */
|
|
20
|
+
export declare function isSafeProductHref(href: unknown): href is string;
|
|
21
|
+
/**
|
|
22
|
+
* The href to render for a product tile: the authored URL when its scheme is
|
|
23
|
+
* safe, otherwise `'#'`.
|
|
24
|
+
*
|
|
25
|
+
* The tile keeps its anchor either way. A shopper who can reach a tile with
|
|
26
|
+
* the keyboard must still be able to focus it and hear its name, and a tile
|
|
27
|
+
* whose link we silently dropped is a tile that looks tappable and is not.
|
|
28
|
+
* Callers mark the fallback case with `data-unsafe-url` so it is visible in
|
|
29
|
+
* the DOM rather than looking like an ordinary link.
|
|
30
|
+
*/
|
|
31
|
+
export declare function safeProductHref(href: unknown): string;
|
|
32
|
+
//# sourceMappingURL=hrefSafety.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hrefSafety.d.ts","sourceRoot":"","sources":["../src/hrefSafety.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,0EAA0E;AAC1E,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAW/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAErD"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adaptive-search — region modules
|
|
3
|
+
*
|
|
4
|
+
* One function per module the plan can put in a region. Everything here is
|
|
5
|
+
* rendered with Lit `${}` interpolation only: never `unsafeHTML`, because
|
|
6
|
+
* every string on this surface is either shopper-authored (the query, the
|
|
7
|
+
* chips) or merchant catalogue text arriving over the wire.
|
|
8
|
+
*
|
|
9
|
+
* A module this build does not know renders nothing and throws nothing. The
|
|
10
|
+
* plan service ships new modules on its own cadence; an older SDK on a
|
|
11
|
+
* merchant's page must degrade to a quieter surface, never to a broken one.
|
|
12
|
+
*/
|
|
13
|
+
import { nothing, type TemplateResult } from 'lit';
|
|
14
|
+
import type { SurfaceRegion } from './variants';
|
|
15
|
+
/** A product as the plan service and the store search API both describe one. */
|
|
16
|
+
export interface SearchProduct {
|
|
17
|
+
handle: string;
|
|
18
|
+
title: string;
|
|
19
|
+
url: string;
|
|
20
|
+
price?: string;
|
|
21
|
+
imageUrl?: string;
|
|
22
|
+
}
|
|
23
|
+
/** A constraint the surface understood, shown as a removable chip. */
|
|
24
|
+
export interface SearchChip {
|
|
25
|
+
key: string;
|
|
26
|
+
label: string;
|
|
27
|
+
}
|
|
28
|
+
/** A follow-up the plan suggests, shown as a tappable button. */
|
|
29
|
+
export interface SearchSuggestion {
|
|
30
|
+
value: string;
|
|
31
|
+
label: string;
|
|
32
|
+
}
|
|
33
|
+
/** Follow-up bar payload: the shopper's words, what we understood, what next. */
|
|
34
|
+
export interface FollowupBarPayload {
|
|
35
|
+
query?: string;
|
|
36
|
+
chips?: SearchChip[];
|
|
37
|
+
suggestions?: SearchSuggestion[];
|
|
38
|
+
/**
|
|
39
|
+
* A short line the plan wants under the chips, e.g. "Nothing under $5. Here
|
|
40
|
+
* are the closest matches." Merchant-neutral server prose, interpolated like
|
|
41
|
+
* every other string here and never `unsafeHTML`.
|
|
42
|
+
*/
|
|
43
|
+
notice?: string;
|
|
44
|
+
}
|
|
45
|
+
/** How a tile reports itself when it is tapped. */
|
|
46
|
+
export interface ResultTapDetail {
|
|
47
|
+
handle: string;
|
|
48
|
+
regionId: string;
|
|
49
|
+
module: string;
|
|
50
|
+
position: number;
|
|
51
|
+
}
|
|
52
|
+
/** The three shapes a follow-up takes. All patch the same server session. */
|
|
53
|
+
export type FollowupDetail = {
|
|
54
|
+
kind: 'refine';
|
|
55
|
+
value: string;
|
|
56
|
+
} | {
|
|
57
|
+
kind: 'chip_removed';
|
|
58
|
+
value: string;
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'item_dismissed';
|
|
61
|
+
value: string;
|
|
62
|
+
};
|
|
63
|
+
export declare const RESULT_TAP_EVENT = "syntro:search:result-tap";
|
|
64
|
+
export declare const FOLLOWUP_EVENT = "syntro:search:followup";
|
|
65
|
+
export declare const SEE_STORE_RESULTS_EVENT = "syntro:search:see-store-results";
|
|
66
|
+
/** Read the follow-up bar's payload. Exported so the surface can merge the query in. */
|
|
67
|
+
export declare function readFollowupBarPayload(payload: unknown): Required<FollowupBarPayload>;
|
|
68
|
+
/**
|
|
69
|
+
* Handles the shopper has hidden. A dismissal is optimistic — the tile goes as
|
|
70
|
+
* soon as it is tapped, before any server round trip — and it OUTLIVES the
|
|
71
|
+
* payload it was tapped in: a later repaint of the same region, or a whole new
|
|
72
|
+
* plan for a surface with no server session, would otherwise hand the shopper
|
|
73
|
+
* back the product they just rejected.
|
|
74
|
+
*
|
|
75
|
+
* `visible` / `visibleOne` below apply this to what is RENDERED, which is what
|
|
76
|
+
* hides a tile in a region that has already painted and is not repainted. The
|
|
77
|
+
* runtime keeps the same rule over incoming payloads — `withoutDismissed` in
|
|
78
|
+
* `runtime-sdk/src/search/SearchSurfaceController.ts` — so a dismissed product
|
|
79
|
+
* is also out of `result_count`. Both are needed; neither replaces the other.
|
|
80
|
+
*/
|
|
81
|
+
export type DismissedHandles = ReadonlySet<string>;
|
|
82
|
+
/**
|
|
83
|
+
* How many products a filled region put on screen. The surface announces the
|
|
84
|
+
* running total through its live region, so this has to agree with what the
|
|
85
|
+
* module above actually rendered — dismissals included, because a hidden tile
|
|
86
|
+
* is not a result the shopper was shown.
|
|
87
|
+
*/
|
|
88
|
+
export declare function countRegionProducts(region: SurfaceRegion, dismissed?: DismissedHandles): number;
|
|
89
|
+
/** Use the same payload readers as rendering to decide whether there is an answer. */
|
|
90
|
+
export declare function regionHasAnswer(region: SurfaceRegion, dismissed?: DismissedHandles): boolean;
|
|
91
|
+
/** Render a region's module. An unknown module renders nothing and throws nothing. */
|
|
92
|
+
export declare function renderRegionModule(region: SurfaceRegion, dismissed?: DismissedHandles): TemplateResult | typeof nothing;
|
|
93
|
+
//# sourceMappingURL=regions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"regions.d.ts","sourceRoot":"","sources":["../src/regions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAQ,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAGzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAMhD,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,sEAAsE;AACtE,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAM9C,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AAC3D,eAAO,MAAM,cAAc,2BAA2B,CAAC;AACvD,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AA8DzE,wFAAwF;AACxF,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC,kBAAkB,CAAC,CAQrF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;AAiBnD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EACrB,SAAS,GAAE,gBAAiC,GAC3C,MAAM,CAqBR;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAC7B,MAAM,EAAE,aAAa,EACrB,SAAS,GAAE,gBAAiC,GAC3C,OAAO,CAKT;AA+ND,sFAAsF;AACtF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,SAAS,GAAE,gBAAiC,GAC3C,cAAc,GAAG,OAAO,OAAO,CA0BjC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adaptive-search — Runtime manifest
|
|
3
|
+
*
|
|
4
|
+
* Registers `<syntro-search-surface>` as a side effect of importing this
|
|
5
|
+
* module, and exposes the mountable the runtime's WidgetRegistry uses.
|
|
6
|
+
*/
|
|
7
|
+
import { type MountPlumbing } from '@syntrologie/sdk-contracts';
|
|
8
|
+
import type { SearchSurfaceConfig } from './schema';
|
|
9
|
+
/**
|
|
10
|
+
* Mounts the surface and, when the interceptor already chose a variant,
|
|
11
|
+
* paints its skeleton in the same frame. Later tasks keep the element handle
|
|
12
|
+
* and stream regions into it with `fillRegion`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SearchSurfaceMountable: {
|
|
15
|
+
mount(container: HTMLElement, config?: (SearchSurfaceConfig & MountPlumbing) | null): () => void;
|
|
16
|
+
};
|
|
17
|
+
export declare const runtime: {
|
|
18
|
+
id: string;
|
|
19
|
+
version: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
/** No DOM-mutation executors: this surface renders only. */
|
|
23
|
+
executors: never[];
|
|
24
|
+
widgets: {
|
|
25
|
+
id: string;
|
|
26
|
+
component: {
|
|
27
|
+
mount(container: HTMLElement, config?: (SearchSurfaceConfig & MountPlumbing) | null): () => void;
|
|
28
|
+
};
|
|
29
|
+
metadata: {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
icon: string;
|
|
33
|
+
/**
|
|
34
|
+
* The surface replaces a whole results page, so it needs the
|
|
35
|
+
* full-viewport slot. It is not self-sufficient anywhere else: the
|
|
36
|
+
* scrolling and scroll containment it relies on come from
|
|
37
|
+
* `overlay_full`'s own slot styles (`overflow: auto` plus
|
|
38
|
+
* `overscroll-behavior: contain`), not from this element.
|
|
39
|
+
*/
|
|
40
|
+
slots: string[];
|
|
41
|
+
};
|
|
42
|
+
}[];
|
|
43
|
+
};
|
|
44
|
+
export default runtime;
|
|
45
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,aAAa,EAAsB,MAAM,4BAA4B,CAAC;AAGpF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAIpD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB;qBAChB,WAAW,WAAW,CAAC,mBAAmB,GAAG,aAAa,CAAC,GAAG,IAAI;CASpF,CAAC;AAEF,eAAO,MAAM,OAAO;;;;;IAMlB,4DAA4D;;;;;6BAjB3C,WAAW,WAAW,CAAC,mBAAmB,GAAG,aAAa,CAAC,GAAG,IAAI;;;;;;YA4B7E;;;;;;eAMG;;;;CAKV,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
package/dist/runtime.js
ADDED
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adaptive-search — Zod schema and capabilities documentation
|
|
3
|
+
*
|
|
4
|
+
* The surface is driven by the plan service at runtime, not authored as a
|
|
5
|
+
* canvas tile, so the only config it accepts is the opening state the search
|
|
6
|
+
* interceptor hands it at mount: which variant the rules chose and what the
|
|
7
|
+
* shopper typed. Everything after that arrives through `fillRegion`.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
export declare const searchSurfaceSchema: z.ZodObject<{
|
|
11
|
+
variant: z.ZodOptional<z.ZodEnum<["grid", "stacked", "rail", "hero", "compare"]>>;
|
|
12
|
+
query: z.ZodOptional<z.ZodString>;
|
|
13
|
+
}, "strict", z.ZodTypeAny, {
|
|
14
|
+
query?: string | undefined;
|
|
15
|
+
variant?: "grid" | "stacked" | "rail" | "hero" | "compare" | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
query?: string | undefined;
|
|
18
|
+
variant?: "grid" | "stacked" | "rail" | "hero" | "compare" | undefined;
|
|
19
|
+
}>;
|
|
20
|
+
export type SearchSurfaceConfig = z.infer<typeof searchSurfaceSchema>;
|
|
21
|
+
/**
|
|
22
|
+
* No tile widgets: this surface is not something a config author places on a
|
|
23
|
+
* page. It is mounted by the search interceptor into the full-viewport
|
|
24
|
+
* overlay slot when a shopper searches.
|
|
25
|
+
*/
|
|
26
|
+
export declare const tileWidgets: {
|
|
27
|
+
widget: string;
|
|
28
|
+
defName: string;
|
|
29
|
+
propsSchema: z.ZodTypeAny;
|
|
30
|
+
}[];
|
|
31
|
+
export declare const CAPABILITIES_DOCUMENTATION: {
|
|
32
|
+
packageId: string;
|
|
33
|
+
plannerSummary: string;
|
|
34
|
+
description: string;
|
|
35
|
+
whenToUse: {
|
|
36
|
+
goal: string;
|
|
37
|
+
action: string;
|
|
38
|
+
}[];
|
|
39
|
+
conventions: {
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
}[];
|
|
43
|
+
events: {
|
|
44
|
+
name: string;
|
|
45
|
+
when: string;
|
|
46
|
+
props: string;
|
|
47
|
+
}[];
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,eAAO,MAAM,mBAAmB;;;;;;;;;EAcrB,CAAC;AAEZ,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,CAAC,CAAC,UAAU,CAAC;CAC3B,EAAO,CAAC;AAMT,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;CAoDtC,CAAC"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SURFACE_VARIANTS,
|
|
3
|
+
external_exports
|
|
4
|
+
} from "./chunk-NKOTQPVZ.js";
|
|
5
|
+
|
|
6
|
+
// src/schema.ts
|
|
7
|
+
var MAX_QUERY_LENGTH = 512;
|
|
8
|
+
var searchSurfaceSchema = external_exports.object({
|
|
9
|
+
variant: external_exports.enum(SURFACE_VARIANTS).optional().describe(
|
|
10
|
+
"Layout to paint immediately: grid, stacked, rail, hero or compare. Omit to mount an empty surface and wait for the plan."
|
|
11
|
+
),
|
|
12
|
+
query: external_exports.string().max(MAX_QUERY_LENGTH).optional().describe("The shopper's own words, shown in the follow-up bar and still editable.")
|
|
13
|
+
}).strict();
|
|
14
|
+
var tileWidgets = [];
|
|
15
|
+
var CAPABILITIES_DOCUMENTATION = {
|
|
16
|
+
packageId: "adaptive-search",
|
|
17
|
+
plannerSummary: "The search results surface. Reach for this when the strategy is about what a shopper sees after they search, not about placing a tile on a page.",
|
|
18
|
+
description: "Draws a search results page as a Syntro surface: a layout skeleton painted per variant, then filled region by region as content arrives.",
|
|
19
|
+
whenToUse: [
|
|
20
|
+
{
|
|
21
|
+
goal: "Turn on the search takeover for a workspace",
|
|
22
|
+
action: "Set search.enabled to true in the config. The surface mounts itself when a shopper searches."
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
goal: "Watch searches without changing what a shopper sees",
|
|
26
|
+
action: "Set search.observe to true instead. Telemetry is emitted and the store\u2019s own search still runs."
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
conventions: [
|
|
30
|
+
{
|
|
31
|
+
name: "The plan chooses contents, never shape",
|
|
32
|
+
description: "Each variant owns a fixed set of named regions. The plan service fills them by region id; it cannot invent a region or move one."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "Theme tokens",
|
|
36
|
+
description: "The surface reads CSS custom properties from the canvas theme: --sc-color-primary, --sc-font-family, --sc-tile-background, --sc-tile-text-color, --sc-chip-background, --sc-border-radius. It hard-codes no brand colors."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "The way back to the store",
|
|
40
|
+
description: 'Every variant carries a "See store results" button in its sticky query region, so a shopper can always reach the store\u2019s own results.'
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
events: [
|
|
44
|
+
{
|
|
45
|
+
name: "syntro:search:followup",
|
|
46
|
+
when: "Shopper refines the search: types a new query, removes a chip, or dismisses a product",
|
|
47
|
+
props: "{ kind: 'refine' | 'chip_removed' | 'item_dismissed', value }"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "syntro:search:result-tap",
|
|
51
|
+
when: "Shopper taps a product tile",
|
|
52
|
+
props: "{ handle, regionId, module, position }"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "syntro:search:see-store-results",
|
|
56
|
+
when: "Shopper asks for the store's own search results instead",
|
|
57
|
+
props: "{}"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
};
|
|
61
|
+
export {
|
|
62
|
+
CAPABILITIES_DOCUMENTATION,
|
|
63
|
+
searchSurfaceSchema,
|
|
64
|
+
tileWidgets
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/schema.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * adaptive-search \u2014 Zod schema and capabilities documentation\n *\n * The surface is driven by the plan service at runtime, not authored as a\n * canvas tile, so the only config it accepts is the opening state the search\n * interceptor hands it at mount: which variant the rules chose and what the\n * shopper typed. Everything after that arrives through `fillRegion`.\n */\n\nimport { z } from 'zod';\n\nimport { SURFACE_VARIANTS } from './variants';\n\n/** The classifier stops reading a query here, so nothing longer can be real. */\nconst MAX_QUERY_LENGTH = 512;\n\nexport const searchSurfaceSchema = z\n .object({\n variant: z\n .enum(SURFACE_VARIANTS)\n .optional()\n .describe(\n 'Layout to paint immediately: grid, stacked, rail, hero or compare. Omit to mount an empty surface and wait for the plan.'\n ),\n query: z\n .string()\n .max(MAX_QUERY_LENGTH)\n .optional()\n .describe(\"The shopper's own words, shown in the follow-up bar and still editable.\"),\n })\n .strict();\n\nexport type SearchSurfaceConfig = z.infer<typeof searchSurfaceSchema>;\n\n/**\n * No tile widgets: this surface is not something a config author places on a\n * page. It is mounted by the search interceptor into the full-viewport\n * overlay slot when a shopper searches.\n */\nexport const tileWidgets: {\n widget: string;\n defName: string;\n propsSchema: z.ZodTypeAny;\n}[] = [];\n\n// ============================================================================\n// Capabilities Documentation (injected into JSON Schema for LLM prompts)\n// ============================================================================\n\nexport const CAPABILITIES_DOCUMENTATION = {\n packageId: 'adaptive-search',\n plannerSummary:\n 'The search results surface. Reach for this when the strategy is about what a shopper sees after they search, not about placing a tile on a page.',\n description:\n 'Draws a search results page as a Syntro surface: a layout skeleton painted per variant, then filled region by region as content arrives.',\n whenToUse: [\n {\n goal: 'Turn on the search takeover for a workspace',\n action:\n 'Set search.enabled to true in the config. The surface mounts itself when a shopper searches.',\n },\n {\n goal: 'Watch searches without changing what a shopper sees',\n action:\n 'Set search.observe to true instead. Telemetry is emitted and the store\u2019s own search still runs.',\n },\n ],\n conventions: [\n {\n name: 'The plan chooses contents, never shape',\n description:\n 'Each variant owns a fixed set of named regions. The plan service fills them by region id; it cannot invent a region or move one.',\n },\n {\n name: 'Theme tokens',\n description:\n 'The surface reads CSS custom properties from the canvas theme: --sc-color-primary, --sc-font-family, --sc-tile-background, --sc-tile-text-color, --sc-chip-background, --sc-border-radius. It hard-codes no brand colors.',\n },\n {\n name: 'The way back to the store',\n description:\n 'Every variant carries a \"See store results\" button in its sticky query region, so a shopper can always reach the store\u2019s own results.',\n },\n ],\n events: [\n {\n name: 'syntro:search:followup',\n when: 'Shopper refines the search: types a new query, removes a chip, or dismisses a product',\n props: \"{ kind: 'refine' | 'chip_removed' | 'item_dismissed', value }\",\n },\n {\n name: 'syntro:search:result-tap',\n when: 'Shopper taps a product tile',\n props: '{ handle, regionId, module, position }',\n },\n {\n name: 'syntro:search:see-store-results',\n when: \"Shopper asks for the store's own search results instead\",\n props: '{}',\n },\n ],\n};\n"],
|
|
5
|
+
"mappings": ";;;;;;AAcA,IAAM,mBAAmB;AAElB,IAAM,sBAAsB,iBAChC,OAAO;AAAA,EACN,SAAS,iBACN,KAAK,gBAAgB,EACrB,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,OAAO,iBACJ,OAAO,EACP,IAAI,gBAAgB,EACpB,SAAS,EACT,SAAS,yEAAyE;AACvF,CAAC,EACA,OAAO;AASH,IAAM,cAIP,CAAC;AAMA,IAAM,6BAA6B;AAAA,EACxC,WAAW;AAAA,EACX,gBACE;AAAA,EACF,aACE;AAAA,EACF,WAAW;AAAA,IACT;AAAA,MACE,MAAM;AAAA,MACN,QACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,QACE;AAAA,IACJ;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX;AAAA,MACE,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,aACE;AAAA,IACJ;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,IACT;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adaptive-search — layout variants
|
|
3
|
+
*
|
|
4
|
+
* A variant declares the fixed set of named regions the surface paints. The
|
|
5
|
+
* plan chooses what goes IN each region; it never chooses the shape. That
|
|
6
|
+
* split is what lets the surface paint a skeleton the instant the shopper
|
|
7
|
+
* presses Enter, before a single byte of content has arrived.
|
|
8
|
+
*
|
|
9
|
+
* `src/__fixtures__/search-variant-roles.json` is the wire contract: the
|
|
10
|
+
* plan service keeps a byte-equal copy and asserts against this path, so the
|
|
11
|
+
* region ids the server emits always resolve to a region this surface owns.
|
|
12
|
+
*/
|
|
13
|
+
/** The five layouts. Same values as the SDK classifier's `SearchVariantHint`. */
|
|
14
|
+
export declare const SURFACE_VARIANTS: readonly ["grid", "stacked", "rail", "hero", "compare"];
|
|
15
|
+
export type SurfaceVariant = (typeof SURFACE_VARIANTS)[number];
|
|
16
|
+
/** What a region is FOR. A module fills it; the role decides where it sits. */
|
|
17
|
+
export type RegionRole = 'query' | 'primary' | 'secondary' | 'support' | 'converse';
|
|
18
|
+
/** One painted region: an id the plan addresses, a role, and the module inside. */
|
|
19
|
+
export interface SurfaceRegion {
|
|
20
|
+
id: string;
|
|
21
|
+
role: RegionRole;
|
|
22
|
+
module: string;
|
|
23
|
+
payload?: unknown;
|
|
24
|
+
}
|
|
25
|
+
/** A server plan: the variant to paint and the regions to fill. */
|
|
26
|
+
export interface SurfacePlan {
|
|
27
|
+
variant: SurfaceVariant;
|
|
28
|
+
regions: SurfaceRegion[];
|
|
29
|
+
suppress?: string[];
|
|
30
|
+
}
|
|
31
|
+
/** The fixed slots each variant owns. The plan chooses contents, never shape. */
|
|
32
|
+
export declare const VARIANT_REGION_ROLES: Record<SurfaceVariant, readonly RegionRole[]>;
|
|
33
|
+
/** A region slot in the painted skeleton: `<role>-<n>`, n restarting per role. */
|
|
34
|
+
export interface SkeletonRegion {
|
|
35
|
+
id: string;
|
|
36
|
+
role: RegionRole;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The region slots a variant paints, in paint order, with deterministic ids.
|
|
40
|
+
*
|
|
41
|
+
* Deterministic because the plan service numbers its regions the same way and
|
|
42
|
+
* addresses them by id alone. Derived from `VARIANT_REGION_ROLES` rather than
|
|
43
|
+
* listed a second time, so the two can never disagree.
|
|
44
|
+
*/
|
|
45
|
+
export declare function skeletonRegions(variant: SurfaceVariant): SkeletonRegion[];
|
|
46
|
+
/**
|
|
47
|
+
* True when every region in the plan has a slot of its role left in the
|
|
48
|
+
* variant. A plan that does not fit is a plan whose extra regions would paint
|
|
49
|
+
* nowhere, which is worse than painting the variant the plan asked for.
|
|
50
|
+
*/
|
|
51
|
+
export declare function planRegionsFit(plan: SurfacePlan): boolean;
|
|
52
|
+
//# sourceMappingURL=variants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variants.d.ts","sourceRoot":"","sources":["../src/variants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,yDAA0D,CAAC;AAExF,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,UAAU,CAAC;AAEpF,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,mEAAmE;AACnE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,cAAc,EAAE,SAAS,UAAU,EAAE,CAM9E,CAAC;AAEF,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE,CAOzE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAQzD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@syntrologie/adapt-search",
|
|
3
|
+
"version": "2.8.0-canary.568",
|
|
4
|
+
"description": "Adaptive Search: the search results surface, a layout skeleton painted per variant and then filled region by region as content arrives.",
|
|
5
|
+
"license": "Proprietary",
|
|
6
|
+
"private": false,
|
|
7
|
+
"author": "Syntrologie <eng@syntrologie.com>",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/SyntropyForge/amazing-demos.git",
|
|
11
|
+
"directory": "packages/adaptives/adaptive-search"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"syntrologie": {
|
|
17
|
+
"bundlingMode": "cdn-only"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
"./runtime": {
|
|
22
|
+
"types": "./dist/runtime.d.ts",
|
|
23
|
+
"import": "./dist/runtime.js"
|
|
24
|
+
},
|
|
25
|
+
"./schema": {
|
|
26
|
+
"types": "./dist/schema.d.ts",
|
|
27
|
+
"import": "./dist/schema.js"
|
|
28
|
+
},
|
|
29
|
+
"./cdn": {
|
|
30
|
+
"types": "./dist/cdn.d.ts",
|
|
31
|
+
"import": "./dist/cdn.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc --emitDeclarationOnly && node ../scripts/build-lib.mjs",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"clean": "rm -rf dist",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"lint": "biome check ."
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"lit": "3.3.2"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@open-wc/testing": "4.0.0",
|
|
50
|
+
"@open-wc/testing-helpers": "3.0.1",
|
|
51
|
+
"@syntrologie/sdk-contracts": "*",
|
|
52
|
+
"jsdom": "26.1.0",
|
|
53
|
+
"lit": "3.3.2",
|
|
54
|
+
"typescript": "5.9.3",
|
|
55
|
+
"vitest": "4.0.18",
|
|
56
|
+
"zod": "3.25.76"
|
|
57
|
+
}
|
|
58
|
+
}
|