climaybe 1.7.2 → 1.8.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.
Files changed (39) hide show
  1. package/README.md +9 -7
  2. package/bin/version.txt +1 -1
  3. package/package.json +6 -7
  4. package/src/commands/add-cursor-skill.js +12 -7
  5. package/src/commands/init.js +10 -5
  6. package/src/cursor/rules/00-rule-index.mdc +24 -0
  7. package/src/cursor/rules/accessibility-rules.mdc +527 -0
  8. package/src/cursor/rules/commit-rules.mdc +286 -0
  9. package/src/cursor/rules/cursor-rule-template.mdc +66 -0
  10. package/src/cursor/rules/examples/section-example.liquid +52 -0
  11. package/src/cursor/rules/examples/snippet-example.liquid +83 -0
  12. package/src/cursor/rules/figma-design-system.mdc +182 -0
  13. package/src/cursor/rules/global-rules-reference.mdc +62 -0
  14. package/src/cursor/rules/javascript-standards.mdc +1125 -0
  15. package/src/cursor/rules/js-refactor-tasks.mdc +123 -0
  16. package/src/cursor/rules/linear-task-creation.mdc +105 -0
  17. package/src/cursor/rules/liquid-doc-rules.mdc +595 -0
  18. package/src/cursor/rules/liquid.mdc +228 -0
  19. package/src/cursor/rules/project-overview.mdc +81 -0
  20. package/src/cursor/rules/schemas.mdc +150 -0
  21. package/src/cursor/rules/sections.mdc +25 -0
  22. package/src/cursor/rules/snippets.mdc +134 -0
  23. package/src/cursor/rules/tailwindcss-rules.mdc +410 -0
  24. package/src/cursor/skills/accessibility-pass/SKILL.md +54 -0
  25. package/src/cursor/skills/changelog-release/SKILL.md +50 -0
  26. package/src/cursor/skills/commit/SKILL.md +27 -0
  27. package/src/cursor/skills/commit-in-groups/SKILL.md +55 -0
  28. package/src/cursor/skills/linear-create-task/SKILL.md +81 -0
  29. package/src/cursor/skills/liquid-doc-comments/SKILL.md +37 -0
  30. package/src/cursor/skills/locale-translation-prep/SKILL.md +49 -0
  31. package/src/cursor/skills/schema-section-change/SKILL.md +39 -0
  32. package/src/cursor/skills/section-from-spec/SKILL.md +39 -0
  33. package/src/cursor/skills/theme-check-fix/SKILL.md +47 -0
  34. package/src/index.js +3 -2
  35. package/src/lib/commit-tooling.js +0 -44
  36. package/src/lib/config.js +1 -1
  37. package/src/lib/cursor-bundle.js +47 -0
  38. package/src/lib/prompts.js +3 -2
  39. package/src/workflows/shared/version-bump.yml +5 -2
@@ -0,0 +1,527 @@
1
+ ---
2
+ description: Accessibility rules for general languages used in the project
3
+ alwaysApply: false
4
+ ---
5
+ # Accessibility Rules for Electric Maybe Shopify Theme
6
+
7
+ ## Overview
8
+ This document establishes comprehensive accessibility standards for the Electric Maybe Shopify theme project, ensuring WCAG 2.1 AA compliance across all components, sections, and interactions. All code must prioritize accessibility alongside performance and user experience.
9
+
10
+ ## Core Accessibility Principles
11
+
12
+ ### 1. Semantic HTML Structure
13
+ - Use semantic HTML elements for their intended purpose
14
+ - Maintain proper heading hierarchy (h1 → h2 → h3, etc.)
15
+ - Use landmarks (header, nav, main, aside, footer) appropriately
16
+ - Implement proper list structures (ul, ol, dl)
17
+
18
+ ### 2. ARIA Implementation Standards
19
+ - Use ARIA attributes only when necessary (prefer semantic HTML)
20
+ - Ensure ARIA attributes are properly supported and tested
21
+ - Maintain ARIA state synchronization with JavaScript
22
+ - Use ARIA live regions for dynamic content updates
23
+
24
+ ### 3. Keyboard Navigation
25
+ - All interactive elements must be keyboard accessible
26
+ - Implement logical tab order
27
+ - Provide visible focus indicators
28
+ - Support keyboard shortcuts for common actions
29
+
30
+ ### 4. Screen Reader Support
31
+ - Provide meaningful alt text for images
32
+ - Use descriptive link text
33
+ - Implement proper form labels and error messages
34
+ - Ensure content is announced in logical order
35
+
36
+ ## Component-Specific Accessibility Rules
37
+
38
+ ### Web Components (JavaScript)
39
+ ```javascript
40
+ // Required accessibility implementation for all web components
41
+ class AccessibleComponent extends HTMLElement {
42
+ constructor() {
43
+ super();
44
+ this.#setupAccessibility();
45
+ }
46
+
47
+ #setupAccessibility() {
48
+ // Set ARIA attributes
49
+ this.setAttribute('role', 'region');
50
+ this.setAttribute('aria-label', 'Component description');
51
+
52
+ // Ensure keyboard navigation
53
+ this.addEventListener('keydown', this.#handleKeyboard.bind(this));
54
+
55
+ // Set tabindex for focusable elements
56
+ this.querySelectorAll('[data-focusable]').forEach(el => {
57
+ el.setAttribute('tabindex', '0');
58
+ });
59
+ }
60
+
61
+ #handleKeyboard(event) {
62
+ switch(event.key) {
63
+ case 'Enter':
64
+ case ' ':
65
+ event.preventDefault();
66
+ this.#activate();
67
+ break;
68
+ case 'Escape':
69
+ this.#close();
70
+ break;
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### Liquid Components
77
+ ```liquid
78
+ {%- comment -%}
79
+ Required accessibility attributes for all interactive components
80
+ {%- endcomment -%}
81
+
82
+ {%- comment -%}Buttons{%- endcomment -%}
83
+ <button
84
+ type="button"
85
+ aria-label="{{ button_label | default: 'Button' }}"
86
+ {% if disabled %}disabled aria-disabled="true"{% endif %}
87
+ {% if expanded %}aria-expanded="{{ expanded }}"{% endif %}
88
+ {% if controls %}aria-controls="{{ controls }}"{% endif %}
89
+ class="focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
90
+ >
91
+ {{ button_text }}
92
+ </button>
93
+
94
+ {%- comment -%}Images{%- endcomment -%}
95
+ <img
96
+ src="{{ image.src }}"
97
+ alt="{{ image.alt | default: 'Image description' }}"
98
+ {% if image.width %}width="{{ image.width }}"{% endif %}
99
+ {% if image.height %}height="{{ image.height }}"{% endif %}
100
+ loading="lazy"
101
+ decoding="async"
102
+ class="focus:outline-none focus:ring-2 focus:ring-blue-500"
103
+ >
104
+
105
+ {%- comment -%}Forms{%- endcomment -%}
106
+ <form role="search" aria-label="Search form">
107
+ <label for="search-input" class="sr-only">Search</label>
108
+ <input
109
+ type="search"
110
+ id="search-input"
111
+ name="q"
112
+ aria-describedby="search-help"
113
+ aria-invalid="{{ has_error }}"
114
+ aria-required="true"
115
+ required
116
+ class="focus:outline-none focus:ring-2 focus:ring-blue-500"
117
+ >
118
+ <div id="search-help" class="sr-only">Enter your search terms</div>
119
+ {% if has_error %}
120
+ <div role="alert" aria-live="polite" class="error-message">
121
+ {{ error_message }}
122
+ </div>
123
+ {% endif %}
124
+ </form>
125
+ ```
126
+
127
+ ## WCAG 2.1 AA Compliance Requirements
128
+
129
+ ### 1. Perceivable
130
+ - **Color Contrast**: Minimum 4.5:1 for normal text, 3:1 for large text
131
+ - **Text Alternatives**: All non-text content must have text alternatives
132
+ - **Adaptable**: Content must be adaptable without losing structure
133
+ - **Distinguishable**: Users must be able to see and hear content
134
+
135
+ ### 2. Operable
136
+ - **Keyboard Accessible**: All functionality available via keyboard
137
+ - **Enough Time**: Users must have enough time to read and use content
138
+ - **Seizures**: Content must not cause seizures or physical reactions
139
+ - **Navigable**: Users must be able to navigate and find content
140
+
141
+ ### 3. Understandable
142
+ - **Readable**: Text must be readable and understandable
143
+ - **Predictable**: Pages must operate in predictable ways
144
+ - **Input Assistance**: Users must be helped to avoid and correct mistakes
145
+
146
+ ### 4. Robust
147
+ - **Compatible**: Content must be compatible with current and future tools
148
+
149
+ ## Implementation Standards
150
+
151
+ ### Focus Management
152
+ ```javascript
153
+ // Proper focus management for modals and overlays
154
+ class ModalComponent extends HTMLElement {
155
+ #previousFocus = null;
156
+ #focusableElements = null;
157
+
158
+ #trapFocus() {
159
+ this.#focusableElements = this.querySelectorAll(
160
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
161
+ );
162
+
163
+ const firstElement = this.#focusableElements[0];
164
+ const lastElement = this.#focusableElements[this.#focusableElements.length - 1];
165
+
166
+ this.addEventListener('keydown', (e) => {
167
+ if (e.key === 'Tab') {
168
+ if (e.shiftKey) {
169
+ if (document.activeElement === firstElement) {
170
+ e.preventDefault();
171
+ lastElement.focus();
172
+ }
173
+ } else {
174
+ if (document.activeElement === lastElement) {
175
+ e.preventDefault();
176
+ firstElement.focus();
177
+ }
178
+ }
179
+ }
180
+ });
181
+ }
182
+
183
+ #restoreFocus() {
184
+ if (this.#previousFocus) {
185
+ this.#previousFocus.focus();
186
+ }
187
+ }
188
+ }
189
+ ```
190
+
191
+ ### Live Regions
192
+ ```javascript
193
+ // Announce dynamic content changes
194
+ class LiveRegionComponent extends HTMLElement {
195
+ #announce(message, priority = 'polite') {
196
+ const liveRegion = this.querySelector('[aria-live]') || this.#createLiveRegion();
197
+ liveRegion.setAttribute('aria-live', priority);
198
+ liveRegion.textContent = message;
199
+
200
+ // Clear after announcement
201
+ setTimeout(() => {
202
+ liveRegion.textContent = '';
203
+ }, 1000);
204
+ }
205
+
206
+ #createLiveRegion() {
207
+ const liveRegion = document.createElement('div');
208
+ liveRegion.setAttribute('aria-live', 'polite');
209
+ liveRegion.setAttribute('aria-atomic', 'true');
210
+ liveRegion.className = 'sr-only';
211
+ this.appendChild(liveRegion);
212
+ return liveRegion;
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### Skip Links
218
+ ```html
219
+ <!-- Skip to main content link -->
220
+ <a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 bg-blue-600 text-white px-4 py-2 rounded z-50">
221
+ Skip to main content
222
+ </a>
223
+
224
+ <main id="main-content" tabindex="-1">
225
+ <!-- Main content here -->
226
+ </main>
227
+ ```
228
+
229
+ ## CSS Accessibility Standards
230
+
231
+ ### Focus Indicators
232
+ ```css
233
+ /* Visible focus indicators for all interactive elements */
234
+ .focus-visible {
235
+ outline: 2px solid #3b82f6;
236
+ outline-offset: 2px;
237
+ }
238
+
239
+ /* High contrast focus for better visibility */
240
+ @media (prefers-contrast: high) {
241
+ .focus-visible {
242
+ outline: 3px solid #000000;
243
+ outline-offset: 1px;
244
+ }
245
+ }
246
+
247
+ /* Reduced motion support */
248
+ @media (prefers-reduced-motion: reduce) {
249
+ *,
250
+ *::before,
251
+ *::after {
252
+ animation-duration: 0.01ms !important;
253
+ animation-iteration-count: 1 !important;
254
+ transition-duration: 0.01ms !important;
255
+ }
256
+ }
257
+ ```
258
+
259
+ ### Screen Reader Only Content
260
+ ```css
261
+ /* Hide content visually but keep it available to screen readers */
262
+ .sr-only {
263
+ position: absolute;
264
+ width: 1px;
265
+ height: 1px;
266
+ padding: 0;
267
+ margin: -1px;
268
+ overflow: hidden;
269
+ clip: rect(0, 0, 0, 0);
270
+ white-space: nowrap;
271
+ border: 0;
272
+ }
273
+
274
+ /* Show content on focus for keyboard navigation */
275
+ .sr-only:focus {
276
+ position: static;
277
+ width: auto;
278
+ height: auto;
279
+ padding: inherit;
280
+ margin: inherit;
281
+ overflow: visible;
282
+ clip: auto;
283
+ white-space: normal;
284
+ }
285
+ ```
286
+
287
+ ## Form Accessibility
288
+
289
+ ### Required Form Patterns
290
+ ```liquid
291
+ {%- comment -%}Accessible form field with proper labeling{%- endcomment -%}
292
+ <div class="form-field">
293
+ <label for="{{ field_id }}" class="block text-sm font-medium text-gray-700">
294
+ {{ field_label }}
295
+ {% if required %}<span class="text-red-500" aria-label="required">*</span>{% endif %}
296
+ </label>
297
+
298
+ <input
299
+ type="{{ field_type | default: 'text' }}"
300
+ id="{{ field_id }}"
301
+ name="{{ field_name }}"
302
+ {% if required %}required aria-required="true"{% endif %}
303
+ {% if placeholder %}placeholder="{{ placeholder }}"{% endif %}
304
+ {% if value %}value="{{ value }}"{% endif %}
305
+ {% if disabled %}disabled aria-disabled="true"{% endif %}
306
+ aria-describedby="{{ field_id }}-help {{ field_id }}-error"
307
+ aria-invalid="{{ has_error }}"
308
+ class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
309
+ >
310
+
311
+ {% if help_text %}
312
+ <div id="{{ field_id }}-help" class="mt-1 text-sm text-gray-500">
313
+ {{ help_text }}
314
+ </div>
315
+ {% endif %}
316
+
317
+ {% if has_error %}
318
+ <div id="{{ field_id }}-error" role="alert" aria-live="polite" class="mt-1 text-sm text-system-red">
319
+ {{ error_message }}
320
+ </div>
321
+ {% endif %}
322
+ </div>
323
+ ```
324
+
325
+ ## Interactive Component Patterns
326
+
327
+ ### Toggle Components
328
+ ```javascript
329
+ class ToggleComponent extends HTMLElement {
330
+ #button = null;
331
+ #content = null;
332
+ #isExpanded = false;
333
+
334
+ connectedCallback() {
335
+ this.#button = this.querySelector('[data-toggle-button]');
336
+ this.#content = this.querySelector('[data-toggle-content]');
337
+
338
+ this.#setupAccessibility();
339
+ this.#bindEvents();
340
+ }
341
+
342
+ #setupAccessibility() {
343
+ this.#button.setAttribute('aria-expanded', 'false');
344
+ this.#button.setAttribute('aria-controls', this.#content.id);
345
+ this.#content.setAttribute('aria-hidden', 'true');
346
+ }
347
+
348
+ #toggle() {
349
+ this.#isExpanded = !this.#isExpanded;
350
+
351
+ this.#button.setAttribute('aria-expanded', this.#isExpanded.toString());
352
+ this.#content.setAttribute('aria-hidden', (!this.#isExpanded).toString());
353
+
354
+ // Announce state change
355
+ const message = this.#isExpanded ? 'Expanded' : 'Collapsed';
356
+ this.#announce(message);
357
+ }
358
+ }
359
+ ```
360
+
361
+ ### Carousel/Slider Components
362
+ ```javascript
363
+ class AccessibleCarousel extends HTMLElement {
364
+ #currentSlide = 0;
365
+ #slides = [];
366
+ #indicators = [];
367
+
368
+ #setupAccessibility() {
369
+ this.setAttribute('role', 'region');
370
+ this.setAttribute('aria-label', 'Image carousel');
371
+
372
+ this.#slides.forEach((slide, index) => {
373
+ slide.setAttribute('aria-hidden', (index !== 0).toString());
374
+ slide.setAttribute('aria-label', `Slide ${index + 1} of ${this.#slides.length}`);
375
+ });
376
+
377
+ this.#indicators.forEach((indicator, index) => {
378
+ indicator.setAttribute('role', 'tab');
379
+ indicator.setAttribute('aria-selected', (index === 0).toString());
380
+ indicator.setAttribute('aria-label', `Go to slide ${index + 1}`);
381
+ });
382
+ }
383
+
384
+ #goToSlide(index) {
385
+ this.#slides[this.#currentSlide].setAttribute('aria-hidden', 'true');
386
+ this.#indicators[this.#currentSlide].setAttribute('aria-selected', 'false');
387
+
388
+ this.#currentSlide = index;
389
+
390
+ this.#slides[this.#currentSlide].setAttribute('aria-hidden', 'false');
391
+ this.#indicators[this.#currentSlide].setAttribute('aria-selected', 'true');
392
+
393
+ this.#announce(`Slide ${index + 1} of ${this.#slides.length}`);
394
+ }
395
+ }
396
+ ```
397
+
398
+ ## Performance and Accessibility
399
+
400
+ ### Lazy Loading with Accessibility
401
+ ```javascript
402
+ // Lazy load images while maintaining accessibility
403
+ class LazyImage extends HTMLElement {
404
+ #observer = null;
405
+
406
+ connectedCallback() {
407
+ this.#observer = new IntersectionObserver(this.#handleIntersection.bind(this), {
408
+ rootMargin: '50px'
409
+ });
410
+ this.#observer.observe(this);
411
+ }
412
+
413
+ #handleIntersection(entries) {
414
+ entries.forEach(entry => {
415
+ if (entry.isIntersecting) {
416
+ this.#loadImage();
417
+ this.#observer.unobserve(this);
418
+ }
419
+ });
420
+ }
421
+
422
+ #loadImage() {
423
+ const img = this.querySelector('img');
424
+ if (img) {
425
+ img.src = img.dataset.src;
426
+ img.alt = img.dataset.alt || 'Image';
427
+ img.removeAttribute('data-src');
428
+ img.removeAttribute('data-alt');
429
+ }
430
+ }
431
+ }
432
+ ```
433
+
434
+ ## Testing and Validation
435
+
436
+ ### Automated Testing Checklist
437
+ - [ ] All images have alt text
438
+ - [ ] All form fields have labels
439
+ - [ ] All interactive elements are keyboard accessible
440
+ - [ ] Color contrast meets WCAG 2.1 AA standards
441
+ - [ ] Heading hierarchy is logical
442
+ - [ ] ARIA attributes are properly implemented
443
+ - [ ] Focus indicators are visible
444
+ - [ ] Screen reader announcements work correctly
445
+
446
+ ### Manual Testing Requirements
447
+ - [ ] Test with screen readers (NVDA, JAWS, VoiceOver)
448
+ - [ ] Test keyboard-only navigation
449
+ - [ ] Test with high contrast mode
450
+ - [ ] Test with reduced motion preferences
451
+ - [ ] Test with zoom levels up to 200%
452
+ - [ ] Test with different font sizes
453
+
454
+ ## Error Handling and Feedback
455
+
456
+ ### Accessible Error Messages
457
+ ```javascript
458
+ class FormValidator extends HTMLElement {
459
+ #showError(field, message) {
460
+ const errorElement = document.createElement('div');
461
+ errorElement.setAttribute('role', 'alert');
462
+ errorElement.setAttribute('aria-live', 'polite');
463
+ errorElement.className = 'error-message text-system-red text-sm mt-1';
464
+ errorElement.textContent = message;
465
+
466
+ field.setAttribute('aria-invalid', 'true');
467
+ field.parentNode.appendChild(errorElement);
468
+
469
+ // Announce error to screen readers
470
+ this.#announce(message, 'assertive');
471
+ }
472
+
473
+ #clearError(field) {
474
+ field.setAttribute('aria-invalid', 'false');
475
+ const errorElement = field.parentNode.querySelector('.error-message');
476
+ if (errorElement) {
477
+ errorElement.remove();
478
+ }
479
+ }
480
+ }
481
+ ```
482
+
483
+ ## Compliance Checklist
484
+
485
+ Before committing any code, ensure:
486
+
487
+ ### HTML/Liquid
488
+ - [ ] Semantic HTML elements used appropriately
489
+ - [ ] Proper heading hierarchy (h1 → h2 → h3)
490
+ - [ ] All images have meaningful alt text
491
+ - [ ] All form fields have associated labels
492
+ - [ ] Interactive elements have proper ARIA attributes
493
+ - [ ] Skip links implemented for main content
494
+ - [ ] Color is not the only way to convey information
495
+
496
+ ### JavaScript
497
+ - [ ] Keyboard navigation implemented for all interactions
498
+ - [ ] Focus management for modals and overlays
499
+ - [ ] ARIA state synchronization with JavaScript
500
+ - [ ] Live regions for dynamic content updates
501
+ - [ ] Error handling with accessible feedback
502
+ - [ ] Reduced motion preferences respected
503
+
504
+ ### CSS
505
+ - [ ] Visible focus indicators for all interactive elements
506
+ - [ ] High contrast mode support
507
+ - [ ] Reduced motion media queries implemented
508
+ - [ ] Screen reader only content properly hidden
509
+ - [ ] Color contrast meets WCAG 2.1 AA standards
510
+
511
+ ### Performance
512
+ - [ ] Lazy loading maintains accessibility
513
+ - [ ] Dynamic content updates are announced
514
+ - [ ] Loading states are communicated
515
+ - [ ] Error states are clearly indicated
516
+
517
+ ## Resources and References
518
+
519
+ - [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
520
+ - [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/)
521
+ - [Shopify Accessibility Guidelines](https://shopify.dev/docs/storefronts/themes/best-practices/accessibility)
522
+ - [WebAIM Color Contrast Checker](https://webaim.org/resources/contrastchecker/)
523
+ - [axe-core Testing Library](https://github.com/dequelabs/axe-core)
524
+ description:
525
+ globs:
526
+ alwaysApply: false
527
+ ---