niahere 0.2.61 → 0.2.63
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/defaults/memory-promoter.md +99 -0
- package/defaults/self/staging.md +48 -0
- package/package.json +7 -3
- package/skills/code-review/pr-review.md +14 -1
- package/skills/cro/page.md +22 -0
- package/skills/frontend-design/SKILL.md +7 -3
- package/skills/frontend-design/building.md +17 -2
- package/skills/qa/SKILL.md +108 -72
- package/skills/userinterface-wiki/AGENTS.md +3749 -0
- package/skills/userinterface-wiki/SKILL.md +253 -0
- package/skills/userinterface-wiki/metadata.json +26 -0
- package/skills/userinterface-wiki/rules/_sections.md +66 -0
- package/skills/userinterface-wiki/rules/_template.md +24 -0
- package/skills/userinterface-wiki/rules/a11y-reduced-motion-check.md +30 -0
- package/skills/userinterface-wiki/rules/a11y-toggle-setting.md +30 -0
- package/skills/userinterface-wiki/rules/a11y-visual-equivalent.md +36 -0
- package/skills/userinterface-wiki/rules/a11y-volume-control.md +28 -0
- package/skills/userinterface-wiki/rules/appropriate-confirmations-only.md +19 -0
- package/skills/userinterface-wiki/rules/appropriate-errors-warnings.md +18 -0
- package/skills/userinterface-wiki/rules/appropriate-no-decorative.md +21 -0
- package/skills/userinterface-wiki/rules/appropriate-no-high-frequency.md +28 -0
- package/skills/userinterface-wiki/rules/appropriate-no-punishing.md +27 -0
- package/skills/userinterface-wiki/rules/container-callback-ref.md +31 -0
- package/skills/userinterface-wiki/rules/container-guard-initial-zero.md +25 -0
- package/skills/userinterface-wiki/rules/container-no-excessive-use.md +13 -0
- package/skills/userinterface-wiki/rules/container-overflow-hidden.md +25 -0
- package/skills/userinterface-wiki/rules/container-transition-delay.md +21 -0
- package/skills/userinterface-wiki/rules/container-two-div-pattern.md +35 -0
- package/skills/userinterface-wiki/rules/container-use-resize-observer.md +48 -0
- package/skills/userinterface-wiki/rules/context-cleanup-nodes.md +25 -0
- package/skills/userinterface-wiki/rules/context-resume-suspended.md +28 -0
- package/skills/userinterface-wiki/rules/context-reuse-single.md +30 -0
- package/skills/userinterface-wiki/rules/design-filter-for-character.md +25 -0
- package/skills/userinterface-wiki/rules/design-noise-for-percussion.md +26 -0
- package/skills/userinterface-wiki/rules/design-oscillator-for-tonal.md +22 -0
- package/skills/userinterface-wiki/rules/duration-max-300ms.md +21 -0
- package/skills/userinterface-wiki/rules/duration-press-hover.md +21 -0
- package/skills/userinterface-wiki/rules/duration-shorten-before-curve.md +21 -0
- package/skills/userinterface-wiki/rules/duration-small-state.md +15 -0
- package/skills/userinterface-wiki/rules/easing-entrance-ease-out.md +21 -0
- package/skills/userinterface-wiki/rules/easing-exit-ease-in.md +21 -0
- package/skills/userinterface-wiki/rules/easing-for-state-change.md +27 -0
- package/skills/userinterface-wiki/rules/easing-linear-only-progress.md +21 -0
- package/skills/userinterface-wiki/rules/easing-natural-decay.md +22 -0
- package/skills/userinterface-wiki/rules/easing-no-linear-motion.md +22 -0
- package/skills/userinterface-wiki/rules/easing-transition-ease-in-out.md +15 -0
- package/skills/userinterface-wiki/rules/envelope-exponential-decay.md +21 -0
- package/skills/userinterface-wiki/rules/envelope-no-zero-target.md +21 -0
- package/skills/userinterface-wiki/rules/envelope-set-initial-value.md +22 -0
- package/skills/userinterface-wiki/rules/exit-key-required.md +29 -0
- package/skills/userinterface-wiki/rules/exit-matches-initial.md +29 -0
- package/skills/userinterface-wiki/rules/exit-prop-required.md +33 -0
- package/skills/userinterface-wiki/rules/exit-requires-wrapper.md +27 -0
- package/skills/userinterface-wiki/rules/impl-default-subtle.md +21 -0
- package/skills/userinterface-wiki/rules/impl-preload-audio.md +34 -0
- package/skills/userinterface-wiki/rules/impl-reset-current-time.md +26 -0
- package/skills/userinterface-wiki/rules/mode-pop-layout-for-lists.md +25 -0
- package/skills/userinterface-wiki/rules/mode-sync-layout-conflict.md +29 -0
- package/skills/userinterface-wiki/rules/mode-wait-doubles-duration.md +25 -0
- package/skills/userinterface-wiki/rules/morphing-aria-hidden.md +21 -0
- package/skills/userinterface-wiki/rules/morphing-consistent-viewbox.md +23 -0
- package/skills/userinterface-wiki/rules/morphing-group-variants.md +33 -0
- package/skills/userinterface-wiki/rules/morphing-jump-non-grouped.md +29 -0
- package/skills/userinterface-wiki/rules/morphing-reduced-motion.md +28 -0
- package/skills/userinterface-wiki/rules/morphing-spring-rotation.md +23 -0
- package/skills/userinterface-wiki/rules/morphing-strokelinecap-round.md +21 -0
- package/skills/userinterface-wiki/rules/morphing-three-lines.md +32 -0
- package/skills/userinterface-wiki/rules/morphing-use-collapsed.md +33 -0
- package/skills/userinterface-wiki/rules/native-backdrop-styling.md +27 -0
- package/skills/userinterface-wiki/rules/native-placeholder-styling.md +27 -0
- package/skills/userinterface-wiki/rules/native-selection-styling.md +18 -0
- package/skills/userinterface-wiki/rules/nested-consistent-timing.md +25 -0
- package/skills/userinterface-wiki/rules/nested-propagate-required.md +41 -0
- package/skills/userinterface-wiki/rules/none-context-menu-entrance.md +25 -0
- package/skills/userinterface-wiki/rules/none-high-frequency.md +29 -0
- package/skills/userinterface-wiki/rules/none-keyboard-navigation.md +32 -0
- package/skills/userinterface-wiki/rules/param-click-duration.md +21 -0
- package/skills/userinterface-wiki/rules/param-filter-frequency-range.md +21 -0
- package/skills/userinterface-wiki/rules/param-q-value-range.md +21 -0
- package/skills/userinterface-wiki/rules/param-reasonable-gain.md +21 -0
- package/skills/userinterface-wiki/rules/physics-active-state.md +23 -0
- package/skills/userinterface-wiki/rules/physics-no-excessive-stagger.md +22 -0
- package/skills/userinterface-wiki/rules/physics-spring-for-overshoot.md +23 -0
- package/skills/userinterface-wiki/rules/physics-subtle-deformation.md +22 -0
- package/skills/userinterface-wiki/rules/prefetch-hit-slop.md +27 -0
- package/skills/userinterface-wiki/rules/prefetch-keyboard-tab.md +19 -0
- package/skills/userinterface-wiki/rules/prefetch-not-everything.md +22 -0
- package/skills/userinterface-wiki/rules/prefetch-touch-fallback.md +34 -0
- package/skills/userinterface-wiki/rules/prefetch-trajectory-over-hover.md +32 -0
- package/skills/userinterface-wiki/rules/prefetch-use-selectively.md +13 -0
- package/skills/userinterface-wiki/rules/presence-disable-interactions.md +31 -0
- package/skills/userinterface-wiki/rules/presence-hook-in-child.md +31 -0
- package/skills/userinterface-wiki/rules/presence-safe-to-remove.md +37 -0
- package/skills/userinterface-wiki/rules/pseudo-content-required.md +28 -0
- package/skills/userinterface-wiki/rules/pseudo-first-line-styling.md +27 -0
- package/skills/userinterface-wiki/rules/pseudo-hit-target-expansion.md +31 -0
- package/skills/userinterface-wiki/rules/pseudo-marker-styling.md +28 -0
- package/skills/userinterface-wiki/rules/pseudo-over-dom-node.md +32 -0
- package/skills/userinterface-wiki/rules/pseudo-position-relative-parent.md +33 -0
- package/skills/userinterface-wiki/rules/pseudo-z-index-layering.md +37 -0
- package/skills/userinterface-wiki/rules/spring-for-gestures.md +27 -0
- package/skills/userinterface-wiki/rules/spring-for-interruptible.md +27 -0
- package/skills/userinterface-wiki/rules/spring-params-balanced.md +29 -0
- package/skills/userinterface-wiki/rules/spring-preserves-velocity.md +28 -0
- package/skills/userinterface-wiki/rules/staging-dim-background.md +22 -0
- package/skills/userinterface-wiki/rules/staging-one-focal-point.md +24 -0
- package/skills/userinterface-wiki/rules/staging-z-index-hierarchy.md +22 -0
- package/skills/userinterface-wiki/rules/timing-consistent.md +24 -0
- package/skills/userinterface-wiki/rules/timing-no-entrance-context-menu.md +22 -0
- package/skills/userinterface-wiki/rules/timing-under-300ms.md +22 -0
- package/skills/userinterface-wiki/rules/transition-name-cleanup.md +28 -0
- package/skills/userinterface-wiki/rules/transition-name-required.md +27 -0
- package/skills/userinterface-wiki/rules/transition-name-unique.md +24 -0
- package/skills/userinterface-wiki/rules/transition-over-js-library.md +32 -0
- package/skills/userinterface-wiki/rules/transition-style-pseudo-elements.md +24 -0
- package/skills/userinterface-wiki/rules/type-antialiased-on-retina.md +18 -0
- package/skills/userinterface-wiki/rules/type-disambiguation-stylistic-set.md +15 -0
- package/skills/userinterface-wiki/rules/type-font-display-swap.md +28 -0
- package/skills/userinterface-wiki/rules/type-justify-with-hyphens.md +24 -0
- package/skills/userinterface-wiki/rules/type-letter-spacing-uppercase.md +28 -0
- package/skills/userinterface-wiki/rules/type-no-font-synthesis.md +18 -0
- package/skills/userinterface-wiki/rules/type-oldstyle-nums-for-prose.md +21 -0
- package/skills/userinterface-wiki/rules/type-opentype-contextual-alternates.md +15 -0
- package/skills/userinterface-wiki/rules/type-optical-sizing-auto.md +25 -0
- package/skills/userinterface-wiki/rules/type-proper-fractions.md +15 -0
- package/skills/userinterface-wiki/rules/type-slashed-zero.md +17 -0
- package/skills/userinterface-wiki/rules/type-tabular-nums-for-data.md +21 -0
- package/skills/userinterface-wiki/rules/type-text-wrap-balance-headings.md +21 -0
- package/skills/userinterface-wiki/rules/type-text-wrap-pretty.md +16 -0
- package/skills/userinterface-wiki/rules/type-underline-offset.md +25 -0
- package/skills/userinterface-wiki/rules/type-variable-weight-continuous.md +23 -0
- package/skills/userinterface-wiki/rules/ux-aesthetic-usability.md +32 -0
- package/skills/userinterface-wiki/rules/ux-cognitive-load-reduce.md +49 -0
- package/skills/userinterface-wiki/rules/ux-common-region-boundaries.md +50 -0
- package/skills/userinterface-wiki/rules/ux-doherty-perceived-speed.md +29 -0
- package/skills/userinterface-wiki/rules/ux-doherty-under-400ms.md +30 -0
- package/skills/userinterface-wiki/rules/ux-fitts-hit-area.md +32 -0
- package/skills/userinterface-wiki/rules/ux-fitts-target-size.md +31 -0
- package/skills/userinterface-wiki/rules/ux-goal-gradient-progress.md +33 -0
- package/skills/userinterface-wiki/rules/ux-hicks-minimize-choices.md +45 -0
- package/skills/userinterface-wiki/rules/ux-jakobs-familiar-patterns.md +37 -0
- package/skills/userinterface-wiki/rules/ux-millers-chunking.md +23 -0
- package/skills/userinterface-wiki/rules/ux-pareto-prioritize-features.md +36 -0
- package/skills/userinterface-wiki/rules/ux-peak-end-finish-strong.md +35 -0
- package/skills/userinterface-wiki/rules/ux-postels-accept-messy-input.md +45 -0
- package/skills/userinterface-wiki/rules/ux-pragnanz-simplify.md +33 -0
- package/skills/userinterface-wiki/rules/ux-progressive-disclosure.md +41 -0
- package/skills/userinterface-wiki/rules/ux-proximity-grouping.md +38 -0
- package/skills/userinterface-wiki/rules/ux-serial-position.md +31 -0
- package/skills/userinterface-wiki/rules/ux-similarity-consistency.md +35 -0
- package/skills/userinterface-wiki/rules/ux-teslers-complexity.md +28 -0
- package/skills/userinterface-wiki/rules/ux-uniform-connectedness.md +43 -0
- package/skills/userinterface-wiki/rules/ux-von-restorff-emphasis.md +29 -0
- package/skills/userinterface-wiki/rules/ux-zeigarnik-show-incomplete.md +36 -0
- package/skills/userinterface-wiki/rules/visual-animate-shadow-pseudo.md +49 -0
- package/skills/userinterface-wiki/rules/visual-border-alpha-colors.md +25 -0
- package/skills/userinterface-wiki/rules/visual-button-shadow-anatomy.md +49 -0
- package/skills/userinterface-wiki/rules/visual-concentric-radius.md +40 -0
- package/skills/userinterface-wiki/rules/visual-consistent-spacing-scale.md +35 -0
- package/skills/userinterface-wiki/rules/visual-layered-shadows.md +30 -0
- package/skills/userinterface-wiki/rules/visual-no-pure-black-shadow.md +25 -0
- package/skills/userinterface-wiki/rules/visual-shadow-direction.md +25 -0
- package/skills/userinterface-wiki/rules/visual-shadow-matches-elevation.md +23 -0
- package/skills/userinterface-wiki/rules/weight-duration-matches-action.md +29 -0
- package/skills/userinterface-wiki/rules/weight-match-action.md +32 -0
- package/src/cli/index.ts +23 -73
- package/src/cli/job.ts +25 -92
- package/src/cli/status.ts +17 -9
- package/src/commands/init.ts +1 -0
- package/src/commands/validate.ts +12 -10
- package/src/core/agents.ts +6 -19
- package/src/core/consolidator.ts +97 -91
- package/src/core/daemon.ts +71 -43
- package/src/core/finalizer.ts +31 -3
- package/src/core/health.ts +5 -17
- package/src/core/runner.ts +8 -44
- package/src/core/scheduler.ts +12 -49
- package/src/core/skills.ts +4 -11
- package/src/core/summarizer.ts +7 -21
- package/src/db/connection.ts +0 -11
- package/src/db/models/job.ts +23 -22
- package/src/db/with-db.ts +11 -0
- package/src/mcp/server.ts +1 -1
- package/src/prompts/environment.md +44 -41
- package/src/utils/pid.ts +44 -0
- package/src/utils/schedule.ts +39 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Exit Prop Required Inside AnimatePresence
|
|
3
|
+
impact: HIGH
|
|
4
|
+
tags: exit, prop, animate-presence
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Exit Prop Required Inside AnimatePresence
|
|
8
|
+
|
|
9
|
+
Elements inside AnimatePresence should have exit prop defined.
|
|
10
|
+
|
|
11
|
+
**Incorrect (missing exit):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<AnimatePresence>
|
|
15
|
+
{isOpen && (
|
|
16
|
+
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
|
|
17
|
+
)}
|
|
18
|
+
</AnimatePresence>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (exit defined):**
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<AnimatePresence>
|
|
25
|
+
{isOpen && (
|
|
26
|
+
<motion.div
|
|
27
|
+
initial={{ opacity: 0 }}
|
|
28
|
+
animate={{ opacity: 1 }}
|
|
29
|
+
exit={{ opacity: 0 }}
|
|
30
|
+
/>
|
|
31
|
+
)}
|
|
32
|
+
</AnimatePresence>
|
|
33
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AnimatePresence Wrapper Required
|
|
3
|
+
impact: HIGH
|
|
4
|
+
tags: exit, animate-presence, wrapper
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## AnimatePresence Wrapper Required
|
|
8
|
+
|
|
9
|
+
Conditional motion elements must be wrapped in AnimatePresence.
|
|
10
|
+
|
|
11
|
+
**Incorrect (no wrapper):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
{isVisible && (
|
|
15
|
+
<motion.div exit={{ opacity: 0 }} />
|
|
16
|
+
)}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (wrapped):**
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<AnimatePresence>
|
|
23
|
+
{isVisible && (
|
|
24
|
+
<motion.div exit={{ opacity: 0 }} />
|
|
25
|
+
)}
|
|
26
|
+
</AnimatePresence>
|
|
27
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Subtle Default Volume
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: impl, volume, default
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Subtle Default Volume
|
|
8
|
+
|
|
9
|
+
Default volume should be subtle, not loud.
|
|
10
|
+
|
|
11
|
+
**Incorrect (too loud):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
const DEFAULT_VOLUME = 1.0;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Correct (subtle):**
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
const DEFAULT_VOLUME = 0.3;
|
|
21
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Preload Audio Files
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: impl, preload, performance
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Preload Audio Files
|
|
8
|
+
|
|
9
|
+
Preload audio files to avoid playback delay.
|
|
10
|
+
|
|
11
|
+
**Incorrect (loads on demand):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
function playSound(name: string) {
|
|
15
|
+
const audio = new Audio(`/sounds/${name}.mp3`);
|
|
16
|
+
audio.play();
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (preloaded):**
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
const sounds = {
|
|
24
|
+
success: new Audio("/sounds/success.mp3"),
|
|
25
|
+
error: new Audio("/sounds/error.mp3"),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
Object.values(sounds).forEach(audio => audio.load());
|
|
29
|
+
|
|
30
|
+
function playSound(name: keyof typeof sounds) {
|
|
31
|
+
sounds[name].currentTime = 0;
|
|
32
|
+
sounds[name].play();
|
|
33
|
+
}
|
|
34
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Reset currentTime Before Replay
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: impl, replay, current-time
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Reset currentTime Before Replay
|
|
8
|
+
|
|
9
|
+
Reset audio currentTime before replay to allow rapid triggering.
|
|
10
|
+
|
|
11
|
+
**Incorrect (won't replay if playing):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
function playSound() {
|
|
15
|
+
audio.play();
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (reset before play):**
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
function playSound() {
|
|
23
|
+
audio.currentTime = 0;
|
|
24
|
+
audio.play();
|
|
25
|
+
}
|
|
26
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: popLayout for List Reordering
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: mode, pop-layout, list
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## popLayout for List Reordering
|
|
8
|
+
|
|
9
|
+
Use popLayout mode for list reordering animations.
|
|
10
|
+
|
|
11
|
+
**Incorrect (default mode causes shifts):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<AnimatePresence>
|
|
15
|
+
{items.map(item => <ListItem key={item.id} />)}
|
|
16
|
+
</AnimatePresence>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (popLayout prevents shifts):**
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<AnimatePresence mode="popLayout">
|
|
23
|
+
{items.map(item => <ListItem key={item.id} />)}
|
|
24
|
+
</AnimatePresence>
|
|
25
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Mode "sync" Causes Layout Conflicts
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: mode, sync, layout
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Mode "sync" Causes Layout Conflicts
|
|
8
|
+
|
|
9
|
+
Mode "sync" causes layout conflicts; position exiting elements absolutely.
|
|
10
|
+
|
|
11
|
+
**Incorrect (sync with layout competition):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<AnimatePresence mode="sync">
|
|
15
|
+
{items.map(item => (
|
|
16
|
+
<motion.div exit={{ opacity: 0 }}>{item}</motion.div>
|
|
17
|
+
))}
|
|
18
|
+
</AnimatePresence>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (popLayout instead):**
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<AnimatePresence mode="popLayout">
|
|
25
|
+
{items.map(item => (
|
|
26
|
+
<motion.div exit={{ opacity: 0 }}>{item}</motion.div>
|
|
27
|
+
))}
|
|
28
|
+
</AnimatePresence>
|
|
29
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Mode "wait" Doubles Duration
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: mode, wait, duration
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Mode "wait" Doubles Duration
|
|
8
|
+
|
|
9
|
+
Mode "wait" nearly doubles animation duration; adjust timing accordingly.
|
|
10
|
+
|
|
11
|
+
**Incorrect (too slow with wait):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<AnimatePresence mode="wait">
|
|
15
|
+
<motion.div transition={{ duration: 0.3 }} />
|
|
16
|
+
</AnimatePresence>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (halved timing):**
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<AnimatePresence mode="wait">
|
|
23
|
+
<motion.div transition={{ duration: 0.15 }} />
|
|
24
|
+
</AnimatePresence>
|
|
25
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Aria Hidden on Icon SVGs
|
|
3
|
+
impact: LOW
|
|
4
|
+
tags: morphing, a11y, aria
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Aria Hidden on Icon SVGs
|
|
8
|
+
|
|
9
|
+
Icon SVGs should be aria-hidden since they're decorative.
|
|
10
|
+
|
|
11
|
+
**Incorrect (no aria attribute):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<svg width={size} height={size}>...</svg>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Correct (aria-hidden):**
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<svg width={size} height={size} aria-hidden="true">...</svg>
|
|
21
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Consistent ViewBox Size
|
|
3
|
+
impact: HIGH
|
|
4
|
+
tags: morphing, viewbox, svg
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Consistent ViewBox Size
|
|
8
|
+
|
|
9
|
+
All icons must use the same viewBox (14x14 recommended).
|
|
10
|
+
|
|
11
|
+
**Incorrect (mixed scales):**
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const icon1 = { lines: [{ x1: 2, y1: 7, x2: 12, y2: 7 }, ...] }; // 14x14
|
|
15
|
+
const icon2 = { lines: [{ x1: 4, y1: 14, x2: 24, y2: 14 }, ...] }; // 28x28
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct (consistent scale):**
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
const VIEWBOX_SIZE = 14;
|
|
22
|
+
const CENTER = 7;
|
|
23
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Shared Group for Rotational Variants
|
|
3
|
+
impact: HIGH
|
|
4
|
+
tags: morphing, group, rotation
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Shared Group for Rotational Variants
|
|
8
|
+
|
|
9
|
+
Icons that are rotational variants MUST share the same group and base lines.
|
|
10
|
+
|
|
11
|
+
**Incorrect (different line definitions):**
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const arrowRight = { lines: [{ x1: 2, y1: 7, x2: 12, y2: 7 }, ...] };
|
|
15
|
+
const arrowDown = { lines: [{ x1: 7, y1: 2, x2: 7, y2: 12 }, ...] };
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct (shared base lines):**
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
const arrowLines: [IconLine, IconLine, IconLine] = [
|
|
22
|
+
{ x1: 2, y1: 7, x2: 12, y2: 7 },
|
|
23
|
+
{ x1: 7.5, y1: 2.5, x2: 12, y2: 7 },
|
|
24
|
+
{ x1: 7.5, y1: 11.5, x2: 12, y2: 7 },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const icons = {
|
|
28
|
+
"arrow-right": { lines: arrowLines, rotation: 0, group: "arrow" },
|
|
29
|
+
"arrow-down": { lines: arrowLines, rotation: 90, group: "arrow" },
|
|
30
|
+
"arrow-left": { lines: arrowLines, rotation: 180, group: "arrow" },
|
|
31
|
+
"arrow-up": { lines: arrowLines, rotation: -90, group: "arrow" },
|
|
32
|
+
};
|
|
33
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Instant Jump for Non-Grouped Icons
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: morphing, jump, group
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Instant Jump for Non-Grouped Icons
|
|
8
|
+
|
|
9
|
+
When transitioning between icons NOT in the same group, rotation should jump instantly.
|
|
10
|
+
|
|
11
|
+
**Incorrect (always animates rotation):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
rotation.set(definition.rotation ?? 0);
|
|
16
|
+
}, [definition]);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (jumps when not grouped):**
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (shouldRotate) {
|
|
24
|
+
rotation.set(definition.rotation ?? 0);
|
|
25
|
+
} else {
|
|
26
|
+
rotation.jump(definition.rotation ?? 0);
|
|
27
|
+
}
|
|
28
|
+
}, [definition, shouldRotate]);
|
|
29
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Reduced Motion Support for Icons
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: morphing, a11y, reduced-motion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Reduced Motion Support for Icons
|
|
8
|
+
|
|
9
|
+
Respect prefers-reduced-motion by disabling animations.
|
|
10
|
+
|
|
11
|
+
**Incorrect (always animates):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
function MorphingIcon({ icon }: Props) {
|
|
15
|
+
return <motion.line animate={...} transition={{ duration: 0.4 }} />;
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (respects preference):**
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
function MorphingIcon({ icon }: Props) {
|
|
23
|
+
const reducedMotion = useReducedMotion() ?? false;
|
|
24
|
+
const activeTransition = reducedMotion ? { duration: 0 } : transition;
|
|
25
|
+
|
|
26
|
+
return <motion.line animate={...} transition={activeTransition} />;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Spring Physics for Rotation
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: morphing, spring, rotation
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Spring Physics for Rotation
|
|
8
|
+
|
|
9
|
+
Rotation between grouped icons should use spring physics for natural motion.
|
|
10
|
+
|
|
11
|
+
**Incorrect (duration-based rotation):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<motion.g animate={{ rotate: rotation }} transition={{ duration: 0.3 }} />
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Correct (spring rotation):**
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
const rotation = useSpring(definition.rotation ?? 0, activeTransition);
|
|
21
|
+
|
|
22
|
+
<motion.g style={{ rotate: rotation, transformOrigin: "center" }} />
|
|
23
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Round Stroke Line Caps
|
|
3
|
+
impact: LOW
|
|
4
|
+
tags: morphing, stroke, svg
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Round Stroke Line Caps
|
|
8
|
+
|
|
9
|
+
Lines should use strokeLinecap="round" for polished endpoints.
|
|
10
|
+
|
|
11
|
+
**Incorrect (butt caps):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<motion.line strokeLinecap="butt" />
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Correct (round caps):**
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<motion.line strokeLinecap="round" />
|
|
21
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Icons Must Use Exactly Three Lines
|
|
3
|
+
impact: HIGH
|
|
4
|
+
tags: morphing, svg, structure
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Icons Must Use Exactly Three Lines
|
|
8
|
+
|
|
9
|
+
Every icon MUST use exactly 3 lines. No more, no fewer.
|
|
10
|
+
|
|
11
|
+
**Incorrect (only 2 lines):**
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const checkIcon = {
|
|
15
|
+
lines: [
|
|
16
|
+
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
|
|
17
|
+
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (3 lines with collapsed):**
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
const checkIcon = {
|
|
26
|
+
lines: [
|
|
27
|
+
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
|
|
28
|
+
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
|
|
29
|
+
collapsed,
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use Collapsed Constant for Unused Lines
|
|
3
|
+
impact: HIGH
|
|
4
|
+
tags: morphing, collapsed, structure
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Use Collapsed Constant for Unused Lines
|
|
8
|
+
|
|
9
|
+
Unused lines must use the collapsed constant, not omission or null.
|
|
10
|
+
|
|
11
|
+
**Incorrect (null for unused):**
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const minusIcon = {
|
|
15
|
+
lines: [
|
|
16
|
+
{ x1: 2, y1: 7, x2: 12, y2: 7 },
|
|
17
|
+
null,
|
|
18
|
+
null,
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (collapsed constant):**
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const minusIcon = {
|
|
27
|
+
lines: [
|
|
28
|
+
{ x1: 2, y1: 7, x2: 12, y2: 7 },
|
|
29
|
+
collapsed,
|
|
30
|
+
collapsed,
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use ::backdrop for Dialog Backgrounds
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: native, backdrop, dialog
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Use ::backdrop for Dialog Backgrounds
|
|
8
|
+
|
|
9
|
+
Use ::backdrop pseudo-element for dialog/popover backgrounds.
|
|
10
|
+
|
|
11
|
+
**Incorrect (extra overlay node):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<>
|
|
15
|
+
<div className={styles.overlay} onClick={close} />
|
|
16
|
+
<dialog className={styles.dialog}>{children}</dialog>
|
|
17
|
+
</>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (native ::backdrop):**
|
|
21
|
+
|
|
22
|
+
```css
|
|
23
|
+
dialog::backdrop {
|
|
24
|
+
background: var(--black-a6);
|
|
25
|
+
backdrop-filter: blur(4px);
|
|
26
|
+
}
|
|
27
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use ::placeholder for Input Styling
|
|
3
|
+
impact: LOW
|
|
4
|
+
tags: native, placeholder, input
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Use ::placeholder for Input Styling
|
|
8
|
+
|
|
9
|
+
Use ::placeholder for input placeholder styling, not wrapper elements.
|
|
10
|
+
|
|
11
|
+
**Incorrect (custom placeholder node):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<div className={styles.inputWrapper}>
|
|
15
|
+
{!value && <span className={styles.placeholder}>Enter text...</span>}
|
|
16
|
+
<input value={value} />
|
|
17
|
+
</div>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (native ::placeholder):**
|
|
21
|
+
|
|
22
|
+
```css
|
|
23
|
+
input::placeholder {
|
|
24
|
+
color: var(--gray-9);
|
|
25
|
+
opacity: 1;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use ::selection for Text Styling
|
|
3
|
+
impact: LOW
|
|
4
|
+
tags: native, selection, text
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Use ::selection for Text Styling
|
|
8
|
+
|
|
9
|
+
Use ::selection for text selection styling.
|
|
10
|
+
|
|
11
|
+
**Correct:**
|
|
12
|
+
|
|
13
|
+
```css
|
|
14
|
+
::selection {
|
|
15
|
+
background: var(--blue-a5);
|
|
16
|
+
color: var(--gray-12);
|
|
17
|
+
}
|
|
18
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Coordinated Parent-Child Exit Timing
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: nested, timing, exit
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Coordinated Parent-Child Exit Timing
|
|
8
|
+
|
|
9
|
+
Parent and child exit durations should be coordinated.
|
|
10
|
+
|
|
11
|
+
**Incorrect (parent too fast):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<motion.div exit={{ opacity: 0 }} transition={{ duration: 0.1 }}>
|
|
15
|
+
<motion.div exit={{ scale: 0 }} transition={{ duration: 0.5 }} />
|
|
16
|
+
</motion.div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (coordinated timing):**
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<motion.div exit={{ opacity: 0 }} transition={{ duration: 0.2 }}>
|
|
23
|
+
<motion.div exit={{ scale: 0 }} transition={{ duration: 0.15 }} />
|
|
24
|
+
</motion.div>
|
|
25
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Propagate Prop for Nested AnimatePresence
|
|
3
|
+
impact: HIGH
|
|
4
|
+
tags: nested, propagate, exit
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Propagate Prop for Nested AnimatePresence
|
|
8
|
+
|
|
9
|
+
Nested AnimatePresence must use propagate prop for coordinated exits.
|
|
10
|
+
|
|
11
|
+
**Incorrect (children vanish instantly):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<AnimatePresence>
|
|
15
|
+
{isOpen && (
|
|
16
|
+
<motion.div exit={{ opacity: 0 }}>
|
|
17
|
+
<AnimatePresence>
|
|
18
|
+
{items.map(item => (
|
|
19
|
+
<motion.div key={item.id} exit={{ scale: 0 }} />
|
|
20
|
+
))}
|
|
21
|
+
</AnimatePresence>
|
|
22
|
+
</motion.div>
|
|
23
|
+
)}
|
|
24
|
+
</AnimatePresence>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Correct (propagate on both):**
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
<AnimatePresence propagate>
|
|
31
|
+
{isOpen && (
|
|
32
|
+
<motion.div exit={{ opacity: 0 }}>
|
|
33
|
+
<AnimatePresence propagate>
|
|
34
|
+
{items.map(item => (
|
|
35
|
+
<motion.div key={item.id} exit={{ scale: 0 }} />
|
|
36
|
+
))}
|
|
37
|
+
</AnimatePresence>
|
|
38
|
+
</motion.div>
|
|
39
|
+
)}
|
|
40
|
+
</AnimatePresence>
|
|
41
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Entrance Animation for Context Menus
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
tags: none, context-menu, entrance
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## No Entrance Animation for Context Menus
|
|
8
|
+
|
|
9
|
+
Context menus should not animate on entrance (exit only).
|
|
10
|
+
|
|
11
|
+
**Incorrect (entrance animation):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<motion.div
|
|
15
|
+
initial={{ opacity: 0, scale: 0.95 }}
|
|
16
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
17
|
+
exit={{ opacity: 0 }}
|
|
18
|
+
/>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (exit only):**
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<motion.div exit={{ opacity: 0, scale: 0.95 }} />
|
|
25
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: No Animation for High-Frequency Interactions
|
|
3
|
+
impact: HIGH
|
|
4
|
+
tags: none, high-frequency, performance
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## No Animation for High-Frequency Interactions
|
|
8
|
+
|
|
9
|
+
High-frequency interactions should have no animation.
|
|
10
|
+
|
|
11
|
+
**Incorrect (animated on every keystroke):**
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
function SearchInput() {
|
|
15
|
+
return (
|
|
16
|
+
<motion.div animate={{ scale: [1, 1.02, 1] }}>
|
|
17
|
+
<input onChange={handleSearch} />
|
|
18
|
+
</motion.div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (no animation):**
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
function SearchInput() {
|
|
27
|
+
return <input onChange={handleSearch} />;
|
|
28
|
+
}
|
|
29
|
+
```
|