@dianzhong/create-harness-app 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 (95) hide show
  1. package/dist/index.mjs +412 -0
  2. package/package.json +29 -0
  3. package/templates/axios/.env.example +2 -0
  4. package/templates/axios/src/api/auth.ts +19 -0
  5. package/templates/axios/src/api/request.ts +61 -0
  6. package/templates/axios/src/types/api.ts +26 -0
  7. package/templates/axios/src/utils/auth.ts +5 -0
  8. package/templates/axios/src/utils/storage.ts +17 -0
  9. package/templates/harness/full/.agents/skills/find-skills/SKILL.md +143 -0
  10. package/templates/harness/full/.agents/skills/vue-best-practices/LICENSE.md +21 -0
  11. package/templates/harness/full/.agents/skills/vue-best-practices/SKILL.md +155 -0
  12. package/templates/harness/full/.agents/skills/vue-best-practices/SYNC.md +5 -0
  13. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-class-based-technique.md +258 -0
  14. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-state-driven-technique.md +287 -0
  15. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-async.md +99 -0
  16. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-data-flow.md +313 -0
  17. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-fallthrough-attrs.md +179 -0
  18. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-keep-alive.md +139 -0
  19. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-slots.md +226 -0
  20. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-suspense.md +231 -0
  21. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-teleport.md +110 -0
  22. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition-group.md +131 -0
  23. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition.md +135 -0
  24. package/templates/harness/full/.agents/skills/vue-best-practices/references/composables.md +303 -0
  25. package/templates/harness/full/.agents/skills/vue-best-practices/references/directives.md +168 -0
  26. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +177 -0
  27. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +185 -0
  28. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-virtualize-large-lists.md +182 -0
  29. package/templates/harness/full/.agents/skills/vue-best-practices/references/plugins.md +178 -0
  30. package/templates/harness/full/.agents/skills/vue-best-practices/references/reactivity.md +371 -0
  31. package/templates/harness/full/.agents/skills/vue-best-practices/references/render-functions.md +227 -0
  32. package/templates/harness/full/.agents/skills/vue-best-practices/references/sfc.md +355 -0
  33. package/templates/harness/full/.agents/skills/vue-best-practices/references/state-management.md +138 -0
  34. package/templates/harness/full/.agents/skills/vue-best-practices/references/updated-hook-performance.md +193 -0
  35. package/templates/harness/full/.claude/agents/code-reviewer.md +109 -0
  36. package/templates/harness/full/.claude/agents/harness-reviewer.md +51 -0
  37. package/templates/harness/full/.claude/hooks/guard-tool.cjs +234 -0
  38. package/templates/harness/full/.claude/hooks/notify.cjs +168 -0
  39. package/templates/harness/full/.claude/hooks/quality-gate.cjs +135 -0
  40. package/templates/harness/full/.claude/rules/delivery.md +66 -0
  41. package/templates/harness/full/.claude/rules/formatting.md +7 -0
  42. package/templates/harness/full/.claude/rules/git.md +8 -0
  43. package/templates/harness/full/.claude/rules/skills-mcp.md +13 -0
  44. package/templates/harness/full/.claude/rules/vue.md +227 -0
  45. package/templates/harness/full/.claude/settings.json +123 -0
  46. package/templates/harness/full/.claude/skills/find-skills/SKILL.md +143 -0
  47. package/templates/harness/full/.claude/skills/vue-best-practices/LICENSE.md +21 -0
  48. package/templates/harness/full/.claude/skills/vue-best-practices/SKILL.md +155 -0
  49. package/templates/harness/full/.claude/skills/vue-best-practices/SYNC.md +5 -0
  50. package/templates/harness/full/.claude/skills/vue-best-practices/references/animation-class-based-technique.md +258 -0
  51. package/templates/harness/full/.claude/skills/vue-best-practices/references/animation-state-driven-technique.md +287 -0
  52. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-async.md +99 -0
  53. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-data-flow.md +313 -0
  54. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-fallthrough-attrs.md +179 -0
  55. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-keep-alive.md +139 -0
  56. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-slots.md +226 -0
  57. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-suspense.md +231 -0
  58. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-teleport.md +110 -0
  59. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-transition-group.md +131 -0
  60. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-transition.md +135 -0
  61. package/templates/harness/full/.claude/skills/vue-best-practices/references/composables.md +303 -0
  62. package/templates/harness/full/.claude/skills/vue-best-practices/references/directives.md +168 -0
  63. package/templates/harness/full/.claude/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +177 -0
  64. package/templates/harness/full/.claude/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +185 -0
  65. package/templates/harness/full/.claude/skills/vue-best-practices/references/perf-virtualize-large-lists.md +182 -0
  66. package/templates/harness/full/.claude/skills/vue-best-practices/references/plugins.md +178 -0
  67. package/templates/harness/full/.claude/skills/vue-best-practices/references/reactivity.md +371 -0
  68. package/templates/harness/full/.claude/skills/vue-best-practices/references/render-functions.md +227 -0
  69. package/templates/harness/full/.claude/skills/vue-best-practices/references/sfc.md +355 -0
  70. package/templates/harness/full/.claude/skills/vue-best-practices/references/state-management.md +138 -0
  71. package/templates/harness/full/.claude/skills/vue-best-practices/references/updated-hook-performance.md +193 -0
  72. package/templates/harness/full/.editorconfig +8 -0
  73. package/templates/harness/full/.husky/commit-msg +1 -0
  74. package/templates/harness/full/.husky/pre-commit +1 -0
  75. package/templates/harness/full/.lintstagedrc.json +4 -0
  76. package/templates/harness/full/.nvmrc +1 -0
  77. package/templates/harness/full/.oxlintrc.json +11 -0
  78. package/templates/harness/full/.prettierrc.json +6 -0
  79. package/templates/harness/full/AGENTS.md +3 -0
  80. package/templates/harness/full/CLAUDE.md +28 -0
  81. package/templates/harness/full/GEMINI.md +3 -0
  82. package/templates/harness/full/commitlint.config.ts +3 -0
  83. package/templates/harness/full/docs/ai-harness.md +77 -0
  84. package/templates/harness/full/docs/delivery-template.md +66 -0
  85. package/templates/harness/full/docs/git.md +24 -0
  86. package/templates/harness/full/docs/harness-quick-reference.md +89 -0
  87. package/templates/harness/full/docs/review-checklist.md +49 -0
  88. package/templates/harness/full/scripts/harness-hooks.test.mjs +218 -0
  89. package/templates/harness/full/scripts/verify-skills.mjs +248 -0
  90. package/templates/harness/full/scripts/verify-skills.test.mjs +72 -0
  91. package/templates/harness/full/skills-lock.json +50 -0
  92. package/templates/harness/minimal/.claude/hooks/guard-tool.cjs +234 -0
  93. package/templates/harness/minimal/.claude/hooks/quality-gate.cjs +135 -0
  94. package/templates/harness/minimal/.claude/settings.json +27 -0
  95. package/templates/harness/minimal/CLAUDE.md +12 -0
@@ -0,0 +1,139 @@
1
+ ---
2
+ title: KeepAlive Component Best Practices
3
+ impact: HIGH
4
+ impactDescription: KeepAlive caches component instances; misuse causes stale data, memory growth, or unexpected lifecycle behavior
5
+ type: best-practice
6
+ tags: [vue3, keepalive, cache, performance, router, dynamic-components]
7
+ ---
8
+
9
+ # KeepAlive Component Best Practices
10
+
11
+ **Impact: HIGH** - `<KeepAlive>` caches component instances instead of destroying them. Use it to preserve state across switches, but manage cache size and freshness explicitly to avoid memory growth or stale UI.
12
+
13
+ ## Task List
14
+
15
+ - Use KeepAlive only where state preservation improves UX
16
+ - Set a reasonable `max` to cap cache size
17
+ - Declare component names for include/exclude matching
18
+ - Use `onActivated`/`onDeactivated` for cache-aware logic
19
+ - Decide how and when cached views refresh their data
20
+ - Avoid caching memory-heavy or security-sensitive views
21
+
22
+ ## When to Use KeepAlive
23
+
24
+ Use KeepAlive when switching between views where state should persist (tabs, multi-step forms, dashboards). Avoid it when each visit should start fresh.
25
+
26
+ **BAD:**
27
+
28
+ ```vue
29
+ <template>
30
+ <!-- State resets on every switch -->
31
+ <component :is="currentTab" />
32
+ </template>
33
+ ```
34
+
35
+ **GOOD:**
36
+
37
+ ```vue
38
+ <template>
39
+ <!-- State preserved between switches -->
40
+ <KeepAlive>
41
+ <component :is="currentTab" />
42
+ </KeepAlive>
43
+ </template>
44
+ ```
45
+
46
+ ## When NOT to Use KeepAlive
47
+
48
+ - Search or filter pages where users expect fresh results
49
+ - Memory-heavy components (maps, large tables, media players)
50
+ - Sensitive flows where data must be cleared on exit
51
+ - Components with heavy background activity you cannot pause
52
+
53
+ ## Limit and Control the Cache
54
+
55
+ Always cap cache size with `max` and restrict caching to specific components when possible.
56
+
57
+ ```vue
58
+ <template>
59
+ <KeepAlive :max="5" include="Dashboard,Settings">
60
+ <component :is="currentView" />
61
+ </KeepAlive>
62
+ </template>
63
+ ```
64
+
65
+ ## Ensure Component Names Match include/exclude
66
+
67
+ `include` and `exclude` match the component `name` option. Explicitly set names for reliable caching.
68
+
69
+ ```vue
70
+ <!-- TabA.vue -->
71
+ <script setup>
72
+ defineOptions({ name: 'TabA' })
73
+ </script>
74
+ ```
75
+
76
+ ```vue
77
+ <template>
78
+ <KeepAlive include="TabA,TabB">
79
+ <component :is="currentTab" />
80
+ </KeepAlive>
81
+ </template>
82
+ ```
83
+
84
+ ## Cache Invalidation Strategies
85
+
86
+ Vue 3 has no direct API to remove a specific cached instance. Use keys or dynamic include/exclude to force refreshes.
87
+
88
+ ```vue
89
+ <script setup>
90
+ import { reactive, ref } from 'vue'
91
+
92
+ const currentView = ref('Dashboard')
93
+ const viewKeys = reactive({ Dashboard: 0, Settings: 0 })
94
+
95
+ function invalidateCache(view) {
96
+ viewKeys[view]++
97
+ }
98
+ </script>
99
+
100
+ <template>
101
+ <KeepAlive>
102
+ <component :is="currentView" :key="`${currentView}-${viewKeys[currentView]}`" />
103
+ </KeepAlive>
104
+ </template>
105
+ ```
106
+
107
+ ## Lifecycle Hooks for Cached Components
108
+
109
+ Cached components are not destroyed on switch. Use activation hooks for refresh and cleanup.
110
+
111
+ ```vue
112
+ <script setup>
113
+ import { onActivated, onDeactivated } from 'vue'
114
+
115
+ onActivated(() => {
116
+ refreshData()
117
+ })
118
+
119
+ onDeactivated(() => {
120
+ pauseTimers()
121
+ })
122
+ </script>
123
+ ```
124
+
125
+ ## Router Caching and Freshness
126
+
127
+ Decide whether navigation should show cached state or a fresh view. A common pattern is to key by route when params change.
128
+
129
+ ```vue
130
+ <template>
131
+ <router-view v-slot="{ Component, route }">
132
+ <KeepAlive>
133
+ <component :is="Component" :key="route.fullPath" />
134
+ </KeepAlive>
135
+ </router-view>
136
+ </template>
137
+ ```
138
+
139
+ If you want cache reuse but fresh data, refresh in `onActivated` and compare query/params before fetching.
@@ -0,0 +1,226 @@
1
+ ---
2
+ title: Component Slots Best Practices
3
+ impact: MEDIUM
4
+ impactDescription: Poor slot API design causes empty DOM wrappers, weak TypeScript safety, brittle defaults, and unnecessary component overhead
5
+ type: best-practice
6
+ tags: [vue3, slots, components, typescript, composables]
7
+ ---
8
+
9
+ # Component Slots Best Practices
10
+
11
+ **Impact: MEDIUM** - Slots are a core component API surface in Vue. Structure them intentionally so templates stay predictable, typed, and performant.
12
+
13
+ ## Task List
14
+
15
+ - Use shorthand syntax for named slots (`#` instead of `v-slot:`)
16
+ - Render optional slot wrapper elements only when slot content exists (`$slots` checks)
17
+ - Type scoped slot contracts with `defineSlots` in TypeScript components
18
+ - Provide fallback content for optional slots
19
+ - Prefer composables over renderless components for pure logic reuse
20
+
21
+ ## Shorthand syntax for named slots
22
+
23
+ **BAD:**
24
+
25
+ ```vue
26
+ <MyComponent>
27
+ <template v-slot:header> ... </template>
28
+ </MyComponent>
29
+ ```
30
+
31
+ **GOOD:**
32
+
33
+ ```vue
34
+ <MyComponent>
35
+ <template #header> ... </template>
36
+ </MyComponent>
37
+ ```
38
+
39
+ ## Conditionally Render Optional Slot Wrappers
40
+
41
+ Use `$slots` checks when wrapper elements add spacing, borders, or layout constraints.
42
+
43
+ **BAD:**
44
+
45
+ ```vue
46
+ <!-- Card.vue -->
47
+ <template>
48
+ <article class="card">
49
+ <header class="card-header">
50
+ <slot name="header" />
51
+ </header>
52
+
53
+ <section class="card-body">
54
+ <slot />
55
+ </section>
56
+
57
+ <footer class="card-footer">
58
+ <slot name="footer" />
59
+ </footer>
60
+ </article>
61
+ </template>
62
+ ```
63
+
64
+ **GOOD:**
65
+
66
+ ```vue
67
+ <!-- Card.vue -->
68
+ <template>
69
+ <article class="card">
70
+ <header v-if="$slots.header" class="card-header">
71
+ <slot name="header" />
72
+ </header>
73
+
74
+ <section v-if="$slots.default" class="card-body">
75
+ <slot />
76
+ </section>
77
+
78
+ <footer v-if="$slots.footer" class="card-footer">
79
+ <slot name="footer" />
80
+ </footer>
81
+ </article>
82
+ </template>
83
+ ```
84
+
85
+ ## Type Scoped Slot Props with defineSlots
86
+
87
+ In `<script setup lang="ts">`, use `defineSlots` so slot consumers get autocomplete and static checks.
88
+
89
+ **BAD:**
90
+
91
+ ```vue
92
+ <!-- ProductList.vue -->
93
+ <script setup lang="ts">
94
+ interface Product {
95
+ id: number
96
+ name: string
97
+ }
98
+
99
+ defineProps<{ products: Product[] }>()
100
+ </script>
101
+
102
+ <template>
103
+ <ul>
104
+ <li v-for="(product, index) in products" :key="product.id">
105
+ <slot :product="product" :index="index" />
106
+ </li>
107
+ </ul>
108
+ </template>
109
+ ```
110
+
111
+ **GOOD:**
112
+
113
+ ```vue
114
+ <!-- ProductList.vue -->
115
+ <script setup lang="ts">
116
+ interface Product {
117
+ id: number
118
+ name: string
119
+ }
120
+
121
+ defineProps<{ products: Product[] }>()
122
+
123
+ defineSlots<{
124
+ default: (props: { product: Product; index: number }) => any
125
+ empty: () => any
126
+ }>()
127
+ </script>
128
+
129
+ <template>
130
+ <ul v-if="products.length">
131
+ <li v-for="(product, index) in products" :key="product.id">
132
+ <slot :product="product" :index="index" />
133
+ </li>
134
+ </ul>
135
+ <slot v-else name="empty" />
136
+ </template>
137
+ ```
138
+
139
+ ## Provide Slot Fallback Content
140
+
141
+ Fallback content makes components resilient when parents omit optional slots.
142
+
143
+ **BAD:**
144
+
145
+ ```vue
146
+ <!-- SubmitButton.vue -->
147
+ <template>
148
+ <button type="submit" class="btn-primary">
149
+ <slot />
150
+ </button>
151
+ </template>
152
+ ```
153
+
154
+ **GOOD:**
155
+
156
+ ```vue
157
+ <!-- SubmitButton.vue -->
158
+ <template>
159
+ <button type="submit" class="btn-primary">
160
+ <slot>Submit</slot>
161
+ </button>
162
+ </template>
163
+ ```
164
+
165
+ ## Prefer Composables for Pure Logic Reuse
166
+
167
+ Renderless components are still useful for slot-driven composition, but composables are usually cleaner for logic-only reuse.
168
+
169
+ **BAD:**
170
+
171
+ ```vue
172
+ <!-- MouseTracker.vue -->
173
+ <script setup lang="ts">
174
+ import { onMounted, onUnmounted, ref } from 'vue'
175
+
176
+ const x = ref(0)
177
+ const y = ref(0)
178
+
179
+ function onMove(event: MouseEvent) {
180
+ x.value = event.pageX
181
+ y.value = event.pageY
182
+ }
183
+
184
+ onMounted(() => window.addEventListener('mousemove', onMove))
185
+ onUnmounted(() => window.removeEventListener('mousemove', onMove))
186
+ </script>
187
+
188
+ <template>
189
+ <slot :x="x" :y="y" />
190
+ </template>
191
+ ```
192
+
193
+ **GOOD:**
194
+
195
+ ```ts
196
+ // composables/useMouse.ts
197
+ import { onMounted, onUnmounted, ref } from 'vue'
198
+
199
+ export function useMouse() {
200
+ const x = ref(0)
201
+ const y = ref(0)
202
+
203
+ function onMove(event: MouseEvent) {
204
+ x.value = event.pageX
205
+ y.value = event.pageY
206
+ }
207
+
208
+ onMounted(() => window.addEventListener('mousemove', onMove))
209
+ onUnmounted(() => window.removeEventListener('mousemove', onMove))
210
+
211
+ return { x, y }
212
+ }
213
+ ```
214
+
215
+ ```vue
216
+ <!-- MousePosition.vue -->
217
+ <script setup lang="ts">
218
+ import { useMouse } from '@/composables/useMouse'
219
+
220
+ const { x, y } = useMouse()
221
+ </script>
222
+
223
+ <template>
224
+ <p>{{ x }}, {{ y }}</p>
225
+ </template>
226
+ ```
@@ -0,0 +1,231 @@
1
+ ---
2
+ title: Suspense Component Best Practices
3
+ impact: MEDIUM
4
+ impactDescription: Suspense coordinates async dependencies with fallback UI; misconfiguration leads to missing loading states or confusing UX
5
+ type: best-practice
6
+ tags:
7
+ [vue3, suspense, async-components, async-setup, loading, fallback, router, transition, keepalive]
8
+ ---
9
+
10
+ # Suspense Component Best Practices
11
+
12
+ **Impact: MEDIUM** - `<Suspense>` coordinates async dependencies (async components or async setup) and renders a fallback while they resolve. Misconfiguration leads to missing loading states, empty renders, or subtle UX bugs.
13
+
14
+ ## Task List
15
+
16
+ - Wrap default and fallback slot content in a single root node
17
+ - Use `timeout` when you need the fallback to appear on reverts
18
+ - Force root replacement with `:key` when you need Suspense to re-trigger
19
+ - Add `suspensible` to nested Suspense boundaries (Vue 3.3+)
20
+ - Use `@pending`, `@resolve`, and `@fallback` for programmatic loading state
21
+ - Nest `RouterView` -> `Transition` -> `KeepAlive` -> `Suspense` in that order
22
+ - Keep Suspense usage centralized and documented in production
23
+
24
+ ## Single Root in Default and Fallback Slots
25
+
26
+ Suspense tracks a single immediate child in both slots. Wrap multiple elements in a single element or component.
27
+
28
+ **BAD:**
29
+
30
+ ```vue
31
+ <template>
32
+ <Suspense>
33
+ <AsyncHeader />
34
+ <AsyncList />
35
+
36
+ <template #fallback>
37
+ <LoadingSpinner />
38
+ <LoadingHint />
39
+ </template>
40
+ </Suspense>
41
+ </template>
42
+ ```
43
+
44
+ **GOOD:**
45
+
46
+ ```vue
47
+ <template>
48
+ <Suspense>
49
+ <div>
50
+ <AsyncHeader />
51
+ <AsyncList />
52
+ </div>
53
+
54
+ <template #fallback>
55
+ <div>
56
+ <LoadingSpinner />
57
+ <LoadingHint />
58
+ </div>
59
+ </template>
60
+ </Suspense>
61
+ </template>
62
+ ```
63
+
64
+ ## Fallback Timing on Reverts (`timeout`)
65
+
66
+ When Suspense is already resolved and new async work starts, the previous content remains visible until the timeout elapses. Use `timeout="0"` for immediate fallback or a short delay to avoid flicker.
67
+
68
+ **BAD:**
69
+
70
+ ```vue
71
+ <template>
72
+ <Suspense>
73
+ <component :is="currentView" :key="viewKey" />
74
+
75
+ <template #fallback> Loading... </template>
76
+ </Suspense>
77
+ </template>
78
+ ```
79
+
80
+ **GOOD:**
81
+
82
+ ```vue
83
+ <template>
84
+ <Suspense :timeout="200">
85
+ <component :is="currentView" :key="viewKey" />
86
+
87
+ <template #fallback> Loading... </template>
88
+ </Suspense>
89
+ </template>
90
+ ```
91
+
92
+ ## Pending State Only Re-triggers on Root Replacement
93
+
94
+ Once resolved, Suspense only re-enters pending when the root node of the default slot changes. If async work happens deeper in the tree, no fallback appears.
95
+
96
+ **BAD:**
97
+
98
+ ```vue
99
+ <template>
100
+ <Suspense>
101
+ <TabContainer>
102
+ <AsyncDashboard v-if="tab === 'dashboard'" />
103
+ <AsyncSettings v-else />
104
+ </TabContainer>
105
+
106
+ <template #fallback> Loading... </template>
107
+ </Suspense>
108
+ </template>
109
+ ```
110
+
111
+ **GOOD:**
112
+
113
+ ```vue
114
+ <template>
115
+ <Suspense>
116
+ <component :is="tabs[tab]" :key="tab" />
117
+
118
+ <template #fallback> Loading... </template>
119
+ </Suspense>
120
+ </template>
121
+ ```
122
+
123
+ ## Use `suspensible` for Nested Suspense (Vue 3.3+)
124
+
125
+ Nested Suspense boundaries need `suspensible` on the inner boundary so the parent can coordinate loading state. Without it, inner async content may render empty nodes until resolved.
126
+
127
+ **BAD:**
128
+
129
+ ```vue
130
+ <template>
131
+ <Suspense>
132
+ <LayoutShell>
133
+ <Suspense>
134
+ <AsyncWidget />
135
+ <template #fallback> Loading widget... </template>
136
+ </Suspense>
137
+ </LayoutShell>
138
+
139
+ <template #fallback> Loading layout... </template>
140
+ </Suspense>
141
+ </template>
142
+ ```
143
+
144
+ **GOOD:**
145
+
146
+ ```vue
147
+ <template>
148
+ <Suspense>
149
+ <LayoutShell>
150
+ <Suspense suspensible>
151
+ <AsyncWidget />
152
+ <template #fallback> Loading widget... </template>
153
+ </Suspense>
154
+ </LayoutShell>
155
+
156
+ <template #fallback> Loading layout... </template>
157
+ </Suspense>
158
+ </template>
159
+ ```
160
+
161
+ ## Track Loading with Suspense Events
162
+
163
+ Use `@pending`, `@resolve`, and `@fallback` for analytics, global loading indicators, or coordinating UI outside the Suspense boundary.
164
+
165
+ ```vue
166
+ <script setup>
167
+ import { ref } from 'vue'
168
+
169
+ const isLoading = ref(false)
170
+
171
+ function onPending() {
172
+ isLoading.value = true
173
+ }
174
+
175
+ function onResolve() {
176
+ isLoading.value = false
177
+ }
178
+ </script>
179
+
180
+ <template>
181
+ <LoadingBar v-if="isLoading" />
182
+
183
+ <Suspense @pending="onPending" @resolve="onResolve">
184
+ <AsyncPage />
185
+ <template #fallback>
186
+ <PageSkeleton />
187
+ </template>
188
+ </Suspense>
189
+ </template>
190
+ ```
191
+
192
+ ## Recommended Nesting with RouterView, Transition, KeepAlive
193
+
194
+ When combining these components, the nesting order should be `RouterView` -> `Transition` -> `KeepAlive` -> `Suspense` so each wrapper works correctly.
195
+
196
+ **BAD:**
197
+
198
+ ```vue
199
+ <template>
200
+ <RouterView v-slot="{ Component }">
201
+ <Suspense>
202
+ <KeepAlive>
203
+ <Transition mode="out-in">
204
+ <component :is="Component" />
205
+ </Transition>
206
+ </KeepAlive>
207
+ </Suspense>
208
+ </RouterView>
209
+ </template>
210
+ ```
211
+
212
+ **GOOD:**
213
+
214
+ ```vue
215
+ <template>
216
+ <RouterView v-slot="{ Component }">
217
+ <Transition mode="out-in">
218
+ <KeepAlive>
219
+ <Suspense>
220
+ <component :is="Component" />
221
+ <template #fallback> Loading... </template>
222
+ </Suspense>
223
+ </KeepAlive>
224
+ </Transition>
225
+ </RouterView>
226
+ </template>
227
+ ```
228
+
229
+ ## Treat Suspense Cautiously in Production
230
+
231
+ In production code, keep Suspense boundaries minimal, document where they are used, and have a fallback loading strategy if you ever need to replace or refactor them.
@@ -0,0 +1,110 @@
1
+ ---
2
+ title: Teleport Component Best Practices
3
+ impact: MEDIUM
4
+ impactDescription: Teleport renders content outside the component's DOM position, which is essential for overlays but affects styling and layout
5
+ type: best-practice
6
+ tags: [vue3, teleport, modal, overlay, positioning, responsive]
7
+ ---
8
+
9
+ # Teleport Component Best Practices
10
+
11
+ **Impact: MEDIUM** - `<Teleport>` renders part of a component's template in a different place in the DOM while preserving the Vue component hierarchy. Use it for overlays (modals, toasts, tooltips) or any UI that must escape stacking contexts, overflow, or fixed positioning constraints.
12
+
13
+ ## Task List
14
+
15
+ - Teleport overlays to `body` or a dedicated container outside the app root
16
+ - Keep a shared target for similar UI (`#modals`, `#notifications`) and control layering with order or z-index
17
+ - Use `:disabled` for responsive layouts that should render inline on small screens
18
+ - Remember props, emits, and provide/inject still work through teleport
19
+ - Avoid relying on parent stacking contexts or transforms for teleported UI
20
+
21
+ ## Teleport Overlays Out of Transformed Containers
22
+
23
+ When an ancestor has `transform`, `filter`, or `perspective`, fixed-position overlays can behave like they are locally positioned. Teleport escapes that context.
24
+
25
+ **BAD:**
26
+
27
+ ```vue
28
+ <template>
29
+ <div class="animated-container">
30
+ <button @click="open = true">Open</button>
31
+
32
+ <!-- Broken: fixed positioning is scoped to the transformed parent -->
33
+ <div v-if="open" class="modal">Modal</div>
34
+ </div>
35
+ </template>
36
+
37
+ <style>
38
+ .animated-container {
39
+ transform: translateZ(0);
40
+ }
41
+
42
+ .modal {
43
+ position: fixed;
44
+ inset: 0;
45
+ z-index: 9999;
46
+ }
47
+ </style>
48
+ ```
49
+
50
+ **GOOD:**
51
+
52
+ ```vue
53
+ <template>
54
+ <div class="animated-container">
55
+ <button @click="open = true">Open</button>
56
+
57
+ <Teleport to="body">
58
+ <div v-if="open" class="modal">Modal</div>
59
+ </Teleport>
60
+ </div>
61
+ </template>
62
+ ```
63
+
64
+ ## Responsive Layouts with `disabled`
65
+
66
+ Use `:disabled` to render inline on mobile and teleport on larger screens:
67
+
68
+ ```vue
69
+ <script setup>
70
+ import { useMediaQuery } from '@vueuse/core'
71
+
72
+ const isMobile = useMediaQuery('(max-width: 768px)')
73
+ </script>
74
+
75
+ <template>
76
+ <Teleport to="body" :disabled="isMobile">
77
+ <nav class="sidebar">Navigation</nav>
78
+ </Teleport>
79
+ </template>
80
+ ```
81
+
82
+ ## Logical Hierarchy Is Preserved
83
+
84
+ Teleport changes DOM position, not the Vue component tree. Props, emits, slots, and provide/inject still work:
85
+
86
+ ```vue
87
+ <template>
88
+ <Teleport to="body">
89
+ <ChildPanel :message="message" @close="open = false" />
90
+ </Teleport>
91
+ </template>
92
+ ```
93
+
94
+ ## Multiple Teleports to the Same Target
95
+
96
+ Teleports to the same target append in declaration order:
97
+
98
+ ```vue
99
+ <template>
100
+ <Teleport to="#notifications">
101
+ <div>First</div>
102
+ </Teleport>
103
+
104
+ <Teleport to="#notifications">
105
+ <div>Second</div>
106
+ </Teleport>
107
+ </template>
108
+ ```
109
+
110
+ Use a shared container to keep stacking predictable, and apply z-index only when you need explicit layering.