ndomo 0.1.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 (247) hide show
  1. package/.bun-version +1 -0
  2. package/.dockerignore +79 -0
  3. package/.editorconfig +18 -0
  4. package/.env.example +19 -0
  5. package/.github/CODEOWNERS +8 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +62 -0
  7. package/.github/ISSUE_TEMPLATE/config.yml +2 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.yml +34 -0
  9. package/.github/dependabot.yml +36 -0
  10. package/.github/pull_request_template.md +24 -0
  11. package/.github/release.yml +30 -0
  12. package/.github/workflows/gitleaks.yml +28 -0
  13. package/.github/workflows/release-please.yml +27 -0
  14. package/.github/workflows/smoke.yml +29 -0
  15. package/.husky/commit-msg +1 -0
  16. package/CHANGELOG.md +114 -0
  17. package/Dockerfile +32 -0
  18. package/README.es.md +174 -0
  19. package/README.md +187 -0
  20. package/agents/chronicler.md +98 -0
  21. package/agents/ci-smith.md +136 -0
  22. package/agents/craftsman.md +341 -0
  23. package/agents/deploy-smith.md +138 -0
  24. package/agents/foreman.md +377 -0
  25. package/agents/go-smith.md +164 -0
  26. package/agents/guild.md +188 -0
  27. package/agents/inspector.md +83 -0
  28. package/agents/js-smith.md +127 -0
  29. package/agents/ops-scout.md +173 -0
  30. package/agents/painter.md +200 -0
  31. package/agents/python-smith.md +120 -0
  32. package/agents/ranger.md +307 -0
  33. package/agents/release-smith.md +165 -0
  34. package/agents/rust-smith.md +159 -0
  35. package/agents/sage.md +178 -0
  36. package/agents/scout.md +144 -0
  37. package/agents/scribe.md +156 -0
  38. package/agents/smith.md +201 -0
  39. package/agents/vue-smith.md +155 -0
  40. package/agents/warden.md +216 -0
  41. package/agents/zig-smith.md +156 -0
  42. package/bin/ndomo-analyses.ts +4 -0
  43. package/bin/ndomo-status.ts +4 -0
  44. package/biome.json +57 -0
  45. package/bun.lock +514 -0
  46. package/commitlint.config.js +3 -0
  47. package/config/ndomo.config.json +258 -0
  48. package/config/ndomo.schema.json +166 -0
  49. package/docs/agents.md +375 -0
  50. package/docs/bugs/plan-create-orphan-fk.md +131 -0
  51. package/docs/bugs/task_create_batch-order-index-collision.md +158 -0
  52. package/docs/configuration.md +276 -0
  53. package/docs/database.md +364 -0
  54. package/docs/features/feature-flexible-builder-v1.md +724 -0
  55. package/docs/features/feature-flexible-builder-v2.md +882 -0
  56. package/docs/features/feature-flexible-builder.md +974 -0
  57. package/docs/http-server.md +244 -0
  58. package/docs/installation.md +259 -0
  59. package/docs/integrations.md +129 -0
  60. package/docs/operations/anti-pattern-sub-agent-verify-2026-06-21.md +32 -0
  61. package/docs/operations/audit-v1.md +417 -0
  62. package/docs/operations/audit-v2.md +197 -0
  63. package/docs/operations/audit-v3.md +306 -0
  64. package/docs/operations/db-optimize-foundations.md +123 -0
  65. package/docs/operations/verify-gate-architecture.md +82 -0
  66. package/docs/workflows.md +448 -0
  67. package/opencode.json +5 -0
  68. package/package.json +65 -0
  69. package/release-please-config.json +11 -0
  70. package/scripts/dev-bust-cache.sh +164 -0
  71. package/scripts/install.sh +688 -0
  72. package/scripts/smoke-e2e.ts +704 -0
  73. package/scripts/smoke-hot.ts +417 -0
  74. package/scripts/smoke-http.sh +228 -0
  75. package/scripts/smoke-v4.ts +256 -0
  76. package/scripts/smoke-v5.ts +397 -0
  77. package/scripts/smoke.sh +9 -0
  78. package/scripts/uninstall.sh +224 -0
  79. package/skills/api-security-best-practices/SKILL.md +915 -0
  80. package/skills/bash-scripting/SKILL.md +201 -0
  81. package/skills/bun/SKILL.md +313 -0
  82. package/skills/cavecrew/SKILL.md +82 -0
  83. package/skills/caveman/SKILL.md +74 -0
  84. package/skills/caveman-review/README.md +33 -0
  85. package/skills/caveman-review/SKILL.md +55 -0
  86. package/skills/find-skills/SKILL.md +142 -0
  87. package/skills/frontend-design/LICENSE.txt +177 -0
  88. package/skills/frontend-design/SKILL.md +55 -0
  89. package/skills/golang-patterns/SKILL.md +674 -0
  90. package/skills/golang-security/SKILL.md +185 -0
  91. package/skills/golang-security/evals/evals.json +595 -0
  92. package/skills/golang-security/references/architecture.md +268 -0
  93. package/skills/golang-security/references/checklist.md +80 -0
  94. package/skills/golang-security/references/cookies.md +200 -0
  95. package/skills/golang-security/references/cryptography.md +424 -0
  96. package/skills/golang-security/references/filesystem.md +285 -0
  97. package/skills/golang-security/references/injection.md +315 -0
  98. package/skills/golang-security/references/logging.md +163 -0
  99. package/skills/golang-security/references/memory-safety.md +241 -0
  100. package/skills/golang-security/references/network.md +253 -0
  101. package/skills/golang-security/references/secrets.md +189 -0
  102. package/skills/golang-security/references/third-party.md +159 -0
  103. package/skills/golang-security/references/threat-modeling.md +189 -0
  104. package/skills/golang-testing/SKILL.md +720 -0
  105. package/skills/grill-me/SKILL.md +7 -0
  106. package/skills/javascript-testing-patterns/SKILL.md +537 -0
  107. package/skills/javascript-testing-patterns/references/advanced-testing-patterns.md +513 -0
  108. package/skills/modern-javascript-patterns/SKILL.md +43 -0
  109. package/skills/modern-javascript-patterns/references/advanced-patterns.md +487 -0
  110. package/skills/modern-javascript-patterns/references/details.md +457 -0
  111. package/skills/python-anti-patterns/SKILL.md +349 -0
  112. package/skills/python-design-patterns/SKILL.md +85 -0
  113. package/skills/python-design-patterns/references/details.md +353 -0
  114. package/skills/python-error-handling/SKILL.md +193 -0
  115. package/skills/python-error-handling/references/details.md +171 -0
  116. package/skills/python-testing-patterns/SKILL.md +278 -0
  117. package/skills/python-testing-patterns/references/advanced-patterns.md +411 -0
  118. package/skills/python-testing-patterns/references/details.md +349 -0
  119. package/skills/rust-patterns/SKILL.md +500 -0
  120. package/skills/rust-testing/SKILL.md +501 -0
  121. package/skills/security-review/SKILL.md +504 -0
  122. package/skills/security-review/cloud-infrastructure-security.md +361 -0
  123. package/skills/vue-best-practices/SKILL.md +154 -0
  124. package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
  125. package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
  126. package/skills/vue-best-practices/references/component-async.md +97 -0
  127. package/skills/vue-best-practices/references/component-data-flow.md +307 -0
  128. package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
  129. package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
  130. package/skills/vue-best-practices/references/component-slots.md +216 -0
  131. package/skills/vue-best-practices/references/component-suspense.md +228 -0
  132. package/skills/vue-best-practices/references/component-teleport.md +108 -0
  133. package/skills/vue-best-practices/references/component-transition-group.md +128 -0
  134. package/skills/vue-best-practices/references/component-transition.md +125 -0
  135. package/skills/vue-best-practices/references/composables.md +290 -0
  136. package/skills/vue-best-practices/references/directives.md +162 -0
  137. package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
  138. package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
  139. package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
  140. package/skills/vue-best-practices/references/plugins.md +166 -0
  141. package/skills/vue-best-practices/references/reactivity.md +344 -0
  142. package/skills/vue-best-practices/references/render-functions.md +201 -0
  143. package/skills/vue-best-practices/references/sfc.md +310 -0
  144. package/skills/vue-best-practices/references/state-management.md +135 -0
  145. package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
  146. package/skills/vue-pinia-best-practices/SKILL.md +21 -0
  147. package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
  148. package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
  149. package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
  150. package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
  151. package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
  152. package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
  153. package/skills/zig-0.16/SKILL.md +840 -0
  154. package/skills/zig-0.16/scripts/check-zig-version.sh +21 -0
  155. package/src/cli/analyses.ts +280 -0
  156. package/src/cli/index.ts +108 -0
  157. package/src/cli/serve.ts +192 -0
  158. package/src/cli/smoke.ts +131 -0
  159. package/src/cli/status.test.ts +204 -0
  160. package/src/cli/status.ts +263 -0
  161. package/src/cli/vacuum.test.ts +82 -0
  162. package/src/cli/vacuum.ts +96 -0
  163. package/src/config/schema.test.ts +88 -0
  164. package/src/config/schema.ts +64 -0
  165. package/src/db/analyses-migration.test.ts +210 -0
  166. package/src/db/analyses.test.ts +466 -0
  167. package/src/db/analyses.ts +375 -0
  168. package/src/db/auto-checkpoint.ts +131 -0
  169. package/src/db/client.test.ts +129 -0
  170. package/src/db/client.ts +55 -0
  171. package/src/db/fts-escape.ts +20 -0
  172. package/src/db/incidents.test.ts +201 -0
  173. package/src/db/incidents.ts +93 -0
  174. package/src/db/index.ts +86 -0
  175. package/src/db/migrations-v13.test.ts +141 -0
  176. package/src/db/migrations-v8.test.ts +301 -0
  177. package/src/db/migrations.ts +147 -0
  178. package/src/db/plan-archive.test.ts +180 -0
  179. package/src/db/plan-archive.ts +274 -0
  180. package/src/db/plan-create.test.ts +276 -0
  181. package/src/db/plan-create.ts +78 -0
  182. package/src/db/plan-files.test.ts +289 -0
  183. package/src/db/plan-update-status.ts +287 -0
  184. package/src/db/plans.test.ts +490 -0
  185. package/src/db/plans.ts +534 -0
  186. package/src/db/resolve-project-dir.test.ts +143 -0
  187. package/src/db/resolve-project-dir.ts +75 -0
  188. package/src/db/rollbacks.test.ts +150 -0
  189. package/src/db/rollbacks.ts +67 -0
  190. package/src/db/schema.ts +907 -0
  191. package/src/db/sessions.test.ts +80 -0
  192. package/src/db/sessions.ts +135 -0
  193. package/src/db/shutdown.test.ts +147 -0
  194. package/src/db/shutdown.ts +45 -0
  195. package/src/db/tasks.test.ts +921 -0
  196. package/src/db/tasks.ts +747 -0
  197. package/src/db/types.ts +619 -0
  198. package/src/http/__tests__/auth.test.ts +196 -0
  199. package/src/http/__tests__/routes.test.ts +465 -0
  200. package/src/http/__tests__/sse.test.ts +317 -0
  201. package/src/http/auth.ts +72 -0
  202. package/src/http/middleware/cors.ts +53 -0
  203. package/src/http/middleware/security-headers.ts +21 -0
  204. package/src/http/routes/events.ts +112 -0
  205. package/src/http/routes/health.ts +51 -0
  206. package/src/http/routes/plans.ts +66 -0
  207. package/src/http/routes/sessions.ts +50 -0
  208. package/src/http/routes/tasks.ts +60 -0
  209. package/src/http/server.ts +95 -0
  210. package/src/http/sse.ts +116 -0
  211. package/src/index.ts +37 -0
  212. package/src/lib.ts +65 -0
  213. package/src/mem/scoped.ts +65 -0
  214. package/src/orchestrator/background.test.ts +268 -0
  215. package/src/orchestrator/background.ts +293 -0
  216. package/src/orchestrator/memory-hook.ts +182 -0
  217. package/src/orchestrator/reconciler.ts +123 -0
  218. package/src/orchestrator/scheduler.test.ts +300 -0
  219. package/src/orchestrator/scheduler.ts +243 -0
  220. package/src/plugin.test.ts +2574 -0
  221. package/src/plugin.ts +1690 -0
  222. package/src/sdk/client.ts +66 -0
  223. package/src/worktrees/manager.ts +236 -0
  224. package/src/worktrees/state.ts +87 -0
  225. package/tests/integration/ranger-flow.test.ts +257 -0
  226. package/tools/analysis_archive.ts +28 -0
  227. package/tools/analysis_create.ts +55 -0
  228. package/tools/analysis_get.ts +33 -0
  229. package/tools/analysis_link_plan.ts +44 -0
  230. package/tools/analysis_list.ts +48 -0
  231. package/tools/analysis_search.ts +36 -0
  232. package/tools/analysis_update.ts +44 -0
  233. package/tools/plan_approve.ts +31 -0
  234. package/tools/plan_create.ts +58 -0
  235. package/tools/plan_get.ts +40 -0
  236. package/tools/plan_list.ts +37 -0
  237. package/tools/plan_search.ts +34 -0
  238. package/tools/plan_update_status.ts +71 -0
  239. package/tools/session_checkpoint.ts +31 -0
  240. package/tools/session_end.ts +26 -0
  241. package/tools/session_start.ts +43 -0
  242. package/tools/task_create_batch.ts +70 -0
  243. package/tools/task_list.ts +35 -0
  244. package/tools/task_next_for_agent.ts +30 -0
  245. package/tools/task_search.ts +34 -0
  246. package/tools/task_update_status.ts +37 -0
  247. package/tsconfig.json +31 -0
@@ -0,0 +1,125 @@
1
+ ---
2
+ title: Transition Component Best Practices
3
+ impact: MEDIUM
4
+ impactDescription: Transition animates a single element or component; incorrect structure or keys prevent animations
5
+ type: best-practice
6
+ tags: [vue3, transition, animation, performance, keys]
7
+ ---
8
+
9
+ # Transition Component Best Practices
10
+
11
+ **Impact: MEDIUM** - `<Transition>` animates entering/leaving of a single element or component. It is ideal for toggling UI states, swapping views, or animating one component at a time.
12
+
13
+ ## Task List
14
+
15
+ - Wrap a single element or component inside `<Transition>`
16
+ - Provide a `key` when switching between same element types
17
+ - Use `mode="out-in"` when you need sequential swaps
18
+ - Prefer `transform` and `opacity` for smooth animations
19
+
20
+ ## Use Transition for a Single Root Element
21
+
22
+ `<Transition>` only supports one direct child. Wrap multiple nodes in a single element or component.
23
+
24
+ **BAD:**
25
+ ```vue
26
+ <template>
27
+ <Transition name="fade">
28
+ <h3>Title</h3>
29
+ <p>Description</p>
30
+ </Transition>
31
+ </template>
32
+ ```
33
+
34
+ **GOOD:**
35
+ ```vue
36
+ <template>
37
+ <Transition name="fade">
38
+ <div>
39
+ <h3>Title</h3>
40
+ <p>Description</p>
41
+ </div>
42
+ </Transition>
43
+ </template>
44
+ ```
45
+
46
+ ## Force Transitions Between Same Element Types
47
+
48
+ Vue reuses the same DOM element when the tag type does not change. Add `key` so Vue treats it as a new element and triggers enter/leave.
49
+
50
+ **BAD:**
51
+ ```vue
52
+ <template>
53
+ <Transition name="fade">
54
+ <p v-if="isActive">Active</p>
55
+ <p v-else>Inactive</p>
56
+ </Transition>
57
+ </template>
58
+ ```
59
+
60
+ **GOOD:**
61
+ ```vue
62
+ <template>
63
+ <Transition name="fade" mode="out-in">
64
+ <p v-if="isActive" key="active">Active</p>
65
+ <p v-else key="inactive">Inactive</p>
66
+ </Transition>
67
+ </template>
68
+ ```
69
+
70
+ ## Use `mode` to Avoid Overlap During Swaps
71
+
72
+ When swapping components or views, use `mode="out-in"` to prevent both from being visible at the same time.
73
+
74
+ **BAD:**
75
+ ```vue
76
+ <template>
77
+ <Transition name="fade">
78
+ <component :is="currentView" />
79
+ </Transition>
80
+ </template>
81
+ ```
82
+
83
+ **GOOD:**
84
+ ```vue
85
+ <template>
86
+ <Transition name="fade" mode="out-in">
87
+ <component :is="currentView" :key="currentView" />
88
+ </Transition>
89
+ </template>
90
+ ```
91
+
92
+ ## Animate `transform` and `opacity` for Performance
93
+
94
+ Avoid layout-triggering properties such as `height`, `margin`, or `top`. Use `transform` and `opacity` for smooth, GPU-friendly transitions.
95
+
96
+ **BAD:**
97
+ ```css
98
+ .slide-enter-active,
99
+ .slide-leave-active {
100
+ transition: height 0.3s ease;
101
+ }
102
+
103
+ .slide-enter-from,
104
+ .slide-leave-to {
105
+ height: 0;
106
+ }
107
+ ```
108
+
109
+ **GOOD:**
110
+ ```css
111
+ .slide-enter-active,
112
+ .slide-leave-active {
113
+ transition: transform 0.3s ease, opacity 0.3s ease;
114
+ }
115
+
116
+ .slide-enter-from {
117
+ transform: translateX(-12px);
118
+ opacity: 0;
119
+ }
120
+
121
+ .slide-leave-to {
122
+ transform: translateX(12px);
123
+ opacity: 0;
124
+ }
125
+ ```
@@ -0,0 +1,290 @@
1
+ ---
2
+ title: Composable Organization Patterns
3
+ impact: MEDIUM
4
+ impactDescription: Well-structured composables improve maintainability, reusability, and update performance
5
+ type: best-practice
6
+ tags: [vue3, composables, composition-api, code-organization, api-design, readonly, utilities]
7
+ ---
8
+
9
+ # Composable Organization Patterns
10
+
11
+ **Impact: MEDIUM** - Treat composables as reusable, stateful building blocks and keep their code organized by feature concern. This keeps large components maintainable and prevents hard-to-debug mutation and API design issues.
12
+
13
+ ## Task List
14
+
15
+ - Compose complex behavior from small, focused composables
16
+ - Use options objects for composables with multiple optional parameters
17
+ - Return readonly state when updates must flow through explicit actions
18
+ - Keep pure utility functions as plain utilities, not composables
19
+ - Organize composable and component code by feature concern, and extract composables when components grow
20
+
21
+ ## Compose Composables from Smaller Primitives
22
+
23
+ **BAD:**
24
+ ```vue
25
+ <script setup>
26
+ import { ref, computed, onMounted, onUnmounted } from 'vue'
27
+
28
+ const x = ref(0)
29
+ const y = ref(0)
30
+ const inside = ref(false)
31
+ const el = ref(null)
32
+
33
+ function onMove(e) {
34
+ x.value = e.pageX
35
+ y.value = e.pageY
36
+ if (!el.value) return
37
+ const r = el.value.getBoundingClientRect()
38
+ inside.value = x.value >= r.left && x.value <= r.right &&
39
+ y.value >= r.top && y.value <= r.bottom
40
+ }
41
+
42
+ onMounted(() => window.addEventListener('mousemove', onMove))
43
+ onUnmounted(() => window.removeEventListener('mousemove', onMove))
44
+ </script>
45
+ ```
46
+
47
+ **GOOD:**
48
+ ```javascript
49
+ // composables/useEventListener.js
50
+ import { onMounted, onUnmounted, toValue } from 'vue'
51
+
52
+ export function useEventListener(target, event, callback) {
53
+ onMounted(() => toValue(target).addEventListener(event, callback))
54
+ onUnmounted(() => toValue(target).removeEventListener(event, callback))
55
+ }
56
+ ```
57
+
58
+ ```javascript
59
+ // composables/useMouse.js
60
+ import { ref } from 'vue'
61
+ import { useEventListener } from './useEventListener'
62
+
63
+ export function useMouse() {
64
+ const x = ref(0)
65
+ const y = ref(0)
66
+
67
+ useEventListener(window, 'mousemove', (e) => {
68
+ x.value = e.pageX
69
+ y.value = e.pageY
70
+ })
71
+
72
+ return { x, y }
73
+ }
74
+ ```
75
+
76
+ ```javascript
77
+ // composables/useMouseInElement.js
78
+ import { computed } from 'vue'
79
+ import { useMouse } from './useMouse'
80
+
81
+ export function useMouseInElement(elementRef) {
82
+ const { x, y } = useMouse()
83
+
84
+ const isOutside = computed(() => {
85
+ if (!elementRef.value) return true
86
+ const rect = elementRef.value.getBoundingClientRect()
87
+ return x.value < rect.left || x.value > rect.right ||
88
+ y.value < rect.top || y.value > rect.bottom
89
+ })
90
+
91
+ return { x, y, isOutside }
92
+ }
93
+ ```
94
+
95
+ ## Use Options Object Pattern for Composable Parameters
96
+
97
+ **BAD:**
98
+ ```javascript
99
+ export function useFetch(url, method, headers, timeout, retries, immediate) {
100
+ // hard to read and easy to misorder
101
+ }
102
+
103
+ useFetch('/api/users', 'GET', null, 5000, 3, true)
104
+ ```
105
+
106
+ **GOOD:**
107
+ ```javascript
108
+ export function useFetch(url, options = {}) {
109
+ const {
110
+ method = 'GET',
111
+ headers = {},
112
+ timeout = 30000,
113
+ retries = 0,
114
+ immediate = true
115
+ } = options
116
+
117
+ // implementation
118
+ return { method, headers, timeout, retries, immediate }
119
+ }
120
+
121
+ useFetch('/api/users', {
122
+ method: 'POST',
123
+ timeout: 5000,
124
+ retries: 3
125
+ })
126
+ ```
127
+
128
+ ```typescript
129
+ interface UseCounterOptions {
130
+ initial?: number
131
+ min?: number
132
+ max?: number
133
+ step?: number
134
+ }
135
+
136
+ export function useCounter(options: UseCounterOptions = {}) {
137
+ const { initial = 0, min = -Infinity, max = Infinity, step = 1 } = options
138
+ // implementation
139
+ }
140
+ ```
141
+
142
+ ## Return Readonly State with Explicit Actions
143
+
144
+ **BAD:**
145
+ ```javascript
146
+ export function useCart() {
147
+ const items = ref([])
148
+ const total = computed(() => items.value.reduce((sum, item) => sum + item.price, 0))
149
+ return { items, total } // any consumer can mutate directly
150
+ }
151
+
152
+ const { items } = useCart()
153
+ items.value.push({ id: 1, price: 10 })
154
+ ```
155
+
156
+ **GOOD:**
157
+ ```javascript
158
+ import { ref, computed, readonly } from 'vue'
159
+
160
+ export function useCart() {
161
+ const _items = ref([])
162
+
163
+ const total = computed(() =>
164
+ _items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
165
+ )
166
+
167
+ function addItem(product, quantity = 1) {
168
+ const existing = _items.value.find(item => item.id === product.id)
169
+ if (existing) {
170
+ existing.quantity += quantity
171
+ return
172
+ }
173
+ _items.value.push({ ...product, quantity })
174
+ }
175
+
176
+ function removeItem(productId) {
177
+ _items.value = _items.value.filter(item => item.id !== productId)
178
+ }
179
+
180
+ return {
181
+ items: readonly(_items),
182
+ total,
183
+ addItem,
184
+ removeItem
185
+ }
186
+ }
187
+ ```
188
+
189
+ ## Keep Utilities as Utilities
190
+
191
+ **BAD:**
192
+ ```javascript
193
+ export function useFormatters() {
194
+ const formatDate = (date) => new Intl.DateTimeFormat('en-US').format(date)
195
+ const formatCurrency = (amount) =>
196
+ new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount)
197
+ return { formatDate, formatCurrency }
198
+ }
199
+
200
+ const { formatDate } = useFormatters()
201
+ ```
202
+
203
+ **GOOD:**
204
+ ```javascript
205
+ // utils/formatters.js
206
+ export function formatDate(date) {
207
+ return new Intl.DateTimeFormat('en-US').format(date)
208
+ }
209
+
210
+ export function formatCurrency(amount) {
211
+ return new Intl.NumberFormat('en-US', {
212
+ style: 'currency',
213
+ currency: 'USD'
214
+ }).format(amount)
215
+ }
216
+ ```
217
+
218
+ ```javascript
219
+ // composables/useInvoiceSummary.js
220
+ import { computed } from 'vue'
221
+ import { formatCurrency } from '@/utils/formatters'
222
+
223
+ export function useInvoiceSummary(invoiceRef) {
224
+ const totalLabel = computed(() => formatCurrency(invoiceRef.value.total))
225
+ return { totalLabel }
226
+ }
227
+ ```
228
+
229
+ ## Organize Composable and Component Code by Feature Concern
230
+
231
+ **BAD:**
232
+ ```vue
233
+ <script setup>
234
+ import { ref, computed, watch, onMounted } from 'vue'
235
+
236
+ const searchQuery = ref('')
237
+ const items = ref([])
238
+ const selected = ref(null)
239
+ const showModal = ref(false)
240
+ const sortBy = ref('name')
241
+ const filter = ref('all')
242
+ const loading = ref(false)
243
+
244
+ const filtered = computed(() => items.value.filter(i => i.category === filter.value))
245
+ function openModal() { showModal.value = true }
246
+ const sorted = computed(() => [...filtered.value].sort(/* ... */))
247
+ watch(searchQuery, () => { /* ... */ })
248
+ onMounted(() => { /* ... */ })
249
+ </script>
250
+ ```
251
+
252
+ **GOOD:**
253
+ ```vue
254
+ <script setup>
255
+ import { useItems } from '@/composables/useItems'
256
+ import { useSearch } from '@/composables/useSearch'
257
+ import { useSelectionModal } from '@/composables/useSelectionModal'
258
+
259
+ // Data
260
+ const { items, loading, fetchItems } = useItems()
261
+
262
+ // Search/filter/sort
263
+ const { query, visibleItems } = useSearch(items)
264
+
265
+ // Selection + modal
266
+ const { selectedItem, isModalOpen, selectItem, closeModal } = useSelectionModal()
267
+ </script>
268
+ ```
269
+
270
+ ```javascript
271
+ // composables/useItems.js
272
+ import { ref, onMounted } from 'vue'
273
+
274
+ export function useItems() {
275
+ const items = ref([])
276
+ const loading = ref(false)
277
+
278
+ async function fetchItems() {
279
+ loading.value = true
280
+ try {
281
+ items.value = await api.getItems()
282
+ } finally {
283
+ loading.value = false
284
+ }
285
+ }
286
+
287
+ onMounted(fetchItems)
288
+ return { items, loading, fetchItems }
289
+ }
290
+ ```
@@ -0,0 +1,162 @@
1
+ ---
2
+ title: Directive Best Practices
3
+ impact: MEDIUM
4
+ impactDescription: Custom directives are powerful but easy to misuse; following patterns prevents leaks, invalid usage, and unclear abstractions
5
+ type: best-practice
6
+ tags: [vue3, directives, custom-directives, composition, typescript]
7
+ ---
8
+
9
+ # Directive Best Practices
10
+
11
+ **Impact: MEDIUM** - Directives are for low-level DOM access. Use them sparingly, keep them side-effect safe, and prefer components or composables when you need stateful or reusable UI behavior.
12
+
13
+ ## Task List
14
+
15
+ - Use directives only when you need direct DOM access
16
+ - Do not mutate directive arguments or binding objects
17
+ - Clean up timers, listeners, and observers in `unmounted`
18
+ - Register directives in `<script setup>` with the `v-` prefix
19
+ - In TypeScript projects, type directive values and augment template directive types
20
+ - Prefer components or composables for complex behavior
21
+
22
+ ## Treat Directive Arguments as Read-Only
23
+
24
+ Directive bindings are not reactive storage. Don’t write to them.
25
+
26
+ ```ts
27
+ const vFocus = {
28
+ mounted(el, binding) {
29
+ // binding.value is read-only
30
+ el.focus()
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Avoid Directives on Components
36
+
37
+ Directives apply to DOM elements. When used on components, they attach to the root element and can break if the root changes.
38
+
39
+ **BAD:**
40
+ ```vue
41
+ <MyInput v-focus />
42
+ ```
43
+
44
+ **GOOD:**
45
+ ```vue
46
+ <!-- MyInput.vue -->
47
+ <script setup>
48
+ const vFocus = (el) => el.focus()
49
+ </script>
50
+
51
+ <template>
52
+ <input v-focus />
53
+ </template>
54
+ ```
55
+
56
+ ## Clean Up Side Effects in `unmounted`
57
+
58
+ Any timers, listeners, or observers must be removed to avoid leaks.
59
+
60
+ ```ts
61
+ const vResize = {
62
+ mounted(el) {
63
+ const observer = new ResizeObserver(() => {})
64
+ observer.observe(el)
65
+ el._observer = observer
66
+ },
67
+ unmounted(el) {
68
+ el._observer?.disconnect()
69
+ }
70
+ }
71
+ ```
72
+
73
+ ## Prefer Function Shorthand for Single-Hook Directives
74
+
75
+ If you only need `mounted`/`updated`, use the function form.
76
+
77
+ ```ts
78
+ const vAutofocus = (el) => el.focus()
79
+ ```
80
+
81
+ ## Use the `v-` Prefix and Script Setup Registration
82
+
83
+ ```vue
84
+ <script setup>
85
+ const vFocus = (el) => el.focus()
86
+ </script>
87
+
88
+ <template>
89
+ <input v-focus />
90
+ </template>
91
+ ```
92
+
93
+ ## Type Custom Directives in TypeScript Projects
94
+
95
+ Use `Directive<Element, ValueType>` so `binding.value` is typed, and augment Vue's template types so directives are recognized in SFC templates.
96
+
97
+ **BAD:**
98
+ ```ts
99
+ // Untyped directive value and no template type augmentation
100
+ export const vHighlight = {
101
+ mounted(el, binding) {
102
+ el.style.backgroundColor = binding.value
103
+ }
104
+ }
105
+ ```
106
+
107
+ **GOOD:**
108
+ ```ts
109
+ import type { Directive } from 'vue'
110
+
111
+ type HighlightValue = string
112
+
113
+ export const vHighlight = {
114
+ mounted(el, binding) {
115
+ el.style.backgroundColor = binding.value
116
+ }
117
+ } satisfies Directive<HTMLElement, HighlightValue>
118
+
119
+ declare module 'vue' {
120
+ interface ComponentCustomProperties {
121
+ vHighlight: typeof vHighlight
122
+ }
123
+ }
124
+ ```
125
+
126
+ ## Handle SSR with `getSSRProps`
127
+
128
+ Directive hooks such as `mounted` and `updated` do not run during SSR. If a directive sets attributes/classes that affect rendered HTML, provide an SSR equivalent via `getSSRProps` to avoid hydration mismatches.
129
+
130
+ **BAD:**
131
+ ```ts
132
+ const vTooltip = {
133
+ mounted(el, binding) {
134
+ el.setAttribute('data-tooltip', binding.value)
135
+ el.classList.add('has-tooltip')
136
+ }
137
+ }
138
+ ```
139
+
140
+ **GOOD:**
141
+ ```ts
142
+ const vTooltip = {
143
+ mounted(el, binding) {
144
+ el.setAttribute('data-tooltip', binding.value)
145
+ el.classList.add('has-tooltip')
146
+ },
147
+ getSSRProps(binding) {
148
+ return {
149
+ 'data-tooltip': binding.value,
150
+ class: 'has-tooltip'
151
+ }
152
+ }
153
+ }
154
+ ```
155
+
156
+ ## Prefer Declarative Templates When Possible
157
+
158
+ If a standard attribute or binding works, use it instead of a directive.
159
+
160
+ ## Decide Between Directives and Components
161
+
162
+ Use a directive for DOM-level behavior. Use a component when behavior affects structure, state, or rendering.