contactsheet 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +243 -0
- package/dist/canvas/app.js +2675 -0
- package/dist/canvas/app.js.map +7 -0
- package/dist/canvas/favicon.png +0 -0
- package/dist/canvas/index.html +59 -0
- package/dist/canvas/logo.png +0 -0
- package/dist/canvas/style-pins.css +104 -0
- package/dist/canvas/style-select.css +10 -0
- package/dist/canvas/style-sidebar.css +132 -0
- package/dist/canvas/style-wall.css +110 -0
- package/dist/canvas/style.css +1189 -0
- package/dist/cli.js +1783 -0
- package/dist/cli.js.map +7 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bearisbug
|
|
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,243 @@
|
|
|
1
|
+
<p align="center"><img src="assets/logo.png" width="320" alt="contactsheet" /></p>
|
|
2
|
+
|
|
3
|
+
# contactsheet
|
|
4
|
+
|
|
5
|
+
把你 UI 的各种状态摊在一面可缩放的墙上 —— 一个附着在你自己 Next.js repo 上的联络表。
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
看见 → 指着 → 说话 → 改 → 立刻看见
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`npx contactsheet` 在 `:5199` 起一层外壳,反代你正在跑的 `next dev`,往你的 app 目录注入一条画板路由,
|
|
12
|
+
把 `design/**/*.artboard.tsx` 里的每个 export 摊成一块画板。渲染的还是你自己的 next dev,
|
|
13
|
+
所以 Tailwind、字体、provider、RSC、HMR 全都是真的。
|
|
14
|
+
|
|
15
|
+
画布是**视图不是文档**:一切状态都在 repo 的文件里(画板是 .tsx,批注是 JSON,参考图是 png),
|
|
16
|
+
画布随时可以推倒重建,删了也不丢东西。
|
|
17
|
+
|
|
18
|
+
## 环境要求
|
|
19
|
+
|
|
20
|
+
- Node >= 20
|
|
21
|
+
- Next.js **App Router** 项目(`app/` 或 `src/app/`;Pages Router 用不了)
|
|
22
|
+
- 想用截图功能的话本机要装 Microsoft Edge(截图走 playwright-core 的 msedge channel)
|
|
23
|
+
|
|
24
|
+
## 安装与初始化
|
|
25
|
+
|
|
26
|
+
在你的 Next.js 项目根目录:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx contactsheet init
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
它做四件事,都是合并写入,不会覆盖你已有的配置:
|
|
33
|
+
|
|
34
|
+
1. 写 `contactsheet.config.json`(`target` / `port` / `appDir` / `designDir`),已存在就保留你改过的值;
|
|
35
|
+
2. 扫 `components/ui/*.tsx`(或 `src/components/ui/*.tsx`),给每个组件铺一块骨架画板到 `design/`,
|
|
36
|
+
同名画板已存在就跳过 —— 生成出来的是草稿,本来就该改;
|
|
37
|
+
3. `.mcp.json` 里加一个 `contactsheet` MCP server(其他 server 原样保留);
|
|
38
|
+
4. `.claude/settings.json` 里加一条 `UserPromptSubmit` hook(你已有的 hook 原样保留,重复跑不会加第二条)。
|
|
39
|
+
|
|
40
|
+
任何一个 JSON 文件如果坏掉(不是合法 JSON),init 会直接中止并告诉你是哪个文件,不会重写它。
|
|
41
|
+
|
|
42
|
+
## 日常
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 终端 1:照常起你自己的 dev server
|
|
46
|
+
pnpm dev
|
|
47
|
+
|
|
48
|
+
# 终端 2
|
|
49
|
+
npx contactsheet
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
然后打开 <http://localhost:5199/__cs>。
|
|
53
|
+
|
|
54
|
+
flags:`--port`(外壳端口,默认 5199)、`--target`(你的 dev server,默认 `http://localhost:3000`)、
|
|
55
|
+
`--design-dir`(画板目录,默认 `design`)。命令也可以简写成 `csheet`。
|
|
56
|
+
|
|
57
|
+
**只监听 127.0.0.1。** 画布没有登录,而 `p` 推送能以你的名义往 Claude Code 会话里说话 ——
|
|
58
|
+
所以默认只有本机能连。非 GET 请求还会校验 Origin,推送另外要一枚每次启动随机生成的 token
|
|
59
|
+
(只发给画布页,你 app 里的第三方脚本拿不到)。真要局域网访问,改 config 里的 `host`,
|
|
60
|
+
启动时会红字警告。
|
|
61
|
+
|
|
62
|
+
运行时会往你的 app 目录写几个 `__cs` 开头的注入文件,并自动加进 `.gitignore`,不会进你的提交。
|
|
63
|
+
它们在 `NODE_ENV=production` 下直接 404,生产构建里没有这层东西。
|
|
64
|
+
|
|
65
|
+
## artboard 文件约定
|
|
66
|
+
|
|
67
|
+
扫描范围是 `<designDir>/**/*.artboard.{tsx,ts}`(`__generated__` 除外)。文件里的每个 `export const` 就是一块画板。
|
|
68
|
+
|
|
69
|
+
**组件画板**(`.tsx`,有 `render`):
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// design/Button.artboard.tsx
|
|
73
|
+
import { Button } from '@/components/ui/button'
|
|
74
|
+
|
|
75
|
+
export const 默认 = {
|
|
76
|
+
render: (args: any) => <Button {...args}>保存</Button>,
|
|
77
|
+
args: { variant: 'default', disabled: false }, // 可选,会出现在 args 面板里
|
|
78
|
+
env: { width: 390 }, // 可选
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const 危险 = {
|
|
82
|
+
render: () => <Button variant="destructive">删除</Button>,
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**页面画板**(`.ts`,有 `url`):直接把你项目里的某个路由挂上墙。
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// design/screens.artboard.ts
|
|
90
|
+
export const 仪表盘 = { url: '/dashboard', env: { width: 1440 } }
|
|
91
|
+
export const 登录 = { url: '/login', env: { width: 390 } }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
字段:
|
|
95
|
+
|
|
96
|
+
| 字段 | 说明 |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `render(args)` | 组件画板。返回 JSX;`args` 是默认 args 与面板改动浅合并后的结果 |
|
|
99
|
+
| `url` | 页面画板。你项目里的路径,走代理直接渲染真页面 |
|
|
100
|
+
| `args` | 组件画板的默认入参,能在右侧面板里现场改 |
|
|
101
|
+
| `env.width` | 画板宽度 px,默认 480。设 390 就是真的 390 —— `@media (max-width: 430px)` 会命中 |
|
|
102
|
+
| `env.height` | 固定高度;不写就按内容自动测(夹在 88–1200 之间) |
|
|
103
|
+
| `env.theme` | `light` / `dark` |
|
|
104
|
+
|
|
105
|
+
export 名可以用中文。布局见下面「左侧列表与布局」一节。
|
|
106
|
+
|
|
107
|
+
## 三个模式
|
|
108
|
+
|
|
109
|
+
| 操作 | 进入 | 干什么 |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| **浏览**(默认) | `Esc` 随时回来 | 鼠标划过高亮元素,点一下 = 指着它(生成 CSS selector 发给外壳)。组件画板上 `html`/`body`/注入的 wrapper 不参与反查——组件比画板视口小时,空白处什么都不高亮,**指着空白 = 没指任何东西**(页面画板不过滤,body 就是页面本身) |
|
|
112
|
+
| **交互** | 双击画板 | 这块画板可以点、可以填、可以展开菜单,其余压暗 |
|
|
113
|
+
| **走查** | 选中画板后 `Enter` | 这块画板放大居中(最多 2 倍——不改画板声明的宽度,媒体查询保持真实),`Esc` 退出 |
|
|
114
|
+
|
|
115
|
+
其他键:
|
|
116
|
+
|
|
117
|
+
- `c` → 批注模式,点一个元素写一句话,落成一个 pin(存 `design/.canvas/annotations.json`)
|
|
118
|
+
- `p`(或顶栏「推送」)→ **一键把 open 批注 + 选中直接注入本项目正在运行的 Claude Code 会话**,不用切窗口。
|
|
119
|
+
绑定全自动:目标 = 工作目录在本项目下的活会话。**同目录开了多个窗口时弹选择器**(显示各会话的
|
|
120
|
+
名字和 idle/busy 状态,数字键或点击选择)。**不记忆上次选过谁** —— 「上次发给哪个窗口」和「这次
|
|
121
|
+
该发给哪个窗口」没有关系,记住它只会让人把话说给错的窗口。只有一个候选时不弹,直发。
|
|
122
|
+
没找到会话时自动回落为复制到剪贴板。
|
|
123
|
+
⚠️ 通道说明:走的是 Claude Code cross-session messaging 的 inbox socket(`/tmp/cc-socks/`),
|
|
124
|
+
官方文档将其定位为内部机制,版本升级可能失效——失效也只是退回 `y` 复制,不丢功能。
|
|
125
|
+
官方正门是 Channels(MCP `claude/channel`,research preview),列为后续迁移路径。
|
|
126
|
+
- `y`(或顶栏「复制批注」)→ 把所有 open 批注 + 当前选中打包进剪贴板,粘给任何 Claude Code 窗口
|
|
127
|
+
|
|
128
|
+
**批注生命周期**(hover pin 的气泡里操作):
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
open(橙) ──标记完成──▶ resolved(绿·待核验) ──核验通过──▶ verified(从墙上消失)
|
|
132
|
+
Claude 或你 │ 打回 只有人能核验
|
|
133
|
+
▼
|
|
134
|
+
open
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- Claude Code 改完代码后把批注标成 resolved(PATCH `/__cs/api/annotations/:id` 或直接改 JSON 文件),
|
|
138
|
+
**但它到不了 verified —— 核验永远是人的动作**。
|
|
139
|
+
- verified 的批注不删除,永久留在 `annotations.json`(带 `resolvedAt`/`verifiedAt` 时间戳)——
|
|
140
|
+
这份文件就是历史记录,日后沉淀 skill / 复盘"当初都提过什么、怎么改的"的原料。
|
|
141
|
+
- 「删除」只留给误钉的 pin,会真的从历史里抹掉。
|
|
142
|
+
- `Cmd/Ctrl + V` → 贴图,存进 `design/.canvas/refs/`。**在批注输入框或气泡编辑态里粘贴 = 挂到这条批注**
|
|
143
|
+
(缩略图跟着 pin 走,推给 Claude 的上下文里带上它的路径);在别处粘贴才进右下角的全局坞
|
|
144
|
+
- `Ctrl + 滚轮` / 捏合 → 以光标为锚点缩放(0.05–2 倍);空白处拖拽或双指滚动 → 平移
|
|
145
|
+
- `1` 全景(一屏看完整面墙)· `2` 聚焦当前画板 · `0` 回到 100% · `+` / `-` 步进缩放
|
|
146
|
+
- 点一个 pin 选中它,然后 `Enter` 编辑(编辑中再按 `Enter` 保存,`Shift+Enter` 换行)、
|
|
147
|
+
`Backspace` 删除、`Esc` 取消选中
|
|
148
|
+
- **pin 上的数字是批注的永久序号**:按创建顺序分配,全墙唯一,核验/打回都不改号——
|
|
149
|
+
推送给 Claude 的文本里每条批注也带同一个 `#序号`,所以你说「批注 3」,
|
|
150
|
+
你、墙、Claude 指的是同一条。(唯一例外:「删除」误钉的 pin 后,最大号可能被下一条重用)
|
|
151
|
+
|
|
152
|
+
## 左侧列表与布局
|
|
153
|
+
|
|
154
|
+
左侧是图层列表:**上半是页面**(显示各自的真实路由,点一下聚焦过去),**下半是组件**(按文件分组)。
|
|
155
|
+
顶部搜索框按名字/文件/路由过滤;每行右边的圆点是显隐开关——隐藏只是从墙上撤下,随时点回来。
|
|
156
|
+
分区标题(「页面」「组件」)和每个文件组都能**折叠**;标题行右侧的三态圆点是**整组一键显隐**
|
|
157
|
+
(`●` 全显示 / `◐` 部分 / `◌` 全隐藏,点一下在「全显示」和「全隐藏」之间切)。折叠状态按项目记住。
|
|
158
|
+
顶栏 `☰` 收起整个侧栏。
|
|
159
|
+
|
|
160
|
+
**自动排列只在三个时机发生**:这个项目在本机第一次打开、出现了新画板(只给新的找空地,老的不动)、
|
|
161
|
+
你点右上角的「自动排列」。其余情况——拖完、改尺寸、内容变高、HMR、显隐——一律不重排,
|
|
162
|
+
所以你摆好的墙不会被冲掉。排列按类型分区,**页面一行最多 4 块,组件一行最多 15 块**。
|
|
163
|
+
|
|
164
|
+
拖画板的**标题条**挪位置,**每块画板的坐标都按项目存在本机**,刷新和重启都还在原处;
|
|
165
|
+
**双击标题条**把这一块放回自动排列会给它的位置(只动它自己)。拖右缘/下缘/右下角改尺寸,双击手柄还原。
|
|
166
|
+
|
|
167
|
+
## 组件底色
|
|
168
|
+
|
|
169
|
+
iframe 的画布永远是不透明的,所以组件画板必然有一层底。右上角「组件底色」是全局开关:
|
|
170
|
+
|
|
171
|
+
- **融入画布**(默认)—— 把 iframe 的 `html/body` 刷成画布同色,组件看起来像直接浮在墙上;
|
|
172
|
+
- **项目底色** —— 保留你项目自己的背景(通常是白),看的是组件在真实页面里的样子。
|
|
173
|
+
|
|
174
|
+
单块画板的标题条上有 `◻`/`▣` 可以单独覆盖全局设置(半透明 = 跟随全局)。
|
|
175
|
+
**页面画板不参与** —— 它就是一整个真实页面,底色是它自己的事。
|
|
176
|
+
|
|
177
|
+
组件画板**不额外留内边距**:组件贴着画板边缘放。这样「你看到的底」和「反查时高亮的盒子」
|
|
178
|
+
是同一个矩形;一旦中间垫一层内边距,高亮框就永远比可见的底小一圈,怎么看都对不齐。
|
|
179
|
+
需要呼吸感请写进组件自己或 artboard 的 `render`。
|
|
180
|
+
|
|
181
|
+
## args 面板
|
|
182
|
+
|
|
183
|
+
浏览模式下单击画板标题条,右侧展开面板:`args` 里的每个字段按类型出控件(布尔 → 勾选框,数字 → 数字框,
|
|
184
|
+
字符串 → 文本框,其他 → JSON 文本域)。改动直接改 iframe 的 `?args=`,约 100ms 后服务端重渲。
|
|
185
|
+
|
|
186
|
+
面板上那个「存为画板」按钮 v1 是禁用的 —— 想把当前这组参数固化下来,请自己往 artboard 文件里再写一个 export。
|
|
187
|
+
|
|
188
|
+
## tokens 页
|
|
189
|
+
|
|
190
|
+
<http://localhost:5199/__cs/tokens> 列出你项目 CSS 里 `:root` / `@theme` 下的自定义属性(`--color-*`、`--radius-*`、
|
|
191
|
+
`--font-*`、`--spacing-*` 等),分组画成色板。它读的是真实生效的样式表,不是某个配置文件的推测。
|
|
192
|
+
|
|
193
|
+
## 和 Claude Code 一起用
|
|
194
|
+
|
|
195
|
+
`init` 已经把两头都接好了:
|
|
196
|
+
|
|
197
|
+
**MCP 工具**(四个,全都只读):
|
|
198
|
+
|
|
199
|
+
| 工具 | 给出什么 |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `canvas_list` | 当前墙上所有画板(id / 文件 / 类型 / args / env) |
|
|
202
|
+
| `canvas_screenshot` | 某块画板的 png(不传 id 就是整面墙) |
|
|
203
|
+
| `canvas_selection` | 你此刻指着的元素(画板 id + selector + 相对坐标) |
|
|
204
|
+
| `canvas_annotations` | 所有 open 状态的批注 |
|
|
205
|
+
|
|
206
|
+
**UserPromptSubmit hook**:你每次说话,会自动把「未解决的批注 + 你此刻选中的元素」附在提示词前面。
|
|
207
|
+
所以你可以指着屏幕上一个按钮,然后只说「这个圆角太大了」。(contactsheet 没在跑的时候,hook 一秒超时后静默跳过。)
|
|
208
|
+
|
|
209
|
+
**一切修改走文件。** 这些工具不写任何东西 —— Claude 改的是你的组件源码和 artboard 文件,
|
|
210
|
+
改完 HMR 穿过代理推回画布,你立刻看见。画布本身没有「保存」这个动作。
|
|
211
|
+
|
|
212
|
+
## 登录态
|
|
213
|
+
|
|
214
|
+
- **cookie 不分端口**。`localhost:3000` 上登过的账号,`localhost:5199` 直接就是登录态,什么都不用做。
|
|
215
|
+
- **token 存在 localStorage / sessionStorage 里的项目要多登一次**:storage 按源隔离,`:5199` 是另一个源。
|
|
216
|
+
在 `:5199` 下把你的登录流程走一遍(页面画板正好可以直接开 `/login`),之后就一直有了。
|
|
217
|
+
|
|
218
|
+
## 边界(先说清楚,省得你试)
|
|
219
|
+
|
|
220
|
+
- **没有 mock 层**。画板拿到的数据就是你 dev 环境里的真数据。「空态 / 错误态 / 加载中」这类需要拦网络的场景,
|
|
221
|
+
v1 只能靠你自己在组件层传 props 摆出来;内置 mocks 排在 v1.1。
|
|
222
|
+
- **走查模式很勉强**。它就是把一块画板放大居中,方便盯细节;要走完整流程(多页跳转、真实滚动、devtools),
|
|
223
|
+
请照常开浏览器访问 `:3000`。
|
|
224
|
+
- **它不是开发环境,是一扇窗**。构建、测试、调试照旧在你原来的地方做。contactsheet 只负责「同时看见很多状态」
|
|
225
|
+
和「指着其中一个说话」这两件事,别的都不管。
|
|
226
|
+
- **Next 的开发指示器会出现在画板角落**。那是你 next.config 的事(`devIndicators: false`),contactsheet 不动你的配置。
|
|
227
|
+
- export 名的提取用的是正则,`export const` 之外的花式写法(比如先声明再 `export {}` 重命名)可能认不出来。
|
|
228
|
+
- 画板路由在生产环境不可访问:`next build` 的路由清单里它仍然在,但运行时一律 404(`notFound()` 守卫)。
|
|
229
|
+
想连清单都不进,`next build` 前跑一次 `contactsheet clean`。
|
|
230
|
+
|
|
231
|
+
## 卸载
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
npx contactsheet clean # 删掉注入进 app 目录的文件
|
|
235
|
+
rm contactsheet.config.json
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`clean` 只清它自己注入的东西。这些是你的,它不会碰,要删自己删:
|
|
239
|
+
|
|
240
|
+
- `design/`(画板、批注、参考图、截图都在这)
|
|
241
|
+
- `.mcp.json` 里的 `contactsheet` 条目
|
|
242
|
+
- `.claude/settings.json` 里那条 `UserPromptSubmit` hook
|
|
243
|
+
- `.gitignore` 里的 `# contactsheet` 段
|