@tinkcarlos/skillora 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/.temp-skill-index.md +245 -0
- package/.claude/skills/SKILL.md +264 -0
- package/.claude/skills/api-scaffolding/SKILL.md +431 -0
- package/.claude/skills/api-scaffolding/agents/backend-architect.md +282 -0
- package/.claude/skills/api-scaffolding/agents/django-pro.md +144 -0
- package/.claude/skills/api-scaffolding/agents/fastapi-pro.md +156 -0
- package/.claude/skills/api-scaffolding/agents/graphql-architect.md +146 -0
- package/.claude/skills/api-scaffolding/skills/fastapi-templates/SKILL.md +171 -0
- package/.claude/skills/api-testing-observability/SKILL.md +583 -0
- package/.claude/skills/api-testing-observability/agents/api-documenter.md +146 -0
- package/.claude/skills/api-testing-observability/commands/api-mock.md +1320 -0
- package/.claude/skills/brainstorming/SKILL.md +283 -0
- package/.claude/skills/bug-fixing/SKILL.md +382 -0
- package/.claude/skills/bug-fixing/references/backend-guide.md +132 -0
- package/.claude/skills/bug-fixing/references/bug-guide.md +354 -0
- package/.claude/skills/bug-fixing/references/bug-record-template.md +134 -0
- package/.claude/skills/bug-fixing/references/bug-records.md +88 -0
- package/.claude/skills/bug-fixing/references/code-review-gate.md +81 -0
- package/.claude/skills/bug-fixing/references/common-bugs.md +140 -0
- package/.claude/skills/bug-fixing/references/complete-workflow.md +361 -0
- package/.claude/skills/bug-fixing/references/config-driven-fixes.md +136 -0
- package/.claude/skills/bug-fixing/references/context-isolation-protocol.md +268 -0
- package/.claude/skills/bug-fixing/references/cross-surface-regression.md +120 -0
- package/.claude/skills/bug-fixing/references/database-investigation.md +129 -0
- package/.claude/skills/bug-fixing/references/dependency-and-integrity-protocol.md +369 -0
- package/.claude/skills/bug-fixing/references/fix-completeness-checklist.md +239 -0
- package/.claude/skills/bug-fixing/references/frontend-guide.md +219 -0
- package/.claude/skills/bug-fixing/references/fullstack-joint-guide.md +123 -0
- package/.claude/skills/bug-fixing/references/functional-breakage.md +117 -0
- package/.claude/skills/bug-fixing/references/ide-lint-errors-guide.md +176 -0
- package/.claude/skills/bug-fixing/references/impact-analysis.md +511 -0
- package/.claude/skills/bug-fixing/references/investigation-checklist.md +263 -0
- package/.claude/skills/bug-fixing/references/knowledge-extraction-guide.md +531 -0
- package/.claude/skills/bug-fixing/references/knowledge-workflow.md +212 -0
- package/.claude/skills/bug-fixing/references/post-edit-quality-gate.md +30 -0
- package/.claude/skills/bug-fixing/references/python-env-and-testing.md +126 -0
- package/.claude/skills/bug-fixing/references/rca-guide.md +428 -0
- package/.claude/skills/bug-fixing/references/similar-bug-patterns.md +113 -0
- package/.claude/skills/bug-fixing/references/skill-delegation-guide.md +350 -0
- package/.claude/skills/bug-fixing/references/skill-orchestration.md +155 -0
- package/.claude/skills/bug-fixing/references/testing-strategy.md +350 -0
- package/.claude/skills/bug-fixing/references/tooling-build-scripts.md +162 -0
- package/.claude/skills/bug-fixing/references/user-input-validation.md +77 -0
- package/.claude/skills/bug-fixing/references/ux-patterns.md +158 -0
- package/.claude/skills/bug-fixing/references/windows-terminal-hygiene.md +106 -0
- package/.claude/skills/bug-fixing/references/zero-regression-matrix.md +239 -0
- package/.claude/skills/bug-fixing/references/zero-risk-protocol.md +102 -0
- package/.claude/skills/bug-fixing/scripts/format_code.py +611 -0
- package/.claude/skills/bug-fixing/scripts/generate_report_template.py +74 -0
- package/.claude/skills/bug-fixing/scripts/lint_check.py +816 -0
- package/.claude/skills/bug-fixing/scripts/requirements.txt +36 -0
- package/.claude/skills/cicd-pipeline/SKILL.md +300 -0
- package/.claude/skills/code-review/SKILL.md +535 -0
- package/.claude/skills/code-review/references/anti-pattern-scan.md +102 -0
- package/.claude/skills/code-review/references/automated-analysis.md +456 -0
- package/.claude/skills/code-review/references/backend-common-issues.md +589 -0
- package/.claude/skills/code-review/references/backend-expert-guide.md +415 -0
- package/.claude/skills/code-review/references/backend-review.md +868 -0
- package/.claude/skills/code-review/references/batch-processing-strategy.md +198 -0
- package/.claude/skills/code-review/references/call-chain-analysis-protocol.md +166 -0
- package/.claude/skills/code-review/references/common-patterns.md +321 -0
- package/.claude/skills/code-review/references/configuration-review.md +425 -0
- package/.claude/skills/code-review/references/control-flow-completeness.md +114 -0
- package/.claude/skills/code-review/references/database-review.md +298 -0
- package/.claude/skills/code-review/references/dependency-and-integrity-protocol.md +313 -0
- package/.claude/skills/code-review/references/external-standards.md +51 -0
- package/.claude/skills/code-review/references/feature-review.md +329 -0
- package/.claude/skills/code-review/references/file-review-template.md +326 -0
- package/.claude/skills/code-review/references/frontend-advanced.md +654 -0
- package/.claude/skills/code-review/references/frontend-common-issues.md +482 -0
- package/.claude/skills/code-review/references/frontend-expert-guide.md +342 -0
- package/.claude/skills/code-review/references/frontend-review.md +783 -0
- package/.claude/skills/code-review/references/fullstack-consistency.md +418 -0
- package/.claude/skills/code-review/references/fullstack-review.md +477 -0
- package/.claude/skills/code-review/references/functional-completeness.md +386 -0
- package/.claude/skills/code-review/references/hidden-bugs-detection.md +473 -0
- package/.claude/skills/code-review/references/ide-lint-errors-guide.md +173 -0
- package/.claude/skills/code-review/references/infrastructure-review.md +453 -0
- package/.claude/skills/code-review/references/iteration-review.md +264 -0
- package/.claude/skills/code-review/references/job-review.md +335 -0
- package/.claude/skills/code-review/references/layered-checklist-protocol.md +157 -0
- package/.claude/skills/code-review/references/logic-completeness.md +535 -0
- package/.claude/skills/code-review/references/mandatory-checklist.md +288 -0
- package/.claude/skills/code-review/references/multi-language-guide.md +800 -0
- package/.claude/skills/code-review/references/new-project-review.md +226 -0
- package/.claude/skills/code-review/references/non-code-files-review.md +451 -0
- package/.claude/skills/code-review/references/overlooked-issues.md +657 -0
- package/.claude/skills/code-review/references/platform-specific-review.md +195 -0
- package/.claude/skills/code-review/references/precision-analysis-protocol.md +260 -0
- package/.claude/skills/code-review/references/python-patterns.md +494 -0
- package/.claude/skills/code-review/references/rca-techniques.md +362 -0
- package/.claude/skills/code-review/references/report-template.md +430 -0
- package/.claude/skills/code-review/references/resource-limits-and-degradation.md +137 -0
- package/.claude/skills/code-review/references/review-dimensions.md +311 -0
- package/.claude/skills/code-review/references/review-guide.md +202 -0
- package/.claude/skills/code-review/references/review-knowledge-workflow.md +257 -0
- package/.claude/skills/code-review/references/review-progress-tracker-protocol.md +172 -0
- package/.claude/skills/code-review/references/review-record-template.md +195 -0
- package/.claude/skills/code-review/references/skill-orchestration.md +143 -0
- package/.claude/skills/code-review/references/ui-ux-review.md +470 -0
- package/.claude/skills/containerization/SKILL.md +313 -0
- package/.claude/skills/database-migrations/agents/database-admin.md +142 -0
- package/.claude/skills/database-migrations/agents/database-optimizer.md +144 -0
- package/.claude/skills/database-migrations/commands/migration-observability.md +408 -0
- package/.claude/skills/database-migrations/commands/sql-migrations.md +492 -0
- package/.claude/skills/finishing-a-development-branch/SKILL.md +319 -0
- package/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/.claude/skills/frontend-design/SKILL.md +587 -0
- package/.claude/skills/frontend-design/references/color-consistency.md +487 -0
- package/.claude/skills/frontend-design/references/color-palettes-full.md +657 -0
- package/.claude/skills/frontend-design/references/design-system-generator.md +285 -0
- package/.claude/skills/frontend-design/references/font-pairings-full.md +705 -0
- package/.claude/skills/frontend-design/references/industry-anti-patterns.md +281 -0
- package/.claude/skills/frontend-design/references/layout-anti-patterns.md +582 -0
- package/.claude/skills/frontend-design/references/motion-patterns.md +659 -0
- package/.claude/skills/frontend-design/references/pre-delivery-checklist.md +153 -0
- package/.claude/skills/frontend-design/references/responsive-design.md +555 -0
- package/.claude/skills/frontend-design/references/style-modification-rules.md +335 -0
- package/.claude/skills/frontend-design/references/ui-styles-full.md +383 -0
- package/.claude/skills/frontend-design/references/ui-styles-rating.md +191 -0
- package/.claude/skills/frontend-design/references/ux-guidelines.md +640 -0
- package/.claude/skills/fullstack-developer/SKILL.md +512 -0
- package/.claude/skills/fullstack-developer/references/api-contract-guide.md +312 -0
- package/.claude/skills/fullstack-developer/references/api-response-patterns.md +223 -0
- package/.claude/skills/fullstack-developer/references/async-patterns.md +220 -0
- package/.claude/skills/fullstack-developer/references/bug-prevention.md +914 -0
- package/.claude/skills/fullstack-developer/references/code-quality-checklist.md +271 -0
- package/.claude/skills/fullstack-developer/references/complete-development-workflow.md +278 -0
- package/.claude/skills/fullstack-developer/references/context-isolation-protocol.md +256 -0
- package/.claude/skills/fullstack-developer/references/database-migration.md +331 -0
- package/.claude/skills/fullstack-developer/references/dependency-and-integrity-protocol.md +390 -0
- package/.claude/skills/fullstack-developer/references/development-phases.md +333 -0
- package/.claude/skills/fullstack-developer/references/expert-guide.md +214 -0
- package/.claude/skills/fullstack-developer/references/file-import-patterns.md +114 -0
- package/.claude/skills/fullstack-developer/references/graceful-degradation-patterns.md +78 -0
- package/.claude/skills/fullstack-developer/references/ide-lint-errors-guide.md +183 -0
- package/.claude/skills/fullstack-developer/references/integration-testing.md +301 -0
- package/.claude/skills/fullstack-developer/references/mock-api-patterns.md +307 -0
- package/.claude/skills/fullstack-developer/references/phase-gate-template.md +249 -0
- package/.claude/skills/fullstack-developer/references/post-edit-quality-gate.md +30 -0
- package/.claude/skills/fullstack-developer/references/python-engineering.md +79 -0
- package/.claude/skills/fullstack-developer/references/skill-orchestration.md +214 -0
- package/.claude/skills/fullstack-developer/references/skill-router-table.md +304 -0
- package/.claude/skills/fullstack-developer/references/state-sync.md +217 -0
- package/.claude/skills/fullstack-developer/references/ui-testing-checklist.md +292 -0
- package/.claude/skills/fullstack-developer/scripts/format_code.py +611 -0
- package/.claude/skills/fullstack-developer/scripts/lint_check.py +816 -0
- package/.claude/skills/fullstack-developer/scripts/requirements.txt +36 -0
- package/.claude/skills/performance-optimization/SKILL.md +250 -0
- package/.claude/skills/product-requirements/SKILL.md +357 -0
- package/.claude/skills/product-requirements/references/acceptance-criteria.md +335 -0
- package/.claude/skills/product-requirements/references/answer-first-questioning-protocol.md +299 -0
- package/.claude/skills/product-requirements/references/competitive-analysis-guide.md +183 -0
- package/.claude/skills/product-requirements/references/document-accuracy-protocol.md +253 -0
- package/.claude/skills/product-requirements/references/document-management-protocol.md +278 -0
- package/.claude/skills/product-requirements/references/external-standards.md +62 -0
- package/.claude/skills/product-requirements/references/feature-spec-template.md +359 -0
- package/.claude/skills/product-requirements/references/knowledge-acquisition-protocol.md +251 -0
- package/.claude/skills/product-requirements/references/plan-execution-protocol.md +334 -0
- package/.claude/skills/product-requirements/references/plan-generation-protocol.md +264 -0
- package/.claude/skills/product-requirements/references/prioritization-frameworks.md +80 -0
- package/.claude/skills/product-requirements/references/requirement-decomposition-protocol.md +291 -0
- package/.claude/skills/product-requirements/references/user-story-examples.md +297 -0
- package/.claude/skills/product-requirements/references/workflow-templates.md +266 -0
- package/.claude/skills/react-best-practices/SKILL.md +198 -0
- package/.claude/skills/react-best-practices/references/advanced-patterns.md +94 -0
- package/.claude/skills/react-best-practices/references/bundle-optimization.md +182 -0
- package/.claude/skills/react-best-practices/references/client-data-fetching.md +112 -0
- package/.claude/skills/react-best-practices/references/complete-guide.md +2249 -0
- package/.claude/skills/react-best-practices/references/eliminating-waterfalls.md +169 -0
- package/.claude/skills/react-best-practices/references/javascript-performance.md +256 -0
- package/.claude/skills/react-best-practices/references/rendering-performance.md +230 -0
- package/.claude/skills/react-best-practices/references/rerender-optimization.md +214 -0
- package/.claude/skills/react-best-practices/references/server-performance.md +182 -0
- package/.claude/skills/security-audit/SKILL.md +226 -0
- package/.claude/skills/shared-references/advanced-debugging-techniques.md +186 -0
- package/.claude/skills/shared-references/code-quality-checklist.md +218 -0
- package/.claude/skills/shared-references/code-review-efficiency-guide.md +125 -0
- package/.claude/skills/shared-references/mcp-dependency-compatibility-protocol.md +276 -0
- package/.claude/skills/shared-references/skill-call-graph.md +230 -0
- package/.claude/skills/shared-references/skill-orchestration-protocol.md +281 -0
- package/.claude/skills/shared-references/subagent-dispatch-templates.md +199 -0
- package/.claude/skills/skill-expert-skills/LICENSE.txt +204 -0
- package/.claude/skills/skill-expert-skills/QUICK_NAVIGATION.md +374 -0
- package/.claude/skills/skill-expert-skills/SKILL.md +247 -0
- package/.claude/skills/skill-expert-skills/docs/_index.md +91 -0
- package/.claude/skills/skill-expert-skills/references/deep-research-methodology.md +389 -0
- package/.claude/skills/skill-expert-skills/references/docs-generation-workflow.md +398 -0
- package/.claude/skills/skill-expert-skills/references/domain-expertise-protocol.md +343 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/_index.md +54 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/backend-expertise.md +517 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/bug-fixing-expertise.md +363 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/code-review-expertise.md +392 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/frontend-expertise.md +410 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge-template.md +503 -0
- package/.claude/skills/skill-expert-skills/references/examples.md +782 -0
- package/.claude/skills/skill-expert-skills/references/integration-examples.md +655 -0
- package/.claude/skills/skill-expert-skills/references/knowledge-validation-checklist.md +246 -0
- package/.claude/skills/skill-expert-skills/references/latest-knowledge-acquisition.md +461 -0
- package/.claude/skills/skill-expert-skills/references/mcp-tools-guide.md +439 -0
- package/.claude/skills/skill-expert-skills/references/official-best-practices.md +616 -0
- package/.claude/skills/skill-expert-skills/references/patterns.md +218 -0
- package/.claude/skills/skill-expert-skills/references/plugin-skills-guide.md +432 -0
- package/.claude/skills/skill-expert-skills/references/requirement-elicitation-protocol.md +290 -0
- package/.claude/skills/skill-expert-skills/references/skill-creator-SKILL.md +353 -0
- package/.claude/skills/skill-expert-skills/references/skill-templates.md +583 -0
- package/.claude/skills/skill-expert-skills/references/skills-knowledge-base.md +561 -0
- package/.claude/skills/skill-expert-skills/references/tools-guide.md +379 -0
- package/.claude/skills/skill-expert-skills/references/troubleshooting.md +378 -0
- package/.claude/skills/skill-expert-skills/references/universality-guide.md +205 -0
- package/.claude/skills/skill-expert-skills/references/writing-style-guide.md +466 -0
- package/.claude/skills/skill-expert-skills/scripts/__pycache__/quick_validate.cpython-313.pyc +0 -0
- package/.claude/skills/skill-expert-skills/scripts/__pycache__/universal_validate.cpython-313.pyc +0 -0
- package/.claude/skills/skill-expert-skills/scripts/analyze_trigger.py +425 -0
- package/.claude/skills/skill-expert-skills/scripts/diff_with_official.py +188 -0
- package/.claude/skills/skill-expert-skills/scripts/init_skill.py +349 -0
- package/.claude/skills/skill-expert-skills/scripts/package_skill.py +156 -0
- package/.claude/skills/skill-expert-skills/scripts/quick_validate.py +493 -0
- package/.claude/skills/skill-expert-skills/scripts/requirements.txt +2 -0
- package/.claude/skills/skill-expert-skills/scripts/universal_validate.py +182 -0
- package/.claude/skills/skill-expert-skills/scripts/upgrade_skill.py +431 -0
- package/.claude/skills/subagent-driven-development/SKILL.md +268 -0
- package/.claude/skills/test-driven-development/SKILL.md +246 -0
- package/.claude/skills/test-driven-development/references/testing-anti-patterns.md +192 -0
- package/.claude/skills/using-git-worktrees/SKILL.md +266 -0
- package/.claude/skills/using-skillstack/SKILL.md +127 -0
- package/.claude/skills/vercel-deploy/SKILL.md +166 -0
- package/.claude/skills/vercel-deploy/scripts/deploy.sh +249 -0
- package/.claude/skills/verification-before-completion/SKILL.md +305 -0
- package/.claude/skills/writing-plans/SKILL.md +259 -0
- package/README.md +69 -0
- package/bin/cli.js +468 -0
- package/lib/init.js +333 -0
- package/package.json +29 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
# Advanced Frontend Review Patterns
|
|
2
|
+
|
|
3
|
+
Deep dive into CSS, responsive design, browser compatibility, Vue.js, and advanced frontend concerns.
|
|
4
|
+
|
|
5
|
+
## CSS Issues
|
|
6
|
+
|
|
7
|
+
### Specificity Problems
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
/* 🚫 Over-specific selectors */
|
|
11
|
+
div#main .container ul li a.nav-link { color: blue; }
|
|
12
|
+
/* Hard to override without !important */
|
|
13
|
+
|
|
14
|
+
/* ✅ Flat, composable classes */
|
|
15
|
+
.nav-link { color: blue; }
|
|
16
|
+
.nav-link--active { color: red; }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```css
|
|
20
|
+
/* 🚫 !important abuse */
|
|
21
|
+
.button {
|
|
22
|
+
background: blue !important;
|
|
23
|
+
color: white !important;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* ✅ Proper cascade management */
|
|
27
|
+
.button { background: blue; color: white; }
|
|
28
|
+
.button.button--danger { background: red; }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Z-Index Chaos
|
|
32
|
+
|
|
33
|
+
```css
|
|
34
|
+
/* 🚫 Magic z-index values */
|
|
35
|
+
.modal { z-index: 9999; }
|
|
36
|
+
.tooltip { z-index: 99999; }
|
|
37
|
+
.dropdown { z-index: 999999; } /* Race to infinity */
|
|
38
|
+
|
|
39
|
+
/* ✅ Z-index scale system */
|
|
40
|
+
:root {
|
|
41
|
+
--z-dropdown: 100;
|
|
42
|
+
--z-sticky: 200;
|
|
43
|
+
--z-modal: 300;
|
|
44
|
+
--z-tooltip: 400;
|
|
45
|
+
--z-notification: 500;
|
|
46
|
+
}
|
|
47
|
+
.modal { z-index: var(--z-modal); }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Layout Issues
|
|
51
|
+
|
|
52
|
+
```css
|
|
53
|
+
/* 🚫 Fixed heights breaking content */
|
|
54
|
+
.card { height: 200px; } /* Text overflow on long content */
|
|
55
|
+
|
|
56
|
+
/* ✅ Min-height for flexibility */
|
|
57
|
+
.card { min-height: 200px; }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```css
|
|
61
|
+
/* 🚫 Horizontal scroll from 100vw */
|
|
62
|
+
.container { width: 100vw; } /* Includes scrollbar! */
|
|
63
|
+
|
|
64
|
+
/* ✅ Use 100% of parent */
|
|
65
|
+
.container { width: 100%; }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```css
|
|
69
|
+
/* 🚫 Flexbox without flex-wrap */
|
|
70
|
+
.row { display: flex; }
|
|
71
|
+
/* Items shrink to 0 or overflow */
|
|
72
|
+
|
|
73
|
+
/* ✅ Allow wrapping */
|
|
74
|
+
.row { display: flex; flex-wrap: wrap; }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### CSS Grid Issues
|
|
78
|
+
|
|
79
|
+
```css
|
|
80
|
+
/* 🚫 Fixed columns on mobile */
|
|
81
|
+
.grid { display: grid; grid-template-columns: repeat(4, 1fr); }
|
|
82
|
+
|
|
83
|
+
/* ✅ Responsive auto-fit */
|
|
84
|
+
.grid {
|
|
85
|
+
display: grid;
|
|
86
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
87
|
+
gap: 1rem;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Responsive Design
|
|
92
|
+
|
|
93
|
+
### Breakpoint Anti-Patterns
|
|
94
|
+
|
|
95
|
+
```css
|
|
96
|
+
/* 🚫 Device-specific breakpoints */
|
|
97
|
+
@media (width: 375px) { } /* Only iPhone SE? */
|
|
98
|
+
@media (width: 414px) { } /* Only iPhone Plus? */
|
|
99
|
+
|
|
100
|
+
/* ✅ Content-based breakpoints */
|
|
101
|
+
@media (min-width: 40rem) { } /* When content needs more space */
|
|
102
|
+
@media (min-width: 60rem) { }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```css
|
|
106
|
+
/* 🚫 Desktop-first (harder to maintain) */
|
|
107
|
+
.sidebar { width: 300px; }
|
|
108
|
+
@media (max-width: 768px) { .sidebar { display: none; } }
|
|
109
|
+
|
|
110
|
+
/* ✅ Mobile-first */
|
|
111
|
+
.sidebar { display: none; }
|
|
112
|
+
@media (min-width: 768px) { .sidebar { width: 300px; display: block; } }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Touch Target Issues
|
|
116
|
+
|
|
117
|
+
```css
|
|
118
|
+
/* 🚫 Tiny touch targets */
|
|
119
|
+
.icon-button { width: 24px; height: 24px; }
|
|
120
|
+
|
|
121
|
+
/* ✅ Minimum 44x44px for touch */
|
|
122
|
+
.icon-button {
|
|
123
|
+
width: 44px;
|
|
124
|
+
height: 44px;
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
justify-content: center;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Viewport Issues
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<!-- 🚫 Missing viewport meta -->
|
|
135
|
+
<head><title>App</title></head>
|
|
136
|
+
|
|
137
|
+
<!-- ✅ Proper viewport -->
|
|
138
|
+
<head>
|
|
139
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
140
|
+
<title>App</title>
|
|
141
|
+
</head>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```css
|
|
145
|
+
/* 🚫 100vh on mobile (broken by address bar) */
|
|
146
|
+
.fullscreen { height: 100vh; }
|
|
147
|
+
|
|
148
|
+
/* ✅ Use svh/dvh or JS fallback */
|
|
149
|
+
.fullscreen { height: 100svh; }
|
|
150
|
+
/* Or with CSS custom property set by JS */
|
|
151
|
+
.fullscreen { height: var(--vh, 100vh); }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Browser Compatibility
|
|
155
|
+
|
|
156
|
+
### Feature Detection
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
// 🚫 Browser sniffing
|
|
160
|
+
if (navigator.userAgent.includes('Safari')) {
|
|
161
|
+
// Fragile and often wrong
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ✅ Feature detection
|
|
165
|
+
if ('IntersectionObserver' in window) {
|
|
166
|
+
// Use IntersectionObserver
|
|
167
|
+
} else {
|
|
168
|
+
// Polyfill or fallback
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### CSS Feature Queries
|
|
173
|
+
|
|
174
|
+
```css
|
|
175
|
+
/* ✅ Progressive enhancement */
|
|
176
|
+
.container { display: flex; }
|
|
177
|
+
|
|
178
|
+
@supports (display: grid) {
|
|
179
|
+
.container { display: grid; }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@supports (aspect-ratio: 1) {
|
|
183
|
+
.video { aspect-ratio: 16/9; }
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Common Compatibility Issues
|
|
188
|
+
|
|
189
|
+
```css
|
|
190
|
+
/* 🚫 gap in flexbox (older Safari) */
|
|
191
|
+
.flex { display: flex; gap: 1rem; }
|
|
192
|
+
|
|
193
|
+
/* ✅ Fallback for older browsers */
|
|
194
|
+
.flex { display: flex; }
|
|
195
|
+
.flex > * + * { margin-left: 1rem; }
|
|
196
|
+
@supports (gap: 1rem) {
|
|
197
|
+
.flex { gap: 1rem; }
|
|
198
|
+
.flex > * + * { margin-left: 0; }
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Vue.js Patterns
|
|
203
|
+
|
|
204
|
+
### Reactivity Gotchas
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
// 🚫 Adding new properties (Vue 2)
|
|
208
|
+
this.user.newProp = 'value'; // Not reactive!
|
|
209
|
+
|
|
210
|
+
// ✅ Vue 2: Use $set
|
|
211
|
+
this.$set(this.user, 'newProp', 'value');
|
|
212
|
+
|
|
213
|
+
// ✅ Vue 3: Refs are deeply reactive
|
|
214
|
+
const user = ref({ name: 'John' });
|
|
215
|
+
user.value.newProp = 'value'; // Reactive
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
// 🚫 Destructuring reactive objects
|
|
220
|
+
const { count } = reactive({ count: 0 });
|
|
221
|
+
// count is now a plain number, not reactive
|
|
222
|
+
|
|
223
|
+
// ✅ Use toRefs
|
|
224
|
+
const state = reactive({ count: 0 });
|
|
225
|
+
const { count } = toRefs(state);
|
|
226
|
+
// count.value is reactive
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Composition API Issues
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
// 🚫 Forgetting to return from setup
|
|
233
|
+
setup() {
|
|
234
|
+
const count = ref(0);
|
|
235
|
+
function increment() { count.value++ }
|
|
236
|
+
// Missing return!
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ✅ Return or use <script setup>
|
|
240
|
+
setup() {
|
|
241
|
+
const count = ref(0);
|
|
242
|
+
const increment = () => count.value++;
|
|
243
|
+
return { count, increment };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ✅ Or <script setup> (auto-exposed)
|
|
247
|
+
<script setup>
|
|
248
|
+
const count = ref(0);
|
|
249
|
+
const increment = () => count.value++;
|
|
250
|
+
</script>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Performance
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
// 🚫 Heavy computation in template
|
|
257
|
+
<template>
|
|
258
|
+
<div v-for="item in items.filter(x => x.active).sort((a,b) => a.date - b.date)">
|
|
259
|
+
|
|
260
|
+
// ✅ Use computed
|
|
261
|
+
const sortedActiveItems = computed(() =>
|
|
262
|
+
items.value.filter(x => x.active).sort((a,b) => a.date - b.date)
|
|
263
|
+
);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
// 🚫 v-if with v-for
|
|
268
|
+
<li v-for="user in users" v-if="user.active">
|
|
269
|
+
|
|
270
|
+
// ✅ Computed filter or template wrapper
|
|
271
|
+
<li v-for="user in activeUsers">
|
|
272
|
+
// OR
|
|
273
|
+
<template v-for="user in users">
|
|
274
|
+
<li v-if="user.active">
|
|
275
|
+
</template>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Form Handling
|
|
279
|
+
|
|
280
|
+
### Validation Issues
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// 🚫 Only client-side validation
|
|
284
|
+
const handleSubmit = () => {
|
|
285
|
+
if (isValidEmail(email)) {
|
|
286
|
+
api.createUser({ email });
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// ✅ Client + Server validation
|
|
291
|
+
const handleSubmit = async () => {
|
|
292
|
+
if (!isValidEmail(email)) {
|
|
293
|
+
setError('email', 'Invalid email');
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
await api.createUser({ email });
|
|
298
|
+
} catch (e) {
|
|
299
|
+
if (e.code === 'EMAIL_EXISTS') {
|
|
300
|
+
setError('email', 'Email already registered');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Input Handling
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// 🚫 Uncontrolled to controlled switch
|
|
310
|
+
const [value, setValue] = useState(); // undefined initially
|
|
311
|
+
<input value={value} onChange={e => setValue(e.target.value)} />
|
|
312
|
+
// Warning: uncontrolled to controlled
|
|
313
|
+
|
|
314
|
+
// ✅ Initialize with empty string
|
|
315
|
+
const [value, setValue] = useState('');
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// 🚫 No debounce on search
|
|
320
|
+
<input onChange={e => searchApi(e.target.value)} />
|
|
321
|
+
// API call on every keystroke
|
|
322
|
+
|
|
323
|
+
// ✅ Debounced search
|
|
324
|
+
const debouncedSearch = useMemo(
|
|
325
|
+
() => debounce(searchApi, 300),
|
|
326
|
+
[]
|
|
327
|
+
);
|
|
328
|
+
<input onChange={e => debouncedSearch(e.target.value)} />
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Bundle Size
|
|
332
|
+
|
|
333
|
+
### Import Analysis
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// 🚫 Importing entire library
|
|
337
|
+
import _ from 'lodash';
|
|
338
|
+
_.debounce(fn, 300);
|
|
339
|
+
// Bundles entire lodash (~70KB)
|
|
340
|
+
|
|
341
|
+
// ✅ Cherry-pick imports
|
|
342
|
+
import debounce from 'lodash/debounce';
|
|
343
|
+
debounce(fn, 300);
|
|
344
|
+
// Only ~2KB
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
// 🚫 Importing heavy components directly
|
|
349
|
+
import { DataGrid } from 'heavy-ui-library';
|
|
350
|
+
|
|
351
|
+
// ✅ Dynamic import for heavy components
|
|
352
|
+
const DataGrid = lazy(() => import('heavy-ui-library/DataGrid'));
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Tree Shaking Issues
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// 🚫 Re-exporting everything (breaks tree shaking)
|
|
359
|
+
// utils/index.ts
|
|
360
|
+
export * from './string';
|
|
361
|
+
export * from './date';
|
|
362
|
+
export * from './number';
|
|
363
|
+
|
|
364
|
+
// ✅ Explicit exports
|
|
365
|
+
// utils/index.ts
|
|
366
|
+
export { formatDate } from './date';
|
|
367
|
+
export { capitalize } from './string';
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Accessibility (a11y) Deep Dive
|
|
371
|
+
|
|
372
|
+
### Focus Management
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// 🚫 No focus management in modal
|
|
376
|
+
function Modal({ isOpen }) {
|
|
377
|
+
return isOpen ? <div className="modal">...</div> : null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ✅ Trap focus and restore on close
|
|
381
|
+
function Modal({ isOpen, onClose }) {
|
|
382
|
+
const modalRef = useRef();
|
|
383
|
+
const previousFocus = useRef();
|
|
384
|
+
|
|
385
|
+
useEffect(() => {
|
|
386
|
+
if (isOpen) {
|
|
387
|
+
previousFocus.current = document.activeElement;
|
|
388
|
+
modalRef.current?.focus();
|
|
389
|
+
} else {
|
|
390
|
+
previousFocus.current?.focus();
|
|
391
|
+
}
|
|
392
|
+
}, [isOpen]);
|
|
393
|
+
|
|
394
|
+
return isOpen ? (
|
|
395
|
+
<FocusTrap>
|
|
396
|
+
<div ref={modalRef} role="dialog" aria-modal="true" tabIndex={-1}>
|
|
397
|
+
...
|
|
398
|
+
</div>
|
|
399
|
+
</FocusTrap>
|
|
400
|
+
) : null;
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Screen Reader Issues
|
|
405
|
+
|
|
406
|
+
```html
|
|
407
|
+
<!-- 🚫 Icon button without label -->
|
|
408
|
+
<button><svg>...</svg></button>
|
|
409
|
+
|
|
410
|
+
<!-- ✅ Accessible icon button -->
|
|
411
|
+
<button aria-label="Close dialog">
|
|
412
|
+
<svg aria-hidden="true">...</svg>
|
|
413
|
+
</button>
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
```html
|
|
417
|
+
<!-- 🚫 Image without alt -->
|
|
418
|
+
<img src="chart.png">
|
|
419
|
+
|
|
420
|
+
<!-- ✅ Descriptive alt -->
|
|
421
|
+
<img src="chart.png" alt="Sales increased 25% from Q1 to Q2">
|
|
422
|
+
|
|
423
|
+
<!-- ✅ Decorative image -->
|
|
424
|
+
<img src="decoration.png" alt="" role="presentation">
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### ARIA Mistakes
|
|
428
|
+
|
|
429
|
+
```html
|
|
430
|
+
<!-- 🚫 Redundant ARIA -->
|
|
431
|
+
<button role="button">Click</button>
|
|
432
|
+
<a href="/" role="link">Home</a>
|
|
433
|
+
|
|
434
|
+
<!-- ✅ Native semantics sufficient -->
|
|
435
|
+
<button>Click</button>
|
|
436
|
+
<a href="/">Home</a>
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
```html
|
|
440
|
+
<!-- 🚫 Div with click handler (not accessible) -->
|
|
441
|
+
<div onClick={handleClick}>Click me</div>
|
|
442
|
+
|
|
443
|
+
<!-- ✅ Use button or add proper ARIA -->
|
|
444
|
+
<button onClick={handleClick}>Click me</button>
|
|
445
|
+
<!-- Or if div required: -->
|
|
446
|
+
<div
|
|
447
|
+
role="button"
|
|
448
|
+
tabIndex={0}
|
|
449
|
+
onClick={handleClick}
|
|
450
|
+
onKeyDown={(e) => e.key === 'Enter' && handleClick()}
|
|
451
|
+
>Click me</div>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## SSR/SSG Issues
|
|
455
|
+
|
|
456
|
+
### Hydration Mismatches
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
// 🚫 Client-only values during render
|
|
460
|
+
function Component() {
|
|
461
|
+
return <div>Time: {new Date().toISOString()}</div>;
|
|
462
|
+
// Different on server vs client!
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ✅ useEffect for client-only values
|
|
466
|
+
function Component() {
|
|
467
|
+
const [time, setTime] = useState<string>();
|
|
468
|
+
useEffect(() => setTime(new Date().toISOString()), []);
|
|
469
|
+
return <div>Time: {time ?? 'Loading...'}</div>;
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
// 🚫 Accessing window during SSR
|
|
475
|
+
function Component() {
|
|
476
|
+
const width = window.innerWidth; // window undefined on server!
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ✅ Guard window access
|
|
480
|
+
function Component() {
|
|
481
|
+
const [width, setWidth] = useState(0);
|
|
482
|
+
useEffect(() => {
|
|
483
|
+
setWidth(window.innerWidth);
|
|
484
|
+
}, []);
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Data Fetching
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
// 🚫 Fetching in component (waterfall)
|
|
492
|
+
function Page() {
|
|
493
|
+
const [data, setData] = useState();
|
|
494
|
+
useEffect(() => {
|
|
495
|
+
fetchData().then(setData);
|
|
496
|
+
}, []);
|
|
497
|
+
// Renders blank, then fetches, then re-renders
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ✅ Server-side data fetching (Next.js example)
|
|
501
|
+
export async function getServerSideProps() {
|
|
502
|
+
const data = await fetchData();
|
|
503
|
+
return { props: { data } };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function Page({ data }) {
|
|
507
|
+
// Data available on first render
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## TypeScript/React Critical Bug Patterns
|
|
512
|
+
|
|
513
|
+
### Type Safety Violations
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
// 🚫 Using `any` defeats type safety
|
|
517
|
+
function processData(data: any) {
|
|
518
|
+
return data.value; // No type checking, runtime crash if value undefined
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ✅ Use proper types
|
|
522
|
+
interface DataPayload {
|
|
523
|
+
value: string;
|
|
524
|
+
count?: number;
|
|
525
|
+
}
|
|
526
|
+
function processData(data: DataPayload) {
|
|
527
|
+
return data.value; // Type-safe access
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// 🚫 Type assertion without validation
|
|
531
|
+
const user = JSON.parse(response) as User; // Could be anything!
|
|
532
|
+
|
|
533
|
+
// ✅ Runtime validation with type guard
|
|
534
|
+
function isUser(obj: unknown): obj is User {
|
|
535
|
+
return obj !== null &&
|
|
536
|
+
typeof obj === 'object' &&
|
|
537
|
+
'id' in obj &&
|
|
538
|
+
'name' in obj;
|
|
539
|
+
}
|
|
540
|
+
const parsed = JSON.parse(response);
|
|
541
|
+
if (!isUser(parsed)) throw new Error('Invalid user data');
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Props Mutation (Critical React Anti-Pattern)
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
// 🚫 Mutating props - breaks React's immutability contract
|
|
548
|
+
function UserProfile({ user }: Props) {
|
|
549
|
+
user.lastViewed = new Date(); // 💀 Direct prop mutation!
|
|
550
|
+
user.visits++; // 💀 Side effect in render
|
|
551
|
+
return <div>{user.name}</div>;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// ✅ Use callbacks to notify parent
|
|
555
|
+
function UserProfile({ user, onView }: Props) {
|
|
556
|
+
useEffect(() => {
|
|
557
|
+
onView(user.id, new Date()); // Parent handles state update
|
|
558
|
+
}, [user.id, onView]);
|
|
559
|
+
return <div>{user.name}</div>;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// 🚫 Mutating nested objects in state
|
|
563
|
+
const [user, setUser] = useState({ profile: { name: 'John' } });
|
|
564
|
+
user.profile.name = 'Jane'; // 💀 Mutation won't trigger re-render
|
|
565
|
+
setUser(user); // 💀 Same reference, no update
|
|
566
|
+
|
|
567
|
+
// ✅ Immutable update
|
|
568
|
+
setUser(prev => ({
|
|
569
|
+
...prev,
|
|
570
|
+
profile: { ...prev.profile, name: 'Jane' }
|
|
571
|
+
}));
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Async Error Handling Gaps
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
// 🚫 No error handling on fetch
|
|
578
|
+
async function fetchUser(id: string) {
|
|
579
|
+
const response = await fetch(`/api/users/${id}`);
|
|
580
|
+
return response.json(); // 💀 Crashes if network fails or 404
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ✅ Complete error handling
|
|
584
|
+
async function fetchUser(id: string): Promise<User> {
|
|
585
|
+
try {
|
|
586
|
+
const response = await fetch(`/api/users/${id}`);
|
|
587
|
+
if (!response.ok) {
|
|
588
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
589
|
+
}
|
|
590
|
+
return await response.json();
|
|
591
|
+
} catch (error) {
|
|
592
|
+
console.error('Failed to fetch user:', error);
|
|
593
|
+
throw error; // Re-throw for caller to handle
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// 🚫 Unhandled promise in event handler
|
|
598
|
+
function Button() {
|
|
599
|
+
const handleClick = () => {
|
|
600
|
+
submitForm(); // 💀 Promise ignored, errors swallowed
|
|
601
|
+
};
|
|
602
|
+
return <button onClick={handleClick}>Submit</button>;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ✅ Handle async in event handler
|
|
606
|
+
function Button() {
|
|
607
|
+
const [error, setError] = useState<Error | null>(null);
|
|
608
|
+
const handleClick = async () => {
|
|
609
|
+
try {
|
|
610
|
+
await submitForm();
|
|
611
|
+
} catch (e) {
|
|
612
|
+
setError(e as Error);
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
return <button onClick={handleClick}>Submit</button>;
|
|
616
|
+
}
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### useCallback/useMemo Pitfalls
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
// 🚫 Inline function creates new reference every render
|
|
623
|
+
<Button onClick={() => handleSubmit(formData)} />
|
|
624
|
+
// Child re-renders even if formData unchanged
|
|
625
|
+
|
|
626
|
+
// ✅ Memoize callback
|
|
627
|
+
const handleClick = useCallback(() => {
|
|
628
|
+
handleSubmit(formData);
|
|
629
|
+
}, [formData]);
|
|
630
|
+
<Button onClick={handleClick} />
|
|
631
|
+
|
|
632
|
+
// 🚫 Missing dependency in useCallback
|
|
633
|
+
const handleSave = useCallback(() => {
|
|
634
|
+
save(currentData); // 💀 Stale closure - uses old currentData
|
|
635
|
+
}, []); // Missing currentData dependency
|
|
636
|
+
|
|
637
|
+
// ✅ Include all dependencies
|
|
638
|
+
const handleSave = useCallback(() => {
|
|
639
|
+
save(currentData);
|
|
640
|
+
}, [currentData]);
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
**Detection commands**:
|
|
644
|
+
```bash
|
|
645
|
+
# Find `any` type usage
|
|
646
|
+
grep -rn ": any" --include="*.ts" --include="*.tsx"
|
|
647
|
+
|
|
648
|
+
# Find potential props mutation (assignment in function body)
|
|
649
|
+
grep -rn "props\.\w* =" --include="*.tsx"
|
|
650
|
+
|
|
651
|
+
# Find fetch without error check
|
|
652
|
+
grep -rn "await fetch" --include="*.ts" --include="*.tsx" -A 2 | grep -v "response.ok\|try\|catch"
|
|
653
|
+
```
|
|
654
|
+
|