- prototypeFile 走通 state → Issue → webview 全链路(gitea/youtrack 两个
loader、两份 KNOWN_STATE_FIELDS、issue/patch、webview types),按 planFile
同款当共享字段;路径校验换成 .json 版,允许绝对路径
- 详情面板「计划文件」下加「原型插件路径」行:双击开 manifest.json,
行尾按钮「在 Figma 中导入并打开」;没挂插件时只显示空态
- src/figma/:把 manifest 写进 Figma 桌面版的 localFileExtensions 再拉起
Figma。坑是 Figma 退出会用内存状态覆盖 settings.json,所以写之前先
osascript 优雅退出并轮询等它真没了(10s 等不到就让用户手动关,不强杀);
写前备份 settings.json.bak-<时间戳>;已导入过就跳过写入直接打开;
非 macOS 只把路径复制到剪贴板
- webhook 里 spec/plan 的 marker 同步改成表驱动,顺带认第三种 prototype
- 提示词:prototype.md 要求画完挂 marker;brainstorm-continue.md 新增
{prototypeRef} 占位符,有原型插件时让续聊先读它别从零重画,没有时
连同整段抹掉
Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
116 lines
3.7 KiB
TypeScript
116 lines
3.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
appendManifestEntry,
|
|
asExtensionEntries,
|
|
buildManifestEntry,
|
|
findManifestEntry,
|
|
nextExtensionId,
|
|
} from './localExtensions'
|
|
|
|
/** 造一条 manifest 条目(id 与 codeFileId 成对,和 Figma 真实清单一致)。 */
|
|
function manifest(id: number, manifestPath: string) {
|
|
return {
|
|
id,
|
|
manifestPath,
|
|
lastKnownName: `plugin-${id}`,
|
|
lastKnownPluginId: '',
|
|
fileMetadata: { type: 'manifest' as const, codeFileId: id + 1, uiFileIds: [] },
|
|
}
|
|
}
|
|
|
|
/** Figma 加载插件后补的 code.js 条目,占的是 manifest 的 codeFileId。 */
|
|
function code(id: number, manifestFileId: number, manifestPath: string) {
|
|
return {
|
|
id,
|
|
manifestPath,
|
|
fileMetadata: { type: 'code' as const, manifestFileId },
|
|
}
|
|
}
|
|
|
|
describe('nextExtensionId', () => {
|
|
it('空清单从 1 开始', () => {
|
|
expect(nextExtensionId([])).toBe(1)
|
|
})
|
|
|
|
it('取已有条目 id 最大值 +1', () => {
|
|
expect(nextExtensionId([manifest(1, '/a/manifest.json'), code(2, 1, '/a/code.js')])).toBe(3)
|
|
})
|
|
|
|
it('codeFileId 预定的号也要算进去(code 条目还没建出来时)', () => {
|
|
// id 只到 11,但 11 的 codeFileId 已经把 12 预定了 → 下一个必须是 13
|
|
expect(nextExtensionId([
|
|
manifest(9, '/a/manifest.json'),
|
|
code(10, 9, '/a/code.js'),
|
|
manifest(11, '/b/manifest.json'),
|
|
])).toBe(13)
|
|
})
|
|
|
|
it('顺序乱了也按最大值算', () => {
|
|
expect(nextExtensionId([
|
|
code(8, 7, '/b/code.js'),
|
|
manifest(3, '/a/manifest.json'),
|
|
manifest(7, '/b/manifest.json'),
|
|
])).toBe(9)
|
|
})
|
|
})
|
|
|
|
describe('findManifestEntry', () => {
|
|
const entries = [manifest(1, '/a/manifest.json'), manifest(3, '/b/manifest.json')]
|
|
|
|
it('按 manifestPath 精确命中', () => {
|
|
expect(findManifestEntry(entries, '/b/manifest.json')?.id).toBe(3)
|
|
})
|
|
|
|
it('路径不同就不算重复(同名不同目录)', () => {
|
|
expect(findManifestEntry(entries, '/c/manifest.json')).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('buildManifestEntry', () => {
|
|
it('codeFileId 紧跟 id,给 Figma 之后建 code 条目占号', () => {
|
|
expect(buildManifestEntry({ id: 19, manifestPath: '/x/manifest.json', name: 'X Lab' })).toEqual({
|
|
id: 19,
|
|
manifestPath: '/x/manifest.json',
|
|
lastKnownName: 'X Lab',
|
|
lastKnownPluginId: '',
|
|
fileMetadata: { type: 'manifest', codeFileId: 20, uiFileIds: [] },
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('appendManifestEntry', () => {
|
|
it('新插件追加到末尾,id 接着往下发', () => {
|
|
const entries = [manifest(1, '/a/manifest.json'), code(2, 1, '/a/code.js')]
|
|
const got = appendManifestEntry(entries, { manifestPath: '/b/manifest.json', name: 'B Lab' })
|
|
expect(got.added).toBe(true)
|
|
expect(got.entries).toHaveLength(3)
|
|
expect(got.entries[2]).toMatchObject({ id: 3, manifestPath: '/b/manifest.json', lastKnownName: 'B Lab' })
|
|
// 原数组不被改动
|
|
expect(entries).toHaveLength(2)
|
|
})
|
|
|
|
it('已导入过 → 不追加,名字用清单里记的那个', () => {
|
|
const entries = [manifest(1, '/a/manifest.json')]
|
|
const got = appendManifestEntry(entries, { manifestPath: '/a/manifest.json', name: '新名字' })
|
|
expect(got.added).toBe(false)
|
|
expect(got.entries).toBe(entries)
|
|
expect(got.name).toBe('plugin-1')
|
|
})
|
|
})
|
|
|
|
describe('asExtensionEntries', () => {
|
|
it('不是数组 → 空清单(Figma 没装过插件时该字段可能缺失)', () => {
|
|
expect(asExtensionEntries(undefined)).toEqual([])
|
|
expect(asExtensionEntries({})).toEqual([])
|
|
})
|
|
|
|
it('丢掉缺 id / manifestPath 的脏条目', () => {
|
|
expect(asExtensionEntries([
|
|
manifest(1, '/a/manifest.json'),
|
|
{ id: 'x', manifestPath: '/b/manifest.json' },
|
|
{ id: 2 },
|
|
null,
|
|
])).toHaveLength(1)
|
|
})
|
|
})
|