@nextsparkjs/ai-workflow 0.1.0-beta.100

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 (272) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/claude/_docs/workflows-optimizations.md +359 -0
  4. package/claude/agents/api-tester.md +634 -0
  5. package/claude/agents/architecture-supervisor.md +1351 -0
  6. package/claude/agents/backend-developer.md +997 -0
  7. package/claude/agents/backend-validator.md +417 -0
  8. package/claude/agents/bdd-docs-writer.md +737 -0
  9. package/claude/agents/block-developer.md +677 -0
  10. package/claude/agents/code-reviewer.md +1432 -0
  11. package/claude/agents/db-developer.md +721 -0
  12. package/claude/agents/db-validator.md +407 -0
  13. package/claude/agents/demo-video-generator.md +493 -0
  14. package/claude/agents/documentation-writer.md +1268 -0
  15. package/claude/agents/frontend-developer.md +1234 -0
  16. package/claude/agents/frontend-validator.md +777 -0
  17. package/claude/agents/functional-validator.md +630 -0
  18. package/claude/agents/mock-analyst.md +387 -0
  19. package/claude/agents/product-manager.md +963 -0
  20. package/claude/agents/qa-automation.md +1762 -0
  21. package/claude/agents/release-manager.md +634 -0
  22. package/claude/agents/selectors-translator.md +262 -0
  23. package/claude/agents/unit-test-writer.md +785 -0
  24. package/claude/agents/visual-comparator.md +329 -0
  25. package/claude/agents/workflow-maintainer.md +352 -0
  26. package/claude/commands/do/README.md +88 -0
  27. package/claude/commands/do/create-api.md +64 -0
  28. package/claude/commands/do/create-entity.md +66 -0
  29. package/claude/commands/do/create-migration.md +64 -0
  30. package/claude/commands/do/create-plugin.md +56 -0
  31. package/claude/commands/do/create-theme.md +70 -0
  32. package/claude/commands/do/mock-data.md +67 -0
  33. package/claude/commands/do/reset-db.md +71 -0
  34. package/claude/commands/do/setup-scheduled-action.md +75 -0
  35. package/claude/commands/do/sync-code-review.md +117 -0
  36. package/claude/commands/do/update-selectors.md +112 -0
  37. package/claude/commands/do/use-skills.md +90 -0
  38. package/claude/commands/do/validate-blocks.md +69 -0
  39. package/claude/commands/how-to/README.md +261 -0
  40. package/claude/commands/how-to/add-metadata.md +692 -0
  41. package/claude/commands/how-to/add-taxonomies.md +806 -0
  42. package/claude/commands/how-to/add-translations.md +571 -0
  43. package/claude/commands/how-to/create-api.md +577 -0
  44. package/claude/commands/how-to/create-block.md +575 -0
  45. package/claude/commands/how-to/create-child-entities.md +771 -0
  46. package/claude/commands/how-to/create-entity.md +597 -0
  47. package/claude/commands/how-to/create-migrations.md +605 -0
  48. package/claude/commands/how-to/create-plugin.md +654 -0
  49. package/claude/commands/how-to/customize-app.md +481 -0
  50. package/claude/commands/how-to/customize-dashboard.md +553 -0
  51. package/claude/commands/how-to/customize-theme.md +438 -0
  52. package/claude/commands/how-to/define-features-flows.md +632 -0
  53. package/claude/commands/how-to/deploy.md +507 -0
  54. package/claude/commands/how-to/handle-file-uploads.md +746 -0
  55. package/claude/commands/how-to/implement-search.md +1001 -0
  56. package/claude/commands/how-to/install-plugins.md +352 -0
  57. package/claude/commands/how-to/manage-test-coverage.md +984 -0
  58. package/claude/commands/how-to/run-tests.md +400 -0
  59. package/claude/commands/how-to/set-app-languages.md +601 -0
  60. package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
  61. package/claude/commands/how-to/set-scheduled-actions.md +527 -0
  62. package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
  63. package/claude/commands/how-to/setup-authentication.md +388 -0
  64. package/claude/commands/how-to/setup-claude-code.md +440 -0
  65. package/claude/commands/how-to/setup-database.md +274 -0
  66. package/claude/commands/how-to/setup-email-providers.md +598 -0
  67. package/claude/commands/how-to/setup-mobile-dev.md +627 -0
  68. package/claude/commands/how-to/start.md +500 -0
  69. package/claude/commands/how-to/use-devtools.md +639 -0
  70. package/claude/commands/how-to/use-superadmin.md +622 -0
  71. package/claude/commands/session/README.md +193 -0
  72. package/claude/commands/session/block-create.md +190 -0
  73. package/claude/commands/session/block-list.md +203 -0
  74. package/claude/commands/session/block-update.md +192 -0
  75. package/claude/commands/session/block-validate.md +218 -0
  76. package/claude/commands/session/changelog.md +115 -0
  77. package/claude/commands/session/close.md +225 -0
  78. package/claude/commands/session/commit.md +174 -0
  79. package/claude/commands/session/db-entity.md +206 -0
  80. package/claude/commands/session/db-fix.md +212 -0
  81. package/claude/commands/session/db-sample.md +206 -0
  82. package/claude/commands/session/demo.md +178 -0
  83. package/claude/commands/session/doc-bdd.md +207 -0
  84. package/claude/commands/session/doc-feature.md +218 -0
  85. package/claude/commands/session/doc-read.md +225 -0
  86. package/claude/commands/session/execute.md +204 -0
  87. package/claude/commands/session/explain.md +202 -0
  88. package/claude/commands/session/fix-bug.md +210 -0
  89. package/claude/commands/session/fix-build.md +182 -0
  90. package/claude/commands/session/fix-test.md +189 -0
  91. package/claude/commands/session/pending.md +232 -0
  92. package/claude/commands/session/refine.md +188 -0
  93. package/claude/commands/session/resume.md +192 -0
  94. package/claude/commands/session/review.md +192 -0
  95. package/claude/commands/session/scope-change.md +181 -0
  96. package/claude/commands/session/start-blocks.md +347 -0
  97. package/claude/commands/session/start.md +604 -0
  98. package/claude/commands/session/status.md +169 -0
  99. package/claude/commands/session/test-fix.md +221 -0
  100. package/claude/commands/session/test-run.md +203 -0
  101. package/claude/commands/session/test-write.md +242 -0
  102. package/claude/commands/session/validate.md +162 -0
  103. package/claude/config/context.json +40 -0
  104. package/claude/config/github.json +69 -0
  105. package/claude/config/github.schema.json +106 -0
  106. package/claude/config/team.json +46 -0
  107. package/claude/config/team.schema.json +106 -0
  108. package/claude/config/workspace.json +43 -0
  109. package/claude/config/workspace.schema.json +75 -0
  110. package/claude/skills/README.md +228 -0
  111. package/claude/skills/accessibility/SKILL.md +573 -0
  112. package/claude/skills/api-bypass-layers/SKILL.md +550 -0
  113. package/claude/skills/asana-integration/SKILL.md +499 -0
  114. package/claude/skills/better-auth/SKILL.md +666 -0
  115. package/claude/skills/billing-subscriptions/SKILL.md +660 -0
  116. package/claude/skills/block-decision-matrix/SKILL.md +359 -0
  117. package/claude/skills/clickup-integration/SKILL.md +434 -0
  118. package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
  119. package/claude/skills/create-plugin/SKILL.md +425 -0
  120. package/claude/skills/create-theme/SKILL.md +331 -0
  121. package/claude/skills/cypress-api/SKILL.md +511 -0
  122. package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
  123. package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
  124. package/claude/skills/cypress-e2e/SKILL.md +526 -0
  125. package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
  126. package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
  127. package/claude/skills/cypress-selectors/SKILL.md +309 -0
  128. package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
  129. package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
  130. package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
  131. package/claude/skills/database-migrations/SKILL.md +335 -0
  132. package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
  133. package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
  134. package/claude/skills/design-system/SKILL.md +682 -0
  135. package/claude/skills/documentation/SKILL.md +540 -0
  136. package/claude/skills/entity-api/SKILL.md +482 -0
  137. package/claude/skills/entity-system/SKILL.md +635 -0
  138. package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
  139. package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
  140. package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
  141. package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
  142. package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
  143. package/claude/skills/github/SKILL.md +467 -0
  144. package/claude/skills/i18n-nextintl/SKILL.md +302 -0
  145. package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
  146. package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
  147. package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
  148. package/claude/skills/impact-analysis/SKILL.md +203 -0
  149. package/claude/skills/jest-unit/SKILL.md +306 -0
  150. package/claude/skills/jest-unit/references/component-testing.md +371 -0
  151. package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
  152. package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
  153. package/claude/skills/jira-integration/SKILL.md +539 -0
  154. package/claude/skills/media-library/SKILL.md +743 -0
  155. package/claude/skills/mock-analysis/SKILL.md +276 -0
  156. package/claude/skills/monorepo-architecture/SKILL.md +162 -0
  157. package/claude/skills/nextjs-api-development/SKILL.md +364 -0
  158. package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
  159. package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
  160. package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
  161. package/claude/skills/notion-integration/SKILL.md +641 -0
  162. package/claude/skills/npm-development-workflow/SKILL.md +480 -0
  163. package/claude/skills/page-builder-blocks/SKILL.md +530 -0
  164. package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
  165. package/claude/skills/permissions-system/SKILL.md +619 -0
  166. package/claude/skills/plugins/SKILL.md +340 -0
  167. package/claude/skills/plugins/references/plugin-templates.md +414 -0
  168. package/claude/skills/plugins/references/plugin-testing.md +353 -0
  169. package/claude/skills/plugins/references/plugin-types.md +198 -0
  170. package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
  171. package/claude/skills/pom-patterns/SKILL.md +452 -0
  172. package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
  173. package/claude/skills/rate-limiting/SKILL.md +342 -0
  174. package/claude/skills/react-best-practices/AGENTS.md +2410 -0
  175. package/claude/skills/react-best-practices/README.md +123 -0
  176. package/claude/skills/react-best-practices/SKILL.md +125 -0
  177. package/claude/skills/react-best-practices/metadata.json +15 -0
  178. package/claude/skills/react-best-practices/rules/_sections.md +46 -0
  179. package/claude/skills/react-best-practices/rules/_template.md +28 -0
  180. package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  181. package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
  182. package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
  183. package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
  184. package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
  185. package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
  186. package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  187. package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  188. package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  189. package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  190. package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  191. package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
  192. package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  193. package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
  194. package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
  195. package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  196. package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
  197. package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  198. package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  199. package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  200. package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  201. package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
  202. package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  203. package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
  204. package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  205. package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  206. package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  207. package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  208. package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
  209. package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  210. package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  211. package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  212. package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  213. package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  214. package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  215. package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  216. package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  217. package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  218. package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  219. package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  220. package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
  221. package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  222. package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  223. package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  224. package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
  225. package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
  226. package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
  227. package/claude/skills/react-patterns/SKILL.md +688 -0
  228. package/claude/skills/registry-system/SKILL.md +331 -0
  229. package/claude/skills/scheduled-actions/SKILL.md +671 -0
  230. package/claude/skills/scope-enforcement/SKILL.md +542 -0
  231. package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
  232. package/claude/skills/server-actions/SKILL.md +493 -0
  233. package/claude/skills/service-layer/SKILL.md +587 -0
  234. package/claude/skills/session-management/SKILL.md +266 -0
  235. package/claude/skills/session-management/scripts/create-session.py +166 -0
  236. package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
  237. package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
  238. package/claude/skills/session-management/scripts/session-archive.sh +87 -0
  239. package/claude/skills/session-management/scripts/session-close.sh +133 -0
  240. package/claude/skills/session-management/scripts/session-init.sh +225 -0
  241. package/claude/skills/session-management/scripts/session-list.sh +163 -0
  242. package/claude/skills/session-management/scripts/split-plan.sh +116 -0
  243. package/claude/skills/shadcn-components/SKILL.md +586 -0
  244. package/claude/skills/shadcn-theming/SKILL.md +446 -0
  245. package/claude/skills/suspense-loading/SKILL.md +280 -0
  246. package/claude/skills/tailwind-theming/SKILL.md +507 -0
  247. package/claude/skills/tanstack-query/SKILL.md +608 -0
  248. package/claude/skills/test-coverage/SKILL.md +239 -0
  249. package/claude/skills/web-design-guidelines/SKILL.md +39 -0
  250. package/claude/skills/zod-validation/SKILL.md +537 -0
  251. package/claude/templates/blocks/progress.md +86 -0
  252. package/claude/templates/iteration/changes.md +61 -0
  253. package/claude/templates/iteration/progress.md +55 -0
  254. package/claude/templates/log.md +31 -0
  255. package/claude/templates/story/context.md +77 -0
  256. package/claude/templates/story/pendings.md +37 -0
  257. package/claude/templates/story/plan.md +299 -0
  258. package/claude/templates/story/requirements.md +109 -0
  259. package/claude/templates/story/scope.json +10 -0
  260. package/claude/templates/story/tests.md +91 -0
  261. package/claude/templates/task/progress.md +58 -0
  262. package/claude/templates/task/requirements.md +54 -0
  263. package/claude/workflows/README.md +154 -0
  264. package/claude/workflows/blocks.md +614 -0
  265. package/claude/workflows/story.md +1207 -0
  266. package/claude/workflows/task.md +927 -0
  267. package/claude/workflows/tweak.md +527 -0
  268. package/cursor/.gitkeep +0 -0
  269. package/package.json +35 -0
  270. package/scripts/postinstall.mjs +198 -0
  271. package/scripts/setup.mjs +282 -0
  272. package/scripts/sync.mjs +209 -0
@@ -0,0 +1,1001 @@
1
+ # /how-to:implement-search
2
+
3
+ Interactive tutorial for implementing search functionality using the client-side entity search system.
4
+
5
+ ## Required Skills
6
+ - `.claude/skills/react-patterns/SKILL.md`
7
+ - `.claude/skills/tanstack-query/SKILL.md`
8
+ - `.claude/skills/entity-system/SKILL.md`
9
+ - `.claude/skills/cypress-selectors/SKILL.md`
10
+
11
+ ## Syntax
12
+
13
+ ```
14
+ /how-to:implement-search [options]
15
+ ```
16
+
17
+ ### Options:
18
+ - `--step <n>` - Start at specific step (1-5)
19
+ - `--quick` - Quick reference without explanations
20
+
21
+ ## Behavior
22
+
23
+ This command guides you through implementing search in your NextSpark application.
24
+ It covers the useEntitySearch hook, search components, and custom implementations.
25
+
26
+ ## Tutorial Structure
27
+
28
+ ```
29
+ ┌─────────────────────────────────────────────────────────────┐
30
+ │ /how-to:implement-search - 5 Steps Overview │
31
+ ├─────────────────────────────────────────────────────────────┤
32
+ │ │
33
+ │ [1] Understanding Search → Client-side architecture │
34
+ │ │ │
35
+ │ ↓ │
36
+ │ [2] useEntitySearch Hook → Hook usage & configuration │
37
+ │ │ │
38
+ │ ↓ │
39
+ │ [3] Search Results → EntitySearchResult types │
40
+ │ │ │
41
+ │ ↓ │
42
+ │ [4] EntitySearch Component → Pre-built component variants │
43
+ │ │ │
44
+ │ ↓ │
45
+ │ [5] Custom Search → Advanced patterns │
46
+ │ │
47
+ └─────────────────────────────────────────────────────────────┘
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Step 1: Understanding Search Architecture
53
+
54
+ ```
55
+ ┌─────────────────────────────────────────────────────────────┐
56
+ │ STEP 1: SEARCH ARCHITECTURE │
57
+ │ Client-side search with entity integration │
58
+ └─────────────────────────────────────────────────────────────┘
59
+ ```
60
+
61
+ ### How Search Works in NextSpark
62
+
63
+ NextSpark uses **client-side search** that integrates with the Entity System:
64
+
65
+ ```
66
+ ┌─────────────────────────────────────────────────────────────┐
67
+ │ SEARCH FLOW │
68
+ ├─────────────────────────────────────────────────────────────┤
69
+ │ │
70
+ │ [User Input] → [Debounce 150ms] → [Search Function] │
71
+ │ │ │
72
+ │ ↓ │
73
+ │ ┌──────────────────────────────────────────────────────┐ │
74
+ │ │ SEARCH SOURCES │ │
75
+ │ ├──────────────────────────────────────────────────────┤ │
76
+ │ │ • Entity Registry (searchable entities) │ │
77
+ │ │ • System Pages (dashboard, settings) │ │
78
+ │ │ • User Plan/Permission Filtering │ │
79
+ │ └──────────────────────────────────────────────────────┘ │
80
+ │ │ │
81
+ │ ↓ │
82
+ │ [Relevance Scoring] → [Sort Results] → [Return Top N] │
83
+ │ │
84
+ └─────────────────────────────────────────────────────────────┘
85
+ ```
86
+
87
+ ### Key Characteristics
88
+
89
+ 1. **Client-Side Execution**
90
+ - No server round-trip for basic search
91
+ - Instant results with debouncing
92
+ - Entity registry populated by server component
93
+
94
+ 2. **Multi-Source Search**
95
+ - Entities: From entity registry
96
+ - System Pages: Dashboard, settings, profile
97
+ - Filtered by user permissions and plan
98
+
99
+ 3. **Relevance Scoring**
100
+ - Exact title match: +100 points
101
+ - Title starts with query: +50 points
102
+ - Title contains query: +25 points
103
+ - Entity type match: +80 points
104
+ - Description match: +10 points
105
+
106
+ 4. **Configuration**
107
+ - Minimum 2 characters to trigger search
108
+ - Maximum 8-12 results returned
109
+ - 150ms debounce delay
110
+
111
+ ### Making Entities Searchable
112
+
113
+ To make an entity searchable, enable in config:
114
+
115
+ ```typescript
116
+ // entity.config.ts
117
+ export const productConfig: EntityConfig = {
118
+ slug: 'products',
119
+ // ...
120
+ ui: {
121
+ features: {
122
+ searchable: true, // Enable for global search
123
+ // ...
124
+ }
125
+ },
126
+ fields: [
127
+ {
128
+ name: 'name',
129
+ type: 'text',
130
+ api: {
131
+ searchable: true, // This field is searchable
132
+ // ...
133
+ }
134
+ }
135
+ ]
136
+ }
137
+ ```
138
+
139
+ ### Search Result Priority
140
+
141
+ ```
142
+ ┌─────────────────────────────────────────────────────────────┐
143
+ │ RESULT PRIORITY ORDER │
144
+ ├─────────────────────────────────────────────────────────────┤
145
+ │ │
146
+ │ 1. Relevance Score (highest first) │
147
+ │ 2. Entity vs System (entities prioritized) │
148
+ │ 3. Priority Level (high > medium > low) │
149
+ │ 4. Alphabetical (as tiebreaker) │
150
+ │ │
151
+ └─────────────────────────────────────────────────────────────┘
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Step 2: useEntitySearch Hook
157
+
158
+ ```
159
+ ┌─────────────────────────────────────────────────────────────┐
160
+ │ STEP 2: useEntitySearch HOOK │
161
+ │ Primary hook for search functionality │
162
+ └─────────────────────────────────────────────────────────────┘
163
+ ```
164
+
165
+ ### Hook Location
166
+
167
+ ```
168
+ /packages/core/src/hooks/useEntitySearch.ts
169
+ ```
170
+
171
+ ### Basic Usage
172
+
173
+ ```typescript
174
+ 'use client'
175
+
176
+ import { useEntitySearch } from '@nextsparkjs/core/hooks'
177
+
178
+ export function SearchComponent() {
179
+ const {
180
+ query,
181
+ setQuery,
182
+ results,
183
+ isSearching,
184
+ clearSearch,
185
+ hasResults,
186
+ isEmpty,
187
+ availableEntities
188
+ } = useEntitySearch()
189
+
190
+ return (
191
+ <div>
192
+ <input
193
+ type="text"
194
+ value={query}
195
+ onChange={(e) => setQuery(e.target.value)}
196
+ placeholder="Search..."
197
+ data-cy="search-input"
198
+ />
199
+
200
+ {isSearching && <p>Searching...</p>}
201
+
202
+ {hasResults && (
203
+ <ul>
204
+ {results.map(result => (
205
+ <li key={result.id}>
206
+ <a href={result.url}>{result.title}</a>
207
+ </li>
208
+ ))}
209
+ </ul>
210
+ )}
211
+ </div>
212
+ )
213
+ }
214
+ ```
215
+
216
+ ### Hook Return Values
217
+
218
+ ```
219
+ ┌─────────────────────────────────────────────────────────────┐
220
+ │ useEntitySearch() RETURN TYPE │
221
+ ├─────────────────────────────────────────────────────────────┤
222
+ │ │
223
+ │ interface EntitySearchHookResult { │
224
+ │ query: string // Current search query │
225
+ │ setQuery: (q) => void // Update query function │
226
+ │ results: EntitySearchResult[] // Search results │
227
+ │ isSearching: boolean // Loading state │
228
+ │ clearSearch: () => void // Reset search state │
229
+ │ hasResults: boolean // results.length > 0 │
230
+ │ isEmpty: boolean // query.trim() === '' │
231
+ │ availableEntities: EntityConfig[] // Searchable entities│
232
+ │ } │
233
+ │ │
234
+ └─────────────────────────────────────────────────────────────┘
235
+ ```
236
+
237
+ ### Complete Example with Keyboard Support
238
+
239
+ ```typescript
240
+ 'use client'
241
+
242
+ import { useEntitySearch } from '@nextsparkjs/core/hooks'
243
+ import { useRouter } from 'next/navigation'
244
+ import { useEffect, useRef, useState } from 'react'
245
+
246
+ export function GlobalSearch() {
247
+ const {
248
+ query,
249
+ setQuery,
250
+ results,
251
+ isSearching,
252
+ clearSearch,
253
+ hasResults
254
+ } = useEntitySearch()
255
+
256
+ const router = useRouter()
257
+ const inputRef = useRef<HTMLInputElement>(null)
258
+ const [selectedIndex, setSelectedIndex] = useState(0)
259
+
260
+ // Keyboard navigation
261
+ useEffect(() => {
262
+ const handleKeyDown = (e: KeyboardEvent) => {
263
+ // Cmd/Ctrl + K to focus
264
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
265
+ e.preventDefault()
266
+ inputRef.current?.focus()
267
+ }
268
+ }
269
+
270
+ document.addEventListener('keydown', handleKeyDown)
271
+ return () => document.removeEventListener('keydown', handleKeyDown)
272
+ }, [])
273
+
274
+ // Result keyboard navigation
275
+ const handleInputKeyDown = (e: React.KeyboardEvent) => {
276
+ if (e.key === 'ArrowDown') {
277
+ e.preventDefault()
278
+ setSelectedIndex(prev =>
279
+ Math.min(prev + 1, results.length - 1)
280
+ )
281
+ } else if (e.key === 'ArrowUp') {
282
+ e.preventDefault()
283
+ setSelectedIndex(prev => Math.max(prev - 1, 0))
284
+ } else if (e.key === 'Enter' && hasResults) {
285
+ e.preventDefault()
286
+ router.push(results[selectedIndex].url)
287
+ clearSearch()
288
+ } else if (e.key === 'Escape') {
289
+ clearSearch()
290
+ inputRef.current?.blur()
291
+ }
292
+ }
293
+
294
+ // Reset selection on query change
295
+ useEffect(() => {
296
+ setSelectedIndex(0)
297
+ }, [query])
298
+
299
+ return (
300
+ <div className="relative" data-cy="global-search">
301
+ <input
302
+ ref={inputRef}
303
+ type="text"
304
+ value={query}
305
+ onChange={(e) => setQuery(e.target.value)}
306
+ onKeyDown={handleInputKeyDown}
307
+ placeholder="Search... (Cmd+K)"
308
+ className="w-full px-4 py-2 border rounded"
309
+ data-cy="search-input"
310
+ />
311
+
312
+ {hasResults && (
313
+ <ul className="absolute w-full mt-1 bg-white border rounded shadow-lg">
314
+ {results.map((result, index) => (
315
+ <li
316
+ key={result.id}
317
+ className={`px-4 py-2 cursor-pointer ${
318
+ index === selectedIndex ? 'bg-blue-100' : ''
319
+ }`}
320
+ onClick={() => {
321
+ router.push(result.url)
322
+ clearSearch()
323
+ }}
324
+ data-cy="search-result"
325
+ >
326
+ <div className="font-medium">{result.title}</div>
327
+ {result.description && (
328
+ <div className="text-sm text-gray-500">
329
+ {result.description}
330
+ </div>
331
+ )}
332
+ </li>
333
+ ))}
334
+ </ul>
335
+ )}
336
+ </div>
337
+ )
338
+ }
339
+ ```
340
+
341
+ ---
342
+
343
+ ## Step 3: Search Results Structure
344
+
345
+ ```
346
+ ┌─────────────────────────────────────────────────────────────┐
347
+ │ STEP 3: SEARCH RESULTS STRUCTURE │
348
+ │ Understanding EntitySearchResult type │
349
+ └─────────────────────────────────────────────────────────────┘
350
+ ```
351
+
352
+ ### EntitySearchResult Interface
353
+
354
+ ```typescript
355
+ interface EntitySearchResult {
356
+ id: string // Unique identifier
357
+ title: string // Display title
358
+ description?: string // Optional description
359
+ entityType: string // Entity slug or 'system'
360
+ type: 'entity' | 'system' // Result type
361
+ url: string // Navigation URL
362
+ category?: string // Category for grouping
363
+ priority?: 'low' | 'medium' | 'high' // Display priority
364
+ completed?: boolean // For task-like entities
365
+ icon?: string // Icon name (Lucide)
366
+ limitInfo?: { // Plan limit information
367
+ current: number
368
+ max: number | 'unlimited'
369
+ canCreate: boolean
370
+ }
371
+ }
372
+ ```
373
+
374
+ ### Result Types Explained
375
+
376
+ ```
377
+ ┌─────────────────────────────────────────────────────────────┐
378
+ │ RESULT TYPES │
379
+ ├─────────────────────────────────────────────────────────────┤
380
+ │ │
381
+ │ TYPE: 'entity' │
382
+ │ ├── From entity registry │
383
+ │ ├── URL: /dashboard/{entity.slug} │
384
+ │ ├── Category: 'Entities' │
385
+ │ └── Priority: 'medium' (default) │
386
+ │ │
387
+ │ TYPE: 'system' │
388
+ │ ├── Static system pages │
389
+ │ ├── URL: /dashboard/settings/{page} │
390
+ │ ├── Category: 'Navigation' | 'Settings' │
391
+ │ └── Priority: 'low' (default) │
392
+ │ │
393
+ └─────────────────────────────────────────────────────────────┘
394
+ ```
395
+
396
+ ### Working with Results
397
+
398
+ ```typescript
399
+ // Grouping results by category
400
+ const groupedResults = useMemo(() => {
401
+ const groups = new Map<string, EntitySearchResult[]>()
402
+
403
+ results.forEach(result => {
404
+ const category = result.category || 'Other'
405
+ const group = groups.get(category) || []
406
+ group.push(result)
407
+ groups.set(category, group)
408
+ })
409
+
410
+ return groups
411
+ }, [results])
412
+
413
+ // Render grouped results
414
+ return (
415
+ <div>
416
+ {Array.from(groupedResults.entries()).map(([category, items]) => (
417
+ <div key={category}>
418
+ <h3 className="font-bold">{category}</h3>
419
+ <ul>
420
+ {items.map(item => (
421
+ <li key={item.id}>
422
+ {item.type === 'entity' && (
423
+ <span className="text-blue-500">[Entity]</span>
424
+ )}
425
+ {item.title}
426
+ </li>
427
+ ))}
428
+ </ul>
429
+ </div>
430
+ ))}
431
+ </div>
432
+ )
433
+ ```
434
+
435
+ ### Displaying Plan Limits
436
+
437
+ ```typescript
438
+ // Show limit information for entity results
439
+ function ResultWithLimits({ result }: { result: EntitySearchResult }) {
440
+ const { limitInfo } = result
441
+
442
+ if (!limitInfo || result.type !== 'entity') {
443
+ return <span>{result.title}</span>
444
+ }
445
+
446
+ const limitDisplay = limitInfo.max === 'unlimited'
447
+ ? 'Unlimited'
448
+ : `${limitInfo.current}/${limitInfo.max}`
449
+
450
+ return (
451
+ <div className="flex items-center gap-2">
452
+ <span>{result.title}</span>
453
+ <span className={`text-xs ${
454
+ limitInfo.canCreate ? 'text-green-500' : 'text-red-500'
455
+ }`}>
456
+ ({limitDisplay})
457
+ </span>
458
+ </div>
459
+ )
460
+ }
461
+ ```
462
+
463
+ ---
464
+
465
+ ## Step 4: EntitySearch Component
466
+
467
+ ```
468
+ ┌─────────────────────────────────────────────────────────────┐
469
+ │ STEP 4: EntitySearch COMPONENT │
470
+ │ Pre-built search component with variants │
471
+ └─────────────────────────────────────────────────────────────┘
472
+ ```
473
+
474
+ ### Component Location
475
+
476
+ ```
477
+ /packages/core/src/components/entities/EntitySearch.tsx
478
+ ```
479
+
480
+ ### Available Variants
481
+
482
+ ```
483
+ ┌─────────────────────────────────────────────────────────────┐
484
+ │ EntitySearch VARIANTS │
485
+ ├─────────────────────────────────────────────────────────────┤
486
+ │ │
487
+ │ 'inline' - Full search with results below input │
488
+ │ 'dropdown' - Compact dropdown with popover results │
489
+ │ 'modal' - Full-screen modal search (EntitySearchModal) │
490
+ │ │
491
+ └─────────────────────────────────────────────────────────────┘
492
+ ```
493
+
494
+ ### Basic Usage
495
+
496
+ ```typescript
497
+ // Import from core
498
+ import { EntitySearch } from '@nextsparkjs/core/components/entities'
499
+
500
+ // In a Server Component - fetch entities
501
+ import { getEnabledEntities } from '@nextsparkjs/core/lib/entities/registry'
502
+
503
+ export default async function SearchPage() {
504
+ const entities = getEnabledEntities()
505
+
506
+ return (
507
+ <div>
508
+ <h1>Search</h1>
509
+ <EntitySearch
510
+ entities={entities}
511
+ placeholder="Search everything..."
512
+ variant="inline"
513
+ enableEntityFilter
514
+ maxResults={20}
515
+ />
516
+ </div>
517
+ )
518
+ }
519
+ ```
520
+
521
+ ### Component Props
522
+
523
+ ```typescript
524
+ interface EntitySearchProps {
525
+ /** REQUIRED: Entity configs from server component */
526
+ entities: EntityConfig[]
527
+
528
+ /** Placeholder text */
529
+ placeholder?: string // default: 'Search everything...'
530
+
531
+ /** Display variant */
532
+ variant?: 'inline' | 'modal' | 'dropdown' // default: 'inline'
533
+
534
+ /** Custom result click handler */
535
+ onResultClick?: (result: SearchResult) => void
536
+
537
+ /** Search callback */
538
+ onSearch?: (query: string, entityName?: string) => void
539
+
540
+ /** Enable entity type filter */
541
+ enableEntityFilter?: boolean // default: true
542
+
543
+ /** Maximum results to show */
544
+ maxResults?: number // default: 20
545
+
546
+ /** Additional CSS classes */
547
+ className?: string
548
+ }
549
+ ```
550
+
551
+ ### Dropdown Variant Example
552
+
553
+ ```typescript
554
+ // Compact search in header/navbar
555
+ export function HeaderSearch({ entities }: { entities: EntityConfig[] }) {
556
+ return (
557
+ <EntitySearch
558
+ entities={entities}
559
+ variant="dropdown"
560
+ placeholder="Quick search..."
561
+ maxResults={8}
562
+ data-cy="header-search"
563
+ />
564
+ )
565
+ }
566
+ ```
567
+
568
+ ### Modal Variant Example
569
+
570
+ ```typescript
571
+ import {
572
+ EntitySearchModal
573
+ } from '@nextsparkjs/core/components/entities'
574
+ import { useState } from 'react'
575
+
576
+ export function SearchWithModal({ entities }: { entities: EntityConfig[] }) {
577
+ const [isOpen, setIsOpen] = useState(false)
578
+
579
+ // Open with keyboard shortcut
580
+ useEffect(() => {
581
+ const handleKeyDown = (e: KeyboardEvent) => {
582
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
583
+ e.preventDefault()
584
+ setIsOpen(true)
585
+ }
586
+ }
587
+
588
+ document.addEventListener('keydown', handleKeyDown)
589
+ return () => document.removeEventListener('keydown', handleKeyDown)
590
+ }, [])
591
+
592
+ return (
593
+ <>
594
+ <button
595
+ onClick={() => setIsOpen(true)}
596
+ data-cy="open-search-modal"
597
+ >
598
+ Search (Cmd+K)
599
+ </button>
600
+
601
+ <EntitySearchModal
602
+ isOpen={isOpen}
603
+ onClose={() => setIsOpen(false)}
604
+ entities={entities}
605
+ onResultClick={(result) => {
606
+ console.log('Selected:', result)
607
+ }}
608
+ />
609
+ </>
610
+ )
611
+ }
612
+ ```
613
+
614
+ ### Custom Result Click Handler
615
+
616
+ ```typescript
617
+ export function SearchWithCustomAction({ entities }: { entities: EntityConfig[] }) {
618
+ const handleResultClick = (result: SearchResult) => {
619
+ // Custom action instead of navigation
620
+ if (result.entityName === 'tasks') {
621
+ // Open task in sidebar
622
+ openTaskSidebar(result.id)
623
+ } else {
624
+ // Default navigation
625
+ window.location.href = result.url
626
+ }
627
+ }
628
+
629
+ return (
630
+ <EntitySearch
631
+ entities={entities}
632
+ onResultClick={handleResultClick}
633
+ />
634
+ )
635
+ }
636
+ ```
637
+
638
+ ---
639
+
640
+ ## Step 5: Custom Search Implementation
641
+
642
+ ```
643
+ ┌─────────────────────────────────────────────────────────────┐
644
+ │ STEP 5: CUSTOM SEARCH IMPLEMENTATION │
645
+ │ Advanced patterns for specialized search │
646
+ └─────────────────────────────────────────────────────────────┘
647
+ ```
648
+
649
+ ### Custom Relevance Scoring
650
+
651
+ ```typescript
652
+ // Custom scoring function for your domain
653
+ function calculateRelevanceScore(
654
+ item: EntitySearchResult,
655
+ query: string
656
+ ): number {
657
+ const lowerQuery = query.toLowerCase()
658
+ let score = 0
659
+
660
+ // Exact title match
661
+ if (item.title.toLowerCase() === lowerQuery) {
662
+ score += 100
663
+ }
664
+
665
+ // Title starts with query
666
+ if (item.title.toLowerCase().startsWith(lowerQuery)) {
667
+ score += 50
668
+ }
669
+
670
+ // Title contains query
671
+ if (item.title.toLowerCase().includes(lowerQuery)) {
672
+ score += 25
673
+ }
674
+
675
+ // Entity type matches
676
+ if (item.entityType.toLowerCase() === lowerQuery) {
677
+ score += 80
678
+ }
679
+
680
+ // Description contains query
681
+ if (item.description?.toLowerCase().includes(lowerQuery)) {
682
+ score += 10
683
+ }
684
+
685
+ // Boost entity results
686
+ if (item.type === 'entity') {
687
+ score += 5
688
+ }
689
+
690
+ // Custom domain-specific boosts
691
+ if (item.priority === 'high') {
692
+ score += 20
693
+ }
694
+
695
+ return score
696
+ }
697
+ ```
698
+
699
+ ### API-Backed Search
700
+
701
+ ```typescript
702
+ 'use client'
703
+
704
+ import { useState, useCallback } from 'react'
705
+ import { useDebounce } from '@nextsparkjs/core/hooks'
706
+
707
+ interface APISearchResult {
708
+ id: string
709
+ title: string
710
+ entityType: string
711
+ score: number
712
+ }
713
+
714
+ export function useAPISearch(entitySlug: string) {
715
+ const [query, setQuery] = useState('')
716
+ const [results, setResults] = useState<APISearchResult[]>([])
717
+ const [isSearching, setIsSearching] = useState(false)
718
+
719
+ const debouncedQuery = useDebounce(query, 300)
720
+
721
+ // Search effect
722
+ useEffect(() => {
723
+ if (!debouncedQuery.trim() || debouncedQuery.length < 2) {
724
+ setResults([])
725
+ return
726
+ }
727
+
728
+ const performSearch = async () => {
729
+ setIsSearching(true)
730
+
731
+ try {
732
+ const response = await fetch(
733
+ `/api/v1/${entitySlug}?search=${encodeURIComponent(debouncedQuery)}`
734
+ )
735
+
736
+ if (!response.ok) throw new Error('Search failed')
737
+
738
+ const data = await response.json()
739
+ setResults(data.data || [])
740
+ } catch (error) {
741
+ console.error('Search error:', error)
742
+ setResults([])
743
+ } finally {
744
+ setIsSearching(false)
745
+ }
746
+ }
747
+
748
+ performSearch()
749
+ }, [debouncedQuery, entitySlug])
750
+
751
+ return {
752
+ query,
753
+ setQuery,
754
+ results,
755
+ isSearching,
756
+ clearSearch: useCallback(() => {
757
+ setQuery('')
758
+ setResults([])
759
+ }, [])
760
+ }
761
+ }
762
+ ```
763
+
764
+ ### Multi-Entity Search with Filters
765
+
766
+ ```typescript
767
+ 'use client'
768
+
769
+ import { useState, useMemo } from 'react'
770
+ import { useEntitySearch } from '@nextsparkjs/core/hooks'
771
+
772
+ interface SearchFilters {
773
+ entityTypes: string[]
774
+ dateRange?: { from: Date; to: Date }
775
+ priority?: 'low' | 'medium' | 'high'
776
+ }
777
+
778
+ export function useFilteredSearch() {
779
+ const search = useEntitySearch()
780
+ const [filters, setFilters] = useState<SearchFilters>({
781
+ entityTypes: []
782
+ })
783
+
784
+ // Filter results based on active filters
785
+ const filteredResults = useMemo(() => {
786
+ let results = search.results
787
+
788
+ // Filter by entity types
789
+ if (filters.entityTypes.length > 0) {
790
+ results = results.filter(r =>
791
+ filters.entityTypes.includes(r.entityType)
792
+ )
793
+ }
794
+
795
+ // Filter by priority
796
+ if (filters.priority) {
797
+ results = results.filter(r =>
798
+ r.priority === filters.priority
799
+ )
800
+ }
801
+
802
+ return results
803
+ }, [search.results, filters])
804
+
805
+ return {
806
+ ...search,
807
+ results: filteredResults,
808
+ filters,
809
+ setFilters,
810
+ // Available entity types from results
811
+ availableTypes: useMemo(() => {
812
+ const types = new Set(search.results.map(r => r.entityType))
813
+ return Array.from(types)
814
+ }, [search.results])
815
+ }
816
+ }
817
+
818
+ // Usage
819
+ function FilteredSearchUI() {
820
+ const {
821
+ query,
822
+ setQuery,
823
+ results,
824
+ filters,
825
+ setFilters,
826
+ availableTypes
827
+ } = useFilteredSearch()
828
+
829
+ return (
830
+ <div>
831
+ <input
832
+ value={query}
833
+ onChange={e => setQuery(e.target.value)}
834
+ data-cy="filtered-search-input"
835
+ />
836
+
837
+ <div className="filters">
838
+ {availableTypes.map(type => (
839
+ <label key={type}>
840
+ <input
841
+ type="checkbox"
842
+ checked={filters.entityTypes.includes(type)}
843
+ onChange={e => {
844
+ setFilters(prev => ({
845
+ ...prev,
846
+ entityTypes: e.target.checked
847
+ ? [...prev.entityTypes, type]
848
+ : prev.entityTypes.filter(t => t !== type)
849
+ }))
850
+ }}
851
+ data-cy={`filter-${type}`}
852
+ />
853
+ {type}
854
+ </label>
855
+ ))}
856
+ </div>
857
+
858
+ <ul>
859
+ {results.map(r => (
860
+ <li key={r.id} data-cy="search-result">
861
+ {r.title}
862
+ </li>
863
+ ))}
864
+ </ul>
865
+ </div>
866
+ )
867
+ }
868
+ ```
869
+
870
+ ### Search with Highlighting
871
+
872
+ ```typescript
873
+ // Highlight matches in search results
874
+ function highlightMatches(
875
+ text: string,
876
+ query: string
877
+ ): React.ReactNode {
878
+ if (!query.trim()) return text
879
+
880
+ const parts = text.split(new RegExp(`(${query})`, 'gi'))
881
+
882
+ return (
883
+ <>
884
+ {parts.map((part, i) =>
885
+ part.toLowerCase() === query.toLowerCase() ? (
886
+ <mark key={i} className="bg-yellow-200 px-0.5 rounded">
887
+ {part}
888
+ </mark>
889
+ ) : (
890
+ part
891
+ )
892
+ )}
893
+ </>
894
+ )
895
+ }
896
+
897
+ // Usage in results
898
+ function SearchResultItem({
899
+ result,
900
+ query
901
+ }: {
902
+ result: EntitySearchResult
903
+ query: string
904
+ }) {
905
+ return (
906
+ <div data-cy="search-result">
907
+ <h4>{highlightMatches(result.title, query)}</h4>
908
+ {result.description && (
909
+ <p className="text-sm text-gray-500">
910
+ {highlightMatches(result.description, query)}
911
+ </p>
912
+ )}
913
+ </div>
914
+ )
915
+ }
916
+ ```
917
+
918
+ ### Test Selectors for Search
919
+
920
+ ```typescript
921
+ // Always use data-cy attributes for search components
922
+ const SEARCH_SELECTORS = {
923
+ container: 'search-container',
924
+ input: 'search-input',
925
+ results: 'search-results',
926
+ result: 'search-result',
927
+ resultLink: 'search-result-link',
928
+ clearButton: 'search-clear',
929
+ filter: (type: string) => `search-filter-${type}`,
930
+ loading: 'search-loading',
931
+ empty: 'search-empty'
932
+ }
933
+
934
+ // Usage in component
935
+ <div data-cy={SEARCH_SELECTORS.container}>
936
+ <input data-cy={SEARCH_SELECTORS.input} />
937
+ <div data-cy={SEARCH_SELECTORS.results}>
938
+ {results.map(r => (
939
+ <div key={r.id} data-cy={SEARCH_SELECTORS.result}>
940
+ <a data-cy={SEARCH_SELECTORS.resultLink} href={r.url}>
941
+ {r.title}
942
+ </a>
943
+ </div>
944
+ ))}
945
+ </div>
946
+ </div>
947
+ ```
948
+
949
+ ---
950
+
951
+ ## Interactive Options
952
+
953
+ When running `/how-to:implement-search`:
954
+
955
+ ```
956
+ ┌─────────────────────────────────────────────────────────────┐
957
+ │ What would you like to learn? │
958
+ ├─────────────────────────────────────────────────────────────┤
959
+ │ │
960
+ │ [1] Basic hook usage (useEntitySearch) │
961
+ │ [2] Pre-built component (EntitySearch) │
962
+ │ [3] Custom search implementation │
963
+ │ [4] Search with API integration │
964
+ │ [5] Start from Step 1 (full tutorial) │
965
+ │ │
966
+ └─────────────────────────────────────────────────────────────┘
967
+ ```
968
+
969
+ ---
970
+
971
+ ## Common Questions
972
+
973
+ ### Q: Why is search client-side?
974
+ A: Client-side search provides instant results without server round-trips.
975
+ For large datasets, you can extend with API-backed search.
976
+
977
+ ### Q: How do I make a field searchable?
978
+ A: In your entity config, set `api.searchable: true` on the field.
979
+
980
+ ### Q: Can I search across multiple entities?
981
+ A: Yes, useEntitySearch automatically searches all entities with
982
+ `ui.features.searchable: true` in their config.
983
+
984
+ ### Q: How do I customize result ranking?
985
+ A: Override the relevance scoring function or post-process results
986
+ with your own sorting logic.
987
+
988
+ ### Q: Why do I need to pass entities from server?
989
+ A: Entity configs must come from server components to ensure
990
+ proper permission filtering and avoid client-side registry access.
991
+
992
+ ---
993
+
994
+ ## Related Commands
995
+
996
+ - `/how-to:create-entity` - Create entities with searchable fields
997
+ - `/how-to:create-api` - API endpoints that support search
998
+ - `/how-to:use-devtools` - Test search in API explorer
999
+ - `/skill:react-patterns` - React patterns for search UIs
1000
+ - `/skill:tanstack-query` - Data fetching patterns
1001
+ - `/skill:cypress-selectors` - Test selectors for search components