@yh-ui/yh-ui-skill 1.0.39 → 1.0.43

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.
@@ -1,83 +1,134 @@
1
1
  # Deep Recipe: YhTable
2
2
 
3
- Use this recipe when the user asks for data tables, admin list pages, exports, selection, pagination, or large datasets.
3
+ Use this recipe when building complex data tables, admin list views, large datasets with virtualization, custom cell layouts, context menus, data export, or printing.
4
4
 
5
5
  ## Default Choice
6
6
 
7
- Use `YhTable` with a stable `columns` array and typed row data. Add `YhPagination` only when pagination is not handled through `YhTable`'s `pagination` prop.
7
+ Use `YhTable` with a statically defined `columns` array. Let `YhTable` handle pagination internally through the `pagination` configuration property.
8
8
 
9
9
  ## Source-Aligned API Highlights
10
10
 
11
- - Props: `data`, `columns`, `rowKey`, `loading`, `pagination`, `selectionConfig`, `sortConfig`, `filterConfig`, `virtualConfig`, `toolbarConfig`, `emptyConfig`, `height`, `maxHeight`, `border`, `stripe`, `showIndex`.
12
- - Events: `sort-change`, `filter-change`, `page-change`, `selection-change`, `row-click`, `cell-click`, `scroll`, `update:data`.
13
- - Expose: `refresh`, `clearSelection`, `getSelectionRows`, `sort`, `clearSort`, `filter`, `clearFilter`, `exportData`, `print`, `scrollTo`, `doLayout`.
11
+ - **Props**: `data`, `columns`, `rowKey`, `loading`, `pagination`, `height`, `maxHeight`, `border`, `stripe`, `virtualConfig`, `toolbarConfig`, `rowConfig`, `expandConfig`, `selectionConfig`, `sortConfig`, `filterConfig`.
12
+ - **Events**: `sort-change`, `filter-change`, `page-change`, `selection-change`, `row-click`, `cell-click`, `scroll`, `update:data`.
13
+ - **Slots**: `default` (column definitions), `toolbar`, `toolbar-left`, `toolbar-right`, `toolbar-left-prefix`, `toolbar-right-suffix`.
14
+ - **Exposed APIs**: `refresh()`, `clearSelection()`, `getSelectionRows()`, `exportData(options)`, `importData(options)`, `print(options)`, `scrollTo(options)`, `doLayout()`, `setColumnVisible(prop, visible)`.
14
15
 
15
- ## Pattern: Request-Driven Table
16
+ ## Pattern: Advanced Admin Table
17
+
18
+ This example demonstrates request-driven loading, cell custom slot rendering, toolbars, virtual scrolling for large datasets, printing, and Excel exporting:
16
19
 
17
20
  ```vue
18
21
  <script setup lang="ts">
19
- import { computed, ref } from 'vue'
20
- import { YhButton, YhEmpty, YhForm, YhFormItem, YhInput, YhTable } from '@yh-ui/components'
22
+ import { ref, computed } from 'vue'
23
+ import { YhTable, YhButton, YhTag, YhMessage, YhPopconfirm } from '@yh-ui/components'
21
24
  import { createRequest, useRequest } from '@yh-ui/request'
22
25
 
23
- interface UserRow {
26
+ interface LogRow {
24
27
  id: number
25
- name: string
26
- email: string
27
- status: string
28
+ username: string
29
+ action: string
30
+ status: 'success' | 'failed'
31
+ ip: string
32
+ createdTime: string
28
33
  }
29
34
 
30
35
  const api = createRequest({ baseURL: '/api' })
31
- const keyword = ref('')
32
- const page = ref({ currentPage: 1, pageSize: 20, total: 0 })
36
+ const tableRef = ref<InstanceType<typeof YhTable> | null>(null)
37
+ const page = ref({ currentPage: 1, pageSize: 50, total: 0 })
33
38
 
39
+ // Static columns definition - keep outside template
34
40
  const columns = [
35
- { prop: 'name', label: 'Name', minWidth: 160 },
36
- { prop: 'email', label: 'Email', minWidth: 220 },
37
- { prop: 'status', label: 'Status', width: 120 }
41
+ { prop: 'username', label: 'User', width: 150, sortable: true },
42
+ { prop: 'action', label: 'Action Description', minWidth: 200 },
43
+ { prop: 'status', label: 'Status', width: 120, slot: 'status' },
44
+ { prop: 'ip', label: 'IP Address', width: 140 },
45
+ { prop: 'createdTime', label: 'Timestamp', width: 180 },
46
+ { prop: 'operations', label: 'Operations', width: 120, slot: 'operations' }
38
47
  ]
39
48
 
40
- const { data, loading, error, refresh } = useRequest(async () => {
41
- const result = await api.get<{ list: UserRow[]; total: number }>('/users', {
42
- params: { keyword: keyword.value, page: page.value.currentPage, pageSize: page.value.pageSize }
49
+ // Request client using useRequest
50
+ const { data, loading, refresh } = useRequest(async () => {
51
+ const result = await api.get<{ list: LogRow[]; total: number }>('/audit/logs', {
52
+ params: { page: page.value.currentPage, limit: page.value.pageSize }
43
53
  })
44
54
  page.value.total = result.total
45
55
  return result.list
46
56
  })
47
57
 
48
58
  const rows = computed(() => data.value || [])
59
+
60
+ // Native Excel Export using YhTable exposed method
61
+ function exportExcel() {
62
+ if (!tableRef.value) return
63
+ tableRef.value.exportData({
64
+ filename: 'audit_logs',
65
+ type: 'xlsx',
66
+ original: true
67
+ })
68
+ YhMessage.success('Exporting file...')
69
+ }
70
+
71
+ // Native Print using YhTable exposed method
72
+ function printTable() {
73
+ if (!tableRef.value) return
74
+ tableRef.value.print({
75
+ title: 'Audit Activity Report',
76
+ showPageNumber: true
77
+ })
78
+ }
79
+
80
+ function deleteRow(row: LogRow) {
81
+ YhMessage.success(`Deleted log: ${row.id}`)
82
+ refresh()
83
+ }
49
84
  </script>
50
85
 
51
86
  <template>
52
- <YhForm inline>
53
- <YhFormItem label="Keyword">
54
- <YhInput v-model="keyword" clearable placeholder="Search users" />
55
- </YhFormItem>
56
- <YhFormItem>
57
- <YhButton type="primary" :loading="loading" @click="refresh">Search</YhButton>
58
- </YhFormItem>
59
- </YhForm>
60
-
61
- <YhTable
62
- row-key="id"
63
- :data="rows"
64
- :columns="columns"
65
- :loading="loading"
66
- :pagination="page"
67
- border
68
- stripe
69
- @page-change="refresh"
70
- />
71
-
72
- <YhEmpty v-if="!loading && !rows.length && !error" description="No users found" />
87
+ <div class="table-container">
88
+ <YhTable
89
+ ref="tableRef"
90
+ row-key="id"
91
+ :data="rows"
92
+ :columns="columns"
93
+ :loading="loading"
94
+ :pagination="page"
95
+ height="600px"
96
+ border
97
+ stripe
98
+ :virtual-config="{ enabled: true, itemHeight: 45 }"
99
+ @page-change="refresh"
100
+ >
101
+ <!-- Custom Left Toolbar Slot -->
102
+ <template #toolbar-left>
103
+ <YhButton type="primary" icon="mdi:refresh" @click="refresh">Refresh Logs</YhButton>
104
+ <YhButton type="success" icon="mdi:file-excel" @click="exportExcel"
105
+ >Export to Excel</YhButton
106
+ >
107
+ <YhButton icon="mdi:printer" @click="printTable">Print Report</YhButton>
108
+ </template>
109
+
110
+ <!-- Custom Cell Slots mapped via column.slot -->
111
+ <template #status="{ row }">
112
+ <YhTag :type="row.status === 'success' ? 'success' : 'danger'">
113
+ {{ row.status === 'success' ? 'Success' : 'Failed' }}
114
+ </YhTag>
115
+ </template>
116
+
117
+ <template #operations="{ row }">
118
+ <YhPopconfirm title="Are you sure you want to delete this log?" @confirm="deleteRow(row)">
119
+ <template #reference>
120
+ <YhButton type="text" danger icon="mdi:delete" />
121
+ </template>
122
+ </YhPopconfirm>
123
+ </template>
124
+ </YhTable>
125
+ </div>
73
126
  </template>
74
127
  ```
75
128
 
76
129
  ## Agent Rules
77
130
 
78
- - Keep `columns` outside the template.
79
- - Use `rowKey` whenever rows have identity.
80
- - Use `virtualConfig` for large local datasets.
81
- - Use `loading` and `emptyConfig`/`YhEmpty` for async states.
82
- - Use table events for remote sort/filter/page behavior.
83
- - Do not invent `YhDataTable`; use `YhTable`.
131
+ - **Static Columns**: Always define the `columns` array inside `<script setup>` as a constant or a reactive `computed` array; never define arrays inline inside the template to prevent duplicate component re-renders.
132
+ - **Set Table Height**: When enabling `virtual-config` for large datasets, an explicit `height` or `max-height` (e.g. `600px` or `100%`) must be set on `YhTable` to calculate viewport slots correctly.
133
+ - **Row Identity**: Always pass a unique `row-key` prop (e.g. `id`, `uuid`) to help Vue track element identity during sort, select, and virtual updates.
134
+ - **Avoid wrapper components**: Do not invent a custom `YhDataTable` or wrapper component. Always use the native `YhTable` from `@yh-ui/components`.
@@ -0,0 +1,141 @@
1
+ # Deep Recipe: YH-UI Theme System
2
+
3
+ Use this recipe when integrating or customising themes, dark mode, responsive scaling, compact density, or color-blind friendly palettes using `@yh-ui/theme`.
4
+
5
+ ## Default Choice
6
+
7
+ Use `ThemePlugin` during app registration and access reactive theme state using `useTheme` or `useThemeVars`.
8
+
9
+ ## Setup
10
+
11
+ ```ts
12
+ import { createApp } from 'vue'
13
+ import { ThemePlugin } from '@yh-ui/theme'
14
+ import App from './App.vue'
15
+
16
+ const app = createApp(App)
17
+
18
+ app.use(ThemePlugin, {
19
+ preset: 'default', // 'default' | 'dark' | 'blue' | 'green' | 'purple' | 'orange' | 'rose' | 'amber' | 'teal' | 'indigo' | 'slate' | 'zinc'
20
+ dark: false, // Initial mode
21
+ persist: true, // Save theme choices to localStorage automatically
22
+ persistKey: 'yh-ui-theme',
23
+ followSystem: true, // Follow OS dark/light mode preference
24
+ radius: 'md', // 'none' | 'sm' | 'md' | 'lg' | 'full'
25
+ density: 'comfortable', // 'comfortable' | 'compact' | 'dense'
26
+ colorBlindMode: 'none', // 'none' | 'protanopia' | 'deuteranopia' | 'tritanopia' | 'achromatopsia'
27
+ algorithm: 'default' // 'default' | 'vibrant' | 'muted' | 'pastel'
28
+ })
29
+
30
+ app.mount('#app')
31
+ ```
32
+
33
+ ## Composition APIs
34
+
35
+ Use `useTheme` to get the ThemeManager instance or `useThemeVars` for Vue-friendly reactive properties:
36
+
37
+ ```vue
38
+ <script setup lang="ts">
39
+ import { useTheme, useThemeVars } from '@yh-ui/theme'
40
+
41
+ // Option 1: Accessing the ThemeManager
42
+ const theme = useTheme()
43
+
44
+ function toggleDark() {
45
+ theme.toggleDarkMode()
46
+ }
47
+
48
+ function selectPreset(name: 'blue' | 'green' | 'purple') {
49
+ theme.setPreset(name)
50
+ }
51
+
52
+ function selectDensity(density: 'comfortable' | 'compact' | 'dense') {
53
+ theme.setDensity(density)
54
+ }
55
+
56
+ // Option 2: Using reactive variables directly
57
+ const { dark, theme: currentPreset, density, colorBlindMode } = useThemeVars()
58
+ </script>
59
+
60
+ <template>
61
+ <div class="settings">
62
+ <p>Current Theme: {{ currentPreset }} (Dark mode: {{ dark }})</p>
63
+ <p>Density Factor: {{ density }}</p>
64
+ <p>Accessibility Mode: {{ colorBlindMode }}</p>
65
+
66
+ <button @click="toggleDark">Toggle Dark Mode</button>
67
+ <button @click="selectPreset('blue')">Blue Preset</button>
68
+ <button @click="selectDensity('compact')">Compact Layout</button>
69
+ </div>
70
+ </template>
71
+ ```
72
+
73
+ ## Advanced Theme Customization
74
+
75
+ ### Smart Palette Generation
76
+
77
+ Generate complete secondary semantic status colors (success, warning, danger, info) automatically from a single primary brand color:
78
+
79
+ ```ts
80
+ import { useTheme } from '@yh-ui/theme'
81
+
82
+ const theme = useTheme()
83
+ // Generates and applies primary/success/warning/danger/info hex color scale
84
+ theme.applyFromPrimary('#722ed1')
85
+ ```
86
+
87
+ ### Color Contrast Checks (WCAG 2.1 AA/AAA)
88
+
89
+ Ensure generated colors meet user accessibility contrast guidelines:
90
+
91
+ ```ts
92
+ import { checkContrast, getTextColorForBackground } from '@yh-ui/theme'
93
+
94
+ // Returns true if contrast ratio is >= 4.5 (or >= 7 for AAA)
95
+ const isAccessible = checkContrast('#ffffff', '#1890ff', 'AA')
96
+
97
+ // Returns '#ffffff' or '#000000' based on contrast luminance
98
+ const suitableTextColor = getTextColorForBackground('#1890ff')
99
+ ```
100
+
101
+ ### Component-Level Custom Styles
102
+
103
+ You can override specific design tokens for a single component globally or dynamically:
104
+
105
+ ```ts
106
+ import { useTheme } from '@yh-ui/theme'
107
+
108
+ const theme = useTheme()
109
+ theme.apply({
110
+ components: {
111
+ YhButton: {
112
+ '--yh-button-font-weight': '600',
113
+ '--yh-button-border-radius': '8px'
114
+ }
115
+ }
116
+ })
117
+ ```
118
+
119
+ ## CSS Variable Usage
120
+
121
+ Instead of hardcoding color hexes, border-radii, spacing, or typography values, always leverage YH-UI's global theme tokens:
122
+
123
+ ```vue
124
+ <style scoped>
125
+ .custom-card {
126
+ color: var(--yh-text-color-primary);
127
+ background-color: var(--yh-bg-color);
128
+ border: 1px solid var(--yh-border-color);
129
+ border-radius: var(--yh-border-radius-base);
130
+ padding: calc(var(--yh-spacing-unit) * 2);
131
+
132
+ /* Font size and line height scales dynamically with density setting */
133
+ font-size: var(--yh-font-size-base);
134
+ line-height: var(--yh-line-height-base);
135
+ }
136
+
137
+ .custom-card:hover {
138
+ border-color: var(--yh-color-primary-hover);
139
+ }
140
+ </style>
141
+ ```
@@ -13,13 +13,41 @@ const api = createRequest({
13
13
  retry: 2
14
14
  })
15
15
 
16
- api.interceptors.request.use((config) => {
17
- config.headers = {
18
- ...config.headers,
19
- Authorization: `Bearer ${getToken()}`
16
+ api.interceptors.request.use(
17
+ (config) => {
18
+ const token = localStorage.getItem('token')
19
+ if (token) {
20
+ config.headers = {
21
+ ...config.headers,
22
+ Authorization: `Bearer ${token}`
23
+ }
24
+ }
25
+ return config
26
+ },
27
+ (error) => Promise.reject(error)
28
+ )
29
+
30
+ api.interceptors.response.use(
31
+ (response) => response,
32
+ async (error) => {
33
+ const originalRequest = error.config
34
+ // Automatic JWT token refresh flow
35
+ if (error.response?.status === 401 && !originalRequest._retry) {
36
+ originalRequest._retry = true
37
+ try {
38
+ const { token } = await api.post('/auth/refresh')
39
+ localStorage.setItem('token', token)
40
+ originalRequest.headers['Authorization'] = `Bearer ${token}`
41
+ return api(originalRequest)
42
+ } catch (refreshError) {
43
+ // Redirect to login or clear token on fail
44
+ localStorage.removeItem('token')
45
+ return Promise.reject(refreshError)
46
+ }
47
+ }
48
+ return Promise.reject(error)
20
49
  }
21
- return config
22
- })
50
+ )
23
51
  ```
24
52
 
25
53
  ## useRequest
@@ -27,6 +55,7 @@ api.interceptors.request.use((config) => {
27
55
  ```vue
28
56
  <script setup lang="ts">
29
57
  import { createRequest, useRequest } from '@yh-ui/request'
58
+ import { YhButton, YhMessage } from '@yh-ui/components'
30
59
 
31
60
  interface User {
32
61
  id: number
@@ -34,37 +63,87 @@ interface User {
34
63
  }
35
64
 
36
65
  const api = createRequest({ baseURL: '/api' })
37
- const { data, loading, error, refresh, run } = useRequest<User[]>(() => api.get<User[]>('/users'))
66
+
67
+ // useRequest returns reactive state and trigger helpers
68
+ const { data, loading, error, refresh, run } = useRequest<User[]>(() => api.get<User[]>('/users'), {
69
+ immediate: true,
70
+ onSuccess: (data) => {
71
+ YhMessage.success(`Loaded ${data.length} users successfully`)
72
+ },
73
+ onError: (err) => {
74
+ YhMessage.error(`Failed to load users: ${err.message}`)
75
+ }
76
+ })
38
77
  </script>
39
78
 
40
79
  <template>
41
- <YhButton :loading="loading" @click="refresh">Refresh</YhButton>
42
- <YhButton @click="run()">Run</YhButton>
43
- <pre v-if="error">{{ error.message }}</pre>
44
- <ul>
80
+ <div class="action-bar">
81
+ <YhButton :loading="loading" type="primary" @click="refresh">Refresh</YhButton>
82
+ <YhButton @click="run()">Force Run</YhButton>
83
+ </div>
84
+
85
+ <div v-if="loading" class="loading-state">Loading users...</div>
86
+ <div v-else-if="error" class="error-state">{{ error.message }}</div>
87
+ <ul v-else class="user-list">
45
88
  <li v-for="user in data" :key="user.id">{{ user.name }}</li>
46
89
  </ul>
47
90
  </template>
48
91
  ```
49
92
 
50
- ## SSE
93
+ ## SSE (Server-Sent Events)
51
94
 
52
- ```ts
95
+ Use `useSSE` for real-time streaming notifications, logs, or updates. It handles connection lifecycles, retries, and errors automatically:
96
+
97
+ ```vue
98
+ <script setup lang="ts">
99
+ import { ref, onUnmounted } from 'vue'
53
100
  import { useSSE } from '@yh-ui/request'
54
101
 
102
+ interface EventPayload {
103
+ msg: string
104
+ time: string
105
+ }
106
+
107
+ const events = ref<EventPayload[]>([])
108
+
55
109
  const { isConnected, connect, disconnect, error } = useSSE('/api/events', {
56
110
  reconnect: true,
57
111
  reconnectInterval: 3000,
112
+ maxReconnectAttempts: 5,
58
113
  onMessage: (event) => {
59
- console.log(event.data)
114
+ const payload = JSON.parse(event.data) as EventPayload
115
+ events.value.push(payload)
116
+ },
117
+ onError: (err) => {
118
+ console.error('SSE Error:', err)
60
119
  }
61
120
  })
121
+
122
+ // Always clean up connections when component is unmounted
123
+ onUnmounted(() => {
124
+ disconnect()
125
+ })
126
+ </script>
127
+
128
+ <template>
129
+ <div>
130
+ <p
131
+ >Status:
132
+ <span :class="{ connected: isConnected }">{{
133
+ isConnected ? 'Connected' : 'Disconnected'
134
+ }}</span></p
135
+ >
136
+ <p v-if="error">Connection error: {{ error.message }}</p>
137
+ <ul>
138
+ <li v-for="item in events" :key="item.time">[{{ item.time }}] {{ item.msg }}</li>
139
+ </ul>
140
+ </div>
141
+ </template>
62
142
  ```
63
143
 
64
144
  ## Agent Rules
65
145
 
66
- - Use `createRequest`, not old names.
67
- - Prefer `useRequest` for component state instead of manually maintaining `loading`, `data`, and `error`.
68
- - Use `useSSE` or `useAIStream` for streaming responses.
69
- - Use pagination/load-more hooks when building list pages.
70
- - Keep auth token handling in request interceptors or server middleware.
146
+ - Use `createRequest`, not old/legacy API clients.
147
+ - Always prefer `useRequest` for fetching state in Vue components instead of manual `ref(true)` loading flags.
148
+ - Use `useSSE` for server streaming logs/updates, and make sure to clean up connection via `disconnect()` in `onUnmounted`.
149
+ - Place global request interception logic (like auth header additions) inside the central request client, not scattered around views.
@@ -4,20 +4,43 @@ Use these rules when generating or reviewing YH-UI Vue 3 code. They combine Vue
4
4
 
5
5
  ## SFC Structure
6
6
 
7
- - Prefer `<script setup lang="ts">` for application examples and small components.
8
- - Order SFC blocks as `<script>`, `<template>`, then `<style>`.
9
- - Keep imports at the top, then types, props/emits/models, state, computed values, watchers, lifecycle hooks, and functions grouped by feature.
10
- - Use `scoped` styles for page/example CSS unless the task intentionally edits global theme CSS.
11
- - Avoid large inline template expressions. Move non-trivial logic to computed values or functions.
7
+ - Prefer `<script setup lang="ts">` for all application pages and component definitions.
8
+ - Always order SFC blocks as `<script>`, `<template>`, then `<style scoped>`.
9
+ - Internal setup block sequence:
10
+ 1. Module/library imports (separate external dependencies from local `@yh-ui` packages).
11
+ 2. TypeScript types & interfaces.
12
+ 3. `defineProps`, `defineEmits`, and `defineModel`.
13
+ 4. Reactive state & refs.
14
+ 5. Computed properties.
15
+ 6. Watchers (`watch` / `watchEffect`).
16
+ 7. Lifecycle hooks (`onMounted`, `onUnmounted`, etc.).
17
+ 8. Action functions / methods.
18
+ - Use `scoped` styles to prevent styling leakages. Never use raw global selector tags inside SFC styles.
19
+ - Keep templates clean: move calculations, complex conditions, or array mapping out of the template into computed variables.
12
20
 
13
21
  ## Props, Emits, And v-model
14
22
 
15
- - Type props with TypeScript interfaces or inline type literals.
16
- - Use `withDefaults(defineProps<Props>(), defaults)` when defaults are needed.
17
- - Use typed `defineEmits` for custom events.
18
- - For two-way binding, prefer `defineModel` in new Vue 3.4+ examples when the target project supports it; otherwise use `modelValue` and `update:modelValue`.
19
- - Never mutate props directly. Emit events or copy prop values into local state when local edits are needed.
20
- - Keep event names explicit and user-action oriented, such as `submit`, `cancel`, `refresh`, or `update:filters`.
23
+ - Always type props with clear TypeScript interfaces. Use type-only imports (`import type { ... }`) for external type models.
24
+ - For **Vue 3.5+**, prefer reactive props destructuring:
25
+ ```ts
26
+ const { size = 'medium', disabled = false, data = () => [] } = defineProps<Props>()
27
+ ```
28
+ This is the official Vue 3.5+ best practice and avoids verbose `withDefaults`.
29
+ - For Vue < 3.5 or projects without destructure support, fallback to `withDefaults(defineProps<Props>(), defaults)`.
30
+ - Use typed `defineEmits` or `defineModel` for two-way bindings:
31
+
32
+ ```ts
33
+ // Two-way binding model (Vue 3.4+)
34
+ const modelValue = defineModel<string>({ default: '' })
35
+
36
+ // Typed custom events
37
+ const emit = defineEmits<{
38
+ change: [value: string]
39
+ submit: []
40
+ }>()
41
+ ```
42
+
43
+ - Never mutate props. If props need internal modifications, copy them to a local `ref` or compile them via `computed` getters/setters.
21
44
 
22
45
  ## Slots And Composition
23
46
 
@@ -26,14 +49,20 @@ Use these rules when generating or reviewing YH-UI Vue 3 code. They combine Vue
26
49
  - In app pages, compose YH-UI components directly instead of wrapping every component in a thin local abstraction.
27
50
  - Create composables only when stateful logic is reused or complex enough to deserve a named boundary.
28
51
 
29
- ## Reactivity
30
-
31
- - Use `ref` for primitives and replaceable values.
32
- - Use `reactive` for stable object state that is mutated by field.
33
- - Use `computed` for derived values instead of maintaining duplicated state.
34
- - Use `watch` for side effects, not for deriving values.
35
- - Avoid destructuring reactive objects unless using `toRefs`, `storeToRefs`, or Vue 3.5 reactive props destructure intentionally.
36
- - Use `shallowRef` for large immutable objects, external instances, editors, charts, or flow graphs when deep reactivity is unnecessary.
52
+ ## Reactivity & Lifecycle Cleanups
53
+
54
+ - Use `ref` for primitive values, lists/arrays, or objects that will be completely overwritten.
55
+ - Use `reactive` only for complex nested states that require deep property mutation.
56
+ - Use `computed` for all derived values instead of writing sync watchers or manually maintaining double state variables.
57
+ - Use `watch` or `watchEffect` solely for side-effects (e.g. storage sync, async API triggers). Always specify `immediate: true` or `deep: true` only if required.
58
+ - Use `shallowRef` for heavy objects, DOM nodes, third-party library instances (ECharts, Monco Editor, Flow canvases), as deep reactivity on these will degrade performance.
59
+ - **Critical Lifecycle Cleanups**: Always clean up timeouts, intervals, ResizeObservers, WebSocket event listeners, and custom event listeners in `onUnmounted`:
60
+ ```ts
61
+ const timer = setInterval(tick, 1000)
62
+ onUnmounted(() => {
63
+ clearInterval(timer)
64
+ })
65
+ ```
37
66
 
38
67
  ## Accessibility
39
68
 
@@ -76,6 +105,40 @@ Use these rules when generating or reviewing YH-UI Vue 3 code. They combine Vue
76
105
  - Use `useNamespace` for internal BEM-style component classes when editing package components.
77
106
  - Use `useZIndex`, `useLocale`, `useId`/`useYhId`, and config hooks instead of local one-off implementations.
78
107
 
108
+ ## YH-UI Prioritization & Extension Workflow
109
+
110
+ To ensure we prioritize existing UI framework logic and do not write duplicate custom HTML/CSS controls, follow this decision tree when implementing user interface requests:
111
+
112
+ 1. **Verify Availability**: Search `references/source-truth.md` and `references/component-map.md`. If a component exists (e.g., `YhTable`, `YhDialog`, `YhConfigProvider`, `YhScrollbar`), you **must** use it. Do not write raw `<div class="custom-scroll">` or raw `<dialog>`.
113
+ 2. **Handle Feature Gaps (Encapsulation/Extension)**: If a component is missing a sub-feature, do not abandon the component. Apply extension patterns in order of priority:
114
+ - **Slot Customization**: Use slots (e.g., `#toolbar`, `#empty`, `#default` custom cells) to inject the custom logic.
115
+ - **Props & Event Listeners**: Use component event bindings and dynamic property configs to override behavior.
116
+ - **CSS Variable Injection**: Inject style variables (e.g. style overrides like `--yh-button-font-weight`) to alter theme aesthetics without altering core element code.
117
+ - **Composite Patterns**: Group multiple YH-UI components together (e.g. wrap a `YhTable` and `YhPagination` or nesting slots) before trying to write raw elements.
118
+ 3. **Raw Component Last Resort**: Only build a custom element from scratch if the functionality is entirely absent in the UI framework and cannot be composed/wrapped. You must document this reasoning in the code comments.
119
+
120
+ ## TypeScript Strict Standards
121
+
122
+ To maintain clean typing boundaries and avoid runtime failures, follow these TS conventions:
123
+
124
+ - **Type-only imports**: When importing interfaces, schemas, or types from YH-UI packages, use the `import type` syntax:
125
+ ```ts
126
+ import type { FormSchema } from '@yh-ui/components'
127
+ import type { FlowNode, FlowEdge } from '@yh-ui/flow'
128
+ import type { ThemeOptions } from '@yh-ui/theme'
129
+ ```
130
+ - **Type Template Refs**: Never leave component template refs untyped (`const refVal = ref(null)`). Always use Vue's `InstanceType` utility to extract component exports:
131
+
132
+ ```ts
133
+ import { ref } from 'vue'
134
+ import { YhTable } from '@yh-ui/components'
135
+
136
+ const tableRef = ref<InstanceType<typeof YhTable> | null>(null)
137
+ ```
138
+
139
+ - **Exposed APIs**: Access exposed methods on components strictly through these typed instances (e.g., `tableRef.value?.exportData(...)`, `formRef.value?.validate()`).
140
+ - **Data Models**: Strongly type response objects and row lists using `interface` declarations; never pass `any` into data bindings or request helper promises.
141
+
79
142
  ## Testing Expectations
80
143
 
81
144
  - For reusable components, test props, emitted events, slots, keyboard/focus behavior, and important visual states.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yh-ui/yh-ui-skill",
3
- "version": "1.0.39",
3
+ "version": "1.0.43",
4
4
  "description": "YH-UI skill installer for modern AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {