apple-design-skill 0.0.1
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/.claude-plugin/plugin.json +12 -0
- package/.codex-plugin/plugin.json +27 -0
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/bin/apple-design-skill.mjs +90 -0
- package/lib/installer.mjs +191 -0
- package/package.json +51 -0
- package/skills/apple-design-skill/SKILL.md +115 -0
- package/skills/apple-design-skill/agents/openai.yaml +4 -0
- package/skills/apple-design-skill/references/collaboration.md +38 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apple-design-skill",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Apple-style interface design with complete dropdown verification and optional multi-agent collaboration.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "yqstar",
|
|
7
|
+
"url": "https://github.com/yqstar"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/yqstar/apple-design-skill",
|
|
10
|
+
"repository": "https://github.com/yqstar/apple-design-skill",
|
|
11
|
+
"license": "MIT"
|
|
12
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apple-design-skill",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Apple-style interface design, complete dropdown verification, and optional multi-agent collaboration.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "yqstar",
|
|
7
|
+
"url": "https://github.com/yqstar"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/yqstar/apple-design-skill",
|
|
10
|
+
"repository": "https://github.com/yqstar/apple-design-skill",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"skills": "./skills/",
|
|
13
|
+
"interface": {
|
|
14
|
+
"displayName": "Apple Design",
|
|
15
|
+
"shortDescription": "Consistent interfaces across coding agents.",
|
|
16
|
+
"longDescription": "Design and review Apple-style web interfaces, including complete dropdown appearance, keyboard behavior, accessibility, and coordinated multi-agent work.",
|
|
17
|
+
"developerName": "yqstar",
|
|
18
|
+
"category": "Productivity",
|
|
19
|
+
"capabilities": [
|
|
20
|
+
"Write",
|
|
21
|
+
"Interactive"
|
|
22
|
+
],
|
|
23
|
+
"defaultPrompt": [
|
|
24
|
+
"Use Apple Design to design or review this interface and verify affected interactions."
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.1
|
|
4
|
+
|
|
5
|
+
- First non-prerelease version, published through the GitHub Actions OIDC workflow.
|
|
6
|
+
- Accept CRLF skill sources during validation and keep repository text files consistently LF across platforms.
|
|
7
|
+
- Extend regression coverage for Windows checkout line endings and release tag validation.
|
|
8
|
+
- Retain the candidate version for exact-version installation, rollback and side-by-side comparison.
|
|
9
|
+
|
|
10
|
+
## 1.0.0-rc.1
|
|
11
|
+
|
|
12
|
+
- Package the existing Apple design guidance as a portable Agent Skill.
|
|
13
|
+
- Support project and user installations for Codex, Claude Code, Cursor and the shared Agent Skills directory.
|
|
14
|
+
- Add immutable local version snapshots, offline activation, named side-by-side installations, dry runs, backups and recoverable uninstall.
|
|
15
|
+
- Add optional multi-agent collaboration guidance while preserving host permissions and single-agent workflows.
|
|
16
|
+
- Add Codex and Claude plugin manifests, cross-platform CI, and tag-triggered npm trusted publishing.
|
|
17
|
+
|
|
18
|
+
This release candidate bootstraps the npm package before configuring its GitHub Actions trusted publisher. A stable release follows verification of the automated publishing path.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yqstar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Apple Design Skill
|
|
2
|
+
|
|
3
|
+
[](https://github.com/yqstar/apple-design-skill/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/apple-design-skill)
|
|
5
|
+
|
|
6
|
+
一个可用于 **Codex、Claude Code、Cursor 和 Agent Skills 兼容工具**的 Apple 风格界面设计技能。重点覆盖完整下拉菜单、视觉一致性、键盘与焦点行为,以及按需启用的多 Agent 协作。
|
|
7
|
+
|
|
8
|
+
这是基于 Apple 设计资料的 Web 实践指南,**不是 Apple 官方规范或官方产品**。技能提供工作指引,不包含模型服务、Agent 运行时或付费 API。
|
|
9
|
+
|
|
10
|
+
## 安装
|
|
11
|
+
|
|
12
|
+
需要 Node.js 22 或更新版本。npm 安装包时不会自动写入 Agent 配置;下面的 `install` 命令才会安装技能。
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
# 当前项目:同时安装给 Codex、Claude Code 和 Cursor
|
|
16
|
+
npx apple-design-skill@latest install --all
|
|
17
|
+
|
|
18
|
+
# 固定版本,适合团队复现
|
|
19
|
+
npx apple-design-skill@0.0.1 install --all --project .
|
|
20
|
+
|
|
21
|
+
# 只安装给指定工具,可使用逗号或重复 --agent
|
|
22
|
+
npx apple-design-skill@0.0.1 install --agent codex,claude
|
|
23
|
+
|
|
24
|
+
# 用户级安装
|
|
25
|
+
npx apple-design-skill@0.0.1 install --agent cursor --global
|
|
26
|
+
|
|
27
|
+
# 不指定工具:安装到通用 .agents/skills
|
|
28
|
+
npx apple-design-skill@0.0.1 install
|
|
29
|
+
|
|
30
|
+
# 提前查看目标路径,不写入文件
|
|
31
|
+
npx apple-design-skill@0.0.1 install --all --dry-run
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| 目标 | 项目级目录 | 用户级目录 |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `codex` / `universal` | `.agents/skills/apple-design-skill` | `~/.agents/skills/apple-design-skill` |
|
|
37
|
+
| `claude` | `.claude/skills/apple-design-skill` | `~/.claude/skills/apple-design-skill` |
|
|
38
|
+
| `cursor` | `.cursor/skills/apple-design-skill` | `~/.cursor/skills/apple-design-skill` |
|
|
39
|
+
|
|
40
|
+
路径遵循 [Codex 本地技能发现](https://learn.chatgpt.com/docs/build-skills#where-codex-loads-local-skills)、[Claude Code Skills](https://code.claude.com/docs/en/skills) 和 [Cursor Skills](https://prod.cursor.com/docs/skills) 文档。Cursor 还可能发现 `.agents`、`.claude` 等兼容目录;同时使用多个工具时,`--all` 的复制安装可能在某些工具内显示同名入口。只使用 Codex 与 Cursor 时,单独安装 `--agent universal` 可避免重复副本。
|
|
41
|
+
|
|
42
|
+
已有 `~/.codex/skills/apple-design-skill` 等旧目录不会被静默迁移或删除。新目录与旧目录可能同时被发现,请在确认新版本可用后自行整理旧副本。
|
|
43
|
+
|
|
44
|
+
## 升级、回退和多版本并存
|
|
45
|
+
|
|
46
|
+
`npx 包名@版本` 决定安装器携带的技能版本。安装后,该版本的完整快照保存在当前作用域的 `.apple-design-skill/versions/<版本>`,位于 Agent 自动发现目录之外;实际使用的是复制到 Agent 目录的活动版本。
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
# 升级到最新稳定版本;干净的受管理副本可直接更新
|
|
50
|
+
npx apple-design-skill@latest install --all
|
|
51
|
+
|
|
52
|
+
# 安装候选版(不影响 latest 指向)
|
|
53
|
+
npx apple-design-skill@next install --name apple-design-preview
|
|
54
|
+
|
|
55
|
+
# 查看当前作用域缓存的版本与所选工具的活动版本
|
|
56
|
+
npx apple-design-skill@latest list --all
|
|
57
|
+
|
|
58
|
+
# 切换回已安装过的版本;use 不下载技能内容
|
|
59
|
+
npx apple-design-skill@latest use 0.0.1 --all
|
|
60
|
+
|
|
61
|
+
# 尚未缓存的旧版本,直接从 npm 按精确版本安装
|
|
62
|
+
npx apple-design-skill@1.0.0-rc.1 install --all
|
|
63
|
+
|
|
64
|
+
# 两个版本同时保留,使用不同的技能名称
|
|
65
|
+
npx apple-design-skill@1.0.0-rc.1 install --name apple-design-preview --agent codex
|
|
66
|
+
npx apple-design-skill@0.0.1 install --name apple-design-stable --agent codex
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
并存安装会同时修改 `SKILL.md` 中的 `name` 和 Codex 元数据里的调用名称。因此可以明确调用 `$apple-design-preview` 或 `$apple-design-stable`,不会让两个版本共享同一个技能名称。自动选择时仍有可能同时匹配相似描述,做版本对照时请显式指定要使用的名称。
|
|
70
|
+
|
|
71
|
+
`use` 本身可以离线使用;`npx @latest` 仍可能访问 npm 解析安装器。需要完全离线时,先将安装器固定在本地依赖中,或通过已安装的 CLI 执行:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
npm install --save-dev --save-exact apple-design-skill@0.0.1
|
|
75
|
+
./node_modules/.bin/apple-design-skill use 0.0.1 --all
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Windows 可运行 `node node_modules/apple-design-skill/bin/apple-design-skill.mjs use 0.0.1 --all`。
|
|
79
|
+
|
|
80
|
+
### 保护本地修改
|
|
81
|
+
|
|
82
|
+
- 已存在的非本包目录或有本地修改的安装,默认拒绝覆盖。
|
|
83
|
+
- 使用 `--force` 时,原文件先移动到 `.apple-design-skill/backups/`,CLI 会输出路径。
|
|
84
|
+
- 同一版本不允许出现不同技能内容;发布内容变化必须使用新版本号。
|
|
85
|
+
- 多目标安装先整体检查,再切换目录;切换失败时恢复之前的活动副本。
|
|
86
|
+
- 进程被强制终止可能留下锁或事务目录。检查是否仍有安装器运行,再处理 `.apple-design-skill/lock`;保留事务目录中的 `old-*` 恢复文件。
|
|
87
|
+
- 安装器拒绝目标路径和缓存中的符号链接,不通过 `--force` 绕过。
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
npx apple-design-skill@0.0.1 install --all --force
|
|
91
|
+
npx apple-design-skill@0.0.1 uninstall --all
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
卸载仅处理本安装器管理的活动副本,保留版本快照和备份。`--project` / `--global`、`--agent`、`--name` 都要与目标安装对应。若要把团队技能提交到 Git,可提交对应工具的技能目录,并忽略项目中的 `.apple-design-skill/` 历史缓存。
|
|
95
|
+
|
|
96
|
+
## 使用技能
|
|
97
|
+
|
|
98
|
+
Codex 可显式调用 `$apple-design-skill`;Claude Code 可调用 `/apple-design-skill`;其他工具使用各自的技能入口。未立即发现时,开启一个新的 Agent 会话。
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
使用 apple-design-skill 实现这个页面。
|
|
102
|
+
保留现有产品需求和技术栈,特别检查下拉按钮与展开菜单的一致性,
|
|
103
|
+
并验证键盘选择、关闭方式和窄屏布局。
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 多 Agent 协作
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
使用 apple-design-skill,通过多个 Agent 协作改进这个页面:
|
|
110
|
+
由负责人确定统一视觉变量并整合改动;
|
|
111
|
+
实现者负责分配到的独立组件;
|
|
112
|
+
交互检查者独立验证下拉菜单、焦点和键盘流程。
|
|
113
|
+
请明确各自修改的文件、验收条件和实际完成的检查。
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
协作指南在 [references/collaboration.md](skills/apple-design-skill/references/collaboration.md)。仅在用户请求或宿主已授权时分派 Agent;普通小修改直接完成。不假定每个工具都有子 Agent API,也不替用户安装 Agent 运行时。没有可用的并行能力时,按顺序执行并明确说明。
|
|
117
|
+
|
|
118
|
+
包同时包含 `.codex-plugin/plugin.json` 和 `.claude-plugin/plugin.json`,供支持相应插件格式的宿主或市场打包使用。npm CLI 的安装路径是直接技能安装,不会自动注册插件市场;插件市场版本生命周期由对应宿主管理。
|
|
119
|
+
|
|
120
|
+
## GitHub Actions → npm
|
|
121
|
+
|
|
122
|
+
- `ci.yml`:在 Linux、macOS、Windows 上,分别使用 Node.js 22 和 24 检查技能、脚本、安装器测试和 npm 包内容。
|
|
123
|
+
- `publish.yml`:推送 `v*` 标签时检查标签与包版本一致,测试后打包并通过 npm Trusted Publishing / OIDC 发布;稳定版本使用 `latest`,预发布版本使用 `next`。
|
|
124
|
+
- 发布工作流仅获得 `contents: read` 和 `id-token: write`,不使用长期 `NPM_TOKEN`。
|
|
125
|
+
- `workflow_dispatch` 只在版本标签 ref 上执行,可以重跑失败的发布。已成功发布的版本不应重复发布。
|
|
126
|
+
|
|
127
|
+
本包已配置可信发布。初始化时发布的 `1.0.0-rc.1` 保留在 `next`;正式版本从 `0.0.1` 开始,使用 `latest`。npm 侧配置如下:
|
|
128
|
+
|
|
129
|
+
| 项目 | 值 |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| Provider | GitHub Actions |
|
|
132
|
+
| Organization/user | `yqstar` |
|
|
133
|
+
| Repository | `apple-design-skill` |
|
|
134
|
+
| Workflow filename | `publish.yml` |
|
|
135
|
+
| Environment | 留空(工作流未使用命名环境) |
|
|
136
|
+
| Allowed action | 允许直接 `npm publish` |
|
|
137
|
+
|
|
138
|
+
参考 [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers/);只有工作流文件、仓库和 npm 侧授权同时匹配,OIDC 发布才会成功。
|
|
139
|
+
|
|
140
|
+
后续发版:
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
node scripts/version.mjs 0.0.2
|
|
144
|
+
# 更新 CHANGELOG.md,审查改动
|
|
145
|
+
npm run check
|
|
146
|
+
npm test
|
|
147
|
+
git add package.json package-lock.json .codex-plugin/plugin.json .claude-plugin/plugin.json CHANGELOG.md
|
|
148
|
+
git commit -m "Release 0.0.2"
|
|
149
|
+
git tag v0.0.2
|
|
150
|
+
git push origin main
|
|
151
|
+
git push origin v0.0.2
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 本地开发
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
npm ci --ignore-scripts
|
|
158
|
+
npm run check
|
|
159
|
+
npm test
|
|
160
|
+
npm pack --dry-run
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
零运行时依赖。测试使用临时目录,覆盖多 Agent 目标、升级与回退、并存名称、缓存完整性、本地修改保护、备份、可恢复卸载、路径与符号链接检查、事务失败恢复,以及真实 npm 压缩包的安装。
|
|
164
|
+
|
|
165
|
+
## 来源与许可证
|
|
166
|
+
|
|
167
|
+
MIT,Copyright 2026 yqstar。原有设计指导保留在 [SKILL.md](skills/apple-design-skill/SKILL.md),其中链接到 Apple、W3C、MDN 等参考资料;这些第三方资料的权利仍属于原权利人。本包不包含 Apple 品牌图形或官方授权声明。
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { execute, resolveTargets, validateName, validateVersion } from '../lib/installer.mjs';
|
|
7
|
+
|
|
8
|
+
const packageRoot = fileURLToPath(new URL('../', import.meta.url));
|
|
9
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
10
|
+
const help = `apple-design-skill ${pkg.version}
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
apple-design-skill install [options] Install this npm package's skill version
|
|
14
|
+
apple-design-skill use VERSION [options] Activate a previously installed version, offline
|
|
15
|
+
apple-design-skill list [options] List cached versions and selected installations
|
|
16
|
+
apple-design-skill uninstall [options] Remove a managed active copy; keep version history
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--agent NAME[,NAME] codex, claude, cursor, universal (repeatable)
|
|
20
|
+
--all Codex + Claude Code + Cursor
|
|
21
|
+
--project PATH Project scope (default: current directory)
|
|
22
|
+
--global User scope, in your home directory
|
|
23
|
+
--name NAME Skill name; use a distinct name for side-by-side versions
|
|
24
|
+
--force Back up and replace existing or locally modified files
|
|
25
|
+
--dry-run Show planned changes without writing files
|
|
26
|
+
--json Emit machine-readable results
|
|
27
|
+
-v, --version Print npm package version
|
|
28
|
+
-h, --help Show help
|
|
29
|
+
|
|
30
|
+
Default target: universal (.agents/skills), also discovered by Codex and Cursor.
|
|
31
|
+
No files are installed by npm postinstall; run the install command explicitly.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
npx apple-design-skill@${pkg.version} install --all --project .
|
|
35
|
+
npx apple-design-skill@${pkg.version} install --agent claude --global
|
|
36
|
+
npx apple-design-skill@${pkg.version} install --name apple-design-pinned
|
|
37
|
+
npx apple-design-skill@latest use ${pkg.version} --all
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const argv = process.argv.slice(2);
|
|
42
|
+
if (!argv.length || argv.includes('--help') || argv.includes('-h')) {
|
|
43
|
+
process.stdout.write(help); process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
if (argv.length === 1 && ['--version', '-v'].includes(argv[0])) {
|
|
46
|
+
process.stdout.write(`${pkg.version}\n`); process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
const command = argv.shift();
|
|
49
|
+
if (!['install', 'use', 'list', 'uninstall'].includes(command)) throw new Error(`Unknown command: ${command}`);
|
|
50
|
+
const requestedVersion = command === 'use' ? argv.shift() : pkg.version;
|
|
51
|
+
if (command === 'use') validateVersion(requestedVersion);
|
|
52
|
+
let scopeRoot = process.cwd();
|
|
53
|
+
let scopeFlag;
|
|
54
|
+
const agents = [];
|
|
55
|
+
const options = { command, packageRoot, version: requestedVersion, name: 'apple-design-skill', force: false, dryRun: false };
|
|
56
|
+
let json = false;
|
|
57
|
+
const next = (flag, i) => {
|
|
58
|
+
if (!argv[i + 1] || argv[i + 1].startsWith('-')) throw new Error(`${flag} requires a value`);
|
|
59
|
+
return argv[i + 1];
|
|
60
|
+
};
|
|
61
|
+
for (let i = 0; i < argv.length; i++) {
|
|
62
|
+
const arg = argv[i];
|
|
63
|
+
if (arg === '--agent') { agents.push(...next(arg, i++).split(',')); }
|
|
64
|
+
else if (arg === '--all') { agents.push('codex', 'claude', 'cursor'); }
|
|
65
|
+
else if (arg === '--project' || arg === '--global') {
|
|
66
|
+
if (scopeFlag) throw new Error('Choose one of --project or --global');
|
|
67
|
+
scopeFlag = arg;
|
|
68
|
+
scopeRoot = arg === '--project' ? resolve(next(arg, i++)) : homedir();
|
|
69
|
+
} else if (arg === '--name') { options.name = next(arg, i++); }
|
|
70
|
+
else if (arg === '--force') options.force = true;
|
|
71
|
+
else if (arg === '--dry-run') options.dryRun = true;
|
|
72
|
+
else if (arg === '--json') json = true;
|
|
73
|
+
else throw new Error(`Unknown option: ${arg}`);
|
|
74
|
+
}
|
|
75
|
+
validateName(options.name);
|
|
76
|
+
options.scopeRoot = resolve(scopeRoot);
|
|
77
|
+
options.targets = resolveTargets(options.scopeRoot, agents.length ? agents : ['universal'], options.name);
|
|
78
|
+
const result = execute(options);
|
|
79
|
+
if (json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
80
|
+
else {
|
|
81
|
+
if (result.versions) process.stdout.write(`Cached versions: ${result.versions.join(', ') || '(none)'}\n`);
|
|
82
|
+
for (const entry of result.targets) process.stdout.write(`${entry.status}: ${entry.path}${entry.version ? ` (${entry.version})` : ''}\n`);
|
|
83
|
+
if (result.backups?.length) process.stdout.write(`Preserved backups:\n${result.backups.join('\n')}\n`);
|
|
84
|
+
if (options.dryRun) process.stdout.write('Dry run; no files changed.\n');
|
|
85
|
+
else if (['install', 'use'].includes(command)) process.stdout.write('Open a new agent session if the skill is not discovered immediately.\n');
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
process.stderr.write(`apple-design-skill: ${error.message}\n`);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const PACKAGE = 'apple-design-skill';
|
|
6
|
+
const META = '.apple-design-skill-install.json';
|
|
7
|
+
const roots = { codex: '.agents', universal: '.agents', claude: '.claude', cursor: '.cursor' };
|
|
8
|
+
export function validateName(value) {
|
|
9
|
+
if (typeof value !== 'string' || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value) || value.length > 64) throw new Error('Skill name must be 1–64 lowercase letters, digits and single hyphens');
|
|
10
|
+
}
|
|
11
|
+
export function validateVersion(value) {
|
|
12
|
+
const match = typeof value === 'string' && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value);
|
|
13
|
+
if (!match || match[4]?.split('.').some(part => /^\d+$/.test(part) && part.length > 1 && part[0] === '0')) throw new Error(`Invalid exact semantic version: ${value}`);
|
|
14
|
+
}
|
|
15
|
+
function stat(path) { try { return lstatSync(path); } catch (error) { if (error.code === 'ENOENT') return null; throw error; } }
|
|
16
|
+
function json(path) { return JSON.parse(readFileSync(path, 'utf8')); }
|
|
17
|
+
function writeJson(path, value) { writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { flag: 'wx' }); }
|
|
18
|
+
|
|
19
|
+
export function resolveTargets(scopeRoot, agents, name) {
|
|
20
|
+
validateName(name);
|
|
21
|
+
return [...new Set(agents.map(agent => {
|
|
22
|
+
if (!Object.hasOwn(roots, agent)) throw new Error(`Unsupported agent: ${agent}. Choose codex, claude, cursor or universal.`);
|
|
23
|
+
return join(resolve(scopeRoot), roots[agent], 'skills', name);
|
|
24
|
+
}))];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Do not follow symlinks within managed paths, even with --force.
|
|
28
|
+
function assertSafe(scopeRoot, path) {
|
|
29
|
+
const rel = relative(scopeRoot, path);
|
|
30
|
+
if (rel === '..' || rel.startsWith(`..${sep}`) || resolve(scopeRoot, rel) !== resolve(path)) throw new Error(`Path is outside installation scope: ${path}`);
|
|
31
|
+
let current = scopeRoot;
|
|
32
|
+
for (const part of rel.split(sep).filter(Boolean)) {
|
|
33
|
+
current = join(current, part);
|
|
34
|
+
const value = stat(current);
|
|
35
|
+
if (value?.isSymbolicLink()) throw new Error(`Refusing symlink in managed path: ${current}`);
|
|
36
|
+
if (value && !value.isDirectory()) throw new Error(`Expected a directory: ${current}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function treeHash(root) {
|
|
41
|
+
const hash = createHash('sha256');
|
|
42
|
+
function walk(directory, prefix = '') {
|
|
43
|
+
for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
|
|
44
|
+
if (!prefix && entry.name === META) continue;
|
|
45
|
+
const name = `${prefix}${entry.name}`;
|
|
46
|
+
if (entry.isSymbolicLink()) throw new Error(`Symlink is not allowed in skill content: ${name}`);
|
|
47
|
+
if (entry.isDirectory()) { hash.update(`dir:${name}\0`); walk(join(directory, entry.name), `${name}/`); }
|
|
48
|
+
else if (entry.isFile()) {
|
|
49
|
+
const bytes = readFileSync(join(directory, entry.name));
|
|
50
|
+
hash.update(`file:${name}\0${bytes.length}\0`); hash.update(bytes);
|
|
51
|
+
} else throw new Error(`Unsupported file type: ${name}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
walk(root);
|
|
55
|
+
return hash.digest('hex');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function installed(path) {
|
|
59
|
+
if (!stat(path)) return { status: 'absent', path };
|
|
60
|
+
try {
|
|
61
|
+
const m = json(join(path, META));
|
|
62
|
+
if (m.schema !== 1 || m.package !== PACKAGE || typeof m.hash !== 'string') return { status: 'unmanaged', path };
|
|
63
|
+
validateVersion(m.version);
|
|
64
|
+
return { status: treeHash(path) === m.hash ? 'managed' : 'modified', path, version: m.version };
|
|
65
|
+
} catch { return { status: 'unmanaged', path }; }
|
|
66
|
+
}
|
|
67
|
+
function inspectCache(path, version) {
|
|
68
|
+
const metadata = json(join(path, META));
|
|
69
|
+
if (metadata.schema !== 1 || metadata.package !== PACKAGE || metadata.version !== version || metadata.hash !== treeHash(path)) throw new Error(`Cached version is damaged: ${version}`);
|
|
70
|
+
return metadata;
|
|
71
|
+
}
|
|
72
|
+
function renderAlias(path, name) {
|
|
73
|
+
if (name === PACKAGE) return;
|
|
74
|
+
const skill = join(path, 'SKILL.md');
|
|
75
|
+
const text = readFileSync(skill, 'utf8');
|
|
76
|
+
if (!/^---\r?\n[\s\S]*?\r?\n---/.test(text) || !/^name: apple-design-skill$/m.test(text)) throw new Error('Packaged skill has an unexpected name');
|
|
77
|
+
writeFileSync(skill, text.replace(/^name: apple-design-skill$/m, `name: ${name}`));
|
|
78
|
+
const ui = join(path, 'agents', 'openai.yaml');
|
|
79
|
+
if (existsSync(ui)) writeFileSync(ui, readFileSync(ui, 'utf8').replaceAll('$apple-design-skill', `$${name}`));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function execute(options) {
|
|
83
|
+
const { command, name, force = false, dryRun = false, version, packageRoot } = options;
|
|
84
|
+
validateName(name);
|
|
85
|
+
if (!['install', 'use', 'list', 'uninstall'].includes(command)) throw new Error('Unsupported command');
|
|
86
|
+
if (!stat(options.scopeRoot)?.isDirectory()) throw new Error(`Scope directory does not exist: ${options.scopeRoot}`);
|
|
87
|
+
const scopeRoot = realpathSync(options.scopeRoot);
|
|
88
|
+
const targets = options.targets.map(path => join(scopeRoot, relative(options.scopeRoot, path)));
|
|
89
|
+
const store = join(scopeRoot, '.apple-design-skill');
|
|
90
|
+
assertSafe(scopeRoot, store);
|
|
91
|
+
for (const child of ['versions', 'transactions', 'backups']) assertSafe(scopeRoot, join(store, child));
|
|
92
|
+
targets.forEach(path => assertSafe(scopeRoot, path));
|
|
93
|
+
if (command === 'list') {
|
|
94
|
+
const versionsPath = join(store, 'versions');
|
|
95
|
+
assertSafe(scopeRoot, versionsPath);
|
|
96
|
+
const versions = stat(versionsPath) ? readdirSync(versionsPath).filter(v => { try { validateVersion(v); return stat(join(versionsPath, v))?.isDirectory(); } catch { return false; } }).sort() : [];
|
|
97
|
+
return { command, store, versions, targets: targets.map(installed) };
|
|
98
|
+
}
|
|
99
|
+
if (command !== 'uninstall') validateVersion(version);
|
|
100
|
+
const states = targets.map(installed);
|
|
101
|
+
for (const state of states) {
|
|
102
|
+
if (command === 'uninstall' && state.status === 'unmanaged') throw new Error(`Refusing to uninstall an unmanaged directory: ${state.path}`);
|
|
103
|
+
if (['modified', 'unmanaged'].includes(state.status) && !force) throw new Error(`Existing ${state.status} files at ${state.path}; use --force to preserve a backup and replace them`);
|
|
104
|
+
}
|
|
105
|
+
let source;
|
|
106
|
+
let sourceHash;
|
|
107
|
+
const versionPath = command === 'uninstall' ? null : join(store, 'versions', version);
|
|
108
|
+
if (versionPath) assertSafe(scopeRoot, versionPath);
|
|
109
|
+
if (command === 'install') {
|
|
110
|
+
const pkg = json(join(packageRoot, 'package.json'));
|
|
111
|
+
if (pkg.name !== PACKAGE || pkg.version !== version) throw new Error('Package version does not match requested installation');
|
|
112
|
+
source = join(packageRoot, 'skills', PACKAGE);
|
|
113
|
+
if (!stat(join(source, 'SKILL.md'))?.isFile()) throw new Error('Packaged SKILL.md is missing');
|
|
114
|
+
sourceHash = treeHash(source);
|
|
115
|
+
if (stat(versionPath) && inspectCache(versionPath, version).hash !== sourceHash) throw new Error(`Version ${version} already exists with different content; publish a new version`);
|
|
116
|
+
} else if (command === 'use') {
|
|
117
|
+
if (!stat(versionPath)) throw new Error(`Version ${version} is not cached. Install it first with npx apple-design-skill@${version} install.`);
|
|
118
|
+
inspectCache(versionPath, version); source = versionPath;
|
|
119
|
+
}
|
|
120
|
+
const plan = { command, store, targets: states.map(s => ({ ...s, status: command === 'uninstall' ? (s.status === 'absent' ? 'absent' : 'remove') : 'install', ...(command === 'uninstall' ? {} : { version }) })) };
|
|
121
|
+
if (dryRun) return plan;
|
|
122
|
+
if (command === 'uninstall' && states.every(s => s.status === 'absent')) return { ...plan, targets: states, backups: [] };
|
|
123
|
+
mkdirSync(store, { recursive: true });
|
|
124
|
+
const lock = join(store, 'lock');
|
|
125
|
+
try { mkdirSync(lock); } catch (error) { if (error.code === 'EEXIST') throw new Error(`Another installation is active, or an interrupted lock remains: ${lock}. Check for a running installer before removing the lock.`); throw error; }
|
|
126
|
+
const transaction = join(store, 'transactions', randomUUID());
|
|
127
|
+
const entries = [];
|
|
128
|
+
const backups = [];
|
|
129
|
+
let finished = false;
|
|
130
|
+
try {
|
|
131
|
+
// Recheck after acquiring the per-scope lock; another invocation may have finished.
|
|
132
|
+
states.forEach(s => {
|
|
133
|
+
assertSafe(scopeRoot, s.path);
|
|
134
|
+
const now = installed(s.path);
|
|
135
|
+
if (JSON.stringify(now) !== JSON.stringify(s)) throw new Error(`Installation changed during preflight: ${s.path}. Retry.`);
|
|
136
|
+
});
|
|
137
|
+
mkdirSync(transaction, { recursive: true });
|
|
138
|
+
if (command === 'install') {
|
|
139
|
+
if (stat(versionPath)) {
|
|
140
|
+
if (inspectCache(versionPath, version).hash !== sourceHash) throw new Error('Version cache changed during preflight');
|
|
141
|
+
} else {
|
|
142
|
+
const cacheStage = join(transaction, 'cache');
|
|
143
|
+
cpSync(source, cacheStage, { recursive: true, errorOnExist: true, force: false });
|
|
144
|
+
writeJson(join(cacheStage, META), { schema: 1, package: PACKAGE, version, hash: sourceHash });
|
|
145
|
+
mkdirSync(dirname(versionPath), { recursive: true });
|
|
146
|
+
renameSync(cacheStage, versionPath);
|
|
147
|
+
}
|
|
148
|
+
source = versionPath;
|
|
149
|
+
}
|
|
150
|
+
if (source) inspectCache(source, version);
|
|
151
|
+
for (const [index, state] of states.entries()) {
|
|
152
|
+
const staged = join(transaction, `new-${index}`);
|
|
153
|
+
if (command !== 'uninstall') {
|
|
154
|
+
cpSync(source, staged, { recursive: true, errorOnExist: true, force: false });
|
|
155
|
+
rmSync(join(staged, META));
|
|
156
|
+
renderAlias(staged, name);
|
|
157
|
+
writeJson(join(staged, META), { schema: 1, package: PACKAGE, version, name, hash: treeHash(staged) });
|
|
158
|
+
}
|
|
159
|
+
entries.push({ ...state, staged, old: join(transaction, `old-${index}`), movedOld: false, movedNew: false });
|
|
160
|
+
}
|
|
161
|
+
for (const entry of entries) {
|
|
162
|
+
mkdirSync(dirname(entry.path), { recursive: true });
|
|
163
|
+
if (stat(entry.path)) { renameSync(entry.path, entry.old); entry.movedOld = true; }
|
|
164
|
+
if (command !== 'uninstall') { renameSync(entry.staged, entry.path); entry.movedNew = true; }
|
|
165
|
+
}
|
|
166
|
+
// Keep replaced local work and all uninstalls recoverable, outside discovery directories.
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
if (entry.movedOld && (command === 'uninstall' || ['unmanaged', 'modified'].includes(entry.status))) {
|
|
169
|
+
const backup = join(store, 'backups', `${Date.now()}-${randomUUID()}`);
|
|
170
|
+
mkdirSync(dirname(backup), { recursive: true }); renameSync(entry.old, backup);
|
|
171
|
+
entry.old = backup; backups.push(backup);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
finished = true;
|
|
175
|
+
return { ...plan, backups, targets: targets.map(installed) };
|
|
176
|
+
} catch (error) {
|
|
177
|
+
const failures = [];
|
|
178
|
+
for (const entry of entries.toReversed()) {
|
|
179
|
+
try {
|
|
180
|
+
if (entry.movedNew) rmSync(entry.path, { recursive: true });
|
|
181
|
+
if (entry.movedOld) renameSync(entry.old, entry.path);
|
|
182
|
+
} catch (rollbackError) { failures.push(rollbackError.message); }
|
|
183
|
+
}
|
|
184
|
+
if (failures.length) throw new Error(`${error.message}; recovery files retained in ${transaction}: ${failures.join('; ')}`);
|
|
185
|
+
finished = true;
|
|
186
|
+
throw error;
|
|
187
|
+
} finally {
|
|
188
|
+
if (finished) rmSync(transaction, { recursive: true, force: true });
|
|
189
|
+
rmSync(lock, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apple-design-skill",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Apple-style interface design skills for Codex, Claude Code, Cursor and Agent Skills hosts, with collaboration guidance and versioned installation.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"apple-design-skill": "bin/apple-design-skill.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"skills/",
|
|
13
|
+
".codex-plugin/",
|
|
14
|
+
".claude-plugin/",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"CHANGELOG.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"check": "node scripts/check.mjs",
|
|
21
|
+
"test": "node --test tests/*.test.mjs",
|
|
22
|
+
"prepack": "npm run check",
|
|
23
|
+
"release:check": "node scripts/release-check.mjs"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/yqstar/apple-design-skill.git"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/yqstar/apple-design-skill#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/yqstar/apple-design-skill/issues"
|
|
35
|
+
},
|
|
36
|
+
"author": "yqstar",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"keywords": [
|
|
39
|
+
"agent-skills",
|
|
40
|
+
"apple-design",
|
|
41
|
+
"codex",
|
|
42
|
+
"claude-code",
|
|
43
|
+
"cursor",
|
|
44
|
+
"multi-agent",
|
|
45
|
+
"accessibility"
|
|
46
|
+
],
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"registry": "https://registry.npmjs.org/"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: apple-design-skill
|
|
3
|
+
description: "Design, implement, and review Apple-style web interfaces, with particular attention to consistent dropdown triggers and expanded menus. Also use for drag, swipe, sheet, and spring interactions needing direct tracking, velocity handoff, or interruption fixes. Apply gesture guidance only to motion work."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Apple Design
|
|
7
|
+
|
|
8
|
+
Build responsive, coherent interfaces using the project's design system. User requirements take precedence over stylistic defaults. This is web adaptation guidance informed by Apple resources, not an official Apple specification. Instructions are consolidated here; apply the sections relevant to the task.
|
|
9
|
+
|
|
10
|
+
## Agent hosts and collaboration
|
|
11
|
+
|
|
12
|
+
These instructions are portable across Agent Skills hosts, including Codex, Claude Code and Cursor. Use the host's available tools and the project's existing stack; references to browser inspection or delegation describe capabilities, not mandatory vendor-specific APIs. If a required verification capability is unavailable, say what remains unverified instead of inventing evidence.
|
|
13
|
+
|
|
14
|
+
For user-requested or otherwise authorized multi-agent work, read [Collaboration](references/collaboration.md). Keep a single design owner, assign independent work with explicit file boundaries, and review the integrated result. Do ordinary focused tasks directly; this skill does not itself authorize spawning agents, installing software, publishing, or changing external services.
|
|
15
|
+
|
|
16
|
+
**Dropdown priority:** When dropdown appearance is in scope, style and verify both the closed control and the expanded menu. A polished trigger with a mismatched or unverified popup is incomplete. Keep this requirement when refining or shortening this skill.
|
|
17
|
+
|
|
18
|
+
## Workflow
|
|
19
|
+
|
|
20
|
+
1. Inspect affected components, shared tokens, installed dependencies, and actual behavior. Identify the primary task, input methods, states, and affected variants. For a review, report findings without unsolicited edits.
|
|
21
|
+
2. Prioritize correct operation, semantics, focus, and recovery; then continuity and visual consistency; then optional effects. Fix shared components or purposeful variants within scope.
|
|
22
|
+
3. Reuse accessible primitives and existing tools. CSS usually suffices for hover, press, and simple visibility changes; playback control may use Web Animations. Dragging and velocity handoff need readable presentation values, cancellation, and retargeting. Preserve native scrolling and suitable scroll snap; do not add a framework just to apply this skill.
|
|
23
|
+
4. Verify relevant states and input paths before delivery. Report the change, observable result, checks performed, and remaining limitations. Review findings should give location, trigger, impact, evidence, and a minimal correction.
|
|
24
|
+
|
|
25
|
+
## Dropdowns — required when affected
|
|
26
|
+
|
|
27
|
+
### Choose a mechanism that meets the visual requirement
|
|
28
|
+
|
|
29
|
+
- Inspect every affected variant closed and open. Identify whether options come from an OS/browser picker, customizable native picker, or DOM popup; locate mismatches in the trigger, surface, rows, states, or anchoring.
|
|
30
|
+
- Traditional native popup styling is limited: `appearance: none`, a chevron, or CSS on `option` does not establish control over the expanded surface. For a cohesive Apple-style page or a reported menu mismatch, matching that surface is part of the task.
|
|
31
|
+
- Reuse the project's accessible select/combobox primitive, or a small shared accessible component. Keep native controls when intentional to the product or when popup styling is outside scope. Do not silently switch to a mismatched native picker on narrow screens; make an intentional platform fallback and its visual consequence explicit.
|
|
32
|
+
- Use customizable native selects only after verifying target support and actual open/closed behavior. A feature query alone is insufficient; unsupported targets that matter need a coherent accessible fallback.
|
|
33
|
+
- Choose semantics by purpose: value selection, searchable input, listbox, and action menu require their respective keyboard/focus patterns. Do not assign `role="menu"` to every dropdown.
|
|
34
|
+
|
|
35
|
+
### Keep the complete control visually consistent
|
|
36
|
+
|
|
37
|
+
Use shared tokens and documented variants across affected screens; avoid isolated overrides.
|
|
38
|
+
|
|
39
|
+
| Part | Format requirements |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| Trigger | Consistent control-height scale, typography, padding, border, radius, background, placeholder, and label alignment; chevron size and position follow the icon system. |
|
|
42
|
+
| Expanded surface | Matching colors, border, radius, elevation, padding, width/alignment policy, and restrained opening/closing motion. Accommodate content and viewport constraints. |
|
|
43
|
+
| Option rows | Consistent typography, density, spacing, icons, checkmarks, groups, and separators. Keep active/focused, selected, and disabled options distinguishable. |
|
|
44
|
+
| States | Shared default, hover, pressed, focus-visible, open, selected, disabled, and invalid treatment where applicable; handle loading/empty results only when supported. |
|
|
45
|
+
| Placement and theme | Preserve tokens in portals/top-layer popups, dialogs, toolbars, and supported themes. Align to the trigger, flip above when needed, constrain height, scroll active rows into view, and avoid clipping or incorrect stacking within the actual viewport/iframe. |
|
|
46
|
+
|
|
47
|
+
### Preserve selection, focus, and application state
|
|
48
|
+
|
|
49
|
+
- Keep one committed value; separate the active navigation option when the chosen pattern requires it. Define consistent commit/cancel behavior for selection, blur, and outside dismissal. Follow the corresponding [W3C patterns](#sources), including the select-only example when applicable.
|
|
50
|
+
- Keep accessible naming, expanded state, popup ownership, active option, selected value, and disabled state synchronized. A backing native select and custom trigger must not become duplicate accessible/tabbable controls.
|
|
51
|
+
- Preserve real values, input/change behavior, programmatic updates, form reset, and disabled changes. Application logic must read the committed value, not a decorative label.
|
|
52
|
+
- Opening another picker, disabling a field, changing views, or unmounting must close or safely transfer the popup without stale focus or listeners. Standard value-list popups are non-modal: do not add a focus trap, blocking full-screen scrim, or page scroll lock. Respect any enclosing dialog's focus boundary.
|
|
53
|
+
- Reduced motion must retain placement, selection, dismissal, and focus without depending on animation completion callbacks.
|
|
54
|
+
|
|
55
|
+
### Verify the expanded menu
|
|
56
|
+
|
|
57
|
+
- Inspect each affected variant beside surrounding controls, closed and open, including selected and distinct active/hovered rows where supported. Cover long options, many results, relevant themes, the narrowest target viewport, and containing dialogs/iframes.
|
|
58
|
+
- **For visual dropdown work, retain an image that actually shows the expanded popup.** An “expanded” accessibility state, successful selection, or screenshot omitting the OS menu cannot establish visual integration. If capture is unavailable, report appearance as unverified.
|
|
59
|
+
- Exercise pattern-appropriate keyboard navigation and selection. For a select-only combobox, include arrows, Home/End, typeahead, Enter/Space, Escape cancellation, and Tab/Shift+Tab exit. Check pointer selection, outside dismissal, focus return/exit, disabled behavior, and one relevant interruption; read back the real value or resulting application behavior.
|
|
60
|
+
|
|
61
|
+
## Visual design and accessibility
|
|
62
|
+
|
|
63
|
+
- Establish content hierarchy, primary action, location, and exit paths before effects. Keep useful labels and accessible names. Transitions should explain origin and hierarchy without changing the component's interaction model.
|
|
64
|
+
- Share typography, colors, spacing, geometry, icons, elevation, and state feedback across equivalent controls. Preserve purposeful semantic, density, and platform variants; report unrelated inconsistencies without broadening a focused task.
|
|
65
|
+
- Prefer the project's typeface or a system stack. Tune size, weight, line height, and spacing together; assess Chinese and mixed scripts separately from Latin headings. Use flexible layouts and content-driven heights; preserve essential labels under long text, enlargement, and reflow. Do not disable zoom. Web rem units are not native Dynamic Type.
|
|
66
|
+
- Use semantic colors and readable surfaces in supported themes and busy backgrounds. Glass, blur, bounce, sound, and haptics are optional. Provide solid/high-opacity material fallbacks; do not make every content card glass or claim CSS filters reproduce native Liquid Glass. Measure costly filters/layers when performance is affected.
|
|
67
|
+
- Use native elements or verified accessible primitives with visible focus, correct roles, names, and states. Give immediate press feedback while preserving semantic click/keyboard/assistive activation. Provide click and keyboard alternatives to dragging; a role alone does not implement behavior.
|
|
68
|
+
- Modals need suitable initial focus, focus containment, background inertness, naming, explicit dismissal, pattern-appropriate Escape, and focus restoration to the trigger or a logical destination. Coordinate exit animation and removal so invisible layers cannot remain interactive. `aria-modal` alone is insufficient; keep non-modal panels non-modal.
|
|
69
|
+
- Handle reduced motion, reduced transparency, and increased contrast separately, checking target support. Reduce large movement and oscillation, increase opacity or boundaries as appropriate, and retain necessary direct-manipulation feedback. Keep the base interface usable without preference detection.
|
|
70
|
+
- Reduced motion must preserve positioning, open/closed state, and operability. Do not globally clear positioning transforms. Handle CSS and JavaScript animation, runtime preference changes, and immediate completion without requiring callbacks to remove content, return focus, or unlock interaction.
|
|
71
|
+
- Sound/haptics need visual and semantic equivalents and must tolerate unavailable capabilities. Web vibration is not native Apple haptics or a guarantee of synchronized presentation.
|
|
72
|
+
|
|
73
|
+
## Motion and gestures
|
|
74
|
+
|
|
75
|
+
Use for dragging, swiping, inertia, directly manipulated sheets, and spring handoffs. Static styling does not require gesture machinery.
|
|
76
|
+
|
|
77
|
+
### Ownership and lifecycle
|
|
78
|
+
|
|
79
|
+
- Give each animated value one owner. New input starts at the current on-screen position with the grab offset preserved; a logical target is not the presentation value. Capture browser-owned presentation before cancellation removes animated styles, then stop the old writer.
|
|
80
|
+
- On press, validate the pointer/button and record position, offset, and monotonic time. Set local `touch-action` before tracking and arbitrate with nested scrolling. Adjust the baseline when crossing an activation threshold to avoid a jump; do not globally disable scrolling or zoom.
|
|
81
|
+
- Track directly rather than easing each pointer update. Use pointer capture where needed; keep pointer, element, and snap coordinates consistent under scrolling, scaling, and transforms. Avoid per-move layout read/write loops.
|
|
82
|
+
- On release, choose a valid snap target from position, velocity, bounds, and semantics, then settle. Cancellation is not submission. Handle `pointercancel`, lost capture, external dismissal, and unmounting with idempotent cleanup; intentional release of capture must not undo a completed action. Suppress the following click only for an actual drag.
|
|
83
|
+
- Preserve position on interruption and velocity when handing off automatic motion. During direct manipulation, current input takes priority over old velocity. Refresh stale geometry/time after resize or resume.
|
|
84
|
+
|
|
85
|
+
### Velocity and spring constraints
|
|
86
|
+
|
|
87
|
+
- Estimate velocity from recent timestamped samples, account for a pause before release, and convert units once. Hand off the animated property's velocity, including resistance/scale mapping. Sampling windows are tunable heuristics, not Apple constants.
|
|
88
|
+
- Verify installed API semantics and units. Physical damping coefficient `c` differs from damping ratio `ζ`: `c = 2 × ζ × sqrt(k × m)`. Do not copy Apple's ratio into Motion's `damping` or equate response, duration, and natural frequency.
|
|
89
|
+
- In Motion, physical `stiffness`/`damping`/`mass` springs support velocity handoff; verify whether the controller inherits velocity or needs an explicit value. Duration/`bounce` springs do not incorporate existing gesture/animation velocity. Do not mix parameter groups to approximate a handoff.
|
|
90
|
+
- Use normalized velocity `propertyVelocity / (target - current)` only if the API specifies that convention; guard near-zero distance against NaN/Infinity. Critical damping does not guarantee no overshoot with a large initial velocity; enforce required bounds.
|
|
91
|
+
- Projection chooses a candidate destination, not a permitted business state. Favor position at low speed; use flick intent, hysteresis, allowed snap points, and boundaries rather than velocity sign alone. Apply continuous resistance only to overshoot and settle within valid bounds; guard zero/unavailable geometry.
|
|
92
|
+
- Use elapsed time with `requestAnimationFrame` for custom updates. Prefer transform/opacity without assuming they are free. Use `will-change` only when beneficial, and clean up animation resources and listeners.
|
|
93
|
+
|
|
94
|
+
## Verification and delivery
|
|
95
|
+
|
|
96
|
+
Select checks for the affected behavior; spacing changes do not require the gesture suite. Use existing project checks and browser interactions. Add tests for meaningful state/input defects, not wording or animation constants.
|
|
97
|
+
|
|
98
|
+
| Change | Relevant checks |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| Visual/layout | Equivalent affected controls, supported themes, long/localized labels, enlarged text, narrow viewports, contrast, and material fallbacks. |
|
|
101
|
+
| Dropdown | Complete [expanded-menu verification](#verify-the-expanded-menu), including actual-value checks and appearance evidence. |
|
|
102
|
+
| Gesture/motion | Continuous drag, slow release, fast/reverse flick, pause before release, takeover mid-animation, cancellation/lost capture, nested scrolling, and relevant unmount/resize paths. Check jumps, competing writers, stale velocity, invalid targets, and stuck input. |
|
|
103
|
+
| Accessibility | Primary task by keyboard, focus/naming/states, relevant modal dismissal, reduced motion at startup and during motion, and immediate completion. |
|
|
104
|
+
| Performance | When affected, inspect sustained input, long tasks, layout/paint, and expensive filters before tuning effects. |
|
|
105
|
+
|
|
106
|
+
Record browser/device, operations, outcomes, and simulation limits. Distinguish runtime observations, automated checks, static risks, and unverified conditions. Screenshots show appearance; interaction checks show behavior. DOM assertions or emulation do not establish real-device, actual browser zoom, OS-preference, or screen-reader coverage.
|
|
107
|
+
|
|
108
|
+
## Sources
|
|
109
|
+
|
|
110
|
+
Apple resources inform feedback, direct manipulation, spatial relationships, and materials. Component consistency rules and web implementation/verification procedures are this skill's adaptations. Consult relevant official documentation for unfamiliar APIs, attribution, and target compatibility; examples require target-environment testing.
|
|
111
|
+
|
|
112
|
+
- Apple: [Fluid Interfaces](https://developer.apple.com/videos/play/wwdc2018/803/), [Principles of great design](https://developer.apple.com/videos/play/wwdc2026/250/), [HIG Materials](https://developer.apple.com/design/human-interface-guidelines/materials).
|
|
113
|
+
- Dropdowns: [Select styling](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/select#styling_with_css), W3C [Combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/), [Select-only example](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/), [Listbox](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/), [Menu Button](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/).
|
|
114
|
+
- Motion/input: [Motion animate](https://motion.dev/docs/animate), [spring](https://motion.dev/docs/spring), [CSS reversal](https://www.w3.org/TR/css-transitions-1/#reversing), [Web Animations](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API), [touch-action](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/touch-action), [pointercancel](https://developer.mozilla.org/en-US/docs/Web/API/Element/pointercancel_event). Playback control alone does not provide gesture velocity inheritance.
|
|
115
|
+
- Accessibility: [W3C Modal Dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/), [Web Vibration API](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API).
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "Apple Design"
|
|
3
|
+
short_description: "Apple-style interfaces with consistent expanded dropdowns"
|
|
4
|
+
default_prompt: "Use $apple-design-skill to design or review this interface, keep dropdown triggers and expanded menus visually consistent, and verify appearance and behavior."
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Coordinating design work across agents
|
|
2
|
+
|
|
3
|
+
Read this when the user requests a team or parallel work, or when the host's applicable instructions already authorize delegation. This reference does not grant permission to spawn agents or to change external systems. A small edit normally needs one agent.
|
|
4
|
+
|
|
5
|
+
## Choose independent work
|
|
6
|
+
|
|
7
|
+
Use the host's actual delegation mechanism. Do not invent tool names, emulate independent reviewers inside one conversation, or launch paid model requests merely because a role is listed here. If delegation is unavailable, do the work sequentially and disclose that limitation. Do not assume one provider's models, tools or effort settings exist on another host.
|
|
8
|
+
|
|
9
|
+
Useful bounded roles:
|
|
10
|
+
|
|
11
|
+
- **Design owner:** resolves the user's task, freezes shared visual tokens and the interaction contract, assigns file ownership, integrates changes, and owns the final result.
|
|
12
|
+
- **Implementer:** works on a named route or component using the supplied tokens and primitives. Receives an exclusive file list or isolated branch; returns code and the states it checked.
|
|
13
|
+
- **Interaction reviewer:** reads the relevant implementation and tests a concrete user journey without editing shared source. For dropdowns, checks the closed and expanded appearance, selected versus active state, keyboard behavior, dismissal and real committed value. For gestures, uses only the relevant motion checks from SKILL.md.
|
|
14
|
+
|
|
15
|
+
Do not assign multiple agents to edit the same stylesheet, token file or shared component. Have the owner establish those files first, or assign them to one implementer. Parallelize independent components or read-only reviews. If a prerequisite is still undecided, resolve it before dependent implementation.
|
|
16
|
+
|
|
17
|
+
## Handoff contract
|
|
18
|
+
|
|
19
|
+
Each assignment should state only what the agent needs:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
User goal and agreed scope:
|
|
23
|
+
Task and acceptance criteria:
|
|
24
|
+
Relevant skill sections and references:
|
|
25
|
+
Existing components / tokens / allowed files:
|
|
26
|
+
Constraints and authorization boundaries:
|
|
27
|
+
Required evidence and return format:
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Provide the same product requirements to implementers. Give reviewers the user's requirements and current artifact, not a desired verdict or presumed bug. If requesting a skill/no-skill comparison, use isolated contexts where possible and disclose any shared context; don't label a review independent if it inherited the proposed answer.
|
|
31
|
+
|
|
32
|
+
Return file paths, changed behavior, checks actually performed, and unresolved issues. Keep screenshot evidence of expanded dropdowns when that UI changed. Distinguish screenshots from interaction evidence and real devices from viewport simulation.
|
|
33
|
+
|
|
34
|
+
## Integration
|
|
35
|
+
|
|
36
|
+
The owner reviews diffs, resolves token and ownership conflicts, then checks the integrated journey. A component passing alone does not prove that placement inside a dialog, iframe or narrow viewport works. Review the specific finding before applying a repair; don't merge conflicting suggestions mechanically.
|
|
37
|
+
|
|
38
|
+
Stop spawning agents when useful independent work is exhausted. Cancel obsolete assignments after scope changes. Keep one owner for deployment, release, messages, and other external side effects; delegation does not expand the user's authorization. Report which work was parallel, what was integrated, and any remaining verification gaps.
|