@girardmedia/bootspring 3.3.2 → 3.4.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 (171) hide show
  1. package/assets/agents/accessibility-auditor.md +39 -0
  2. package/assets/agents/api-designer.md +40 -0
  3. package/assets/agents/auth-implementer.md +64 -0
  4. package/assets/agents/bug-hunter.md +42 -0
  5. package/assets/agents/bundle-analyzer.md +40 -0
  6. package/assets/agents/cache-optimizer.md +55 -0
  7. package/assets/agents/changelog-writer.md +55 -0
  8. package/assets/agents/ci-cd-builder.md +40 -0
  9. package/assets/agents/code-explainer.md +39 -0
  10. package/assets/agents/code-reviewer.md +39 -0
  11. package/assets/agents/cost-optimizer.md +57 -0
  12. package/assets/agents/cron-scheduler.md +51 -0
  13. package/assets/agents/data-seeder.md +56 -0
  14. package/assets/agents/database-architect.md +40 -0
  15. package/assets/agents/dependency-updater.md +40 -0
  16. package/assets/agents/deploy-checker.md +40 -0
  17. package/assets/agents/docker-optimizer.md +40 -0
  18. package/assets/agents/documentation-writer.md +40 -0
  19. package/assets/agents/email-builder.md +55 -0
  20. package/assets/agents/env-setup.md +40 -0
  21. package/assets/agents/error-handler.md +40 -0
  22. package/assets/agents/eslint-fixer.md +46 -0
  23. package/assets/agents/feature-flagger.md +69 -0
  24. package/assets/agents/git-detective.md +39 -0
  25. package/assets/agents/graphql-builder.md +60 -0
  26. package/assets/agents/incident-responder.md +59 -0
  27. package/assets/agents/log-analyzer.md +39 -0
  28. package/assets/agents/migration-planner.md +41 -0
  29. package/assets/agents/monorepo-navigator.md +39 -0
  30. package/assets/agents/nextjs-expert.md +57 -0
  31. package/assets/agents/notification-builder.md +56 -0
  32. package/assets/agents/onboarding-guide.md +39 -0
  33. package/assets/agents/performance-profiler.md +40 -0
  34. package/assets/agents/prisma-expert.md +57 -0
  35. package/assets/agents/rate-limiter.md +58 -0
  36. package/assets/agents/react-expert.md +58 -0
  37. package/assets/agents/refactorer.md +42 -0
  38. package/assets/agents/regex-builder.md +46 -0
  39. package/assets/agents/release-manager.md +40 -0
  40. package/assets/agents/s3-manager.md +58 -0
  41. package/assets/agents/schema-validator.md +40 -0
  42. package/assets/agents/search-builder.md +62 -0
  43. package/assets/agents/security-auditor.md +39 -0
  44. package/assets/agents/sitemap-generator.md +53 -0
  45. package/assets/agents/stripe-integrator.md +59 -0
  46. package/assets/agents/tailwind-expert.md +55 -0
  47. package/assets/agents/tech-debt-tracker.md +39 -0
  48. package/assets/agents/test-writer.md +42 -0
  49. package/assets/agents/type-fixer.md +45 -0
  50. package/assets/agents/webhook-builder.md +54 -0
  51. package/assets/rules/cpp.md +53 -0
  52. package/assets/rules/css.md +52 -0
  53. package/assets/rules/go.md +50 -0
  54. package/assets/rules/html.md +52 -0
  55. package/assets/rules/java.md +51 -0
  56. package/assets/rules/kotlin.md +50 -0
  57. package/assets/rules/php.md +51 -0
  58. package/assets/rules/python.md +51 -0
  59. package/assets/rules/ruby.md +51 -0
  60. package/assets/rules/rust.md +49 -0
  61. package/assets/rules/shell.md +52 -0
  62. package/assets/rules/sql.md +49 -0
  63. package/assets/rules/swift.md +50 -0
  64. package/assets/rules/typescript.md +52 -0
  65. package/assets/rules/yaml-json.md +51 -0
  66. package/assets/skills/accessibility.md +210 -0
  67. package/assets/skills/agent-patterns.md +387 -0
  68. package/assets/skills/ai-integration.md +263 -0
  69. package/assets/skills/animation-patterns.md +224 -0
  70. package/assets/skills/api-design.md +218 -0
  71. package/assets/skills/api-gateway.md +341 -0
  72. package/assets/skills/api-versioning.md +226 -0
  73. package/assets/skills/astro-patterns.md +233 -0
  74. package/assets/skills/auth-patterns.md +248 -0
  75. package/assets/skills/aws-patterns.md +171 -0
  76. package/assets/skills/background-jobs.md +162 -0
  77. package/assets/skills/browser-extensions.md +309 -0
  78. package/assets/skills/caching-patterns.md +253 -0
  79. package/assets/skills/ci-cd.md +251 -0
  80. package/assets/skills/cli-development.md +296 -0
  81. package/assets/skills/code-review.md +185 -0
  82. package/assets/skills/cron-patterns.md +327 -0
  83. package/assets/skills/data-fetching.md +231 -0
  84. package/assets/skills/database-migrations.md +346 -0
  85. package/assets/skills/database-patterns.md +219 -0
  86. package/assets/skills/debugging.md +281 -0
  87. package/assets/skills/design-system.md +289 -0
  88. package/assets/skills/django-patterns.md +182 -0
  89. package/assets/skills/docker-patterns.md +235 -0
  90. package/assets/skills/e2e-testing.md +287 -0
  91. package/assets/skills/edge-computing.md +268 -0
  92. package/assets/skills/electron-patterns.md +266 -0
  93. package/assets/skills/email-templates.md +206 -0
  94. package/assets/skills/error-handling.md +265 -0
  95. package/assets/skills/event-driven.md +232 -0
  96. package/assets/skills/express-patterns.md +239 -0
  97. package/assets/skills/fastapi-patterns.md +198 -0
  98. package/assets/skills/feature-flags.md +212 -0
  99. package/assets/skills/figma-to-code.md +298 -0
  100. package/assets/skills/file-upload.md +228 -0
  101. package/assets/skills/forms-patterns.md +264 -0
  102. package/assets/skills/gcp-patterns.md +189 -0
  103. package/assets/skills/git-workflow.md +187 -0
  104. package/assets/skills/golang-patterns.md +185 -0
  105. package/assets/skills/graphql-patterns.md +244 -0
  106. package/assets/skills/i18n-patterns.md +172 -0
  107. package/assets/skills/image-processing.md +350 -0
  108. package/assets/skills/java-springboot.md +226 -0
  109. package/assets/skills/kotlin-patterns.md +207 -0
  110. package/assets/skills/kubernetes-patterns.md +326 -0
  111. package/assets/skills/laravel-patterns.md +261 -0
  112. package/assets/skills/llm-fine-tuning.md +335 -0
  113. package/assets/skills/load-testing.md +303 -0
  114. package/assets/skills/logging-observability.md +228 -0
  115. package/assets/skills/markdown-processing.md +318 -0
  116. package/assets/skills/mcp-server-patterns.md +292 -0
  117. package/assets/skills/microservices.md +272 -0
  118. package/assets/skills/migration-patterns.md +239 -0
  119. package/assets/skills/mongodb-patterns.md +189 -0
  120. package/assets/skills/monorepo-patterns.md +287 -0
  121. package/assets/skills/nextjs-app-router.md +237 -0
  122. package/assets/skills/notification-patterns.md +348 -0
  123. package/assets/skills/oauth-patterns.md +246 -0
  124. package/assets/skills/payment-integration.md +222 -0
  125. package/assets/skills/pdf-generation.md +307 -0
  126. package/assets/skills/performance-optimization.md +277 -0
  127. package/assets/skills/php-patterns.md +210 -0
  128. package/assets/skills/prisma-patterns.md +241 -0
  129. package/assets/skills/prompt-engineering.md +193 -0
  130. package/assets/skills/pwa-patterns.md +247 -0
  131. package/assets/skills/python-patterns.md +158 -0
  132. package/assets/skills/python-testing.md +172 -0
  133. package/assets/skills/queue-patterns.md +295 -0
  134. package/assets/skills/rag-patterns.md +159 -0
  135. package/assets/skills/rate-limiting.md +319 -0
  136. package/assets/skills/react-components.md +201 -0
  137. package/assets/skills/react-native-patterns.md +299 -0
  138. package/assets/skills/real-time-patterns.md +181 -0
  139. package/assets/skills/redis-patterns.md +188 -0
  140. package/assets/skills/refactoring.md +218 -0
  141. package/assets/skills/regex-patterns.md +191 -0
  142. package/assets/skills/remix-patterns.md +262 -0
  143. package/assets/skills/responsive-design.md +199 -0
  144. package/assets/skills/ruby-rails-patterns.md +178 -0
  145. package/assets/skills/rust-patterns.md +211 -0
  146. package/assets/skills/search-patterns.md +227 -0
  147. package/assets/skills/security-hardening.md +237 -0
  148. package/assets/skills/seo-patterns.md +179 -0
  149. package/assets/skills/serverless-patterns.md +223 -0
  150. package/assets/skills/sql-optimization.md +154 -0
  151. package/assets/skills/state-management.md +254 -0
  152. package/assets/skills/storybook-patterns.md +330 -0
  153. package/assets/skills/svelte-patterns.md +258 -0
  154. package/assets/skills/swift-patterns.md +227 -0
  155. package/assets/skills/tailwind-patterns.md +272 -0
  156. package/assets/skills/tdd-workflow.md +199 -0
  157. package/assets/skills/terraform-patterns.md +270 -0
  158. package/assets/skills/testing-react.md +240 -0
  159. package/assets/skills/testing-vitest.md +232 -0
  160. package/assets/skills/typescript-strict.md +159 -0
  161. package/assets/skills/video-processing.md +340 -0
  162. package/assets/skills/vue-patterns.md +247 -0
  163. package/assets/skills/web-workers.md +327 -0
  164. package/assets/skills/webhooks-patterns.md +283 -0
  165. package/assets/skills/websocket-patterns.md +306 -0
  166. package/dist/cli/index.js +941 -958
  167. package/dist/core/index.d.ts +341 -11
  168. package/dist/core.js +58 -95
  169. package/dist/mcp/index.d.ts +33 -1
  170. package/dist/mcp-server.js +177 -255
  171. package/package.json +4 -1
@@ -0,0 +1,218 @@
1
+ ---
2
+ name: refactoring
3
+ description: Refactoring patterns including extract function, introduce parameter object, replace conditional with polymorphism, and strangler fig.
4
+ ---
5
+
6
+ # Refactoring Patterns
7
+
8
+ ## When to Use
9
+ Apply when code is correct but hard to understand, extend, or test. Refactor before adding new features to the area, after getting a feature working in TDD, or when code review reveals structural problems. Never refactor and change behavior in the same commit.
10
+
11
+ ## How It Works
12
+
13
+ ### Extract Function
14
+
15
+ Pull a block of code into a named function when it does one identifiable thing. The signal: you write a comment explaining what the next block does. The comment is the function name.
16
+
17
+ ```typescript
18
+ // Before -- one function doing three things
19
+ async function processOrder(order: Order) {
20
+ if (!order.items.length) throw new Error("Empty order");
21
+ if (order.items.some((i) => i.qty <= 0)) throw new Error("Invalid quantity");
22
+
23
+ let total = 0;
24
+ for (const item of order.items) {
25
+ total += item.price * item.qty;
26
+ if (item.taxable) total += item.price * item.qty * 0.08;
27
+ }
28
+
29
+ await db.orders.insert({ ...order, total, status: "pending" });
30
+ await emailService.send(order.email, "Order confirmed", `Total: $${total}`);
31
+ }
32
+
33
+ // After -- each concern is a named, testable function
34
+ const TAX_RATE = 0.08;
35
+
36
+ function validateOrder(order: Order): void {
37
+ if (!order.items.length) throw new Error("Empty order");
38
+ if (order.items.some((i) => i.qty <= 0)) throw new Error("Invalid quantity");
39
+ }
40
+
41
+ function calculateTotal(items: OrderItem[]): number {
42
+ return items.reduce((sum, item) => {
43
+ const subtotal = item.price * item.qty;
44
+ return sum + subtotal + (item.taxable ? subtotal * TAX_RATE : 0);
45
+ }, 0);
46
+ }
47
+
48
+ async function processOrder(order: Order) {
49
+ validateOrder(order);
50
+ const total = calculateTotal(order.items);
51
+ await db.orders.insert({ ...order, total, status: "pending" });
52
+ await emailService.send(order.email, "Order confirmed", `Total: $${total}`);
53
+ }
54
+ ```
55
+
56
+ ### Introduce Parameter Object
57
+
58
+ When three or more parameters travel together, group them:
59
+
60
+ ```typescript
61
+ // Before -- long parameter list
62
+ function searchProducts(
63
+ query: string, minPrice: number, maxPrice: number,
64
+ category: string, sortBy: string, page: number, pageSize: number
65
+ ) { /* ... */ }
66
+
67
+ // After -- cohesive parameter object
68
+ interface ProductSearchParams {
69
+ query: string;
70
+ priceRange: { min: number; max: number };
71
+ category: string;
72
+ sort: { field: string; direction: "asc" | "desc" };
73
+ pagination: { page: number; pageSize: number };
74
+ }
75
+
76
+ function searchProducts(params: ProductSearchParams) { /* ... */ }
77
+
78
+ // Call site is self-documenting
79
+ const results = searchProducts({
80
+ query: "widget",
81
+ priceRange: { min: 10, max: 100 },
82
+ category: "hardware",
83
+ sort: { field: "price", direction: "asc" },
84
+ pagination: { page: 1, pageSize: 20 },
85
+ });
86
+ ```
87
+
88
+ ### Replace Conditional with Polymorphism
89
+
90
+ When a switch/if-else chain selects behavior based on a type field, use a strategy map:
91
+
92
+ ```typescript
93
+ // Before -- switch grows with every new notification type
94
+ function sendNotification(notification: Notification) {
95
+ switch (notification.type) {
96
+ case "email": return sendEmail(notification.to, notification.body);
97
+ case "sms": return sendSms(notification.phone, notification.body);
98
+ case "push": return sendPush(notification.token, notification.body);
99
+ case "slack": return postSlack(notification.channel, notification.body);
100
+ }
101
+ }
102
+
103
+ // After -- each type handles itself
104
+ interface NotificationSender {
105
+ send(notification: Notification): Promise<void>;
106
+ }
107
+
108
+ const senders: Record<string, NotificationSender> = {
109
+ email: new EmailSender(),
110
+ sms: new SmsSender(),
111
+ push: new PushSender(),
112
+ slack: new SlackSender(),
113
+ };
114
+
115
+ function sendNotification(notification: Notification) {
116
+ const sender = senders[notification.type];
117
+ if (!sender) throw new Error(`Unknown type: ${notification.type}`);
118
+ return sender.send(notification);
119
+ }
120
+ ```
121
+
122
+ ### Strangler Fig Pattern
123
+
124
+ Migrate a legacy system incrementally by routing traffic through a facade:
125
+
126
+ ```typescript
127
+ // Phase 1: Facade routes to old system
128
+ class OrderFacade {
129
+ async createOrder(data: OrderData): Promise<Order> {
130
+ return this.legacyOrderService.create(data);
131
+ }
132
+ }
133
+
134
+ // Phase 2: Route some traffic to new system
135
+ class OrderFacade {
136
+ async createOrder(data: OrderData): Promise<Order> {
137
+ if (await this.featureFlags.isEnabled("new-order-service", data.userId)) {
138
+ return this.newOrderService.create(data);
139
+ }
140
+ return this.legacyOrderService.create(data);
141
+ }
142
+ }
143
+
144
+ // Phase 3: All traffic to new system, legacy removed
145
+ class OrderFacade {
146
+ async createOrder(data: OrderData): Promise<Order> {
147
+ return this.newOrderService.create(data);
148
+ }
149
+ }
150
+ ```
151
+
152
+ ### Simplify Boolean Logic with Guard Clauses
153
+
154
+ ```typescript
155
+ // Before -- nested conditions
156
+ function canUserPurchase(user: User, product: Product): boolean {
157
+ if (user.isActive) {
158
+ if (!user.isBanned) {
159
+ if (product.inStock) {
160
+ if (user.balance >= product.price) {
161
+ return true;
162
+ }
163
+ }
164
+ }
165
+ }
166
+ return false;
167
+ }
168
+
169
+ // After -- guard clauses with early returns
170
+ function canUserPurchase(user: User, product: Product): boolean {
171
+ if (!user.isActive) return false;
172
+ if (user.isBanned) return false;
173
+ if (!product.inStock) return false;
174
+ if (user.balance < product.price) return false;
175
+ return true;
176
+ }
177
+ ```
178
+
179
+ ### Replace Temp with Query
180
+
181
+ Eliminate intermediate variables that exist only to hold a computed value:
182
+
183
+ ```typescript
184
+ // Before -- temps obscure the logic
185
+ const basePrice = order.quantity * order.itemPrice;
186
+ const discount = Math.max(0, order.quantity - 500) * order.itemPrice * 0.05;
187
+ const shipping = Math.min(basePrice * 0.1, 100);
188
+ const total = basePrice - discount + shipping;
189
+
190
+ // After -- named, testable computed properties
191
+ class Order {
192
+ get basePrice(): number { return this.quantity * this.itemPrice; }
193
+ get discount(): number { return Math.max(0, this.quantity - 500) * this.itemPrice * 0.05; }
194
+ get shipping(): number { return Math.min(this.basePrice * 0.1, 100); }
195
+ get total(): number { return this.basePrice - this.discount + this.shipping; }
196
+ }
197
+ ```
198
+
199
+ ## Examples
200
+
201
+ | Code Smell | Refactoring | Benefit |
202
+ |-----------|-------------|---------|
203
+ | Function > 30 lines | Extract Function | Each piece testable and named |
204
+ | 5+ positional parameters | Introduce Parameter Object | Self-documenting call sites |
205
+ | switch on type field | Replace Conditional with Polymorphism | Open for extension |
206
+ | Nested if/else 4 levels deep | Guard clauses | Linear, readable flow |
207
+ | Legacy system rewrite | Strangler Fig | Incremental, zero-downtime migration |
208
+ | Same 3 lines copy-pasted | Extract Function + call from each site | Single source of truth |
209
+
210
+ ## Checklist
211
+ - [ ] Tests pass before and after every refactoring step
212
+ - [ ] Each commit is either a refactoring OR a behavior change, never both
213
+ - [ ] No function exceeds 30 lines (extract when it does)
214
+ - [ ] No parameter list exceeds 3 positional arguments
215
+ - [ ] Switch/if-else chains on type fields replaced with polymorphism
216
+ - [ ] Duplicated code extracted to a shared function or module
217
+ - [ ] Boolean conditions use guard clauses or named predicates
218
+ - [ ] Strangler fig pattern used for incremental legacy migration
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: regex-patterns
3
+ description: Write effective regex — lookahead/behind, named groups, common validation patterns, and performance pitfalls.
4
+ ---
5
+
6
+ # Regex Patterns
7
+
8
+ ## When to Use
9
+
10
+ Use regex for input validation, text extraction, search-and-replace, and log
11
+ parsing. This skill covers modern regex features (named groups, lookbehind,
12
+ Unicode properties), common real-world patterns (emails, URLs, dates), and
13
+ performance pitfalls that cause catastrophic backtracking. Apply when you need
14
+ structured pattern matching — but prefer dedicated parsers for HTML, JSON, or
15
+ complex grammars.
16
+
17
+ ## How It Works
18
+
19
+ ### 1. Named Groups — Readable Captures
20
+
21
+ ```typescript
22
+ // Named groups make extracted data self-documenting
23
+ const datePattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
24
+ const match = '2026-05-26'.match(datePattern);
25
+ if (match?.groups) {
26
+ const { year, month, day } = match.groups;
27
+ // year = "2026", month = "05", day = "26"
28
+ }
29
+
30
+ // Backreference by name
31
+ const duplicateWord = /\b(?<word>\w+)\s+\k<word>\b/gi;
32
+ 'the the quick brown fox'.replace(duplicateWord, '$<word>');
33
+ // "the quick brown fox"
34
+
35
+ // Named groups in replace
36
+ const isoDate = /(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/g;
37
+ 'Date: 2026-05-26'.replace(isoDate, '$<m>/$<d>/$<y>');
38
+ // "Date: 05/26/2026"
39
+ ```
40
+
41
+ ### 2. Lookahead and Lookbehind
42
+
43
+ Zero-width assertions — match a position, not characters.
44
+
45
+ ```typescript
46
+ // Positive lookahead: match "foo" only if followed by "bar"
47
+ /foo(?=bar)/ // matches "foo" in "foobar", not in "foobaz"
48
+
49
+ // Negative lookahead: match "foo" only if NOT followed by "bar"
50
+ /foo(?!bar)/ // matches "foo" in "foobaz", not in "foobar"
51
+
52
+ // Positive lookbehind: match "bar" only if preceded by "foo"
53
+ /(?<=foo)bar/ // matches "bar" in "foobar", not in "bazbar"
54
+
55
+ // Negative lookbehind: match digits NOT preceded by "$"
56
+ /(?<!\$)\d+/ // matches "42" in "item 42", not "42" in "$42"
57
+
58
+ // Practical: extract price values without the currency symbol
59
+ const prices = 'Items: $29.99, EUR49.50, $100.00';
60
+ const usdPrices = prices.matchAll(/(?<=\$)\d+\.\d{2}/g);
61
+ // ["29.99", "100.00"]
62
+ ```
63
+
64
+ ### 3. Common Validation Patterns
65
+
66
+ ```typescript
67
+ // Email (simplified — use a library for RFC 5322 compliance)
68
+ const EMAIL = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
69
+
70
+ // URL (http/https)
71
+ const URL_PATTERN = /^https?:\/\/[^\s/$.?#].[^\s]*$/i;
72
+
73
+ // ISO 8601 date (YYYY-MM-DD)
74
+ const ISO_DATE = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
75
+
76
+ // Semantic version
77
+ const SEMVER = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:-(?<pre>[a-zA-Z0-9.]+))?(?:\+(?<build>[a-zA-Z0-9.]+))?$/;
78
+
79
+ // UUID v4
80
+ const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
81
+
82
+ // Password (min 8 chars, at least one upper, one lower, one digit)
83
+ const STRONG_PASSWORD = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
84
+
85
+ // IPv4 address
86
+ const IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})$/;
87
+
88
+ // Hex color (#RGB or #RRGGBB)
89
+ const HEX_COLOR = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
90
+
91
+ // Phone (international E.164)
92
+ const PHONE_E164 = /^\+[1-9]\d{1,14}$/;
93
+ ```
94
+
95
+ ### 4. Text Extraction Patterns
96
+
97
+ ```typescript
98
+ // Extract all hashtags
99
+ const hashtags = text.matchAll(/#(\w+)/g);
100
+
101
+ // Extract key-value pairs from logs
102
+ const logLine = '2026-05-26T10:30:00Z level=error msg="Connection refused" host=db.prod retries=3';
103
+ const kvPattern = /(?<key>\w+)=(?:"(?<quoted>[^"]*)"|(?<bare>\S+))/g;
104
+ for (const m of logLine.matchAll(kvPattern)) {
105
+ const key = m.groups!.key;
106
+ const value = m.groups!.quoted ?? m.groups!.bare;
107
+ // { level: "error", msg: "Connection refused", host: "db.prod", retries: "3" }
108
+ }
109
+
110
+ // Extract markdown links
111
+ const mdLinks = /\[(?<text>[^\]]+)\]\((?<url>[^)]+)\)/g;
112
+ for (const m of content.matchAll(mdLinks)) {
113
+ console.log(m.groups!.text, m.groups!.url);
114
+ }
115
+
116
+ // Split on multiple delimiters
117
+ 'one, two;three four'.split(/[,;\s]+/);
118
+ // ["one", "two", "three", "four"]
119
+ ```
120
+
121
+ ### 5. Unicode-Aware Patterns
122
+
123
+ ```typescript
124
+ // Match any letter (including accented, CJK, etc.)
125
+ const anyLetter = /\p{Letter}+/gu;
126
+ 'cafe resume'.match(anyLetter); // ["cafe", "resume"]
127
+
128
+ // Match emoji
129
+ const emojiPattern = /\p{Emoji_Presentation}/gu;
130
+
131
+ // Match currency symbols
132
+ const currencyPattern = /\p{Currency_Symbol}/gu;
133
+
134
+ // Always use the 'u' flag for Unicode correctness
135
+ const wordBoundary = /\b\w+\b/gu; // Unicode-safe word matching
136
+ ```
137
+
138
+ ### 6. Performance — Avoid Catastrophic Backtracking
139
+
140
+ ```typescript
141
+ // BAD — exponential backtracking on non-matching input
142
+ const bad = /^(a+)+$/; // O(2^n) on "aaaaaaaaaaab"
143
+
144
+ // GOOD — possessive-style (use atomic groups or rewrite)
145
+ const good = /^a+$/; // O(n)
146
+
147
+ // BAD — nested quantifiers with overlap
148
+ const bad2 = /^(\w+\s*)*$/;
149
+
150
+ // GOOD — specific alternation
151
+ const good2 = /^[\w\s]+$/;
152
+
153
+ // Rule: Never nest quantifiers on overlapping character classes
154
+ // Test with: "aaaaaaaaaaaaaaaaaab" — if it hangs, you have backtracking
155
+
156
+ // Use RegExp timeout (Node.js 20+) or test with redos-detector
157
+ ```
158
+
159
+ ### 7. Flags Reference
160
+
161
+ | Flag | Name | Effect |
162
+ |------|------|--------|
163
+ | `g` | global | Match all occurrences, not just first |
164
+ | `i` | ignoreCase | Case-insensitive matching |
165
+ | `m` | multiline | `^`/`$` match line starts/ends |
166
+ | `s` | dotAll | `.` matches newlines too |
167
+ | `u` | unicode | Enable Unicode property escapes |
168
+ | `v` | unicodeSets | Extended Unicode sets (ES2024) |
169
+ | `d` | hasIndices | Include start/end indices in match |
170
+
171
+ ## Examples
172
+
173
+ | Pattern | Matches | Note |
174
+ |---------|---------|------|
175
+ | `(?<=@)\w+\.\w+` | Domain from email | Lookbehind extracts without `@` |
176
+ | `\b\d{1,3}(,\d{3})*\b` | Formatted numbers | `1,000,000` but not `12,3` |
177
+ | `(?:https?://)?\S+\.\w{2,}` | URLs with optional scheme | Non-capturing group |
178
+ | `^(?!.*password).*$` | Lines without "password" | Negative lookahead filter |
179
+
180
+ ## Checklist
181
+
182
+ - [ ] Named groups used instead of positional `$1`, `$2` captures
183
+ - [ ] Unicode flag (`u` or `v`) enabled for international text
184
+ - [ ] No nested quantifiers on overlapping character classes
185
+ - [ ] Patterns tested against non-matching input for backtracking
186
+ - [ ] `matchAll` used instead of `exec` loop for global matches
187
+ - [ ] Complex validation uses a library, not a single mega-regex
188
+ - [ ] Regex documented with comments (use `x` flag or string concatenation)
189
+ - [ ] Lookaheads/lookbehinds used to avoid consuming matched text
190
+ - [ ] Common patterns extracted to named constants, not inline literals
191
+ - [ ] Regex tested with edge cases: empty string, Unicode, very long input
@@ -0,0 +1,262 @@
1
+ ---
2
+ name: remix-patterns
3
+ description: Remix patterns for loaders, actions, nested routes, error boundaries, and progressive enhancement.
4
+ ---
5
+
6
+ # Remix Patterns
7
+
8
+ ## When to Use
9
+ Use Remix for full-stack web applications where you want server-rendered pages with progressive enhancement. Remix excels at data loading, form handling, and nested layouts. It works without JavaScript on the client by default, making it ideal for apps that need accessibility, SEO, and resilient UX. Choose Remix when you want to lean into web platform standards rather than fight them.
10
+
11
+ ## How It Works
12
+
13
+ ### Loaders (Server-Side Data Fetching)
14
+
15
+ ```typescript
16
+ // app/routes/posts._index.tsx
17
+ import type { LoaderFunctionArgs } from '@remix-run/node';
18
+ import { json } from '@remix-run/node';
19
+ import { useLoaderData, Link } from '@remix-run/react';
20
+ import { db } from '~/lib/db.server';
21
+
22
+ export async function loader({ request }: LoaderFunctionArgs) {
23
+ const url = new URL(request.url);
24
+ const page = parseInt(url.searchParams.get('page') ?? '1');
25
+ const limit = 20;
26
+
27
+ const [posts, total] = await Promise.all([
28
+ db.post.findMany({
29
+ take: limit,
30
+ skip: (page - 1) * limit,
31
+ orderBy: { createdAt: 'desc' },
32
+ select: { id: true, title: true, slug: true, createdAt: true },
33
+ }),
34
+ db.post.count(),
35
+ ]);
36
+
37
+ return json(
38
+ { posts, page, totalPages: Math.ceil(total / limit) },
39
+ { headers: { 'Cache-Control': 'public, max-age=60, s-maxage=300' } }
40
+ );
41
+ }
42
+
43
+ export default function Posts() {
44
+ const { posts, page, totalPages } = useLoaderData<typeof loader>();
45
+
46
+ return (
47
+ <div>
48
+ <h1>Posts</h1>
49
+ <ul>
50
+ {posts.map((post) => (
51
+ <li key={post.id}>
52
+ <Link to={`/posts/${post.slug}`} prefetch="intent">{post.title}</Link>
53
+ </li>
54
+ ))}
55
+ </ul>
56
+ <nav>
57
+ {page > 1 && <Link to={`?page=${page - 1}`}>Previous</Link>}
58
+ <span>Page {page} of {totalPages}</span>
59
+ {page < totalPages && <Link to={`?page=${page + 1}`}>Next</Link>}
60
+ </nav>
61
+ </div>
62
+ );
63
+ }
64
+ ```
65
+
66
+ ### Actions (Form Handling)
67
+
68
+ ```typescript
69
+ // app/routes/posts.new.tsx
70
+ import type { ActionFunctionArgs } from '@remix-run/node';
71
+ import { json, redirect } from '@remix-run/node';
72
+ import { Form, useActionData, useNavigation } from '@remix-run/react';
73
+ import { db } from '~/lib/db.server';
74
+ import { requireAuth } from '~/lib/auth.server';
75
+
76
+ export async function action({ request }: ActionFunctionArgs) {
77
+ const user = await requireAuth(request);
78
+ const formData = await request.formData();
79
+
80
+ const title = formData.get('title')?.toString().trim();
81
+ const body = formData.get('body')?.toString().trim();
82
+
83
+ const errors: Record<string, string> = {};
84
+ if (!title || title.length < 3) errors.title = 'Title must be at least 3 characters';
85
+ if (!body || body.length < 10) errors.body = 'Body must be at least 10 characters';
86
+
87
+ if (Object.keys(errors).length > 0) {
88
+ return json({ errors, values: { title, body } }, { status: 400 });
89
+ }
90
+
91
+ const slug = title!.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
92
+ const post = await db.post.create({ data: { title: title!, body: body!, slug, authorId: user.id } });
93
+
94
+ return redirect(`/posts/${post.slug}`);
95
+ }
96
+
97
+ export default function NewPost() {
98
+ const actionData = useActionData<typeof action>();
99
+ const navigation = useNavigation();
100
+ const isSubmitting = navigation.state === 'submitting';
101
+
102
+ return (
103
+ <Form method="post">
104
+ <div>
105
+ <label htmlFor="title">Title</label>
106
+ <input
107
+ id="title"
108
+ name="title"
109
+ defaultValue={actionData?.values?.title ?? ''}
110
+ aria-invalid={!!actionData?.errors?.title}
111
+ aria-describedby={actionData?.errors?.title ? 'title-error' : undefined}
112
+ />
113
+ {actionData?.errors?.title && <p id="title-error">{actionData.errors.title}</p>}
114
+ </div>
115
+
116
+ <div>
117
+ <label htmlFor="body">Body</label>
118
+ <textarea id="body" name="body" rows={10} defaultValue={actionData?.values?.body ?? ''} />
119
+ {actionData?.errors?.body && <p>{actionData.errors.body}</p>}
120
+ </div>
121
+
122
+ <button type="submit" disabled={isSubmitting}>
123
+ {isSubmitting ? 'Creating...' : 'Create Post'}
124
+ </button>
125
+ </Form>
126
+ );
127
+ }
128
+ ```
129
+
130
+ ### Nested Routes and Layouts
131
+
132
+ ```typescript
133
+ // app/routes/dashboard.tsx — layout route
134
+ import { Outlet, NavLink } from '@remix-run/react';
135
+
136
+ export default function DashboardLayout() {
137
+ return (
138
+ <div className="flex">
139
+ <aside className="w-64">
140
+ <nav>
141
+ <NavLink to="/dashboard" end className={({ isActive }) => isActive ? 'font-bold' : ''}>
142
+ Overview
143
+ </NavLink>
144
+ <NavLink to="/dashboard/analytics" className={({ isActive }) => isActive ? 'font-bold' : ''}>
145
+ Analytics
146
+ </NavLink>
147
+ <NavLink to="/dashboard/settings" className={({ isActive }) => isActive ? 'font-bold' : ''}>
148
+ Settings
149
+ </NavLink>
150
+ </nav>
151
+ </aside>
152
+ <main className="flex-1">
153
+ <Outlet /> {/* Child routes render here */}
154
+ </main>
155
+ </div>
156
+ );
157
+ }
158
+
159
+ // app/routes/dashboard._index.tsx — renders at /dashboard
160
+ // app/routes/dashboard.analytics.tsx — renders at /dashboard/analytics
161
+ // app/routes/dashboard.settings.tsx — renders at /dashboard/settings
162
+ ```
163
+
164
+ ### Error Boundaries
165
+
166
+ ```typescript
167
+ // app/routes/posts.$slug.tsx
168
+ import { isRouteErrorResponse, useRouteError } from '@remix-run/react';
169
+
170
+ export function ErrorBoundary() {
171
+ const error = useRouteError();
172
+
173
+ if (isRouteErrorResponse(error)) {
174
+ return (
175
+ <div className="error-container">
176
+ <h1>{error.status} {error.statusText}</h1>
177
+ <p>{error.data?.message ?? 'Something went wrong'}</p>
178
+ </div>
179
+ );
180
+ }
181
+
182
+ return (
183
+ <div className="error-container">
184
+ <h1>Unexpected Error</h1>
185
+ <p>{error instanceof Error ? error.message : 'Unknown error'}</p>
186
+ </div>
187
+ );
188
+ }
189
+
190
+ // In loaders, throw responses to trigger error boundaries:
191
+ export async function loader({ params }: LoaderFunctionArgs) {
192
+ const post = await db.post.findUnique({ where: { slug: params.slug } });
193
+ if (!post) throw json({ message: 'Post not found' }, { status: 404 });
194
+ return json({ post });
195
+ }
196
+ ```
197
+
198
+ ### Resource Routes (API Endpoints)
199
+
200
+ ```typescript
201
+ // app/routes/api.posts.ts — no default export = resource route
202
+ import type { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node';
203
+ import { json } from '@remix-run/node';
204
+
205
+ export async function loader({ request }: LoaderFunctionArgs) {
206
+ const posts = await db.post.findMany({ take: 50 });
207
+ return json(posts);
208
+ }
209
+
210
+ export async function action({ request }: ActionFunctionArgs) {
211
+ if (request.method === 'DELETE') {
212
+ const { id } = await request.json();
213
+ await db.post.delete({ where: { id } });
214
+ return json({ success: true });
215
+ }
216
+ return json({ error: 'Method not allowed' }, { status: 405 });
217
+ }
218
+ ```
219
+
220
+ ### Optimistic UI
221
+
222
+ ```typescript
223
+ // app/routes/todos.tsx
224
+ import { useFetcher } from '@remix-run/react';
225
+
226
+ function TodoItem({ todo }: { todo: { id: string; title: string; done: boolean } }) {
227
+ const fetcher = useFetcher();
228
+ const optimisticDone = fetcher.formData
229
+ ? fetcher.formData.get('done') === 'true'
230
+ : todo.done;
231
+
232
+ return (
233
+ <fetcher.Form method="post" action="/todos/toggle">
234
+ <input type="hidden" name="id" value={todo.id} />
235
+ <input type="hidden" name="done" value={String(!todo.done)} />
236
+ <button type="submit" className={optimisticDone ? 'line-through' : ''}>
237
+ {todo.title}
238
+ </button>
239
+ </fetcher.Form>
240
+ );
241
+ }
242
+ ```
243
+
244
+ ## Examples
245
+
246
+ | Pattern | HTTP Method | URL | Purpose |
247
+ |---------|-------------|-----|---------|
248
+ | Loader | GET | `/posts` | Fetch list of posts |
249
+ | Action | POST | `/posts/new` | Create a new post |
250
+ | Resource route | GET/DELETE | `/api/posts` | JSON API endpoint |
251
+ | Nested route | GET | `/dashboard/analytics` | Child layout rendering |
252
+ | Fetcher | POST | `/todos/toggle` | In-place mutation without navigation |
253
+
254
+ ## Checklist
255
+ - [ ] Loaders use `json()` with appropriate Cache-Control headers
256
+ - [ ] Actions validate form data and return errors with `status: 400`
257
+ - [ ] Forms use `<Form method="post">` for progressive enhancement
258
+ - [ ] Error boundaries handle both route errors and unexpected errors
259
+ - [ ] Links use `prefetch="intent"` for preloading on hover
260
+ - [ ] `useNavigation().state` used to show loading states during transitions
261
+ - [ ] Resource routes return proper status codes and Content-Type headers
262
+ - [ ] Server-only imports use `.server.ts` suffix to prevent client bundling