rebly-sections 1.6.0 → 1.6.1
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.
|
@@ -90,15 +90,24 @@ def check_has_responsive_visibility(content, schema_json):
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def check_has_section_dimensions(content, schema_json):
|
|
93
|
-
"""Check if section has width and height dimension controls.
|
|
93
|
+
"""Check if section has width and height dimension controls.
|
|
94
|
+
Height should use section_height select (vh options), NOT a px-only range.
|
|
95
|
+
"""
|
|
94
96
|
if not schema_json:
|
|
95
97
|
return ("WARN", "No schema to check for section dimensions")
|
|
96
98
|
settings = schema_json.get("settings", [])
|
|
97
99
|
setting_ids = [s.get("id", "") for s in settings]
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
# Width: both required
|
|
101
|
+
for req in ["section_width", "section_width_custom"]:
|
|
102
|
+
if req not in setting_ids:
|
|
103
|
+
return ("WARN", f"Missing {req} — merchants can't control section width")
|
|
104
|
+
# Height: prefer section_height (vh select), accept section_min_height for legacy
|
|
105
|
+
has_vh = "section_height" in setting_ids
|
|
106
|
+
has_px = "section_min_height" in setting_ids
|
|
107
|
+
if not has_vh and not has_px:
|
|
108
|
+
return ("WARN", "Missing section height setting — add section_height select (auto/50vh/75vh/100vh/custom) for responsive control")
|
|
109
|
+
if has_px and not has_vh:
|
|
110
|
+
return ("WARN", "section_min_height (px) is not responsive — replace with section_height select (auto/50vh/75vh/100vh/custom) + section_height_custom range (step:20)")
|
|
102
111
|
return ("PASS", "Section dimension settings present")
|
|
103
112
|
|
|
104
113
|
|
|
@@ -117,3 +126,58 @@ def check_has_font_size_controls(content, schema_json):
|
|
|
117
126
|
if has_headings and not has_heading_size:
|
|
118
127
|
return ("WARN", "Missing heading font size range — merchants can't adjust heading size")
|
|
119
128
|
return ("PASS", "Font size controls present")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def check_no_overflow_clip_heading(content, schema_json):
|
|
132
|
+
"""Heading selectors must not have max-height + overflow:hidden combo — causes text clipping."""
|
|
133
|
+
style_match = re.search(r'<style[^>]*>(.*?)</style>', content, re.DOTALL)
|
|
134
|
+
if not style_match:
|
|
135
|
+
return ("PASS", "No overflow clipping on heading — no style block")
|
|
136
|
+
css = style_match.group(1)
|
|
137
|
+
# Find CSS blocks whose selector contains 'heading'
|
|
138
|
+
heading_blocks = re.findall(r'[^}]*heading[^{]*\{[^}]+\}', css, re.DOTALL)
|
|
139
|
+
for block in heading_blocks:
|
|
140
|
+
has_max_height = bool(re.search(r'max-height\s*:', block))
|
|
141
|
+
has_overflow = bool(re.search(r'overflow\s*:\s*hidden', block))
|
|
142
|
+
if has_max_height and has_overflow:
|
|
143
|
+
return ("WARN", "Heading selector has max-height + overflow:hidden — will clip descenders. Remove max-height or use min-height instead.")
|
|
144
|
+
return ("PASS", "No heading overflow clipping detected")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def check_font_applied_directly(content, schema_json):
|
|
148
|
+
"""font-family must be set directly on the heading selector, not only inherited from parent."""
|
|
149
|
+
if not schema_json:
|
|
150
|
+
return ("WARN", "No schema — cannot check font-family specificity")
|
|
151
|
+
settings = schema_json.get("settings", [])
|
|
152
|
+
has_font_picker = any(s.get("type") == "font_picker" for s in settings)
|
|
153
|
+
if not has_font_picker:
|
|
154
|
+
return ("PASS", "No font_picker — font-family specificity check skipped")
|
|
155
|
+
style_match = re.search(r'<style[^>]*>(.*?)</style>', content, re.DOTALL)
|
|
156
|
+
if not style_match:
|
|
157
|
+
return ("WARN", "font_picker found but no <style> block — font not applied")
|
|
158
|
+
css = style_match.group(1)
|
|
159
|
+
# Check that a heading-class selector directly declares font-family (not just parent section)
|
|
160
|
+
has_heading_font = bool(re.search(
|
|
161
|
+
r'\.rebly-[\w-]+__heading[\w\s,]*\{[^}]*font-family', css, re.DOTALL
|
|
162
|
+
))
|
|
163
|
+
if not has_heading_font:
|
|
164
|
+
return ("WARN", "font-family not set directly on __heading selector — theme CSS may override. Declare font-family inside .rebly-{slug}__heading { } rule.")
|
|
165
|
+
return ("PASS", "font-family applied directly on heading selector")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def check_css_vars_in_gradients(content, schema_json):
|
|
169
|
+
"""Gradient stops should use CSS custom properties, not hardcoded high opacity values."""
|
|
170
|
+
style_match = re.search(r'<style[^>]*>(.*?)</style>', content, re.DOTALL)
|
|
171
|
+
if not style_match:
|
|
172
|
+
return ("PASS", "No gradient opacity hardcode — no style block")
|
|
173
|
+
# Strip Liquid output tags before scanning
|
|
174
|
+
css_no_liquid = re.sub(r'\{\{.*?\}\}', '', style_match.group(1))
|
|
175
|
+
# Flag rgba() with literal opacity >= 0.6 that appears inside a gradient (linear/radial)
|
|
176
|
+
gradient_blocks = re.findall(r'(?:linear|radial)-gradient\([^;]+\)', css_no_liquid, re.DOTALL)
|
|
177
|
+
hardcoded = []
|
|
178
|
+
for block in gradient_blocks:
|
|
179
|
+
hits = re.findall(r'rgba\([^)]+,\s*(0\.[6-9]\d*|1\.0?)\)', block)
|
|
180
|
+
hardcoded.extend(hits)
|
|
181
|
+
if hardcoded:
|
|
182
|
+
return ("WARN", f"Gradient stops with hardcoded high opacity {hardcoded[:3]} — use calc(var(--overlay-opacity) * N) so merchant slider controls the overlay")
|
|
183
|
+
return ("PASS", "Gradient stops use CSS variables or acceptable opacity values")
|
|
@@ -34,6 +34,7 @@ QUALITY_CHECKS = [
|
|
|
34
34
|
("has_section_tag", "Uses <section> HTML element"),
|
|
35
35
|
("images_lazy_load", "Non-hero images use loading='lazy'"),
|
|
36
36
|
("settings_have_labels", "All settings have label property"),
|
|
37
|
+
("range_steps_valid", "Range settings have at most 101 steps (Shopify limit)"),
|
|
37
38
|
("text_elements_explicit_color", "Text elements have explicit color in CSS"),
|
|
38
39
|
("layout_responsive_breakpoints", "Responsive breakpoints (749px/989px) present"),
|
|
39
40
|
("layout_reduced_motion", "prefers-reduced-motion media query present"),
|
|
@@ -50,6 +51,9 @@ ADVISORY_CHECKS = [
|
|
|
50
51
|
("has_section_dimensions", "Section dimension settings present"),
|
|
51
52
|
("naming_rebly_prefix", "Schema name uses Rebly: prefix"),
|
|
52
53
|
("naming_bem_classes", "CSS classes follow BEM rebly-{component} convention"),
|
|
54
|
+
("no_overflow_clip_heading", "Heading has no max-height + overflow:hidden clipping"),
|
|
55
|
+
("font_applied_directly", "font-family set directly on heading selector"),
|
|
56
|
+
("css_vars_in_gradients", "Gradient stops use CSS vars, not hardcoded opacity"),
|
|
53
57
|
]
|
|
54
58
|
|
|
55
59
|
|
|
@@ -145,6 +149,43 @@ def check_settings_have_labels(content):
|
|
|
145
149
|
return True
|
|
146
150
|
|
|
147
151
|
|
|
152
|
+
def check_range_steps_valid(content):
|
|
153
|
+
"""Shopify hard limit: range settings must have at most 101 steps (max-min)/step <= 100.
|
|
154
|
+
Violation causes upload failure: 'step invalid. Range settings must have at most 101 steps'
|
|
155
|
+
"""
|
|
156
|
+
schema = extract_schema_json(content)
|
|
157
|
+
if not schema:
|
|
158
|
+
return True # Can't check without valid schema
|
|
159
|
+
violations = []
|
|
160
|
+
all_settings = list(schema.get("settings", []))
|
|
161
|
+
for block in schema.get("blocks", []):
|
|
162
|
+
all_settings += block.get("settings", [])
|
|
163
|
+
for s in all_settings:
|
|
164
|
+
if s.get("type") != "range":
|
|
165
|
+
continue
|
|
166
|
+
try:
|
|
167
|
+
mn = float(s.get("min", 0))
|
|
168
|
+
mx = float(s.get("max", 100))
|
|
169
|
+
st = float(s.get("step", 1))
|
|
170
|
+
if st <= 0:
|
|
171
|
+
violations.append(f"{s.get('id','?')}: step={st} must be > 0")
|
|
172
|
+
continue
|
|
173
|
+
num_steps = (mx - mn) / st
|
|
174
|
+
if num_steps > 100:
|
|
175
|
+
violations.append(
|
|
176
|
+
f"{s.get('id','?')}: min={mn}, max={mx}, step={st} → {num_steps:.0f} steps (max 100)"
|
|
177
|
+
)
|
|
178
|
+
except (TypeError, ValueError):
|
|
179
|
+
pass
|
|
180
|
+
if violations:
|
|
181
|
+
# Print details to help developer fix it
|
|
182
|
+
import sys
|
|
183
|
+
for v in violations:
|
|
184
|
+
print(f" ✗ Range steps violation: {v}", file=sys.stderr)
|
|
185
|
+
return False
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
|
|
148
189
|
def check_text_elements_explicit_color(content):
|
|
149
190
|
"""Verify text elements within section scope have explicit color in CSS."""
|
|
150
191
|
style_match = re.search(r'<style[^>]*>(.*?)</style>', content, re.DOTALL)
|
|
@@ -228,6 +269,7 @@ CHECK_FUNCTIONS = {
|
|
|
228
269
|
"has_section_tag": check_has_section_tag,
|
|
229
270
|
"images_lazy_load": check_images_lazy_load,
|
|
230
271
|
"settings_have_labels": check_settings_have_labels,
|
|
272
|
+
"range_steps_valid": check_range_steps_valid,
|
|
231
273
|
"text_elements_explicit_color": check_text_elements_explicit_color,
|
|
232
274
|
"layout_responsive_breakpoints": check_layout_responsive_breakpoints,
|
|
233
275
|
"layout_reduced_motion": check_layout_reduced_motion,
|
|
@@ -274,6 +316,9 @@ def run_quality_gate(filepath):
|
|
|
274
316
|
"has_font_size_controls": _checks_mod.check_has_font_size_controls,
|
|
275
317
|
"has_responsive_visibility": _checks_mod.check_has_responsive_visibility,
|
|
276
318
|
"has_section_dimensions": _checks_mod.check_has_section_dimensions,
|
|
319
|
+
"no_overflow_clip_heading": _checks_mod.check_no_overflow_clip_heading,
|
|
320
|
+
"font_applied_directly": _checks_mod.check_font_applied_directly,
|
|
321
|
+
"css_vars_in_gradients": _checks_mod.check_css_vars_in_gradients,
|
|
277
322
|
}
|
|
278
323
|
# Built-in naming convention advisory checks (always available)
|
|
279
324
|
advisory_fns.update({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rebly-sections",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Shopify section AI coding skill installer for Claude Code and Antigravity Kit",
|
|
5
5
|
"author": "Rebly Sections",
|
|
6
6
|
"homepage": "https://github.com/rebly-sections/sections-ai#readme",
|