td-ai-tools 1.2.0 → 1.2.2
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/skills/README.md +3 -0
- package/skills/client-overview/SKILL.md +93 -0
- package/skills/client-overview/agents/openai.yaml +4 -0
- package/skills/client-overview/scripts/branch_client_overview_context.py +317 -0
- package/skills/debugging-ios-webkit/SKILL.md +60 -0
- package/skills/debugging-ios-webkit/references/device.md +62 -0
- package/skills/debugging-ios-webkit/references/playwright.md +60 -0
- package/skills/debugging-ios-webkit/references/simulator.md +76 -0
- package/skills/debugging-ios-webkit/scripts/device_eval.py +62 -0
- package/skills/debugging-ios-webkit/scripts/device_experiment.py +99 -0
- package/skills/debugging-ios-webkit/scripts/device_snapshot.py +62 -0
- package/skills/record-changes/SKILL.md +3 -3
- package/skills/shopify-lint/SKILL.md +50 -0
- package/skills/shopify-lint/agents/openai.yaml +4 -0
- package/skills/shopify-lint/scripts/setup.sh +60 -0
- package/skills/shopify-lint/scripts/shopify_lint.py +259 -0
- package/skills/shopify-lint/tests/test_shopify_lint.py +137 -0
- package/skills/shopify-lint/theme-check-theory/.theme-check.example.yml +14 -0
- package/skills/shopify-lint/theme-check-theory/README.md +123 -0
- package/skills/shopify-lint/theme-check-theory/configs/recommended.yml +8 -0
- package/skills/shopify-lint/theme-check-theory/package-lock.json +1947 -0
- package/skills/shopify-lint/theme-check-theory/package.json +44 -0
- package/skills/shopify-lint/theme-check-theory/src/checks/unguarded-text-setting.test.ts +198 -0
- package/skills/shopify-lint/theme-check-theory/src/checks/unguarded-text-setting.ts +143 -0
- package/skills/shopify-lint/theme-check-theory/src/checks/unused-section-settings.test.ts +137 -0
- package/skills/shopify-lint/theme-check-theory/src/checks/unused-section-settings.ts +64 -0
- package/skills/shopify-lint/theme-check-theory/src/index.test.ts +20 -0
- package/skills/shopify-lint/theme-check-theory/src/index.ts +11 -0
- package/skills/shopify-lint/theme-check-theory/src/test-utils.ts +31 -0
- package/skills/shopify-lint/theme-check-theory/src/utils/ast.ts +126 -0
- package/skills/shopify-lint/theme-check-theory/tsconfig.build.json +10 -0
- package/skills/shopify-lint/theme-check-theory/tsconfig.json +15 -0
- package/skills/shopify-lint/theme-check-theory/vitest.config.ts +11 -0
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
- `browser-validation`: Before completing a task validate frontend or template changes in a real browser with the Playwright-CLI…
|
|
8
8
|
- `cache-reset`: Clear and warm Laravel and Statamic caches (including Statamic Glide image caches) after content or template…
|
|
9
9
|
- `car-ticket-generator`: Generate a ticket for the codex-auto-runner queue
|
|
10
|
+
- `client-overview`: Generate a client-facing markdown report that summarizes all changes on the current branch against the…
|
|
11
|
+
- `debugging-ios-webkit`: Debugs iOS Safari/Chrome-iOS rendering bugs — stale paints, viewport/browser-chrome clipping, mobile-only CSS…
|
|
10
12
|
- `everhour-basecamp-estimates`: Bulk update Everhour task estimates from a Basecamp todo or todolist URL, then append bracketed hours to the…
|
|
11
13
|
- `forge-cli`: Manage Laravel Forge servers, sites, and provisioned resources from the terminal with the Laravel Forge CLI,…
|
|
12
14
|
- `horizon-component-migration`: Bundle Shopify Horizon components into a migration package for a different theme, including recursive…
|
|
@@ -16,6 +18,7 @@
|
|
|
16
18
|
- `pull-request-statamic`: Generates GitHub pull request descriptions for Statamic and Laravel development by analyzing git diffs and…
|
|
17
19
|
- `record-changes`: Update `docs/changes.md` by summarizing the current branch against the primary development branch.
|
|
18
20
|
- `shopify-cli`: Shopify CLI workflows for theme development.
|
|
21
|
+
- `shopify-lint`: Run Shopify CLI Theme Check with Theory Digital's bundled custom checks while reporting and failing only on offenses in files modified on the current Git branch.
|
|
19
22
|
- `stylesheet-migration`: Migrate Shopify Liquid `{% stylesheet %}` blocks into theme CSS assets using bundled Python scripts.
|
|
20
23
|
- `td-js-vanilla-rules`: Theory Digital vanilla JavaScript standards for Shopify theme work.
|
|
21
24
|
- `td-review`: Run parallel code review agents on a PR (including TD theme compliance) and produce a synthesized findings…
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: client-overview
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Generate a client-facing markdown report that summarizes all changes on the current branch against the primary development branch for a non-technical audience, with accessibility changes explained through end-user experience, accessibility benchmark categories, and practical impact.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Client Overview
|
|
8
|
+
|
|
9
|
+
Generate a markdown report for a client or stakeholder audience. Cover all meaningful branch changes, but refer to the work as an update (client's will not understand the term branch). Give extra attention to accessibility-related changes by explaining what people experience when using the affected feature, as well as which WCAG guidelines the change addresses.
|
|
10
|
+
|
|
11
|
+
## Workflow
|
|
12
|
+
|
|
13
|
+
### 1. Collect Branch Context
|
|
14
|
+
|
|
15
|
+
Run the bundled script first:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
python3 .agents/skills/client-overview/scripts/branch_client_overview_context.py
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If the user specifies another comparison branch, pass it explicitly:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
python3 .agents/skills/client-overview/scripts/branch_client_overview_context.py --base release/x.y
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Default output folder: `docs/`, if the repo does not have a docs folder use the root instead. Do not use or reference the changes.md file, as that is intended for internal technical users.
|
|
28
|
+
|
|
29
|
+
### 2. Inspect the Actual Changes
|
|
30
|
+
|
|
31
|
+
Use the script output to identify changed files, then inspect relevant diffs and source files with `git diff` and targeted reads. Do not rely on branch names or file names alone.
|
|
32
|
+
|
|
33
|
+
Prioritize:
|
|
34
|
+
|
|
35
|
+
- User-facing behavior, layout, copy, navigation, forms, search, cart, checkout, account, and content changes
|
|
36
|
+
- Accessibility-relevant markup, ARIA, labels, headings, landmarks, alt text, focus handling, keyboard behavior, validation, contrast, spacing, motion, responsive behavior, and interactive controls
|
|
37
|
+
- CSS and JavaScript changes that affect visibility, focus, scroll behavior, modals, drawers, menus, accordions, sliders, media, filters, or dynamic content
|
|
38
|
+
- CMS settings or schema changes that alter what site admins can configure for visitors
|
|
39
|
+
|
|
40
|
+
Do not summarize the report file edit itself as branch work. If the branch includes tooling-only changes, include them only when they affect the non-technical reader's experience or confidence in the release.
|
|
41
|
+
|
|
42
|
+
### 3. Translate Accessibility Changes
|
|
43
|
+
|
|
44
|
+
For each accessibility-relevant change, write in terms of:
|
|
45
|
+
|
|
46
|
+
- **Benchmark category:** Use plain category names such as keyboard access, screen reader clarity, focus visibility, form labels and errors, color contrast, reduced motion, responsive reflow, touch target usability, readable content, or predictable navigation. Add WCAG references only if the code clearly maps to them.
|
|
47
|
+
- **User experience:** State what someone with a disability, temporary impairment, or assistive technology setup can now do, understand, avoid, or recover from.
|
|
48
|
+
- **Affected journey:** Name the feature or page area in shopper terms, such as product options, search filters, cart drawer, checkout path, navigation menu, newsletter signup, or collection browsing.
|
|
49
|
+
- **Before/after effect:** Describe the practical improvement without blaming previous work. Example: "Keyboard shoppers can now see which control is active while moving through the menu."
|
|
50
|
+
|
|
51
|
+
Avoid implementation-heavy language in the main summary. Keep file paths, selectors, variables, and component names out of the file unless there is a clear edge case requiring them.
|
|
52
|
+
|
|
53
|
+
### 4. Write the Report
|
|
54
|
+
|
|
55
|
+
Example entry structure:
|
|
56
|
+
|
|
57
|
+
```markdown
|
|
58
|
+
## <short user-facing title>
|
|
59
|
+
**Date:** YYYY-MM-DD
|
|
60
|
+
|
|
61
|
+
### At a Glance
|
|
62
|
+
- <plain-language summary of the update outcome>
|
|
63
|
+
|
|
64
|
+
### What Changed for Users
|
|
65
|
+
| Area | User-facing change | Why it matters |
|
|
66
|
+
|------|--------------------|----------------|
|
|
67
|
+
|
|
68
|
+
### Accessibility Experience
|
|
69
|
+
| Benchmark category | Affected experience | User impact |
|
|
70
|
+
|--------------------|---------------------|-------------|
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Guidelines:
|
|
74
|
+
|
|
75
|
+
- Title the entry by the experience improved, not the branch name.
|
|
76
|
+
- Write for clients, support, QA, designers, and stakeholders who may not read code.
|
|
77
|
+
- In the output file, refer to the branch as "this update" or "the update"; do not call it "the branch".
|
|
78
|
+
- Do not refer to yourself, the agent, or the writing process in the output file. Avoid first-person process statements such as "I reviewed," "I checked," "this summary," or "this report."
|
|
79
|
+
- Prefer "people using keyboard navigation" over "keyboard users" when the sentence is about lived experience.
|
|
80
|
+
- Prefer "screen reader announces..." only when the code change truly affects accessible names, roles, states, landmarks, headings, or live regions.
|
|
81
|
+
|
|
82
|
+
### 5. Verify Before Finishing
|
|
83
|
+
|
|
84
|
+
Before wrapping up:
|
|
85
|
+
|
|
86
|
+
- Re-read the new markdown entry in context.
|
|
87
|
+
- Confirm every listed file or area appears in the branch diff.
|
|
88
|
+
- Confirm the report describes observed code behavior, not intended behavior inferred from a branch name.
|
|
89
|
+
- Confirm accessibility claims are tied to a benchmark category and an affected user experience.
|
|
90
|
+
|
|
91
|
+
## Resource
|
|
92
|
+
|
|
93
|
+
- `scripts/branch_client_overview_context.py`: Resolve the best available base branch and print branch file changes with accessibility inspection hints.
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
DEFAULT_BASE_CANDIDATES = ("main", "origin/main", "master", "origin/master")
|
|
11
|
+
|
|
12
|
+
ACCESSIBILITY_HINTS = (
|
|
13
|
+
(
|
|
14
|
+
"keyboard access and focus behavior",
|
|
15
|
+
(
|
|
16
|
+
"focus",
|
|
17
|
+
"tabindex",
|
|
18
|
+
"keydown",
|
|
19
|
+
"keyup",
|
|
20
|
+
"keypress",
|
|
21
|
+
"escape",
|
|
22
|
+
"drawer",
|
|
23
|
+
"modal",
|
|
24
|
+
"menu",
|
|
25
|
+
"accordion",
|
|
26
|
+
"slider",
|
|
27
|
+
"carousel",
|
|
28
|
+
),
|
|
29
|
+
),
|
|
30
|
+
(
|
|
31
|
+
"screen reader clarity and semantic structure",
|
|
32
|
+
(
|
|
33
|
+
"aria",
|
|
34
|
+
"role",
|
|
35
|
+
"label",
|
|
36
|
+
"heading",
|
|
37
|
+
"h1",
|
|
38
|
+
"h2",
|
|
39
|
+
"h3",
|
|
40
|
+
"landmark",
|
|
41
|
+
"sr-only",
|
|
42
|
+
"visually-hidden",
|
|
43
|
+
"alt",
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
(
|
|
47
|
+
"form labels, instructions, and error recovery",
|
|
48
|
+
(
|
|
49
|
+
"form",
|
|
50
|
+
"input",
|
|
51
|
+
"select",
|
|
52
|
+
"textarea",
|
|
53
|
+
"error",
|
|
54
|
+
"invalid",
|
|
55
|
+
"required",
|
|
56
|
+
"newsletter",
|
|
57
|
+
"contact",
|
|
58
|
+
),
|
|
59
|
+
),
|
|
60
|
+
(
|
|
61
|
+
"color contrast, readable content, and visual state",
|
|
62
|
+
(
|
|
63
|
+
"color",
|
|
64
|
+
"contrast",
|
|
65
|
+
"opacity",
|
|
66
|
+
"background",
|
|
67
|
+
"foreground",
|
|
68
|
+
"text",
|
|
69
|
+
"font",
|
|
70
|
+
"hover",
|
|
71
|
+
"active",
|
|
72
|
+
),
|
|
73
|
+
),
|
|
74
|
+
(
|
|
75
|
+
"responsive reflow, zoom, and touch target usability",
|
|
76
|
+
(
|
|
77
|
+
"mobile",
|
|
78
|
+
"responsive",
|
|
79
|
+
"breakpoint",
|
|
80
|
+
"media",
|
|
81
|
+
"width",
|
|
82
|
+
"height",
|
|
83
|
+
"spacing",
|
|
84
|
+
"padding",
|
|
85
|
+
"touch",
|
|
86
|
+
),
|
|
87
|
+
),
|
|
88
|
+
(
|
|
89
|
+
"motion, animation, and scroll comfort",
|
|
90
|
+
(
|
|
91
|
+
"motion",
|
|
92
|
+
"animation",
|
|
93
|
+
"transition",
|
|
94
|
+
"scroll",
|
|
95
|
+
"autoplay",
|
|
96
|
+
"video",
|
|
97
|
+
"prefers-reduced-motion",
|
|
98
|
+
),
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def run_git(*args: str) -> str:
|
|
104
|
+
completed = subprocess.run(
|
|
105
|
+
["git", *args],
|
|
106
|
+
check=True,
|
|
107
|
+
capture_output=True,
|
|
108
|
+
text=True,
|
|
109
|
+
)
|
|
110
|
+
return completed.stdout.strip()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def ref_exists(ref: str) -> bool:
|
|
114
|
+
completed = subprocess.run(
|
|
115
|
+
["git", "rev-parse", "--verify", f"{ref}^{{commit}}"],
|
|
116
|
+
capture_output=True,
|
|
117
|
+
text=True,
|
|
118
|
+
)
|
|
119
|
+
return completed.returncode == 0
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def resolve_base_ref(explicit_base: str | None) -> str:
|
|
123
|
+
if explicit_base:
|
|
124
|
+
if not ref_exists(explicit_base):
|
|
125
|
+
raise SystemExit(f"Base ref not found: {explicit_base}")
|
|
126
|
+
return explicit_base
|
|
127
|
+
|
|
128
|
+
for candidate in DEFAULT_BASE_CANDIDATES:
|
|
129
|
+
if ref_exists(candidate):
|
|
130
|
+
return candidate
|
|
131
|
+
|
|
132
|
+
searched = ", ".join(DEFAULT_BASE_CANDIDATES)
|
|
133
|
+
raise SystemExit(f"No base ref found. Tried: {searched}")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def parse_name_status(output: str) -> list[dict[str, str]]:
|
|
137
|
+
files = []
|
|
138
|
+
for line in output.splitlines():
|
|
139
|
+
if not line:
|
|
140
|
+
continue
|
|
141
|
+
parts = line.split("\t")
|
|
142
|
+
status = parts[0]
|
|
143
|
+
|
|
144
|
+
if status.startswith("R") and len(parts) >= 3:
|
|
145
|
+
files.append(
|
|
146
|
+
{
|
|
147
|
+
"status": status,
|
|
148
|
+
"path": parts[2],
|
|
149
|
+
"old_path": parts[1],
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
path = parts[1] if len(parts) > 1 else ""
|
|
155
|
+
files.append({"status": status, "path": path, "old_path": ""})
|
|
156
|
+
return files
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def parse_numstat(output: str) -> dict[str, dict[str, str]]:
|
|
160
|
+
stats = {}
|
|
161
|
+
for line in output.splitlines():
|
|
162
|
+
if not line:
|
|
163
|
+
continue
|
|
164
|
+
parts = line.split("\t")
|
|
165
|
+
if len(parts) < 3:
|
|
166
|
+
continue
|
|
167
|
+
additions, deletions, path = parts[0], parts[1], parts[2]
|
|
168
|
+
stats[path] = {"additions": additions, "deletions": deletions}
|
|
169
|
+
return stats
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def resolve_stats(
|
|
173
|
+
numstat: dict[str, dict[str, str]], path: str, old_path: str
|
|
174
|
+
) -> dict[str, str] | None:
|
|
175
|
+
stats = numstat.get(path)
|
|
176
|
+
if stats or not old_path:
|
|
177
|
+
return stats
|
|
178
|
+
|
|
179
|
+
path_dir = os.path.dirname(path)
|
|
180
|
+
old_dir = os.path.dirname(old_path)
|
|
181
|
+
path_name = os.path.basename(path)
|
|
182
|
+
old_name = os.path.basename(old_path)
|
|
183
|
+
|
|
184
|
+
for candidate_path, candidate_stats in numstat.items():
|
|
185
|
+
if old_path in candidate_path and path in candidate_path:
|
|
186
|
+
return candidate_stats
|
|
187
|
+
if (
|
|
188
|
+
path_dir == old_dir
|
|
189
|
+
and path_dir
|
|
190
|
+
and candidate_path.startswith(f"{path_dir}/")
|
|
191
|
+
and old_name in candidate_path
|
|
192
|
+
and path_name in candidate_path
|
|
193
|
+
):
|
|
194
|
+
return candidate_stats
|
|
195
|
+
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def accessibility_hints_for(path: str) -> list[str]:
|
|
200
|
+
path_lower = path.lower()
|
|
201
|
+
_, ext = os.path.splitext(path_lower)
|
|
202
|
+
hints = []
|
|
203
|
+
|
|
204
|
+
for label, keywords in ACCESSIBILITY_HINTS:
|
|
205
|
+
if any(keyword in path_lower for keyword in keywords):
|
|
206
|
+
hints.append(label)
|
|
207
|
+
|
|
208
|
+
if ext in {".css", ".scss", ".sass"}:
|
|
209
|
+
hints.extend(
|
|
210
|
+
[
|
|
211
|
+
"color contrast, readable content, and visual state",
|
|
212
|
+
"responsive reflow, zoom, and touch target usability",
|
|
213
|
+
]
|
|
214
|
+
)
|
|
215
|
+
elif ext in {".js", ".ts", ".jsx", ".tsx"}:
|
|
216
|
+
hints.extend(
|
|
217
|
+
[
|
|
218
|
+
"keyboard access and focus behavior",
|
|
219
|
+
"motion, animation, and scroll comfort",
|
|
220
|
+
]
|
|
221
|
+
)
|
|
222
|
+
elif ext in {".liquid", ".html", ".vue", ".svelte"}:
|
|
223
|
+
hints.extend(
|
|
224
|
+
[
|
|
225
|
+
"screen reader clarity and semantic structure",
|
|
226
|
+
"form labels, instructions, and error recovery",
|
|
227
|
+
]
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
return sorted(set(hints))
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def build_context(base_ref: str) -> dict[str, object]:
|
|
234
|
+
current_branch = run_git("branch", "--show-current")
|
|
235
|
+
merge_base = run_git("merge-base", base_ref, "HEAD")
|
|
236
|
+
shortstat = run_git("diff", "--shortstat", merge_base, "HEAD")
|
|
237
|
+
name_status = parse_name_status(
|
|
238
|
+
run_git("diff", "--name-status", "--find-renames", merge_base, "HEAD")
|
|
239
|
+
)
|
|
240
|
+
numstat = parse_numstat(run_git("diff", "--numstat", "--find-renames", merge_base, "HEAD"))
|
|
241
|
+
|
|
242
|
+
for entry in name_status:
|
|
243
|
+
stats = resolve_stats(numstat, entry["path"], entry["old_path"])
|
|
244
|
+
entry["additions"] = stats["additions"] if stats else "?"
|
|
245
|
+
entry["deletions"] = stats["deletions"] if stats else "?"
|
|
246
|
+
path_for_hints = f"{entry['old_path']} {entry['path']}".strip()
|
|
247
|
+
entry["accessibility_hints"] = accessibility_hints_for(path_for_hints)
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
"current_branch": current_branch,
|
|
251
|
+
"base_ref": base_ref,
|
|
252
|
+
"merge_base": merge_base,
|
|
253
|
+
"shortstat": shortstat,
|
|
254
|
+
"files": name_status,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def print_markdown(context: dict[str, object]) -> None:
|
|
259
|
+
print("# Branch Client Overview Context")
|
|
260
|
+
print()
|
|
261
|
+
print(f"- Current branch: `{context['current_branch']}`")
|
|
262
|
+
print(f"- Base ref: `{context['base_ref']}`")
|
|
263
|
+
print(f"- Merge base: `{context['merge_base']}`")
|
|
264
|
+
print(f"- Summary: {context['shortstat'] or 'No changes detected'}")
|
|
265
|
+
print()
|
|
266
|
+
|
|
267
|
+
files = context["files"]
|
|
268
|
+
if not files:
|
|
269
|
+
print("No changed files detected.")
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
print("| Status | Path | +/- | Accessibility inspection hints |")
|
|
273
|
+
print("|--------|------|-----|----------------------------------|")
|
|
274
|
+
|
|
275
|
+
for entry in files:
|
|
276
|
+
status = entry["status"]
|
|
277
|
+
path = entry["path"]
|
|
278
|
+
if entry["old_path"]:
|
|
279
|
+
path = f"{entry['old_path']} -> {entry['path']}"
|
|
280
|
+
delta = f"+{entry['additions']} / -{entry['deletions']}"
|
|
281
|
+
hints = ", ".join(entry["accessibility_hints"]) or "No obvious hint from file path"
|
|
282
|
+
print(f"| `{status}` | `{path}` | {delta} | {hints} |")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def main() -> int:
|
|
286
|
+
parser = argparse.ArgumentParser(
|
|
287
|
+
description="Resolve a base branch and print context for client-facing change reports."
|
|
288
|
+
)
|
|
289
|
+
parser.add_argument(
|
|
290
|
+
"--base",
|
|
291
|
+
help="Explicit base ref to compare against. Defaults to main/origin-main or master/origin-master fallback.",
|
|
292
|
+
)
|
|
293
|
+
parser.add_argument(
|
|
294
|
+
"--format",
|
|
295
|
+
choices=("markdown", "json"),
|
|
296
|
+
default="markdown",
|
|
297
|
+
help="Output format.",
|
|
298
|
+
)
|
|
299
|
+
args = parser.parse_args()
|
|
300
|
+
|
|
301
|
+
base_ref = resolve_base_ref(args.base)
|
|
302
|
+
context = build_context(base_ref)
|
|
303
|
+
|
|
304
|
+
if args.format == "json":
|
|
305
|
+
print(json.dumps(context, indent=2))
|
|
306
|
+
else:
|
|
307
|
+
print_markdown(context)
|
|
308
|
+
|
|
309
|
+
return 0
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
if __name__ == "__main__":
|
|
313
|
+
try:
|
|
314
|
+
raise SystemExit(main())
|
|
315
|
+
except subprocess.CalledProcessError as error:
|
|
316
|
+
sys.stderr.write(error.stderr or str(error))
|
|
317
|
+
raise SystemExit(error.returncode)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: debugging-ios-webkit
|
|
3
|
+
description: Debugs iOS Safari/Chrome-iOS rendering bugs — stale paints, viewport/browser-chrome clipping, mobile-only CSS issues — in whichever environment is specified. Pass "playwright" (fast emulated WebKit), "simulator" (Xcode iOS Simulator via simctl), or "device" (USB-connected physical iPhone via pymobiledevice3 Web Inspector). Use when a bug is reported "only on iPhone", when the user wants to test on a real device or in the simulator, or when styles are correct but pixels are wrong.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Debugging iOS WebKit Rendering Issues
|
|
7
|
+
|
|
8
|
+
Every iOS browser is WebKit — "Safari and Chrome on iOS" is one engine.
|
|
9
|
+
|
|
10
|
+
## Choose your environment
|
|
11
|
+
|
|
12
|
+
Read ONLY the reference for the environment requested (or per the guidance
|
|
13
|
+
below), then follow it:
|
|
14
|
+
|
|
15
|
+
- **[references/playwright.md](references/playwright.md)** — emulated WebKit,
|
|
16
|
+
headless, seconds per iteration. CSS cascade, layout, load-order races,
|
|
17
|
+
scripted touch loops. Cannot reproduce iOS compositor/GPU or browser-chrome
|
|
18
|
+
behavior.
|
|
19
|
+
- **[references/simulator.md](references/simulator.md)** — real iOS WebKit +
|
|
20
|
+
real Safari chrome via `simctl`. Viewport units (svh/dvh), safe-area,
|
|
21
|
+
pixel-accurate screenshots. No touch synthesis — drive state via URL params
|
|
22
|
+
or temporary page hooks.
|
|
23
|
+
- **[references/device.md](references/device.md)** — physical iPhone over
|
|
24
|
+
USB. Attach to the live (broken) page, evaluate JS, capture render
|
|
25
|
+
snapshots, run stepped on-screen repair experiments with the person
|
|
26
|
+
watching. The whole truth; required for stale-compositor bugs.
|
|
27
|
+
|
|
28
|
+
If the user didn't specify: start with playwright for speed, escalate to
|
|
29
|
+
simulator when the bug involves viewport/chrome behavior, and to device when
|
|
30
|
+
neither reproduces it or someone has the broken state on a phone right now.
|
|
31
|
+
|
|
32
|
+
## Shared diagnostic: layout bug vs paint bug (read this regardless)
|
|
33
|
+
|
|
34
|
+
In the broken state, measure **three** things: computed styles + bounding
|
|
35
|
+
rects (JS), a render-tree snapshot (`Page.snapshotRect` on device, or a fresh
|
|
36
|
+
screenshot in playwright/simulator), and what the eyes/screen actually show.
|
|
37
|
+
|
|
38
|
+
- **Styles or rects wrong** → layout/cascade bug. Fix CSS. Iterate at the
|
|
39
|
+
playwright level.
|
|
40
|
+
- **Styles, rects, AND snapshot correct while the screen shows garbage** →
|
|
41
|
+
the compositor is presenting a stale GPU texture. Fix with a paint
|
|
42
|
+
invalidation in JS, not with CSS. The cheapest reliable one is the opacity
|
|
43
|
+
nudge; to avoid a visible pop, dirty the layer *before* the element becomes
|
|
44
|
+
visible and keep nudging every animation frame through any entrance
|
|
45
|
+
transition:
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
el.style.opacity = '0.99';
|
|
49
|
+
const start = performance.now();
|
|
50
|
+
(function tick() {
|
|
51
|
+
if (performance.now() - start < 350) {
|
|
52
|
+
el.style.opacity = el.style.opacity === '0.99' ? '1' : '0.99';
|
|
53
|
+
requestAnimationFrame(tick);
|
|
54
|
+
} else { el.style.opacity = ''; }
|
|
55
|
+
})();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A painted background measuring exactly the element's *content-box* (instead
|
|
59
|
+
of border-box) is the signature of unpainted padding — stale layer or native
|
|
60
|
+
form-control painter (`-webkit-appearance` missing at first style pass).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
# Debugging on a Real iOS Device
|
|
3
|
+
|
|
4
|
+
Attach to the live page over the cable — including a currently-broken state
|
|
5
|
+
someone is looking at — without reloading or losing it.
|
|
6
|
+
|
|
7
|
+
## Setup (once)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python3.12 -m venv venv && venv/bin/pip install pymobiledevice3
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Python ≥ 3.10 required (Xcode's 3.9 breaks the CLI). On the phone:
|
|
14
|
+
Settings → (Apps →) Safari → Advanced → **Web Inspector** ON. Plug in, trust.
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
venv/bin/python -m pymobiledevice3 webinspector opened-tabs # list tabs
|
|
20
|
+
python scripts/device_eval.py mystore.com '<js expression>' # eval in tab
|
|
21
|
+
python scripts/device_snapshot.py mystore.com out.png # render snapshot
|
|
22
|
+
python scripts/device_experiment.py mystore.com '.my-footer' # live experiments
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Scripts take a URL substring to pick the tab. First sanity check: confirm
|
|
26
|
+
`location.href` and `Shopify.theme` — reporters are frequently on production
|
|
27
|
+
or the wrong theme, which explains many "still happening" reports instantly.
|
|
28
|
+
|
|
29
|
+
## Rules that prevent silent hangs
|
|
30
|
+
|
|
31
|
+
- **One inspector client at a time.** Close Mac Safari's Develop-menu
|
|
32
|
+
inspector, or your attach hangs with no error.
|
|
33
|
+
- The target tab must be **foreground** with the **screen unlocked**.
|
|
34
|
+
- iOS 17+ wraps the protocol in the `Target` domain: sessions need
|
|
35
|
+
`wait_target=True` and responses arrive inside
|
|
36
|
+
`Target.dispatchMessageFromTarget` (the scripts handle both).
|
|
37
|
+
- `ios_webkit_debug_proxy` lists tabs on modern iOS but its eval channel is
|
|
38
|
+
broken — use pymobiledevice3.
|
|
39
|
+
|
|
40
|
+
## Applying the shared diagnostic here
|
|
41
|
+
|
|
42
|
+
Run the layout-vs-paint diagnostic from SKILL.md using `device_eval.py`
|
|
43
|
+
(rects + computed styles) and `device_snapshot.py` (render-tree snapshot,
|
|
44
|
+
via `Page.snapshotRect` — it bypasses stale compositor layers, so a correct
|
|
45
|
+
snapshot against a broken screen is the paint-bug proof).
|
|
46
|
+
|
|
47
|
+
## Live repair experiments (stale-paint bugs)
|
|
48
|
+
|
|
49
|
+
Don't guess which invalidation works — bisect it on the actual broken state
|
|
50
|
+
with the reporter watching (`device_experiment.py`):
|
|
51
|
+
|
|
52
|
+
1. A countdown banner tells them to reproduce the bug ("break it now,
|
|
53
|
+
steps start in 20s") — **warn them before starting so they're watching**.
|
|
54
|
+
2. Numbered colored banners announce each repair candidate ~5s apart:
|
|
55
|
+
banner-only sanity → opacity nudge → `translateZ(0)` layer →
|
|
56
|
+
`display:none` rebuild → class re-toggle → scroll nudge.
|
|
57
|
+
3. They report the step number that visually healed the element. Wire exactly
|
|
58
|
+
that operation into the code path that precedes the broken state.
|
|
59
|
+
|
|
60
|
+
The winning invalidation (usually the opacity nudge — see the shared
|
|
61
|
+
diagnostic in SKILL.md for the production-safe anti-pop version) goes into
|
|
62
|
+
the code path that precedes the broken state.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
|
|
2
|
+
# Debugging with Playwright WebKit
|
|
3
|
+
|
|
4
|
+
Every iOS browser is WebKit (Safari and Chrome-iOS share the engine), so
|
|
5
|
+
Playwright's WebKit build catches most engine-level bugs in seconds, headless.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
const { webkit, devices } = require('playwright');
|
|
11
|
+
const browser = await webkit.launch();
|
|
12
|
+
const ctx = await browser.newContext({ ...devices['iPhone 14 Pro'], hasTouch: true });
|
|
13
|
+
const page = await ctx.newPage();
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
One-off setup: `npm i playwright` in a scratch dir (e.g. `/tmp/repro`).
|
|
17
|
+
|
|
18
|
+
## Instructions
|
|
19
|
+
|
|
20
|
+
1. **Measure two ways, always.** Computed styles/rects via `page.evaluate`
|
|
21
|
+
AND painted pixels via `page.screenshot()` + a pixel scan. A paint bug
|
|
22
|
+
shows correct computed values with wrong pixels; if you only read
|
|
23
|
+
`getComputedStyle` you will wrongly conclude "works for me".
|
|
24
|
+
|
|
25
|
+
2. **Real touch, not synthetic clicks.** Use `locator.tap()` (requires
|
|
26
|
+
`hasTouch: true`) when the bug involves touch-driven UI.
|
|
27
|
+
|
|
28
|
+
3. **Interaction loops.** Before declaring something unreproducible, loop the
|
|
29
|
+
failing interaction 30x, measuring on every iteration:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
for (let i = 1; i <= 30; i++) {
|
|
33
|
+
await page.locator('.js-open-trigger').tap(); // your theme's selector
|
|
34
|
+
await page.waitForTimeout(400);
|
|
35
|
+
const m = await page.evaluate(() => {
|
|
36
|
+
const el = document.querySelector('#target-element');
|
|
37
|
+
const r = el.getBoundingClientRect();
|
|
38
|
+
return { w: Math.round(r.width), h: Math.round(r.height), pad: getComputedStyle(el).padding };
|
|
39
|
+
});
|
|
40
|
+
console.log(i, JSON.stringify(m));
|
|
41
|
+
await page.locator('.js-close-trigger').tap();
|
|
42
|
+
await page.waitForTimeout(300);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
4. **Slow-network CSS races.** Delay one stylesheet with `page.route()`.
|
|
47
|
+
Gotcha: a setup navigation warms the HTTP cache and the route never fires
|
|
48
|
+
on the test navigation — `route.abort()` the asset during setup, then
|
|
49
|
+
switch the handler to delay on the real run.
|
|
50
|
+
|
|
51
|
+
5. **Test against the same origin the reporter used** (CDN preview vs local
|
|
52
|
+
proxy) — asset latency and compiled bundles differ per page and origin.
|
|
53
|
+
|
|
54
|
+
## When this level is not enough
|
|
55
|
+
|
|
56
|
+
Playwright WebKit renders everything from a healthy desktop compositor. If
|
|
57
|
+
computed styles and screenshots are clean here but devices still show the bug,
|
|
58
|
+
it lives in iOS-specific territory: dynamic viewport (`dvh`) lag, browser
|
|
59
|
+
chrome show/hide, native form-control painters, or stale GPU textures.
|
|
60
|
+
Escalate to references/simulator.md.
|