@givolt/i3-composer-agent 2.3.1 → 2.3.3
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.
- package/.agents/plugins/marketplace.json +1 -1
- package/.claude/commands/i3-prototype.md +5 -7
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.gemini/commands/i3-prototype.toml +5 -7
- package/README.md +16 -7
- package/bin/agent-targets.json +4 -2
- package/bin/i3-composer-agent.cjs +74 -7
- package/bin/install.cjs +16 -1
- package/bin/native-layout.cjs +1 -1
- package/bin/native-migration.cjs +27 -3
- package/bin/tool-selector.cjs +24 -7
- package/bin/wizard.cjs +89 -15
- package/commands/i3-prototype.toml +5 -7
- package/docs/codebuddy-setup.md +91 -3
- package/docs/codex-setup.md +6 -0
- package/docs/other-hosts.md +1 -1
- package/docs/ui-prototype-native/MAIN-SESSION-REPORT.md +66 -0
- package/docs/ui-prototype-native/USAGE.md +93 -0
- package/docs/workbuddy-setup.md +6 -0
- package/evals/cases/i3-frontend-ui-engineering.json +16 -0
- package/evals/cases/i3-ui-prototype.json +109 -0
- package/evals/cases/i3-using-agent-skills.json +28 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/assets/reference.css +3 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/assets/reference.js +8 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/capability-definition.json +67 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/index.html +89 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/readme.md +52 -0
- package/evals/fixtures/i3-ui-prototype/admin-list/template.json +8 -0
- package/evals/zh-CN/cases/i3-frontend-ui-engineering.json +17 -0
- package/evals/zh-CN/cases/i3-ui-prototype.json +115 -0
- package/evals/zh-CN/cases/i3-using-agent-skills.json +30 -0
- package/hooks/codebuddy-upstream.cjs +93 -0
- package/hooks/i3-prototype.cjs +12 -0
- package/hooks/manage.cjs +162 -0
- package/package.json +61 -1
- package/plugin.json +1 -1
- package/scripts/design-provider-test.js +7 -0
- package/scripts/enterprise-template-test.js +27 -1
- package/scripts/lib/prototype-fixture.cjs +17 -0
- package/scripts/prototype-cache-test.js +55 -0
- package/scripts/prototype-generation-entry-test.js +6 -0
- package/scripts/prototype-generation-evidence-test.js +10 -0
- package/scripts/prototype-generation-native-test.js +78 -0
- package/scripts/prototype-generation-test.js +99 -0
- package/scripts/prototype-inventory-test.js +43 -0
- package/scripts/prototype-locations-test.js +79 -0
- package/scripts/prototype-project-test.js +183 -0
- package/scripts/ui-prototype-codebuddy-hook-test.js +106 -0
- package/scripts/ui-prototype-content-test.js +123 -0
- package/scripts/ui-prototype-dependencies-test.js +213 -0
- package/scripts/ui-prototype-devtools-test.js +53 -0
- package/scripts/ui-prototype-execution-evidence-test.js +62 -0
- package/scripts/ui-prototype-execution-gates-test.js +66 -0
- package/scripts/ui-prototype-execution-policy-test.js +42 -0
- package/scripts/ui-prototype-execution-runtime-test.js +27 -0
- package/scripts/ui-prototype-ide-source-test.js +73 -0
- package/scripts/ui-prototype-native-test.js +504 -0
- package/scripts/ui-prototype-registration-test.js +53 -0
- package/scripts/ui-prototype-template-test.js +261 -0
- package/scripts/ui-prototype-test.js +10 -0
- package/scripts/ui-prototype-transcript-auto-test.js +67 -0
- package/skills/i3-frontend-ui-engineering/SKILL.md +1 -1
- package/skills/i3-incremental-implementation/SKILL.md +1 -1
- package/skills/i3-ui-prototype/SKILL.md +158 -19
- package/skills/i3-ui-prototype/assets/capabilities/generation.json +833 -0
- package/skills/i3-ui-prototype/assets/capabilities/management.json +294 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/acceptance.json +51 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/component-map.json +13 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/generation-component-map.json +300 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/index.html +11 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/package-lock.json +2298 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/package.json +22 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/public/prototype-config.json +6 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/App.vue +136 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/components/DeviceEditor.vue +111 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/config.ts +19 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/content.ts +28 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/domain/devices.ts +50 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/domain/theme.ts +25 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/main.ts +18 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/style.css +50 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/src/useDevices.ts +66 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tests/devices.test.ts +44 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tests/theme.test.ts +26 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/tsconfig.json +15 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/legacy-starter/vite.config.ts +4 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/profile.json +174 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/readme.md +61 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/index.html +11 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/package-lock.json +2298 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/package.json +22 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/public/prototype-config.json +21 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/App.vue +62 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/PrototypeShell.vue +59 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/config.ts +23 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/domain/presentation.ts +29 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/domain/theme.ts +25 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/main.ts +18 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/style.css +26 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tests/presentation.test.ts +23 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tests/theme.test.ts +26 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tsconfig.json +15 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/vite.config.ts +4 -0
- package/skills/i3-ui-prototype/assets/hosts/codebuddy/profile.json +13 -0
- package/skills/i3-ui-prototype/assets/optional-skills/host-targets.json +739 -0
- package/skills/i3-ui-prototype/assets/optional-skills/sources.json +27 -0
- package/skills/i3-ui-prototype/assets/templates/catalog.js +149 -0
- package/skills/i3-ui-prototype/assets/templates/catalog.json +2 -1
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/capability-definition.json +294 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/component-inventory.json +707 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/generation.json +49 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/index.html +507 -2
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/offline-build.json +265 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/offline.js +8888 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/template-inventory.json +28791 -0
- package/skills/i3-ui-prototype/assets/templates/enterprise-admin/template.json +9 -0
- package/skills/i3-ui-prototype/assets/templates/gallery/index.html +5 -5
- package/skills/i3-ui-prototype/assets/templates/generation-options.js +42 -0
- package/skills/i3-ui-prototype/assets/templates/index.html +3 -0
- package/skills/i3-ui-prototype/assets/templates/presentation.html +17 -0
- package/skills/i3-ui-prototype/references/artifact-contract.md +26 -1
- package/skills/i3-ui-prototype/references/content-template.md +81 -0
- package/skills/i3-ui-prototype/references/enterprise-admin.md +6 -0
- package/skills/i3-ui-prototype/references/entry-contract.md +71 -0
- package/skills/i3-ui-prototype/references/execution-evidence.md +66 -0
- package/skills/i3-ui-prototype/references/expert-adapter.md +33 -5
- package/skills/i3-ui-prototype/references/framework-contract.md +76 -0
- package/skills/i3-ui-prototype/references/generation-contract.md +122 -0
- package/skills/i3-ui-prototype/references/interaction-acceptance.md +169 -0
- package/skills/i3-ui-prototype/references/legacy-native.md +58 -0
- package/skills/i3-ui-prototype/references/optional-skills.md +63 -0
- package/skills/i3-ui-prototype/references/project-layout.md +92 -0
- package/skills/i3-ui-prototype/references/template-adapters.md +7 -1
- package/skills/i3-ui-prototype/references/workflow-contract.md +62 -4
- package/skills/i3-ui-prototype/scripts/adapters/easel-html.cjs +1 -1
- package/skills/i3-ui-prototype/scripts/browser-workspace.cjs +72 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-context.cjs +75 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-hook.cjs +157 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-ide-source.cjs +69 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-registration.cjs +123 -0
- package/skills/i3-ui-prototype/scripts/content-template.cjs +98 -0
- package/skills/i3-ui-prototype/scripts/enterprise-template.cjs +13 -6
- package/skills/i3-ui-prototype/scripts/execution-assertions.cjs +43 -0
- package/skills/i3-ui-prototype/scripts/execution-evidence.cjs +146 -0
- package/skills/i3-ui-prototype/scripts/execution-gates.cjs +147 -0
- package/skills/i3-ui-prototype/scripts/execution-policy.cjs +41 -0
- package/skills/i3-ui-prototype/scripts/execution-runtime.cjs +60 -0
- package/skills/i3-ui-prototype/scripts/generation-assets.cjs +18 -0
- package/skills/i3-ui-prototype/scripts/generation-contract.cjs +214 -0
- package/skills/i3-ui-prototype/scripts/generation-evidence.cjs +50 -0
- package/skills/i3-ui-prototype/scripts/generation-replan.cjs +34 -0
- package/skills/i3-ui-prototype/scripts/hosts/codebuddy.cjs +16 -0
- package/skills/i3-ui-prototype/scripts/manage-baseline.cjs +1 -1
- package/skills/i3-ui-prototype/scripts/native-browser.cjs +785 -0
- package/skills/i3-ui-prototype/scripts/native-build.cjs +91 -0
- package/skills/i3-ui-prototype/scripts/native-delivery.cjs +103 -0
- package/skills/i3-ui-prototype/scripts/native-devtools.cjs +92 -0
- package/skills/i3-ui-prototype/scripts/native-files.cjs +35 -0
- package/skills/i3-ui-prototype/scripts/native-pilot-lib.cjs +292 -0
- package/skills/i3-ui-prototype/scripts/native-pilot.cjs +50 -0
- package/skills/i3-ui-prototype/scripts/optional-skill-install.cjs +203 -0
- package/skills/i3-ui-prototype/scripts/project-registry.cjs +126 -0
- package/skills/i3-ui-prototype/scripts/prototype-dependencies.cjs +81 -0
- package/skills/i3-ui-prototype/scripts/prototype-hooks.cjs +87 -0
- package/skills/i3-ui-prototype/scripts/prototype-lib.cjs +31 -13
- package/skills/i3-ui-prototype/scripts/prototype-locations.cjs +69 -0
- package/skills/i3-ui-prototype/scripts/sync-template.cjs +19 -0
- package/skills/i3-ui-prototype/scripts/template-contract.cjs +407 -0
- package/skills/i3-ui-prototype/scripts/template-inventory.cjs +78 -0
- package/skills/i3-ui-prototype/scripts/validate-result.cjs +117 -0
- package/skills/i3-using-agent-skills/SKILL.md +2 -5
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "business-antdv-prototype",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "npm@11.12.1",
|
|
7
|
+
"engines": { "node": ">=22.18.0", "npm": ">=10" },
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "vite --host 127.0.0.1",
|
|
10
|
+
"typecheck": "vue-tsc --noEmit",
|
|
11
|
+
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
|
12
|
+
"build": "vue-tsc --noEmit && vite build",
|
|
13
|
+
"preview": "vite preview --host 127.0.0.1"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": { "antdv-next": "1.5.3", "vue": "3.5.42" },
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@vitejs/plugin-vue": "6.0.4",
|
|
18
|
+
"typescript": "5.9.3",
|
|
19
|
+
"vite": "7.3.1",
|
|
20
|
+
"vue-tsc": "3.3.13"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/public/prototype-config.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instanceId": "business-preview",
|
|
3
|
+
"storageNamespace": "prototype:business-preview",
|
|
4
|
+
"mockVersion": "business-v1",
|
|
5
|
+
"title": "业务原型",
|
|
6
|
+
"pages": [
|
|
7
|
+
{
|
|
8
|
+
"id": "business",
|
|
9
|
+
"title": "业务页面"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"theme": {
|
|
13
|
+
"defaultTheme": "light",
|
|
14
|
+
"presets": [
|
|
15
|
+
"light",
|
|
16
|
+
"dark",
|
|
17
|
+
"fresh",
|
|
18
|
+
"tech"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
|
3
|
+
import { Alert, ConfigProvider, Select, Tabs, TabPane, theme as antdTheme } from 'antdv-next'
|
|
4
|
+
import zhCN from 'antdv-next/locale/zh_CN'
|
|
5
|
+
import type { PrototypeConfig } from './config'
|
|
6
|
+
import { THEME_IDS, THEME_PRESETS, loadThemePreference, safeThemeStorage, saveThemePreference, themeStorageKey } from './domain/theme'
|
|
7
|
+
import type { ThemeId } from './domain/theme'
|
|
8
|
+
import PrototypeShell from './PrototypeShell.vue'
|
|
9
|
+
import { presentationTokens, resolvePresentation } from './domain/presentation'
|
|
10
|
+
const props = defineProps<{ config: PrototypeConfig }>()
|
|
11
|
+
const storage = safeThemeStorage(() => window.localStorage)
|
|
12
|
+
const key = themeStorageKey(props.config.storageNamespace)
|
|
13
|
+
const selected = ref<ThemeId>(loadThemePreference(storage, key))
|
|
14
|
+
const activePage = ref(props.config.pages[0]?.id)
|
|
15
|
+
const warning = ref('')
|
|
16
|
+
const options = THEME_IDS.map(value => ({ value, label: THEME_PRESETS[value].label }))
|
|
17
|
+
const media = matchMedia('(prefers-reduced-motion: reduce)')
|
|
18
|
+
const reduced = ref(media.matches)
|
|
19
|
+
const motionChanged = (event: MediaQueryListEvent) => { reduced.value = event.matches }
|
|
20
|
+
media.addEventListener('change', motionChanged)
|
|
21
|
+
onBeforeUnmount(() => media.removeEventListener('change', motionChanged))
|
|
22
|
+
const preset = computed(() => THEME_PRESETS[selected.value])
|
|
23
|
+
const recipe = computed(() => props.config.presentation ? presentationTokens(resolvePresentation(props.config.presentation)) : undefined)
|
|
24
|
+
const variables = computed(() => ({ '--prototype-bg': preset.value.bg, '--prototype-surface': preset.value.surface, '--prototype-text': preset.value.text, '--prototype-border': preset.value.border, '--prototype-radius': `${recipe.value?.borderRadius ?? 10}px` }))
|
|
25
|
+
const tokens = computed(() => ({
|
|
26
|
+
algorithm: preset.value.mode === 'dark' ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
|
|
27
|
+
token: { colorPrimary: preset.value.primary, colorBgBase: preset.value.bg, colorBgContainer: preset.value.surface,
|
|
28
|
+
colorBgElevated: preset.value.surface, colorText: preset.value.text, colorTextSecondary: preset.value.muted,
|
|
29
|
+
colorBorder: preset.value.border, colorTextLightSolid: preset.value.onPrimary, motion: !reduced.value,
|
|
30
|
+
...(recipe.value ? { borderRadius: recipe.value.borderRadius, controlHeight: recipe.value.controlHeight } : {}) },
|
|
31
|
+
}))
|
|
32
|
+
function choose(value: unknown) {
|
|
33
|
+
if (typeof value !== 'string' || !THEME_IDS.includes(value as ThemeId)) return
|
|
34
|
+
selected.value = value as ThemeId
|
|
35
|
+
warning.value = saveThemePreference(storage, key, selected.value) ? '' : '主题偏好无法保存,本页仍可切换。'
|
|
36
|
+
}
|
|
37
|
+
watch(preset, value => {
|
|
38
|
+
document.documentElement.dataset.prototypeTheme = selected.value
|
|
39
|
+
document.documentElement.style.colorScheme = value.mode
|
|
40
|
+
}, { immediate: true })
|
|
41
|
+
</script>
|
|
42
|
+
<template>
|
|
43
|
+
<ConfigProvider :locale="zhCN" :theme="tokens" :component-size="recipe?.componentSize">
|
|
44
|
+
<div v-if="config.presentation" :style="variables">
|
|
45
|
+
<PrototypeShell :config="config" :menu-theme="preset.mode">
|
|
46
|
+
<template #appearance><div class="theme-picker"><label for="prototype-theme">主题</label><Select id="prototype-theme" aria-label="主题" :value="selected" :options="options" @change="choose" /></div></template>
|
|
47
|
+
<template #default="{ page }"><Alert v-if="warning" type="warning" :title="warning" show-icon /><section :aria-label="page.title"><h2>{{ page.title }}</h2><Alert type="warning" title="业务页面待实现" description="按固定的页面模式、字段、操作与验收合同实现此页。" show-icon /></section></template>
|
|
48
|
+
</PrototypeShell>
|
|
49
|
+
</div>
|
|
50
|
+
<div v-else class="prototype-shell" :style="{ background: preset.bg, color: preset.text }">
|
|
51
|
+
<header><h1>{{ config.title }}</h1><div class="theme-picker"><label for="prototype-theme">主题</label><Select id="prototype-theme" aria-label="主题" :value="selected" :options="options" @change="choose" /></div></header>
|
|
52
|
+
<main :style="{ background: preset.surface, borderColor: preset.border }">
|
|
53
|
+
<Alert v-if="warning" type="warning" :title="warning" show-icon />
|
|
54
|
+
<Tabs v-model:active-key="activePage">
|
|
55
|
+
<TabPane v-for="page in config.pages" :key="page.id" :tab="page.title">
|
|
56
|
+
<section :aria-label="page.title"><h2>{{ page.title }}</h2><Alert type="warning" title="业务页面待实现" description="按固定需求、完整内容模板和通用能力清单实现此页,再进行交互验收。" show-icon /></section>
|
|
57
|
+
</TabPane>
|
|
58
|
+
</Tabs>
|
|
59
|
+
</main>
|
|
60
|
+
</div>
|
|
61
|
+
</ConfigProvider>
|
|
62
|
+
</template>
|
package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/PrototypeShell.vue
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
|
3
|
+
import { Button, Drawer, Layout, LayoutContent, LayoutHeader, LayoutSider, Menu, Result } from 'antdv-next'
|
|
4
|
+
import type { MenuProps } from 'antdv-next'
|
|
5
|
+
import type { PrototypeConfig } from './config'
|
|
6
|
+
import { navigationMenus, pageFromHash, presentationTokens, resolvePresentation } from './domain/presentation'
|
|
7
|
+
import type { NavigationItem } from './domain/presentation'
|
|
8
|
+
const props = defineProps<{ config: PrototypeConfig; menuTheme?: 'light' | 'dark' }>()
|
|
9
|
+
const presentation = computed(() => resolvePresentation(props.config.presentation))
|
|
10
|
+
const recipe = computed(() => presentationTokens(presentation.value))
|
|
11
|
+
const active = ref(pageFromHash(location.hash, props.config.pages))
|
|
12
|
+
const current = computed(() => props.config.pages.find(page => page.id === active.value))
|
|
13
|
+
const items = computed(() => props.config.navigation || props.config.pages.map(page => ({ key: page.id, label: page.title })))
|
|
14
|
+
const menus = computed(() => navigationMenus(items.value, presentation.value.layout, active.value))
|
|
15
|
+
const collapsed = ref(false), mobileOpen = ref(false), focusAfterClose = ref(false)
|
|
16
|
+
const opened = ref<string[]>([])
|
|
17
|
+
function ancestors(nodes: NavigationItem[], target: string | undefined, parents: string[] = []): string[] | undefined { for (const node of nodes) { if (node.key === target) return parents; if (node.children) { const found = ancestors(node.children, target, [...parents, node.key]); if (found) return found } } }
|
|
18
|
+
watch(active, value => { opened.value = [...new Set([...opened.value, ...(ancestors(items.value, value) || [])])] }, { immediate: true })
|
|
19
|
+
const media = matchMedia('(max-width: 767px)'), narrow = ref(media.matches)
|
|
20
|
+
const onResize = (event: MediaQueryListEvent) => { narrow.value = event.matches; if (!event.matches) mobileOpen.value = false }
|
|
21
|
+
const onHash = () => { active.value = pageFromHash(location.hash, props.config.pages) }
|
|
22
|
+
media.addEventListener('change', onResize); window.addEventListener('hashchange', onHash); window.addEventListener('popstate', onHash)
|
|
23
|
+
onBeforeUnmount(() => { media.removeEventListener('change', onResize); window.removeEventListener('hashchange', onHash); window.removeEventListener('popstate', onHash) })
|
|
24
|
+
function navigate(event: { key: string | number }) {
|
|
25
|
+
const id = String(event.key)
|
|
26
|
+
if (!props.config.pages.some(page => page.id === id)) return
|
|
27
|
+
history.pushState(null, '', `#/${encodeURIComponent(id)}`); active.value = id
|
|
28
|
+
if (mobileOpen.value) { focusAfterClose.value = true; mobileOpen.value = false } else nextTick(focusMain)
|
|
29
|
+
}
|
|
30
|
+
function afterMenuChange(open: boolean) { if (!open && focusAfterClose.value) { focusAfterClose.value = false; nextTick(focusMain) } }
|
|
31
|
+
function focusMain() { document.getElementById('prototype-main')?.focus() }
|
|
32
|
+
function nativeItems(nodes: NavigationItem[]): NonNullable<MenuProps['items']> { return nodes.map(node => node.children ? { key: node.key, label: node.label, children: nativeItems(node.children) } : { key: node.key, label: node.label }) }
|
|
33
|
+
const side = computed(() => ['sidebar','mixed'].includes(presentation.value.layout))
|
|
34
|
+
const top = computed(() => ['topbar','mixed'].includes(presentation.value.layout))
|
|
35
|
+
</script>
|
|
36
|
+
<template>
|
|
37
|
+
<Layout class="generation-shell" :class="[`layout-${presentation.layout}`, `style-${presentation.style}`, `density-${presentation.density}`]" :style="{ '--prototype-spacing': `${recipe.spacing}px` }" :data-layout="presentation.layout" :data-style="presentation.style" :data-density="presentation.density">
|
|
38
|
+
<a class="skip-link" href="#prototype-main" @click.prevent="focusMain">跳到页面内容</a>
|
|
39
|
+
<LayoutHeader class="generation-header">
|
|
40
|
+
<div class="generation-brand"><Button v-if="narrow" aria-label="打开导航" :aria-expanded="mobileOpen" @click="mobileOpen = true">菜单</Button><strong>{{ config.title }}</strong></div>
|
|
41
|
+
<slot name="appearance" />
|
|
42
|
+
</LayoutHeader>
|
|
43
|
+
<nav v-if="top && !narrow" aria-label="顶部导航" class="generation-topnav"><Menu :theme="menuTheme || 'light'" mode="horizontal" :items="nativeItems(menus.top)" :selected-keys="menus.topSelected ? [menus.topSelected] : []" @click="navigate" /></nav>
|
|
44
|
+
<Layout class="generation-body">
|
|
45
|
+
<LayoutSider v-if="side && !narrow" class="generation-sidebar" :width="224" :collapsed-width="72" :collapsed="collapsed" :theme="menuTheme || 'light'">
|
|
46
|
+
<Button block :aria-expanded="!collapsed" :aria-label="collapsed ? '展开侧栏' : '收起侧栏'" @click="collapsed = !collapsed">{{ collapsed ? '展开' : '收起侧栏' }}</Button>
|
|
47
|
+
<nav aria-label="侧栏导航"><Menu :theme="menuTheme || 'light'" v-model:open-keys="opened" mode="inline" :inline-collapsed="collapsed" :items="nativeItems(menus.side)" :selected-keys="active ? [active] : []" @click="navigate" /></nav>
|
|
48
|
+
</LayoutSider>
|
|
49
|
+
<LayoutContent id="prototype-main" class="generation-content" tabindex="-1">
|
|
50
|
+
<nav v-if="presentation.layout === 'focused' && !narrow && config.pages.length > 1" aria-label="工作区页面"><Menu :theme="menuTheme || 'light'" mode="horizontal" :items="nativeItems(items)" :selected-keys="active ? [active] : []" @click="navigate" /></nav>
|
|
51
|
+
<slot v-if="current" :page="current" />
|
|
52
|
+
<Result v-else status="404" title="页面不存在" sub-title="当前地址不在本原型的页面范围内。"><template #extra><Button @click="navigate({ key: config.pages[0].id })">返回首页</Button></template></Result>
|
|
53
|
+
</LayoutContent>
|
|
54
|
+
</Layout>
|
|
55
|
+
<Drawer :open="mobileOpen" title="导航" placement="left" :width="288" :mask="{ enabled: true, blur: true }" destroy-on-hidden @after-open-change="afterMenuChange" @close="mobileOpen = false">
|
|
56
|
+
<nav aria-label="移动导航"><Menu :theme="menuTheme || 'light'" v-model:open-keys="opened" mode="inline" :items="nativeItems(items)" :selected-keys="active ? [active] : []" @click="navigate" /></nav>
|
|
57
|
+
</Drawer>
|
|
58
|
+
</Layout>
|
|
59
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { resolveThemeConfig } from './domain/theme'
|
|
2
|
+
import type { ThemeBinding } from './domain/theme'
|
|
3
|
+
import { resolvePresentation } from './domain/presentation'
|
|
4
|
+
import type { NavigationItem, Presentation } from './domain/presentation'
|
|
5
|
+
export interface PrototypeConfig { instanceId: string; storageNamespace: string; mockVersion: string; title?: string; pages: { id: string; title: string }[]; theme?: ThemeBinding; presentation?: Presentation; navigation?: NavigationItem[]; generation?: { pages: unknown[] } }
|
|
6
|
+
export interface PrototypeMeta extends PrototypeConfig { candidateId: string }
|
|
7
|
+
declare global { interface Window { __PROTOTYPE_META__: PrototypeMeta } }
|
|
8
|
+
|
|
9
|
+
export async function loadConfig(): Promise<PrototypeConfig> {
|
|
10
|
+
const response = await fetch(`${import.meta.env.BASE_URL}prototype-config.json`, { cache: 'no-store' })
|
|
11
|
+
if (!response.ok) throw new Error('无法读取原型配置,请检查当前预览构建。')
|
|
12
|
+
const config: unknown = await response.json()
|
|
13
|
+
if (!config || typeof config !== 'object') throw new Error('原型配置无效。')
|
|
14
|
+
const value = config as Record<string, unknown>
|
|
15
|
+
for (const key of ['instanceId', 'storageNamespace', 'mockVersion']) {
|
|
16
|
+
if (typeof value[key] !== 'string' || !(value[key] as string).trim()) throw new Error(`原型配置缺少 ${key}。`)
|
|
17
|
+
}
|
|
18
|
+
resolveThemeConfig(value.theme)
|
|
19
|
+
if (value.presentation !== undefined) resolvePresentation(value.presentation)
|
|
20
|
+
if (!Array.isArray(value.pages) || !value.pages.length || value.pages.some(page => !page || typeof page.id !== 'string' || !page.id || typeof page.title !== 'string')) throw new Error('原型页面配置无效。')
|
|
21
|
+
if (new Set(value.pages.map(page => page.id)).size !== value.pages.length) throw new Error('原型页面标识重复。')
|
|
22
|
+
return value as unknown as PrototypeConfig
|
|
23
|
+
}
|
package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/src/domain/presentation.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface Presentation { layout: 'sidebar' | 'topbar' | 'mixed' | 'focused'; style: 'business' | 'minimal' | 'soft'; density: 'compact' | 'standard' | 'comfortable' }
|
|
2
|
+
export interface NavigationItem { key: string; label: string; children?: NavigationItem[] }
|
|
3
|
+
export function navigationMenus(items: NavigationItem[], layout: Presentation['layout'], active?: string) {
|
|
4
|
+
const first = (item: NavigationItem): string => item.children?.length ? first(item.children[0]) : item.key
|
|
5
|
+
const contains = (item: NavigationItem): boolean => item.key === active || !!item.children?.some(contains)
|
|
6
|
+
const groups = items.some(item => item.children) ? items : [{ key: '@pages', label: '业务页面', children: items }]
|
|
7
|
+
const current = groups.find(contains) || groups[0]
|
|
8
|
+
return { top: layout === 'mixed' ? groups.map(item => ({ key: first(item), label: item.label })) : items,
|
|
9
|
+
side: layout === 'mixed' ? current?.children || [current] : items,
|
|
10
|
+
topSelected: layout === 'mixed' && current ? first(current) : active }
|
|
11
|
+
}
|
|
12
|
+
export function resolvePresentation(value: unknown): Presentation {
|
|
13
|
+
if (!value || typeof value !== 'object') throw new Error('缺少已固定的页面呈现选择。')
|
|
14
|
+
const item = value as Record<string, unknown>
|
|
15
|
+
for (const [key, choices] of Object.entries({ layout: ['sidebar', 'topbar', 'mixed', 'focused'], style: ['business', 'minimal', 'soft'], density: ['compact', 'standard', 'comfortable'] })) {
|
|
16
|
+
if (!choices.includes(item[key] as string)) throw new Error(`不支持的呈现选择:${key}`)
|
|
17
|
+
}
|
|
18
|
+
return item as unknown as Presentation
|
|
19
|
+
}
|
|
20
|
+
export function presentationTokens(value: Presentation) {
|
|
21
|
+
return { borderRadius: { business: 6, minimal: 4, soft: 10 }[value.style],
|
|
22
|
+
controlHeight: { compact: 28, standard: 32, comfortable: 40 }[value.density],
|
|
23
|
+
spacing: { compact: 12, standard: 20, comfortable: 28 }[value.density],
|
|
24
|
+
componentSize: { compact: 'small', standard: 'middle', comfortable: 'large' }[value.density] as 'small' | 'middle' | 'large' }
|
|
25
|
+
}
|
|
26
|
+
export function pageFromHash(hash: string, pages: { id: string }[]): string | undefined {
|
|
27
|
+
if (!hash || hash === '#/' || hash === '#') return pages[0]?.id
|
|
28
|
+
try { const id = decodeURIComponent(hash.replace(/^#\/?/, '')); return pages.some(page => page.id === id) ? id : undefined } catch { return undefined }
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type ThemeId = 'light' | 'dark' | 'fresh' | 'tech'
|
|
2
|
+
export interface ThemeBinding { defaultTheme: 'light'; presets: ThemeId[] }
|
|
3
|
+
export interface ThemeStorage { getItem(key: string): string | null; setItem(key: string, value: string): void }
|
|
4
|
+
export const THEME_IDS: ThemeId[] = ['light', 'dark', 'fresh', 'tech']
|
|
5
|
+
export const THEME_PRESETS = {
|
|
6
|
+
light: { label: '明亮', mode: 'light', primary: '#155bc2', bg: '#f4f7fc', surface: '#ffffff', subtle: '#f8faff', text: '#172b45', muted: '#506174', border: '#b9c8da', accent: '#155bc2', onPrimary: '#ffffff', error: '#b4232c', success: '#166534' },
|
|
7
|
+
dark: { label: '暗黑', mode: 'dark', primary: '#8fb8ff', bg: '#15181d', surface: '#20242b', subtle: '#1b2028', text: '#edf1f7', muted: '#b2bece', border: '#667387', accent: '#a6c7ff', onPrimary: '#101c2c', error: '#ffb4ab', success: '#9bddad' },
|
|
8
|
+
fresh: { label: '清新', mode: 'light', primary: '#0c6a55', bg: '#edf8f4', surface: '#fbfffd', subtle: '#e6f4ed', text: '#173b31', muted: '#4c665c', border: '#a4c4b5', accent: '#0c6a55', onPrimary: '#ffffff', error: '#b4232c', success: '#166534' },
|
|
9
|
+
tech: { label: '科技', mode: 'dark', primary: '#55ddc6', bg: '#07131f', surface: '#102536', subtle: '#0b1c2b', text: '#e1f4f7', muted: '#a0bdca', border: '#5b8294', accent: '#55ddc6', onPrimary: '#071b20', error: '#ffb4ab', success: '#9bddad' },
|
|
10
|
+
} as const
|
|
11
|
+
export function resolveThemeConfig(value: unknown): ThemeBinding | null {
|
|
12
|
+
if (value === undefined) return null
|
|
13
|
+
const config=value as Partial<ThemeBinding> | null
|
|
14
|
+
if (!config || config.defaultTheme !== 'light' || !Array.isArray(config.presets) || config.presets.length !== 4 || new Set(config.presets).size !== 4 || !THEME_IDS.every(id => config.presets!.includes(id))) throw new Error('主题配置必须包含明亮、暗黑、清新、科技且默认明亮。')
|
|
15
|
+
return { defaultTheme: 'light', presets: [...THEME_IDS] }
|
|
16
|
+
}
|
|
17
|
+
export const themeStorageKey = (namespace: string) => `${namespace}:ui-theme:v1`
|
|
18
|
+
export function safeThemeStorage(access: () => ThemeStorage): ThemeStorage | null { try { return access() } catch { return null } }
|
|
19
|
+
export function loadThemePreference(storage: ThemeStorage | null, key: string): ThemeId {
|
|
20
|
+
try { const value=JSON.parse(storage?.getItem(key) ?? 'null'); if (value?.version===1 && THEME_IDS.includes(value.themeId)) return value.themeId } catch { /* Damaged or denied storage uses the explicit default. */ }
|
|
21
|
+
return 'light'
|
|
22
|
+
}
|
|
23
|
+
export function saveThemePreference(storage: ThemeStorage | null, key: string, themeId: ThemeId): boolean {
|
|
24
|
+
try { if (!storage) return false; storage.setItem(key,JSON.stringify({version:1,themeId})); return true } catch { return false }
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createApp } from 'vue'
|
|
2
|
+
import 'antdv-next/dist/reset.css'
|
|
3
|
+
import App from './App.vue'
|
|
4
|
+
import { loadConfig } from './config'
|
|
5
|
+
import './style.css'
|
|
6
|
+
|
|
7
|
+
loadConfig().then(config => {
|
|
8
|
+
window.__PROTOTYPE_META__ = { ...config, candidateId: import.meta.env.VITE_CANDIDATE_ID || 'unverified-development' }
|
|
9
|
+
createApp(App, { config }).mount('#app')
|
|
10
|
+
}).catch(error => {
|
|
11
|
+
const container = document.querySelector('#app')!
|
|
12
|
+
const heading = document.createElement('h1')
|
|
13
|
+
heading.textContent = '原型无法启动'
|
|
14
|
+
const message = document.createElement('p')
|
|
15
|
+
message.textContent = error instanceof Error ? error.message : '配置读取失败。'
|
|
16
|
+
container.setAttribute('role', 'alert')
|
|
17
|
+
container.append(heading, message)
|
|
18
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
* { box-sizing: border-box; }
|
|
2
|
+
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif; }
|
|
3
|
+
.prototype-shell { min-height: 100dvh; padding: 24px; }
|
|
4
|
+
header { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin: 0 auto 20px; max-width: 1440px; }
|
|
5
|
+
h1 { margin: 0; font-size: 24px; }
|
|
6
|
+
h2 { margin-top: 0; font-size: 20px; }
|
|
7
|
+
.theme-picker { display: flex; align-items: center; gap: 8px; }
|
|
8
|
+
.theme-picker .ant-select { min-width: 100px; }
|
|
9
|
+
main { max-width: 1440px; margin: auto; padding: 20px; border: 1px solid; border-radius: 10px; }
|
|
10
|
+
section { min-height: 240px; }
|
|
11
|
+
:focus-visible { outline: 3px solid currentColor; outline-offset: 3px; }
|
|
12
|
+
@media (max-width: 600px) { .prototype-shell { padding: 12px; } main { padding: 12px; } h1 { font-size: 20px; } }
|
|
13
|
+
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; transition-duration: .01ms !important; } }
|
|
14
|
+
.generation-shell { min-height: 100dvh; background: var(--prototype-bg); color: var(--prototype-text); }
|
|
15
|
+
.generation-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px; width: 100%; max-width: none; min-height: 64px; height: auto; margin: 0; padding: 12px 24px; line-height: 1.5; background: var(--prototype-surface); color: inherit; border-bottom: 1px solid var(--prototype-border); }
|
|
16
|
+
.generation-brand { display: flex; align-items: center; gap: 12px; font-size: 18px; }
|
|
17
|
+
.generation-body { min-width: 0; background: var(--prototype-bg); }
|
|
18
|
+
.generation-sidebar { padding: 12px 8px; background: var(--prototype-surface) !important; border-right: 1px solid var(--prototype-border); }
|
|
19
|
+
.generation-topnav { background: var(--prototype-surface); }
|
|
20
|
+
.generation-content { min-width: 0; max-width: none; margin: var(--prototype-spacing); padding: var(--prototype-spacing); border: 1px solid var(--prototype-border); border-radius: var(--prototype-radius); background: var(--prototype-surface); color: inherit; }
|
|
21
|
+
.layout-focused .generation-content { width: calc(100% - 2 * var(--prototype-spacing)); max-width: 1200px; margin: var(--prototype-spacing) auto; }
|
|
22
|
+
.style-minimal .generation-content { border-color: transparent; }
|
|
23
|
+
.style-soft .generation-content { box-shadow: 0 4px 20px rgb(0 0 0 / 6%); }
|
|
24
|
+
.skip-link { position: fixed; top: 8px; left: 8px; z-index: 2000; padding: 8px 12px; background: var(--prototype-surface); color: var(--prototype-text); transform: translateY(-200%); }
|
|
25
|
+
.skip-link:focus { transform: none; }
|
|
26
|
+
@media (max-width: 767px) { .generation-header { padding: 12px; } .generation-content { margin: 8px; padding: 12px; } }
|
package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tests/presentation.test.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { resolvePresentation, presentationTokens, pageFromHash, navigationMenus } from '../src/domain/presentation.ts'
|
|
4
|
+
test('every supported presentation combination has a usable native token recipe', () => {
|
|
5
|
+
for (const layout of ['sidebar','topbar','mixed','focused']) for (const style of ['business','minimal','soft']) for (const density of ['compact','standard','comfortable']) {
|
|
6
|
+
const value = resolvePresentation({ layout, style, density }), tokens = presentationTokens(value)
|
|
7
|
+
assert.ok(tokens.controlHeight > 0 && tokens.spacing > 0 && tokens.borderRadius >= 0)
|
|
8
|
+
}
|
|
9
|
+
assert.throws(() => resolvePresentation({ layout: 'lost', style: 'minimal', density: 'compact' }), /不支持/)
|
|
10
|
+
})
|
|
11
|
+
test('deep links retain declared page identity and reject unknown or malformed locations', () => {
|
|
12
|
+
const pages = [{ id: 'assets' }, { id: 'details' }]
|
|
13
|
+
assert.equal(pageFromHash('#/details', pages), 'details')
|
|
14
|
+
assert.equal(pageFromHash('', pages), 'assets')
|
|
15
|
+
assert.equal(pageFromHash('#/missing', pages), undefined)
|
|
16
|
+
assert.equal(pageFromHash('#/%broken', pages), undefined)
|
|
17
|
+
})
|
|
18
|
+
test('mixed navigation separates group entries from the selected group pages', () => {
|
|
19
|
+
const value = navigationMenus([{ key: '@assets', label: '资产', children: [{ key: 'list', label: '台账' }, { key: 'detail', label: '详情' }] }, { key: '@work', label: '作业', children: [{ key: 'jobs', label: '任务' }] }], 'mixed', 'detail')
|
|
20
|
+
assert.deepEqual(value.top.map(item => item.key), ['list','jobs'])
|
|
21
|
+
assert.deepEqual(value.side.map(item => item.key), ['list','detail'])
|
|
22
|
+
assert.equal(value.topSelected, 'list')
|
|
23
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
import { THEME_PRESETS, resolveThemeConfig, themeStorageKey, loadThemePreference, saveThemePreference, safeThemeStorage } from '../src/domain/theme.ts'
|
|
4
|
+
|
|
5
|
+
const binding = { defaultTheme: 'light', presets: ['light', 'dark', 'fresh', 'tech'] }
|
|
6
|
+
test('four named presets are explicit while an unbound instance stays legacy', () => {
|
|
7
|
+
assert.equal(resolveThemeConfig(undefined), null)
|
|
8
|
+
assert.deepEqual(resolveThemeConfig(binding), binding)
|
|
9
|
+
assert.deepEqual(Object.values(THEME_PRESETS).map(theme => theme.label), ['明亮', '暗黑', '清新', '科技'])
|
|
10
|
+
assert.deepEqual(Object.values(THEME_PRESETS).map(theme => theme.mode), ['light', 'dark', 'light', 'dark'])
|
|
11
|
+
for (const config of [{ ...binding, defaultTheme: 'dark' }, { ...binding, presets: [1,2,3,4] }, { ...binding, presets: ['light','dark','fresh','fresh'] }]) assert.throws(() => resolveThemeConfig(config))
|
|
12
|
+
})
|
|
13
|
+
test('theme preference is namespaced independently and damaged cache falls back', () => {
|
|
14
|
+
const values=new Map<string,string>(), storage={ getItem:(key:string)=>values.get(key)??null,setItem:(key:string,value:string)=>{values.set(key,value)} }
|
|
15
|
+
const a=themeStorageKey('instance-a'),b=themeStorageKey('instance-b')
|
|
16
|
+
assert.notEqual(a,b);assert.match(a,/:ui-theme:v1$/)
|
|
17
|
+
assert.equal(saveThemePreference(storage,a,'tech'),true)
|
|
18
|
+
assert.equal(loadThemePreference(storage,a),'tech');assert.equal(loadThemePreference(storage,b),'light')
|
|
19
|
+
for (const broken of ['{','null','{"version":1,"themeId":2}','{"version":2,"themeId":"tech"}','{"version":1,"themeId":"unknown"}']) { values.set(a,broken);assert.equal(loadThemePreference(storage,a),'light') }
|
|
20
|
+
})
|
|
21
|
+
test('denied storage never prevents local theme changes or returns a fake saved preference', () => {
|
|
22
|
+
const denied={getItem(){throw Error('denied')},setItem(){throw Error('denied')}}
|
|
23
|
+
assert.equal(safeThemeStorage(()=>{throw Error('getter denied')}),null)
|
|
24
|
+
assert.equal(loadThemePreference(denied,'key'),'light');assert.equal(loadThemePreference(null,'key'),'light')
|
|
25
|
+
assert.equal(saveThemePreference(denied,'key','dark'),false);assert.equal(saveThemePreference(null,'key','dark'),false)
|
|
26
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
10
|
+
"types": ["vite/client"],
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"verbatimModuleSyntax": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "codebuddy-command-v1",
|
|
4
|
+
"nativeValidation": "pending-user",
|
|
5
|
+
"platforms": ["darwin", "linux"],
|
|
6
|
+
"events": ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"],
|
|
7
|
+
"transcriptFormat": "assistant-user-tool-use-jsonl-v1",
|
|
8
|
+
"products": {
|
|
9
|
+
"ide": {"settings": "settings.json", "configuration": "project-overrides-global", "transcriptRoot": "native-hook-transcript-path", "manualTranscriptRoot": true, "nativeReview": "reload-and-verify"},
|
|
10
|
+
"cli": {"settings": "settings.json", "configuration": "merged-project-first-binding", "transcriptRoot": "native-hook-transcript-path", "manualTranscriptRoot": true, "configRootEnvironment": "CODEBUDDY_CONFIG_DIR", "nativeReview": "/hooks"}
|
|
11
|
+
},
|
|
12
|
+
"sources": ["https://www.codebuddy.cn/docs/ide/Features/Hooks", "https://www.codebuddy.cn/docs/cli/hooks", "https://www.codebuddy.cn/docs/cli/env-vars"]
|
|
13
|
+
}
|