similarbuild 0.3.5 → 0.4.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/package.json +1 -1
- package/templates/commands/build-page.md +62 -493
- package/templates/commands/build-site.md +98 -705
- package/templates/commands/clip-section.md +102 -501
- package/templates/skills/sb-build-wp/SKILL.md +37 -265
- package/templates/skills/sb-inspect-live/scripts/inspect-live.mjs +4 -512
- package/templates/skills/sb-review-checks/SKILL.md +0 -113
- package/templates/skills/sb-review-checks/references/review-rules.md +0 -195
- package/templates/skills/sb-review-checks/scripts/lib/anti-patterns.mjs +0 -385
- package/templates/skills/sb-review-checks/scripts/lib/cross-reference.mjs +0 -115
- package/templates/skills/sb-review-checks/scripts/lib/design-quality.mjs +0 -541
- package/templates/skills/sb-review-checks/scripts/review-checks.mjs +0 -250
- package/templates/skills/sb-review-checks/scripts/tests/test-anti-patterns.mjs +0 -366
- package/templates/skills/sb-review-checks/scripts/tests/test-cross-reference.mjs +0 -170
- package/templates/skills/sb-review-checks/scripts/tests/test-design-quality.mjs +0 -493
- package/templates/skills/sb-review-checks/scripts/tests/test-review-checks.mjs +0 -267
- package/templates/skills/sb-test-interactivity/SKILL.md +0 -133
- package/templates/skills/sb-test-interactivity/scripts/test-interactivity.mjs +0 -970
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/aria-controls-broken.html +0 -32
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/aria-controls-good.html +0 -47
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/deferred-listeners.html +0 -67
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/details-good.html +0 -25
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/dialog-good.html +0 -38
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/no-interactive.html +0 -15
- package/templates/skills/sb-test-interactivity/scripts/tests/test-test-interactivity.mjs +0 -223
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "similarbuild",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Visual migration framework for Claude Code — clone a live page, get a paste-ready WordPress/Elementor or Shopify section file, validated and auto-corrected.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,549 +1,118 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
3
|
-
argument-hint: <URL> [--target wp|shopify] [--project-name <slug>] [--
|
|
2
|
+
description: Clones one page by splitting it into sections (using the live DOM's classified sections) and invoking /clip-section for each. Produces a per-section folder with reference, build, render, diff.
|
|
3
|
+
argument-hint: <URL> [--target wp|shopify] [--project-name <slug>] [--max-iterations <n>]
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# /build-page —
|
|
6
|
+
# /build-page — page = list of sections
|
|
7
7
|
|
|
8
|
-
You are the SimilarBuild orchestrator for
|
|
8
|
+
You are the SimilarBuild orchestrator for one page. The page is decomposed into atomic sections; each section runs through `/clip-section`. There is NO page-level HTML. The user pastes each section.html one by one into the destination.
|
|
9
9
|
|
|
10
10
|
## Mission
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Inspect the live page once → list the sections → run `/clip-section` for each → emit final per-section folders + a concat helper.
|
|
13
13
|
|
|
14
|
-
## The
|
|
14
|
+
## The flow
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
2. **Defensive specificity is mandatory.** This is enforced inside `sb-build-wp` / `sb-build-shopify` — your job is to never strip `fixHints` that target it.
|
|
18
|
-
3. **Validate before showing.** NEVER print the build path, contents, or "ready to ship" message to the user before BOTH `sb-validate-static-render` AND `sb-compare-visual` confirm `diffPercent < threshold` (or, after auto-correction exhausted, you escalate explicitly).
|
|
19
|
-
4. **No fabrication.** If `sb-extract-assets` returns a `failed[]` entry, surface it; never invent a substitute URL. If `sb-inspect-live` returns `widgetBlocked: true`, stop and escalate to user — don't compose from a blocked page.
|
|
16
|
+
### Step 0 — Config + project slug
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Architectural decision: how to invoke sub-skills
|
|
24
|
-
|
|
25
|
-
**Decision: hybrid — `Bash` for deterministic skills, `Skill` tool for the composer.**
|
|
26
|
-
|
|
27
|
-
| Sub-skill | Invocation | Why |
|
|
28
|
-
| --- | --- | --- |
|
|
29
|
-
| `sb-inspect-live` | **Bash** → `node .claude/skills/sb-inspect-live/scripts/inspect-live.mjs ...` | 95%+ deterministic. Stable CLI. LLM adds zero value to the invocation — it just passes args. |
|
|
30
|
-
| `sb-extract-assets` | **Bash** → `node .claude/skills/sb-extract-assets/scripts/extract-assets.mjs ...` | Same. Pure download + sanitize + dedupe. |
|
|
31
|
-
| `sb-build-wp` / `sb-build-shopify` | **Skill** tool → `Skill(skill="sb-build-wp", args="...")` | **The composition is creative**: pick the pattern, name the scope, place defensive specificity. The skill's SKILL.md + `references/wp-build-rules.md` are the LLM's manual. Bash can't compose. |
|
|
32
|
-
| `sb-validate-static-render` | **Bash** → `node .claude/skills/sb-validate-static-render/scripts/validate-static-render.mjs ...` | Headless render + token probe. Fully scripted. |
|
|
33
|
-
| `sb-test-interactivity` | **Bash** → `node .claude/skills/sb-test-interactivity/scripts/test-interactivity.mjs ...` | Headless behavioral probe (aria-controls / details / dialog). Diagnostic gate — `passed:false` becomes a warning, not a block. |
|
|
34
|
-
| `sb-compare-visual` | **Bash** → `node .claude/skills/sb-compare-visual/scripts/compare-visual.mjs ...` | pixelmatch + token diff. Pure determinism. |
|
|
35
|
-
| `sb-review-checks` | **Bash** → `node .claude/skills/sb-review-checks/scripts/review-checks.mjs ...` | cheerio + regex. Pure determinism. Its `candidateFix` strings are the contract — you forward them verbatim, never reformat. |
|
|
36
|
-
|
|
37
|
-
**Why not all-Skill:** the deterministic five would burden the orchestrator with their SKILL.md instructions on every loop iteration (the auto-correct loop calls `validate-static-render` + `compare-visual` + `review-checks` up to N+1 times). Bash keeps each call to one CLI invocation and one JSON parse.
|
|
38
|
-
|
|
39
|
-
**Why not all-Bash:** `sb-build-wp` has no compose CLI — its `build-wp.mjs` only handles `validate` and `write` subcommands. The actual composition is the LLM reading the skill's references and producing HTML. That work has to happen via Skill.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Tool dependencies
|
|
44
|
-
|
|
45
|
-
- `Bash` — orchestrate, parse JSON via `jq` or shell, run the .mjs scripts, mkdir, read config
|
|
46
|
-
- `Read` — load `.claude/sb-config.yaml`, memory files, the built fragment for inspection
|
|
47
|
-
- `Write` — write `report.html`, decisions.md, autolearned anti-patterns
|
|
48
|
-
- `Edit` — surgical updates to `report.html` between iterations
|
|
49
|
-
- `Skill` — invoke `sb-build-wp` / `sb-build-shopify`
|
|
50
|
-
- `WebFetch` — only for sanity-checking the input URL responds before launching playwright (optional)
|
|
51
|
-
|
|
52
|
-
Do NOT use the Agent tool to delegate the workflow — the orchestrator stays in the main conversation.
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## Inputs (parse from `ARGUMENTS:`)
|
|
57
|
-
|
|
58
|
-
The text after `ARGUMENTS:` contains the user's input. Extract:
|
|
59
|
-
|
|
60
|
-
| Flag | Required | Default | Behavior |
|
|
61
|
-
| --- | --- | --- | --- |
|
|
62
|
-
| `<URL>` (positional, first) | yes | — | Absolute URL to clone. If missing, stop and ask the user. |
|
|
63
|
-
| `--target wp\|shopify` | no | `default_target` from `.claude/sb-config.yaml`, fallback `wp` | Routes to `sb-build-wp` (`wp`) or `sb-build-shopify` (`shopify`). If `shopify` is requested but the skill doesn't exist yet (check `.claude/skills/sb-build-shopify/SKILL.md`), stop and tell the user the Shopify path isn't built yet. |
|
|
64
|
-
| `--project-name <slug>` | no | auto-derived from hostname (see below) | Override the project folder name. Useful for branch experiments (e.g. `--project-name alpha-v2`). |
|
|
65
|
-
| `--no-auto-correct` | no | false | Escalate on the first comparison failure instead of running the auto-correct loop. |
|
|
66
|
-
| `--dry-run` | no | false | Run inspect + extract + plan, then stop. Print a summary of what *would* be built and the assets that would be downloaded. No build, no validate, no compare. |
|
|
67
|
-
|
|
68
|
-
If the user passes any other flag, ignore it and continue (don't error on unknown flags — just print a one-line warning).
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Step 0 — Config + memory
|
|
18
|
+
Same as `/clip-section`: load `.claude/sb-config.yaml`, derive `{project-slug}`.
|
|
73
19
|
|
|
74
|
-
|
|
20
|
+
Derive `{page-slug}` from URL path: `/products/foo` → `pdp-foo`; root `/` → `home`; `/policies/privacy-policy` → `pages-privacy-policy`; etc.
|
|
75
21
|
|
|
76
|
-
|
|
77
|
-
output_folder: ./sb-output
|
|
78
|
-
default_target: wp
|
|
79
|
-
default_viewport: 390
|
|
80
|
-
auto_correct_max_iterations: 2
|
|
81
|
-
diff_threshold_percent: 10
|
|
82
|
-
strip_metadata: true
|
|
83
|
-
```
|
|
22
|
+
### Step 1 — Single page inspect (Bash)
|
|
84
23
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
2. **Load the memory cascade** (pattern #21). Read these in order; later layers override earlier ones for the same key:
|
|
88
|
-
- **Plugin (versioned)**: `.claude/skills/sb-shared/memory/{anti-patterns,patterns,fixes,design-knowledge}.md` — skip silently if absent (the framework is early; populated by item #13 of the build roadmap).
|
|
89
|
-
- **Per-user (auto-learned)**: `~/.claude/similarbuild-memory/{anti-patterns,patterns,fixes}.md` — skip silently if absent. If the directory itself is missing, do NOT create it eagerly; create it only if step 12 (auto-learn) actually has something to save.
|
|
90
|
-
- **Bundled fallback**: each sub-skill's `references/*.md` — read as part of the skill's own activation, not by you.
|
|
91
|
-
|
|
92
|
-
Hold this knowledge in working memory so you can: (a) filter a relevant subset for `sb-build-{wp,shopify}` (lean context discipline — pattern #3), and (b) recognize a novel pattern in step 12.
|
|
93
|
-
|
|
94
|
-
**What "filtered subset" means for the builder:** when you invoke `sb-build-wp`/`sb-build-shopify`, do NOT pass the entire memory cascade. Pass only:
|
|
95
|
-
- The patterns whose `sectionType` matches `inspection.sectionType` (or the closest siblings).
|
|
96
|
-
- The anti-patterns that are likely relevant for the section type (e.g. anti-pattern #1 `100vh` is relevant for hero, irrelevant for FAQ).
|
|
97
|
-
- The design-knowledge entries tagged for this target (`wp` or `shopify`) — at most 1-2 KB of curated content.
|
|
98
|
-
|
|
99
|
-
If you have nothing curated yet (early framework state), skip — the skill's bundled `references/` is sufficient.
|
|
100
|
-
|
|
101
|
-
3. **Project slug.**
|
|
102
|
-
- If `--project-name <slug>` is given: use it verbatim (validate it's URL-safe; warn if not).
|
|
103
|
-
- Else: derive from URL hostname.
|
|
104
|
-
- Parse hostname (e.g. `https://www.example-store.com/path?q=x` → `www.example-store.com`).
|
|
105
|
-
- Strip `www.`, strip leading subdomain only when it's `www`/`m`/`store`/`shop`.
|
|
106
|
-
- Strip TLD (last `.xxx` or `.xxx.yy` for known compound TLDs like `.com.br`, `.co.uk`).
|
|
107
|
-
- Lowercase, replace any non-`[a-z0-9]` with `-`, collapse repeats, trim leading/trailing `-`.
|
|
108
|
-
- Examples: `https://example-store.com` → `example-store`; `https://www.lojaexemplo.com.br/products/x` → `lojaexemplo`.
|
|
109
|
-
- Hold as `{project-slug}` for the rest of the run.
|
|
110
|
-
|
|
111
|
-
4. **Project structure.** Compute `{project-root}/{output_folder}/{project-slug}/` and ensure these subdirs exist (mkdir -p, idempotent):
|
|
112
|
-
|
|
113
|
-
```
|
|
114
|
-
{output_folder}/{project-slug}/
|
|
115
|
-
├── clean/ ← built fragments land here, organized by type
|
|
116
|
-
├── assets/ ← content-hash images
|
|
117
|
-
├── reports/
|
|
118
|
-
│ ├── diffs/
|
|
119
|
-
│ └── validations/
|
|
120
|
-
└── .sb-memory/ ← project-local memory (decisions, palette, etc.)
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Never write outside `{project-slug}/` (isolation guarantee).
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Step 1 — Inspect live (Bash)
|
|
24
|
+
Run `sb-inspect-live` ONCE for the whole page (no `--selector`). Output to `.sb-memory/inspect-{page-slug}/`:
|
|
128
25
|
|
|
129
26
|
```bash
|
|
130
27
|
node .claude/skills/sb-inspect-live/scripts/inspect-live.mjs \
|
|
131
28
|
--url "{URL}" \
|
|
132
29
|
--viewport-width {default_viewport} \
|
|
133
30
|
--viewport-height 844 \
|
|
134
|
-
--output-dir "{output_folder}/{project-slug}/.sb-memory/inspect-{
|
|
31
|
+
--output-dir "{output_folder}/{project-slug}/.sb-memory/inspect-{page-slug}"
|
|
135
32
|
```
|
|
136
33
|
|
|
137
|
-
|
|
34
|
+
The skill emits `sectionCrops[]` — one crop per visual band (header, hero, products grid, FAQ, footer, etc).
|
|
138
35
|
|
|
139
|
-
|
|
140
|
-
- Exit non-zero → surface stderr to user, stop.
|
|
141
|
-
- `inspection.widgetBlocked === true` → stop. Tell the user: "The live page returned a bot-challenge / Cloudflare wall. Try a different URL, or open it in a real browser, copy the rendered HTML, and we'll wire a Plan B." Do NOT continue.
|
|
142
|
-
- `inspection.dom` empty but `widgetBlocked: false` → also stop with a clear message.
|
|
143
|
-
- Otherwise continue with `{inspection-path}` (the path to the saved `inspection.json`) and `inspection.imgUrls`.
|
|
36
|
+
### Step 2 — Section list
|
|
144
37
|
|
|
145
|
-
|
|
38
|
+
Read `inspection.json.sectionCrops[]`. Filter to capture-worthy bands:
|
|
146
39
|
|
|
147
|
-
|
|
40
|
+
- Drop crops with `bbox.h < 60` or `bbox.w < 200` (spacers).
|
|
41
|
+
- Drop crops named `mainbodycontainer` / `skip-to-content` / similar Loox/wrappers.
|
|
42
|
+
- Order top-to-bottom by `bbox.y`.
|
|
148
43
|
|
|
149
|
-
|
|
44
|
+
Confirm the section list with the user (single human checkpoint per page run):
|
|
150
45
|
|
|
151
|
-
```bash
|
|
152
|
-
node .claude/skills/sb-extract-assets/scripts/extract-assets.mjs \
|
|
153
|
-
--inspection-path "{inspection-path}" \
|
|
154
|
-
--output-dir "{output_folder}/{project-slug}/assets" \
|
|
155
|
-
--target "{target}" \
|
|
156
|
-
--existing-assets-dir "{output_folder}/{project-slug}/assets"
|
|
157
46
|
```
|
|
47
|
+
[3/7] Sections detected on {URL}:
|
|
158
48
|
|
|
159
|
-
(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
- `assetsMap.failed[]` non-empty → DON'T stop. Forward to the builder; the builder decides per-asset (drop decorative, placeholder hero, never fabricate).
|
|
166
|
-
- `assetsMap.assets[].strippedMetadata === false` for any entry → log a warning to the report; continue.
|
|
167
|
-
|
|
168
|
-
**Lean context** — only the URL list (via `--inspection-path`) and the output dir. Nothing else.
|
|
169
|
-
|
|
170
|
-
If `--dry-run` was passed: print a summary now (URL → section type → asset count → estimated complexity), write a basic `reports/plan.{ts}.md`, and exit. Do NOT continue.
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
## Step 3 — Build (Skill)
|
|
49
|
+
1. announcement (h=46)
|
|
50
|
+
2. header (h=62)
|
|
51
|
+
3. shopify-section (h=488) — likely hero
|
|
52
|
+
4. features (h=726) — products grid
|
|
53
|
+
5. faq (h=817)
|
|
54
|
+
6. footer (h=1058)
|
|
175
55
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
- Section type drives the subfolder under `clean/`:
|
|
179
|
-
- `hero`, `intro`, `cta`, `testimonials` (and similar home blocks) → `clean/home/{section-type}.html`
|
|
180
|
-
- `pdp-*` → `clean/pdp/{slug}.html`
|
|
181
|
-
- `header`/`footer` → `clean/global/{header,footer}.html`
|
|
182
|
-
- Other → `clean/sections/{section-type}.html`
|
|
183
|
-
- For Shopify, swap `.html` → `.liquid`.
|
|
56
|
+
Reply: confirm | edit | skip N | only N | cancel
|
|
57
|
+
```
|
|
184
58
|
|
|
185
|
-
|
|
59
|
+
`confirm` → proceed. `edit` → re-prompt the list, user types comma-separated indexes to keep. `only 3` → only section 3. `cancel` → exit.
|
|
186
60
|
|
|
187
|
-
|
|
61
|
+
### Step 3 — Per-section loop
|
|
188
62
|
|
|
189
|
-
|
|
190
|
-
Skill(
|
|
191
|
-
skill="sb-build-wp",
|
|
192
|
-
args="inspection={inspection-path} assets-map={assets-map-path} output-path={output-path} preset=wp-elementor"
|
|
193
|
-
)
|
|
194
|
-
```
|
|
63
|
+
For each confirmed section, run `/clip-section`:
|
|
195
64
|
|
|
196
|
-
For Shopify:
|
|
197
65
|
```
|
|
198
66
|
Skill(
|
|
199
|
-
skill="
|
|
200
|
-
args="
|
|
67
|
+
skill="clip-section",
|
|
68
|
+
args="{URL} --selector '<best-css-for-section-N>' --target {target} --project-name {project-slug} --max-iterations {max}"
|
|
201
69
|
)
|
|
202
70
|
```
|
|
203
71
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
**The skill returns:** absolute path to the written file, validator's report (errors/warnings), and a one-line note on the chosen pattern. Do not request the file's contents back — you'll let `sb-validate-static-render` open it.
|
|
207
|
-
|
|
208
|
-
If the skill's validator returns errors and the skill couldn't fix them, surface to the user; this is a builder-side defect, not an auto-correct case.
|
|
209
|
-
|
|
210
|
-
---
|
|
211
|
-
|
|
212
|
-
## Step 4 — Validate render (Bash)
|
|
213
|
-
|
|
214
|
-
```bash
|
|
215
|
-
node .claude/skills/sb-validate-static-render/scripts/validate-static-render.mjs \
|
|
216
|
-
--file "{output-path}" \
|
|
217
|
-
--preset "{preset}" \
|
|
218
|
-
--output-dir "{output_folder}/{project-slug}/reports/validations/{slug}-{iteration}" \
|
|
219
|
-
--viewport-width {default_viewport} \
|
|
220
|
-
--viewport-height 844 \
|
|
221
|
-
[--assets-map-path "{assets-map-path}"]
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Where `{preset}` is `wp-elementor` or `shopify-section`, `{slug}` is the section slug from step 3, and `{iteration}` is `0` for the first build, `1`/`2` for retries.
|
|
225
|
-
|
|
226
|
-
**`--assets-map-path` (Pattern #32, Shopify-only).** Pass it whenever
|
|
227
|
-
`target === shopify` and Step 2 produced an `assetsMap`. Without it, every
|
|
228
|
-
`image_picker` setting that the builder emitted *without a `default`* (per
|
|
229
|
-
anti-pattern #12) renders as the `{% else %}` placeholder — a neutral grey
|
|
230
|
-
fallback — which inflates `compare-visual` diff% by ~30pp against the live
|
|
231
|
-
photo with no actionable fix. With it, `validate-static-render` mocks the
|
|
232
|
-
image_picker context from matching assetsMap entries (id-keyword → context-
|
|
233
|
-
substring heuristic) so the rendered fragment shows real imagery.
|
|
234
|
-
|
|
235
|
-
For `target === wp` skip the flag — WP path has no Liquid schema and no
|
|
236
|
-
image_picker mocks to populate.
|
|
237
|
-
|
|
238
|
-
Capture stdout JSON → `render`. Hold `{render-screenshot}` (`render.screenshot`) and `{render-json-path}` (the saved `render.json`).
|
|
239
|
-
|
|
240
|
-
**Branch:**
|
|
241
|
-
- Exit non-zero → if stderr mentions `liquidjs` or `playwright` missing, surface install hints; otherwise pass stderr through and stop.
|
|
242
|
-
- `render.warnings[]` includes `tiny-screenshot` → treat as a hard failure of THIS iteration (the build didn't render). Skip Steps 5–7 and route to Step 9 (auto-correct loop) if budget remains, else Step 10 (escalate).
|
|
243
|
-
- `render.geometry.viewportOverflow === true` → flag for the comparator (high-severity hint), and ALSO suppress `--crop-build-bbox` in Step 5 (bbox would underestimate when content overflows). Continue to step 5.
|
|
244
|
-
- Otherwise continue.
|
|
245
|
-
|
|
246
|
-
**Lean context** — file + preset + output-dir + viewport. The skill loads its own preset YAML.
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
250
|
-
## Step 4-bis — Test interactivity (Bash, diagnostic gate)
|
|
251
|
-
|
|
252
|
-
```bash
|
|
253
|
-
node .claude/skills/sb-test-interactivity/scripts/test-interactivity.mjs \
|
|
254
|
-
--file "{output-path}" \
|
|
255
|
-
--preset "{preset}" \
|
|
256
|
-
--output-dir "{output_folder}/{project-slug}/reports/validations/{slug}-{iteration}"
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
Capture `interactivity-report.json` (same dir as `validate-static-render`). Hold the parsed report.
|
|
260
|
-
|
|
261
|
-
**Diagnostic mode (NOT a blocker):**
|
|
262
|
-
|
|
263
|
-
- Exit `!= 0` → log stderr verbatim and continue to Step 5 with no warning. Common hint: `playwright`/chromium missing — suggest `npx playwright install chromium`. Don't escalate.
|
|
264
|
-
- Exit `0` AND `passed === true` → silently proceed. No warning, no status change.
|
|
265
|
-
- Exit `0` AND `passed === false`:
|
|
266
|
-
- **Append** to `interactivityWarnings` array (NOT single-shot overwrite): `interactivityWarnings.push({ iteration: <n>, tests: report.tests.filter(t => !t.passed) })`. Iterations with 0 warnings do NOT append. Cross-iter history preserved — useful for the success/escalation path (Steps 8/10) which renders a section in `report.html` summarizing which iter saw which break.
|
|
267
|
-
- Status badge on the build's report ganha sufixo `+!interactive` (e.g. `✅+!interactive`). Calculated dynamically: applies if `interactivityWarnings.length > 0` em qualquer iter.
|
|
268
|
-
- **Final status remains whatever the decision matrix decides** (Step 7). No promotion to ❌. The skill is diagnostic, not a gatekeeper — policy locked in G2 spec change log. A user who wants hard blocking can re-run with the warning visible and choose to discard the build.
|
|
269
|
-
- Continue to Step 5.
|
|
270
|
-
|
|
271
|
-
**Why diagnostic and not blocker:** `sb-test-interactivity` is new (G2). False-positives during early adoption are expected (web components outside the canonical whitelist, drawers with atypical transitions). Promoting to hard gate before maturity would block good builds. Explicit warning + `interactivityWarnings` in the report gives visibility without blocking. Promotion to blocker is a separate spec decision (G8+).
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
## Step 5 — Compare visual (Bash)
|
|
276
|
-
|
|
277
|
-
```bash
|
|
278
|
-
node .claude/skills/sb-compare-visual/scripts/compare-visual.mjs \
|
|
279
|
-
--live-screenshot "{inspection-screenshot}" \
|
|
280
|
-
--build-screenshot "{render-screenshot}" \
|
|
281
|
-
--output-dir "{output_folder}/{project-slug}/reports/diffs/{slug}-{iteration}" \
|
|
282
|
-
--tokens-live "{inspection-path}" \
|
|
283
|
-
--tokens-build "{render-json-path}" \
|
|
284
|
-
--threshold {diff_threshold_percent} \
|
|
285
|
-
[--crop-live-bbox "{x},{y},{w},{h}"] \
|
|
286
|
-
[--crop-build-bbox "{x},{y},{w},{h}"]
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
**Scope-aligned diff (anti-pattern #10 + Pattern #27).** Anti-pattern #10 isn't
|
|
290
|
-
asymmetric — both screenshots can carry "scope mismatch" pixels that aren't visual
|
|
291
|
-
drift. Crop both sides to their measured bboxes whenever possible.
|
|
292
|
-
|
|
293
|
-
- **`--crop-live-bbox`** — pass `inspection.sectionBoundingBox.{x,y,w,h}` when
|
|
294
|
-
it's non-null. Cropping the full-page live to the detected section slice
|
|
295
|
-
resolves anti-pattern #10. Skip when `inspection.sectionBoundingBox === null`
|
|
296
|
-
(the inspector either ran with `--selector`, so the live is already
|
|
297
|
-
element-only, or only the h1 fallback fired and the bbox isn't trustworthy).
|
|
72
|
+
The selector is derived from the inspection DOM tree: walk to the node matching the crop's bbox.y, generate a stable CSS selector (tag + first class + nth-of-type fallback).
|
|
298
73
|
|
|
299
|
-
|
|
300
|
-
(or `render.geometry.probeRoot.bbox` if `sections[]` is empty) when:
|
|
301
|
-
- `render.geometry.viewportOverflow === false` (otherwise the bbox under-
|
|
302
|
-
estimates and would clip real overflow content), AND
|
|
303
|
-
- the section bbox height is **less than the captured viewport height** by
|
|
304
|
-
a non-trivial margin (e.g. >10% smaller). Sections that fill the viewport
|
|
305
|
-
don't benefit and the crop adds nothing.
|
|
306
|
-
This is Pattern #27 — symmetric of anti-pattern #10. Without it, a
|
|
307
|
-
390×507 hero rendered into a 390×844 viewport carries 337px of empty
|
|
308
|
-
white space that pads against the cropped live and inflates diff% by tens
|
|
309
|
-
of points with no actual visual drift.
|
|
74
|
+
When no good selector is available (anonymous wrappers), invoke `/clip-section` with `--selector` omitted but pass `--section-bbox "{x},{y},{w},{h}"` so the skill crops the reference at that bbox from the page-level screenshot.
|
|
310
75
|
|
|
311
|
-
|
|
312
|
-
`sb-validate-static-render`'s aligned `deviceScaleFactor=3`, Pattern #22). Don't override
|
|
313
|
-
unless one of those skills was invoked at a different DPR.
|
|
76
|
+
Each `/clip-section` invocation writes its own folder at `sections/{page-slug}/{idx:02}-{section-type}/`.
|
|
314
77
|
|
|
315
|
-
|
|
78
|
+
### Step 4 — Concat helper (optional)
|
|
316
79
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
- `1` → script error. Surface stderr, stop.
|
|
320
|
-
- `2` → invalid args. Bug in this orchestrator — fix the call.
|
|
321
|
-
|
|
322
|
-
**Lean context** — two screenshot paths + two token JSONs + threshold + optional bboxes. That's it.
|
|
323
|
-
|
|
324
|
-
---
|
|
325
|
-
|
|
326
|
-
## Step 6 — Review checks (Bash, always runs — Pattern #28)
|
|
80
|
+
Concatenate all section.html files in order into `final/{page-slug}-body.html`:
|
|
327
81
|
|
|
328
82
|
```bash
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
--compare-diffs "{compare-report-path}"
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
Capture stdout JSON → `review`. Note `review.passed` and the `violations[]` array — sorted high → medium → low, with `correlatedDiff` set on entries that overlap a high-severity visual diff.
|
|
337
|
-
|
|
338
|
-
**Why this always runs (Pattern #28):** review-checks is a pure-deterministic auditor (cheerio + regex, no chromium, ~0s overhead). Compare-visual measures *visual drift*; review-checks measures *structural soundness*. They're orthogonal — a build with `100vh` is structurally broken even if the diff happens to be low; a build with perfect HTML can still drift visually due to font rendering. Running review-checks unconditionally makes Step 7's decision matrix fire on signal, not vibes.
|
|
339
|
-
|
|
340
|
-
**Branch on exit code:**
|
|
341
|
-
- `0` → `review.passed === true`. Continue to Step 7.
|
|
342
|
-
- `3` → `review.passed === false`, violations present. Continue to Step 7.
|
|
343
|
-
- `1`/`2` → surface stderr, stop.
|
|
344
|
-
|
|
345
|
-
**Lean context** — file + preset + output-dir + compare report path. Nothing else.
|
|
346
|
-
|
|
347
|
-
---
|
|
348
|
-
|
|
349
|
-
## Step 7 — Decision matrix (Pattern #28)
|
|
350
|
-
|
|
351
|
-
**Pre-check 0 (coverage gate, runs FIRST):**
|
|
352
|
-
|
|
353
|
-
Calcule contra schema **real** do `sb-inspect-live`:
|
|
354
|
-
|
|
355
|
-
```
|
|
356
|
-
buildHeight = render.geometry.totalHeight # primary: from sb-validate-static-render
|
|
357
|
-
liveMainHeight = inspection.dom[0].bbox.h # primary: root walked node bbox (body/main)
|
|
358
|
-
|| (walk inspection.dom recursively, collect classified-sections bboxes,
|
|
359
|
-
return max(s.bbox.y + s.bbox.h) - min(s.bbox.y))
|
|
360
|
-
# fallback: span das classified sections (warn)
|
|
361
|
-
coverageRatio = buildHeight / liveMainHeight
|
|
83
|
+
for sec in {output_folder}/{project-slug}/sections/{page-slug}/*/; do
|
|
84
|
+
cat "$sec/section.html"
|
|
85
|
+
echo ""
|
|
86
|
+
done > "{output_folder}/{project-slug}/final/{page-slug}-body.html"
|
|
362
87
|
```
|
|
363
88
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
If `inspection.dom[]` is empty AND no classified sections collectable → log warn `[build-page] WARN: coverage gate skipped — empty inspection.dom`. Skip coverage gate; proceed to existing matrix. Persist `coverage = null`.
|
|
367
|
-
|
|
368
|
-
If `buildHeight` is missing (defeito do `sb-validate-static-render`) → HALT with clear error `[build-page] FATAL: render.geometry.totalHeight missing`. Don't try to recover.
|
|
369
|
-
|
|
370
|
-
If `liveMainHeight < 100` (degenerate) → log warn `[build-page] WARN: liveMainHeight < 100, coverage gate skipped`; skip; persist `coverage.skippedReason = 'degenerate-height'`.
|
|
371
|
-
|
|
372
|
-
**Tier triage:**
|
|
373
|
-
|
|
374
|
-
| Range | Status | Action |
|
|
375
|
-
| --- | --- | --- |
|
|
376
|
-
| `< 0.40` | ❌ **incomplete** | Status: `"incomplete — built {ratio*100:.0f}% of source"`. Goes to existing Step 9 (auto-correct loop) if budget remains; existing `review.violations[]` driven fixHints feed back to composer. **NO new EXTENSION marker** — Step 9 already handles auto-correct via review-derived fixHints. Coverage `< 0.40` overrides matrix to ❌ (without it, matrix could route to ⚠️ and miss the issue). |
|
|
377
|
-
| `0.40 — 0.85` | ⚠️ **partial** | Status: `"partial — built {ratio*100:.0f}%"`. Proceeds to matrix below (does NOT bypass). Coverage warning rides on top of whatever the matrix decides. |
|
|
378
|
-
| `>= 0.85` | (proceed) | Coverage OK. Matrix below runs silently. |
|
|
379
|
-
|
|
380
|
-
Persist `coverage = { buildHeight, liveMainHeight, ratio, source: 'rootBbox'|'sectionsSpan', missingSections?: [...], skippedReason?: <string> }` in the success/escalation report. `missingSections` (when `ratio < 0.85`): walk `inspection.dom` recursively, collect classified-section nodes whose `bbox.y >= buildHeight`.
|
|
381
|
-
|
|
382
|
-
**Existing matrix (runs only when coverage gate didn't route to ❌):**
|
|
383
|
-
|
|
384
|
-
You hold both `compare.passed` and `review.passed` now. Apply this matrix:
|
|
385
|
-
|
|
386
|
-
| `review.passed` | `compare.passed` | Action |
|
|
387
|
-
| --------------- | ---------------- | ----------------------------------------------------------------------------------------------- |
|
|
388
|
-
| true | true | ✅ **Ship.** Go to Step 8 (success path). |
|
|
389
|
-
| false | true | ⚠️ **Escalate with structural warning.** Go to Step 10. The build *looks* right but `violations[]` carries hidden defects (a11y, perf, anti-patterns) the user must see. Don't auto-loop — review-checks isn't the auto-correct trigger when visuals already match. |
|
|
390
|
-
| true | false | ⚠️ **Escalate (visual drift, no actionable fix).** Go to Step 10. There are no fixHints to feed back — LLM-side patching from raw `structuredDiffs` is guesswork. Surface to user with the diff map. |
|
|
391
|
-
| false | false | 🔄 **Auto-correct loop.** Go to Step 9. fixHints from review can drive `sb-build-{wp,shopify}` to surgically patch. Iterate until matrix flips or budget exhausts. |
|
|
392
|
-
|
|
393
|
-
**Two pre-checks before the matrix fires:**
|
|
394
|
-
1. If `--no-auto-correct` was passed and any cell would route to Step 9 → reroute to Step 10 instead (escalate immediately).
|
|
395
|
-
2. If `iteration >= auto_correct_max_iterations` (default 2) and the matrix would route to Step 9 → reroute to Step 10 (budget exhausted).
|
|
396
|
-
|
|
397
|
-
The matrix replaces the old "diff>50 catastrophic = skip loop" heuristic. That heuristic mis-fired when a clean build had high diff% from comparator scope mismatch (Pattern #27). The matrix uses review-checks as ground truth instead — if the build is structurally sound but visually drifted, that's escalation territory anyway, with no diff% threshold needed.
|
|
398
|
-
|
|
399
|
-
---
|
|
400
|
-
|
|
401
|
-
## Step 8 — Success path
|
|
402
|
-
|
|
403
|
-
You only reach here when `review.passed === true` AND `compare.passed === true`.
|
|
404
|
-
|
|
405
|
-
1. Write/update `{output_folder}/{project-slug}/reports/index.html` with the section's entry: status ✅, diff %, screenshot side-by-side, link to the built file, time elapsed, iteration count. Use the existing report if present (read → modify → write) so multiple `/build-page` runs accumulate; create from scratch otherwise.
|
|
406
|
-
|
|
407
|
-
2. Append a one-line decision record to `{output_folder}/{project-slug}/.sb-memory/decisions.md` (date, URL, target, slug, iterations used, diff %).
|
|
408
|
-
|
|
409
|
-
3. **Auto-learn check (step 12 in the brief).** Only if `auto_correct_iterations_used > 0` AND auto-correct succeeded — meaning a `sb-review-checks` `candidateFix` actually unstuck the build. Inspect the fix that closed the diff:
|
|
410
|
-
- Is it generalizable beyond this section type? (Does it name a pattern not already in the loaded memory cascade?)
|
|
411
|
-
- If yes: ask the user once, in plain Portuguese, whether to persist it to `~/.claude/similarbuild-memory/anti-patterns.md`. Format: `[s/n/sempre/nunca]`. On `sempre`, also flip a one-time hint that you'll auto-save without asking on the next session (track in `.sb-memory/decisions.md`).
|
|
412
|
-
- On confirmation, append the entry (create the directory and file if missing) with: date, anti-pattern name, fix recipe, source URL, section type.
|
|
413
|
-
- On `n`/`nunca`: don't save. On `nunca`: also append `auto_learn_disabled: true` to `.sb-memory/decisions.md`.
|
|
414
|
-
|
|
415
|
-
4. Print the final summary to the user in Portuguese:
|
|
416
|
-
```
|
|
417
|
-
✅ {section-type} de {URL} → {output-path}
|
|
418
|
-
diff: {diff_percent}% (threshold {threshold}%) | iterações: {n}
|
|
419
|
-
report: {output_folder}/{project-slug}/reports/index.html
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
5. Stop.
|
|
423
|
-
|
|
424
|
-
---
|
|
425
|
-
|
|
426
|
-
## Step 9 — Auto-correct loop body
|
|
89
|
+
This concat is a convenience for "paste the whole page body at once" workflows. The canonical artifact remains the per-section folders.
|
|
427
90
|
|
|
428
|
-
|
|
91
|
+
### Step 5 — Summary
|
|
429
92
|
|
|
430
|
-
`review.violations[]` is in hand from Step 6 — fixHints to feed back into the builder.
|
|
431
|
-
|
|
432
|
-
### Step 9a — Re-build with fixHints (Skill)
|
|
433
|
-
|
|
434
|
-
**Critical: the violations are passed through verbatim.** Per the brief: `fixHints` from `sb-review-checks` go DIRECTLY to `sb-build-{wp,shopify}` — no reformatting, no interpretation, no modification. The `candidateFix` strings ARE the contract.
|
|
435
|
-
|
|
436
|
-
```
|
|
437
|
-
Skill(
|
|
438
|
-
skill="sb-build-wp",
|
|
439
|
-
args="inspection={inspection-path} assets-map={assets-map-path} output-path={output-path} preset=wp-elementor previous-html-path={output-path} fix-hints-path={review-report-path}"
|
|
440
|
-
)
|
|
441
93
|
```
|
|
94
|
+
🏁 /build-page {URL} → sections/{page-slug}/
|
|
442
95
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
Go back to Step 4 with the new file (still at `{output-path}` — the skill overwrote it) and the new `{iteration}` index. Then Step 5, then Step 6, then Step 7's matrix again.
|
|
448
|
-
|
|
449
|
-
If the matrix flips to ✅ → Step 8 (success), now reporting the iteration count.
|
|
450
|
-
If the matrix routes to ⚠️ (review passed, compare failed) → Step 10 (no more actionable fixHints).
|
|
451
|
-
If the matrix stays at 🔄 → loop again from Step 9 (decrementing the budget).
|
|
452
|
-
|
|
453
|
-
**The loop is closed (pattern #20 of the bootstrap):** under no circumstance ask the user mid-loop. Only escalate after exhausting the budget.
|
|
454
|
-
|
|
455
|
-
---
|
|
456
|
-
|
|
457
|
-
## Step 10 — Escalation (auto-correct exhausted, or `--no-auto-correct`)
|
|
458
|
-
|
|
459
|
-
You only reach here when the budget is spent. Be honest:
|
|
460
|
-
|
|
461
|
-
1. Write/update `reports/index.html` with status ⚠️ for this section, including: latest diff %, top 5 `structuredDiffs` from the most recent compare report, the `violations[]` from the most recent review report, links to all iteration screenshots and diff maps.
|
|
462
|
-
|
|
463
|
-
2. Append a decision record marking this section as ESCALATED with the URL, target, iterations spent, last diff %.
|
|
464
|
-
|
|
465
|
-
3. Print to the user in Portuguese — be specific, not vague:
|
|
466
|
-
```
|
|
467
|
-
⚠️ {section-type} de {URL} ainda diverge da live após {n} tentativas.
|
|
468
|
-
diff: {diff_percent}% (threshold {threshold}%)
|
|
469
|
-
|
|
470
|
-
Top diffs visuais (do sb-compare-visual):
|
|
471
|
-
- {area}: {issue}
|
|
472
|
-
- ...
|
|
473
|
-
|
|
474
|
-
Candidate fixes (do sb-review-checks):
|
|
475
|
-
- {candidateFix}
|
|
476
|
-
- ...
|
|
477
|
-
|
|
478
|
-
Build atual: {output-path}
|
|
479
|
-
Diff map: {diff-map-png-path}
|
|
480
|
-
Report completo: {reports/index.html}
|
|
96
|
+
Sections: {N} total
|
|
97
|
+
✅ {matching} matching diff < threshold
|
|
98
|
+
⚠️ {residual} with residual diff above threshold
|
|
481
99
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
```
|
|
100
|
+
Folders:
|
|
101
|
+
sections/{page-slug}/01-{type}/ — paste section.html into Elementor
|
|
102
|
+
sections/{page-slug}/02-{type}/
|
|
103
|
+
...
|
|
487
104
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
---
|
|
491
|
-
|
|
492
|
-
## Failure modes (cross-cutting)
|
|
493
|
-
|
|
494
|
-
| Symptom | Likely cause | Action |
|
|
495
|
-
| --- | --- | --- |
|
|
496
|
-
| `playwright` missing | First-time setup | Surface the skill's stderr verbatim and append: "Run `npx playwright install chromium` from the project root." Stop. |
|
|
497
|
-
| `sharp` / `pixelmatch` / `pngjs` / `cheerio` / `liquidjs` missing | Same | Surface install hint as the failing skill says. Stop. |
|
|
498
|
-
| `inspection.widgetBlocked: true` | Bot challenge | Stop. Ask user for an alternative URL or rendered-HTML paste. Don't fabricate. |
|
|
499
|
-
| `assetsMap.failed[]` includes a critical asset (hero) | Source 404 | Don't stop the orchestrator — the builder will use a literal-color placeholder. Note in the report and continue. |
|
|
500
|
-
| `sb-build-{wp,shopify}` validator returns errors after the skill's own retries | Builder-side defect | Surface to user with the file path and the validator output. Skip validate/compare — there's nothing to validate. |
|
|
501
|
-
| Same `violations[]` two iterations in a row | The fixHint isn't sticking — likely a builder bug or memory stale | Don't run a third iteration even if budget remains. Escalate immediately with both reports attached. |
|
|
502
|
-
| Diff% high (e.g. >50%) but `review.passed === true` | Comparator scope mismatch (Pattern #27 not applied) or genuine visual drift the LLM can't patch from raw diff | Step 7 matrix already handles this: `review=true, compare=false` → escalate (no actionable fix). Don't reintroduce a `diff>50 catastrophic` heuristic — it mis-fires on Pattern #27 cases. |
|
|
503
|
-
|
|
504
|
-
---
|
|
505
|
-
|
|
506
|
-
## Output structure (recap, for sanity)
|
|
507
|
-
|
|
508
|
-
After a successful run on `https://example-store.com`:
|
|
105
|
+
Optional concat:
|
|
106
|
+
final/{page-slug}-body.html — all sections concatenated in order
|
|
509
107
|
|
|
108
|
+
Next:
|
|
109
|
+
Paste each section's `section.html` into the destination as a Custom HTML widget,
|
|
110
|
+
in the order matching the folder index (01, 02, 03, ...).
|
|
510
111
|
```
|
|
511
|
-
{output_folder}/example-store/
|
|
512
|
-
├── clean/
|
|
513
|
-
│ └── home/hero.html ← the deliverable
|
|
514
|
-
├── assets/
|
|
515
|
-
│ └── a3f9b2c14e8d7f01.jpg ← content-hash, no metadata
|
|
516
|
-
├── reports/
|
|
517
|
-
│ ├── index.html ← updated incrementally
|
|
518
|
-
│ ├── diffs/hero-0/diff-map.png
|
|
519
|
-
│ └── validations/hero-0/screenshot.png
|
|
520
|
-
└── .sb-memory/
|
|
521
|
-
├── inspect-{ts}/inspection.json
|
|
522
|
-
├── inspect-{ts}/screenshot.png
|
|
523
|
-
└── decisions.md
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
Never write outside `{project-slug}/`. Cross-project assets sharing happens only via the per-user memory at `~/.claude/similarbuild-memory/` (process knowledge, not store content) — and only on explicit auto-learn confirmation.
|
|
527
112
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
## What you do NOT do
|
|
531
|
-
|
|
532
|
-
- Do not run `/build-site`'s crawl logic — this command is single-page only.
|
|
533
|
-
- Do not ask the user to confirm anything mid-flow except the auto-learn prompt at step 8.3 (and only when applicable).
|
|
534
|
-
- Do not rewrite, reformat, or summarize `candidateFix` strings — pass them verbatim to the builder.
|
|
535
|
-
- Do not show the user the built file's path before steps 4 + 5 confirm the diff is under threshold.
|
|
536
|
-
- Do not create `.claude/sb-config.yaml` if it's missing — operate from defaults; the installer owns config creation.
|
|
537
|
-
- Do not load `<plugin>/memory/*.md` if the files are absent — the framework is early; the bundled `references/` inside each skill is the working source of truth until item #13 of the build roadmap lands.
|
|
538
|
-
|
|
539
|
-
---
|
|
540
|
-
|
|
541
|
-
## Quick start (smoke test path)
|
|
542
|
-
|
|
543
|
-
When this command is first wired up, the canonical end-to-end smoke test is:
|
|
544
|
-
|
|
545
|
-
```
|
|
546
|
-
/build-page https://example-store.com
|
|
547
|
-
```
|
|
113
|
+
## Non-negotiables
|
|
548
114
|
|
|
549
|
-
|
|
115
|
+
- **One inspect per page, then sections from crops.** Don't re-inspect every section.
|
|
116
|
+
- **`/clip-section` is the atom.** This command is a thin loop over it.
|
|
117
|
+
- **No globals composition here.** Header/footer come from `/clone-globals` (run before `/build-page`) when shared across the site. Within this page, they're just sections like any other.
|
|
118
|
+
- **Single human checkpoint** = section list confirmation. No mid-batch interaction.
|