@givolt/i3-composer-agent 2.3.2 → 2.3.4
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-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +5 -1
- package/bin/agent-targets.json +4 -2
- package/bin/i3-composer-agent.cjs +48 -9
- package/bin/install.cjs +2 -1
- package/bin/native-layout.cjs +1 -1
- package/bin/tool-selector.cjs +24 -7
- package/bin/wizard.cjs +49 -15
- package/docs/codebuddy-setup.md +43 -4
- package/docs/codex-setup.md +3 -1
- package/docs/other-hosts.md +1 -1
- package/docs/ui-prototype-native/USAGE.md +4 -2
- package/docs/workbuddy-setup.md +20 -2
- package/hooks/codebuddy-upstream.cjs +93 -0
- package/hooks/i3-prototype.cjs +12 -0
- package/hooks/manage.cjs +170 -0
- package/package.json +35 -1
- package/plugin.json +1 -1
- 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 +21 -0
- package/scripts/prototype-project-test.js +183 -0
- package/scripts/ui-prototype-buddy-lifecycle-test.js +89 -0
- package/scripts/ui-prototype-codebuddy-hook-test.js +106 -0
- package/scripts/ui-prototype-dependencies-test.js +13 -0
- package/scripts/ui-prototype-execution-evidence-test.js +62 -0
- package/scripts/ui-prototype-execution-gates-test.js +90 -0
- package/scripts/ui-prototype-execution-policy-test.js +53 -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-recovery-test.js +72 -0
- package/scripts/ui-prototype-registration-test.js +53 -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 +29 -7
- package/skills/i3-ui-prototype/assets/capabilities/generation.json +833 -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/starter/src/App.vue +14 -3
- 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 +6 -1
- 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/style.css +13 -0
- package/skills/i3-ui-prototype/assets/frameworks/vue-antdv-next/starter/tests/presentation.test.ts +23 -0
- package/skills/i3-ui-prototype/assets/hosts/codebuddy/profile.json +17 -0
- package/skills/i3-ui-prototype/assets/optional-skills/host-targets.json +4 -2
- package/skills/i3-ui-prototype/assets/templates/catalog.json +2 -1
- 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/template-inventory.json +28791 -0
- package/skills/i3-ui-prototype/assets/templates/gallery/index.html +1 -1
- package/skills/i3-ui-prototype/assets/templates/generation-options.js +42 -0
- package/skills/i3-ui-prototype/assets/templates/presentation.html +17 -0
- package/skills/i3-ui-prototype/references/artifact-contract.md +1 -1
- package/skills/i3-ui-prototype/references/content-template.md +9 -4
- package/skills/i3-ui-prototype/references/entry-contract.md +1 -1
- package/skills/i3-ui-prototype/references/execution-evidence.md +83 -0
- package/skills/i3-ui-prototype/references/generation-contract.md +122 -0
- package/skills/i3-ui-prototype/references/interaction-acceptance.md +5 -1
- package/skills/i3-ui-prototype/references/optional-skills.md +4 -0
- package/skills/i3-ui-prototype/references/project-layout.md +92 -0
- package/skills/i3-ui-prototype/references/template-adapters.md +6 -0
- package/skills/i3-ui-prototype/references/workflow-contract.md +4 -0
- package/skills/i3-ui-prototype/scripts/browser-workspace.cjs +72 -0
- package/skills/i3-ui-prototype/scripts/buddy-host.cjs +40 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-context.cjs +75 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-hook.cjs +196 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-ide-source.cjs +69 -0
- package/skills/i3-ui-prototype/scripts/codebuddy-registration.cjs +125 -0
- package/skills/i3-ui-prototype/scripts/content-template.cjs +2 -2
- 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 +168 -0
- package/skills/i3-ui-prototype/scripts/execution-policy.cjs +62 -0
- package/skills/i3-ui-prototype/scripts/execution-recovery.cjs +52 -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 +20 -0
- package/skills/i3-ui-prototype/scripts/native-command.cjs +18 -0
- package/skills/i3-ui-prototype/scripts/native-delivery.cjs +17 -5
- package/skills/i3-ui-prototype/scripts/native-devtools.cjs +1 -0
- package/skills/i3-ui-prototype/scripts/native-pilot-lib.cjs +64 -18
- package/skills/i3-ui-prototype/scripts/native-pilot.cjs +23 -3
- package/skills/i3-ui-prototype/scripts/project-registry.cjs +126 -0
- package/skills/i3-ui-prototype/scripts/prototype-dependencies.cjs +28 -6
- package/skills/i3-ui-prototype/scripts/prototype-hooks.cjs +87 -0
- package/skills/i3-ui-prototype/scripts/prototype-lib.cjs +5 -7
- package/skills/i3-ui-prototype/scripts/prototype-locations.cjs +5 -1
- package/skills/i3-ui-prototype/scripts/template-contract.cjs +11 -2
- package/skills/i3-ui-prototype/scripts/template-inventory.cjs +78 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"frameworkId": "vue-antdv-next",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"boundary": "Native controls provide primitives. Business rules, graph behavior and persistence require application implementation and actual browser verification.",
|
|
6
|
+
"capabilities": {
|
|
7
|
+
"layout": {
|
|
8
|
+
"components": [
|
|
9
|
+
"Layout",
|
|
10
|
+
"LayoutHeader",
|
|
11
|
+
"LayoutSider",
|
|
12
|
+
"LayoutContent",
|
|
13
|
+
"Drawer"
|
|
14
|
+
],
|
|
15
|
+
"applicationResponsibilities": [
|
|
16
|
+
"所选导航与内容布局实际呈现,区域顺序和滚动边界与固定选择一致",
|
|
17
|
+
"桌面、平板与窄屏主要内容和操作可达,不通过隐藏必需字段适配"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"navigation": {
|
|
21
|
+
"components": [
|
|
22
|
+
"Menu",
|
|
23
|
+
"Breadcrumb",
|
|
24
|
+
"Button",
|
|
25
|
+
"Result"
|
|
26
|
+
],
|
|
27
|
+
"applicationResponsibilities": [
|
|
28
|
+
"每个声明页面可达,当前项、返回、深链接及不存在页面状态正确",
|
|
29
|
+
"按固定规则检查折叠、移动菜单、返回后的筛选和页码状态"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"accessibility": {
|
|
33
|
+
"components": [
|
|
34
|
+
"Form",
|
|
35
|
+
"Button",
|
|
36
|
+
"Input",
|
|
37
|
+
"Drawer"
|
|
38
|
+
],
|
|
39
|
+
"applicationResponsibilities": [
|
|
40
|
+
"名称、标签、Tab顺序、焦点可见及主要操作键盘可达",
|
|
41
|
+
"实际文字与浮层可读;窄屏无阻断性裁剪,减少动效仍可操作"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"theme": {
|
|
45
|
+
"components": [
|
|
46
|
+
"ConfigProvider",
|
|
47
|
+
"Select"
|
|
48
|
+
],
|
|
49
|
+
"applicationResponsibilities": [
|
|
50
|
+
"明亮、暗黑、清新、科技均实际覆盖组件、浮层与外壳,默认明亮且切换保留业务状态",
|
|
51
|
+
"刷新恢复、非法偏好和拒绝存储安全回退,实例间隔离"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"visual": {
|
|
55
|
+
"components": [
|
|
56
|
+
"ConfigProvider",
|
|
57
|
+
"Flex",
|
|
58
|
+
"Space"
|
|
59
|
+
],
|
|
60
|
+
"applicationResponsibilities": [
|
|
61
|
+
"实际风格、间距密度、圆角、边框与阴影符合所选预设,未覆盖框架和客户明确要求"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"interaction": {
|
|
65
|
+
"components": [
|
|
66
|
+
"Button",
|
|
67
|
+
"Form",
|
|
68
|
+
"Alert",
|
|
69
|
+
"Spin"
|
|
70
|
+
],
|
|
71
|
+
"applicationResponsibilities": [
|
|
72
|
+
"每个声明业务操作有有效结果;不可用操作说明原因,忙碌时防止重复提交",
|
|
73
|
+
"操作失败后对象和输入保留,可按规定重试,不用提示文字冒充操作完成"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"collection": {
|
|
77
|
+
"components": [
|
|
78
|
+
"Table",
|
|
79
|
+
"List"
|
|
80
|
+
],
|
|
81
|
+
"applicationResponsibilities": [
|
|
82
|
+
"记录稳定身份、总数、可见行与行操作对象对应,不串记录",
|
|
83
|
+
"逐项检查列表工具栏和行操作,不以一个按钮代替全部操作"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"filter": {
|
|
87
|
+
"components": [
|
|
88
|
+
"Form",
|
|
89
|
+
"Input",
|
|
90
|
+
"Select",
|
|
91
|
+
"TreeSelect",
|
|
92
|
+
"Button"
|
|
93
|
+
],
|
|
94
|
+
"applicationResponsibilities": [
|
|
95
|
+
"命中、无结果、重置正确;页码与选择按合同重置"
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
"selection": {
|
|
99
|
+
"components": [
|
|
100
|
+
"Table.rowSelection",
|
|
101
|
+
"Checkbox"
|
|
102
|
+
],
|
|
103
|
+
"applicationResponsibilities": [
|
|
104
|
+
"当前页单选、全选、半选及不可选记录正确",
|
|
105
|
+
"筛选、翻页或容量变化按约定清空选择"
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
"pagination": {
|
|
109
|
+
"components": [
|
|
110
|
+
"Table.pagination",
|
|
111
|
+
"Pagination"
|
|
112
|
+
],
|
|
113
|
+
"applicationResponsibilities": [
|
|
114
|
+
"默认容量覆盖首中末页,总数与实际行一致",
|
|
115
|
+
"逐一切换固定选项、恢复默认,页码及选择符合约定"
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
"bulk": {
|
|
119
|
+
"components": [
|
|
120
|
+
"Button",
|
|
121
|
+
"Alert",
|
|
122
|
+
"Modal"
|
|
123
|
+
],
|
|
124
|
+
"applicationResponsibilities": [
|
|
125
|
+
"无选择不可提交,只影响选中身份;成功范围与失败策略来自本次PRD"
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
"feedback": {
|
|
129
|
+
"components": [
|
|
130
|
+
"Spin",
|
|
131
|
+
"Empty",
|
|
132
|
+
"Alert",
|
|
133
|
+
"Result",
|
|
134
|
+
"message"
|
|
135
|
+
],
|
|
136
|
+
"applicationResponsibilities": [
|
|
137
|
+
"逐区域触发加载、无数据、筛选无结果、失败和恢复;不存在记录与权限限制有明确状态"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"columns": {
|
|
141
|
+
"components": [
|
|
142
|
+
"Table",
|
|
143
|
+
"Checkbox",
|
|
144
|
+
"Dropdown",
|
|
145
|
+
"Button"
|
|
146
|
+
],
|
|
147
|
+
"applicationResponsibilities": [
|
|
148
|
+
"显示/隐藏、上移/下移、锁定列和恢复默认均生效",
|
|
149
|
+
"刷新恢复约定列偏好,存储失败仍可本轮操作,宽表可滚动且操作列可达"
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
"deletion": {
|
|
153
|
+
"components": [
|
|
154
|
+
"Modal",
|
|
155
|
+
"Alert",
|
|
156
|
+
"Button"
|
|
157
|
+
],
|
|
158
|
+
"applicationResponsibilities": [
|
|
159
|
+
"核对对象身份、数量、可删除与受阻原因,取消不改变数据",
|
|
160
|
+
"失败保留选择;记录变化重新核对,结果与本次PRD的整批/部分成功策略一致"
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
"edit": {
|
|
164
|
+
"components": [
|
|
165
|
+
"Form",
|
|
166
|
+
"FormItem",
|
|
167
|
+
"Input",
|
|
168
|
+
"Select",
|
|
169
|
+
"Drawer",
|
|
170
|
+
"Modal"
|
|
171
|
+
],
|
|
172
|
+
"applicationResponsibilities": [
|
|
173
|
+
"字段类型、必填、格式、长度、联动和只读状态逐项对应需求",
|
|
174
|
+
"校验失败定位字段,提交失败保留输入,再试成功更新正确记录"
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
"dirty": {
|
|
178
|
+
"components": [
|
|
179
|
+
"Modal",
|
|
180
|
+
"Drawer",
|
|
181
|
+
"Button"
|
|
182
|
+
],
|
|
183
|
+
"applicationResponsibilities": [
|
|
184
|
+
"关闭、返回、切页和身份变化按约定保留/确认丢弃,不静默丢失输入",
|
|
185
|
+
"需要草稿恢复时验证恢复与版本;不需要时明确丢弃策略"
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
"dialog": {
|
|
189
|
+
"components": [
|
|
190
|
+
"Drawer",
|
|
191
|
+
"Modal"
|
|
192
|
+
],
|
|
193
|
+
"applicationResponsibilities": [
|
|
194
|
+
"实际打开与关闭的生命周期完整,抽屉进出中间态存在,未提前卸载",
|
|
195
|
+
"遮罩、Escape、取消、背景滚动与焦点恢复符合固定呈现参数,嵌套弹层不串状态"
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
"menu": {
|
|
199
|
+
"components": [
|
|
200
|
+
"Dropdown",
|
|
201
|
+
"Menu",
|
|
202
|
+
"Button"
|
|
203
|
+
],
|
|
204
|
+
"applicationResponsibilities": [
|
|
205
|
+
"菜单键盘开关和焦点正确,逐操作核对对象、权限与结果"
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
"detail": {
|
|
209
|
+
"components": [
|
|
210
|
+
"Descriptions",
|
|
211
|
+
"Tabs",
|
|
212
|
+
"Timeline",
|
|
213
|
+
"Card"
|
|
214
|
+
],
|
|
215
|
+
"applicationResponsibilities": [
|
|
216
|
+
"详情对象、分组、状态与列表对应",
|
|
217
|
+
"采用Tab/历史时切换和返回保留约定上下文,未声明的示例业务不自动移植"
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
"hierarchy": {
|
|
221
|
+
"components": [
|
|
222
|
+
"Tree",
|
|
223
|
+
"TreeSelect",
|
|
224
|
+
"Table.expandable"
|
|
225
|
+
],
|
|
226
|
+
"applicationResponsibilities": [
|
|
227
|
+
"逐层展开/收起,路径和节点身份稳定,无数据和不可用节点明确",
|
|
228
|
+
"父子选择策略、半选、搜索和关联列表按声明类型实测,树表展开不丢行或误选"
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
"wizard": {
|
|
232
|
+
"components": [
|
|
233
|
+
"Steps",
|
|
234
|
+
"Form",
|
|
235
|
+
"Button"
|
|
236
|
+
],
|
|
237
|
+
"applicationResponsibilities": [
|
|
238
|
+
"每步校验,非法输入不能越过;前后切换保留正确数据",
|
|
239
|
+
"最终复核与提交对象一致,失败保留可重试,关闭和继续符合草稿策略"
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
"canvas": {
|
|
243
|
+
"components": [
|
|
244
|
+
"Button",
|
|
245
|
+
"Tooltip",
|
|
246
|
+
"Form"
|
|
247
|
+
],
|
|
248
|
+
"applicationResponsibilities": [
|
|
249
|
+
"节点选择与属性联动,移动和键盘操作作用于同一节点",
|
|
250
|
+
"缩放、适合画布、滚动正确,拖动坐标随缩放换算",
|
|
251
|
+
"新增删除节点与连线、连线规则及校验反馈正确",
|
|
252
|
+
"只读不能编辑,未保存保护与保存结果符合本次业务状态"
|
|
253
|
+
]
|
|
254
|
+
},
|
|
255
|
+
"authorization": {
|
|
256
|
+
"components": [
|
|
257
|
+
"Tree",
|
|
258
|
+
"Checkbox",
|
|
259
|
+
"Button",
|
|
260
|
+
"Alert"
|
|
261
|
+
],
|
|
262
|
+
"applicationResponsibilities": [
|
|
263
|
+
"菜单、按钮、字段的隐藏/禁用和原因符合实际PRD角色",
|
|
264
|
+
"切换范围及直达路由按模拟权限合同处理;不把前端演示称为服务端授权"
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
"lifecycle": {
|
|
268
|
+
"components": [
|
|
269
|
+
"Tag",
|
|
270
|
+
"Steps",
|
|
271
|
+
"Timeline",
|
|
272
|
+
"Button"
|
|
273
|
+
],
|
|
274
|
+
"applicationResponsibilities": [
|
|
275
|
+
"允许操作随业务状态变化,拒绝无效转移;版本冲突和历史展示有依据"
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
"persistence": {
|
|
279
|
+
"components": [
|
|
280
|
+
"Alert",
|
|
281
|
+
"Button"
|
|
282
|
+
],
|
|
283
|
+
"applicationResponsibilities": [
|
|
284
|
+
"本次实例、用户、范围和页面的偏好或草稿按约定隔离",
|
|
285
|
+
"坏缓存与拒存储有恢复,不静默覆盖其他实例"
|
|
286
|
+
]
|
|
287
|
+
},
|
|
288
|
+
"relation": {
|
|
289
|
+
"components": [
|
|
290
|
+
"Modal",
|
|
291
|
+
"Tree",
|
|
292
|
+
"Table",
|
|
293
|
+
"Input"
|
|
294
|
+
],
|
|
295
|
+
"applicationResponsibilities": [
|
|
296
|
+
"检索、范围过滤、选择、取消和回填对象一致"
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -5,6 +5,8 @@ import zhCN from 'antdv-next/locale/zh_CN'
|
|
|
5
5
|
import type { PrototypeConfig } from './config'
|
|
6
6
|
import { THEME_IDS, THEME_PRESETS, loadThemePreference, safeThemeStorage, saveThemePreference, themeStorageKey } from './domain/theme'
|
|
7
7
|
import type { ThemeId } from './domain/theme'
|
|
8
|
+
import PrototypeShell from './PrototypeShell.vue'
|
|
9
|
+
import { presentationTokens, resolvePresentation } from './domain/presentation'
|
|
8
10
|
const props = defineProps<{ config: PrototypeConfig }>()
|
|
9
11
|
const storage = safeThemeStorage(() => window.localStorage)
|
|
10
12
|
const key = themeStorageKey(props.config.storageNamespace)
|
|
@@ -18,11 +20,14 @@ const motionChanged = (event: MediaQueryListEvent) => { reduced.value = event.ma
|
|
|
18
20
|
media.addEventListener('change', motionChanged)
|
|
19
21
|
onBeforeUnmount(() => media.removeEventListener('change', motionChanged))
|
|
20
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` }))
|
|
21
25
|
const tokens = computed(() => ({
|
|
22
26
|
algorithm: preset.value.mode === 'dark' ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
|
|
23
27
|
token: { colorPrimary: preset.value.primary, colorBgBase: preset.value.bg, colorBgContainer: preset.value.surface,
|
|
24
28
|
colorBgElevated: preset.value.surface, colorText: preset.value.text, colorTextSecondary: preset.value.muted,
|
|
25
|
-
colorBorder: preset.value.border, colorTextLightSolid: preset.value.onPrimary, motion: !reduced.value
|
|
29
|
+
colorBorder: preset.value.border, colorTextLightSolid: preset.value.onPrimary, motion: !reduced.value,
|
|
30
|
+
...(recipe.value ? { borderRadius: recipe.value.borderRadius, controlHeight: recipe.value.controlHeight } : {}) },
|
|
26
31
|
}))
|
|
27
32
|
function choose(value: unknown) {
|
|
28
33
|
if (typeof value !== 'string' || !THEME_IDS.includes(value as ThemeId)) return
|
|
@@ -35,8 +40,14 @@ watch(preset, value => {
|
|
|
35
40
|
}, { immediate: true })
|
|
36
41
|
</script>
|
|
37
42
|
<template>
|
|
38
|
-
<ConfigProvider :locale="zhCN" :theme="tokens">
|
|
39
|
-
<div
|
|
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 }">
|
|
40
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>
|
|
41
52
|
<main :style="{ background: preset.surface, borderColor: preset.border }">
|
|
42
53
|
<Alert v-if="warning" type="warning" :title="warning" show-icon />
|
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>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { resolveThemeConfig } from './domain/theme'
|
|
2
2
|
import type { ThemeBinding } from './domain/theme'
|
|
3
|
-
|
|
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[] } }
|
|
4
6
|
export interface PrototypeMeta extends PrototypeConfig { candidateId: string }
|
|
5
7
|
declare global { interface Window { __PROTOTYPE_META__: PrototypeMeta } }
|
|
6
8
|
|
|
@@ -14,5 +16,8 @@ export async function loadConfig(): Promise<PrototypeConfig> {
|
|
|
14
16
|
if (typeof value[key] !== 'string' || !(value[key] as string).trim()) throw new Error(`原型配置缺少 ${key}。`)
|
|
15
17
|
}
|
|
16
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('原型页面标识重复。')
|
|
17
22
|
return value as unknown as PrototypeConfig
|
|
18
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
|
+
}
|
|
@@ -11,3 +11,16 @@ section { min-height: 240px; }
|
|
|
11
11
|
:focus-visible { outline: 3px solid currentColor; outline-offset: 3px; }
|
|
12
12
|
@media (max-width: 600px) { .prototype-shell { padding: 12px; } main { padding: 12px; } h1 { font-size: 20px; } }
|
|
13
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,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 2,
|
|
3
|
+
"id": "buddy-command-auto-v2",
|
|
4
|
+
"nativeValidation": "pending-user",
|
|
5
|
+
"platforms": ["darwin", "linux"],
|
|
6
|
+
"hosts": ["codebuddy", "workbuddy"],
|
|
7
|
+
"events": ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"],
|
|
8
|
+
"transcriptFormat": ["assistant-user-tool-use-jsonl-v1", "flat-message-function-call-jsonl", "codebuddy-ide-index-v1"],
|
|
9
|
+
"products": {
|
|
10
|
+
"ide": {"settings": "settings.json", "configuration": "codebuddy-project-overrides-global; workbuddy-merged", "transcriptRoot": "native-hook-transcript-path", "nativeReview": "reload-and-verify"},
|
|
11
|
+
"cli": {"settings": "settings.json", "configuration": "merged-project-first-binding", "transcriptRoot": "native-hook-transcript-path", "nativeReview": "/hooks"}
|
|
12
|
+
},
|
|
13
|
+
"identification": "Explicit native client, or a callback log in a configured native host projects directory; unknown/conflicting identity remains unverified.",
|
|
14
|
+
"recovery": {"perMissing": 2, "total": 6, "doesNotForceNextTool": true},
|
|
15
|
+
"sources": ["https://www.codebuddy.cn/docs/ide/Features/Hooks", "https://www.codebuddy.cn/docs/cli/hooks", "https://www.codebuddy.cn/docs/workbuddy/From-Beginner-to-Expert-Guide/Function-Description/Plug-In"],
|
|
16
|
+
"localSourceInspection": {"workbuddy": "5.8.3 bundled CLI: shared command Hook protocol, config/projects/session JSONL, providerData.isMeta; native runtime acceptance pending"}
|
|
17
|
+
}
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
"label": "CodeBuddy",
|
|
9
9
|
"projectSkillsDir": ".codebuddy/skills",
|
|
10
10
|
"globalSkillsDir": {
|
|
11
|
-
"base": "
|
|
12
|
-
"
|
|
11
|
+
"base": "env",
|
|
12
|
+
"env": "CODEBUDDY_CONFIG_DIR",
|
|
13
|
+
"fallback": ".codebuddy",
|
|
14
|
+
"path": "skills"
|
|
13
15
|
}
|
|
14
16
|
},
|
|
15
17
|
{
|