@designfever/web-review-kit 0.6.0 → 0.7.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.
Files changed (41) hide show
  1. package/.env.sample +29 -0
  2. package/README.md +35 -10
  3. package/dist/chunk-AB5B6O77.js +584 -0
  4. package/dist/chunk-AB5B6O77.js.map +1 -0
  5. package/dist/{chunk-IN36JHEU.js → chunk-RPVLRULC.js} +504 -143
  6. package/dist/chunk-RPVLRULC.js.map +1 -0
  7. package/dist/image.types-BmzkFSPX.d.cts +71 -0
  8. package/dist/image.types-BmzkFSPX.d.ts +71 -0
  9. package/dist/index.cjs +1019 -144
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +27 -3
  12. package/dist/index.d.ts +27 -3
  13. package/dist/index.js +50 -3
  14. package/dist/index.js.map +1 -1
  15. package/dist/react-shell.cjs +10293 -5185
  16. package/dist/react-shell.cjs.map +1 -1
  17. package/dist/react-shell.d.cts +37 -4
  18. package/dist/react-shell.d.ts +37 -4
  19. package/dist/react-shell.js +9624 -4856
  20. package/dist/react-shell.js.map +1 -1
  21. package/dist/token-Dt-ZH-YO.d.cts +88 -0
  22. package/dist/token-nJXPPdYX.d.ts +88 -0
  23. package/dist/{types-DFHHVRBc.d.cts → types-DT9Z66mV.d.cts} +13 -1
  24. package/dist/{types-DFHHVRBc.d.ts → types-DT9Z66mV.d.ts} +13 -1
  25. package/dist/vite.cjs +1116 -5
  26. package/dist/vite.cjs.map +1 -1
  27. package/dist/vite.d.cts +45 -1
  28. package/dist/vite.d.ts +45 -1
  29. package/dist/vite.js +800 -5
  30. package/dist/vite.js.map +1 -1
  31. package/docs/README.md +11 -7
  32. package/docs/adapters.md +126 -0
  33. package/docs/adaptor.sample.ts +13 -1
  34. package/docs/architecture.md +2 -1
  35. package/docs/code-review-0.6.0.md +232 -0
  36. package/docs/db-setup.md +4 -2
  37. package/docs/figma-image-mvp-0.7.0.md +327 -0
  38. package/docs/installation.md +39 -7
  39. package/docs/release-notes-0.7.0.md +128 -0
  40. package/package.json +6 -2
  41. package/dist/chunk-IN36JHEU.js.map +0 -1
package/dist/vite.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/vite.ts"],"sourcesContent":["import type { Plugin, ResolvedConfig } from 'vite';\n\ntype SourceLocatorPattern = string | RegExp;\n\nexport interface ReviewSourceLocatorOptions {\n enabled?: boolean;\n root?: string;\n include?: readonly SourceLocatorPattern[];\n exclude?: readonly SourceLocatorPattern[];\n filePath?: 'relative' | 'absolute';\n line?: boolean;\n column?: boolean;\n attributePrefix?: string;\n}\n\ntype RuntimeMatcher =\n | { type: 'path'; value: string }\n | { type: 'regex'; value: string; flags: string };\n\ntype RuntimeOptions = {\n enabled: boolean;\n root: string;\n include: RuntimeMatcher[];\n exclude: RuntimeMatcher[];\n filePath: 'relative' | 'absolute';\n line: boolean;\n column: boolean;\n fileAttribute: string;\n lineAttribute: string;\n columnAttribute: string;\n};\n\nconst VIRTUAL_JSX_DEV_RUNTIME_ID =\n '\\0@designfever/web-review-kit/source-locator/jsx-dev-runtime';\n\nexport const reviewSourceLocator = (\n options: ReviewSourceLocatorOptions = {}\n): Plugin => {\n let runtimeOptions = createRuntimeOptions(options);\n\n return {\n name: 'df-web-review-kit-source-locator',\n enforce: 'pre',\n configResolved(config) {\n runtimeOptions = createRuntimeOptions(options, config);\n },\n resolveId(id, importer) {\n if (!runtimeOptions.enabled) return null;\n if (id !== 'react/jsx-dev-runtime') return null;\n if (importer === VIRTUAL_JSX_DEV_RUNTIME_ID) return null;\n\n return VIRTUAL_JSX_DEV_RUNTIME_ID;\n },\n load(id) {\n if (id !== VIRTUAL_JSX_DEV_RUNTIME_ID) return null;\n return createJsxDevRuntime(runtimeOptions);\n },\n };\n};\n\nexport interface ReviewDataLocatorOptions {\n enabled?: boolean;\n root?: string;\n include?: readonly SourceLocatorPattern[];\n exclude?: readonly SourceLocatorPattern[];\n filePath?: 'relative' | 'absolute';\n /** 매칭할 component 이름 패턴. 기본은 `Section`으로 시작하는 이름. */\n componentPattern?: RegExp;\n fileAttribute?: string;\n lineAttribute?: string;\n}\n\n/**\n * page data 파일의 section 객체(`component: 'SectionXxx'`)에 출처 파일/라인을\n * `__wrkDataFile`/`__wrkDataLine` prop 으로 주입한다. 라인 보존을 위해 같은 줄에만 삽입한다.\n */\nexport const reviewDataLocator = (\n options: ReviewDataLocatorOptions = {}\n): Plugin => {\n let root = normalizePath(options.root ?? '');\n let enabled = options.enabled ?? false;\n const include = (options.include ?? []).map(createRuntimeMatcher);\n const exclude = (options.exclude ?? ['node_modules', 'dist']).map(\n createRuntimeMatcher\n );\n const componentPattern = options.componentPattern ?? /Section[A-Za-z0-9_]*/;\n const fileKey = options.fileAttribute ?? '__wrkDataFile';\n const lineKey = options.lineAttribute ?? '__wrkDataLine';\n\n const componentSource = `(^|[\\\\n,{(\\\\[]\\\\s*)(component:\\\\s*)(['\"\\`])(${componentPattern.source})\\\\3`;\n\n return {\n name: 'df-web-review-kit-data-locator',\n enforce: 'pre',\n configResolved(config) {\n root = normalizePath(options.root ?? config.root ?? '');\n enabled = options.enabled ?? config.command === 'serve';\n },\n transform(code, id) {\n if (!enabled) return null;\n const file = normalizePath(id.split('?')[0]);\n const relativeFile =\n root && file.startsWith(root + '/') ? file.slice(root.length + 1) : file;\n if (include.length > 0 && !include.some((m) => matchesPath(m, file, relativeFile)))\n return null;\n if (exclude.some((m) => matchesPath(m, file, relativeFile))) return null;\n\n const sourceFile = (options.filePath ?? 'relative') === 'absolute' ? file : relativeFile;\n const regex = new RegExp(componentSource, 'g');\n let changed = false;\n const out = code.replace(\n regex,\n (_match, pre: string, comp: string, quote: string, name: string, offset: number) => {\n const line = code.slice(0, offset + pre.length).split('\\n').length;\n changed = true;\n return `${pre}${JSON.stringify(fileKey)}: ${JSON.stringify(sourceFile)}, ${JSON.stringify(lineKey)}: ${line}, ${comp}${quote}${name}${quote}`;\n }\n );\n\n return changed ? { code: out, map: null } : null;\n },\n };\n};\n\nfunction matchesPath(\n matcher: RuntimeMatcher,\n absoluteFile: string,\n relativeFile: string\n) {\n if (matcher.type === 'regex') {\n const regex = new RegExp(matcher.value, matcher.flags);\n return regex.test(absoluteFile) || regex.test(relativeFile);\n }\n const target = matcher.value.startsWith('/') ? absoluteFile : relativeFile;\n return target === matcher.value || target.startsWith(matcher.value + '/') || target.includes('/' + matcher.value);\n}\n\nfunction createRuntimeOptions(\n options: ReviewSourceLocatorOptions,\n config?: ResolvedConfig\n): RuntimeOptions {\n const attributePrefix = (options.attributePrefix ?? 'data-wrk-source').replace(\n /-+$/,\n ''\n );\n const root = normalizePath(options.root ?? config?.root ?? '');\n const enabled = options.enabled ?? (config?.command === 'serve');\n\n return {\n enabled,\n root,\n include: (options.include ?? []).map(createRuntimeMatcher),\n exclude: (options.exclude ?? ['node_modules', 'dist']).map(\n createRuntimeMatcher\n ),\n filePath: options.filePath ?? 'relative',\n line: options.line ?? true,\n column: options.column ?? true,\n fileAttribute: `${attributePrefix}-file`,\n lineAttribute: `${attributePrefix}-line`,\n columnAttribute: `${attributePrefix}-column`,\n };\n}\n\nfunction createRuntimeMatcher(pattern: SourceLocatorPattern): RuntimeMatcher {\n if (pattern instanceof RegExp) {\n return { type: 'regex', value: pattern.source, flags: pattern.flags };\n }\n\n return { type: 'path', value: normalizePath(pattern).replace(/^\\.\\//, '') };\n}\n\nfunction normalizePath(value: string) {\n return value.replace(/\\\\/g, '/').replace(/\\/+$/, '');\n}\n\nfunction createJsxDevRuntime(options: RuntimeOptions) {\n return `\nimport { Fragment, jsxDEV as baseJsxDEV } from 'react/jsx-dev-runtime';\n\nconst OPTIONS = ${JSON.stringify(options)};\n\nexport { Fragment };\n\nexport function jsxDEV(type, props, key, isStaticChildren, source, self) {\n return baseJsxDEV(\n type,\n injectSourceProps(type, props, source),\n key,\n isStaticChildren,\n source,\n self\n );\n}\n\nfunction injectSourceProps(type, props, source) {\n if (typeof type !== 'string') return props;\n if (!source || typeof source.fileName !== 'string') return props;\n\n const sourceFile = getSourceFile(source.fileName);\n if (!sourceFile) return props;\n\n const nextProps = props ? { ...props } : {};\n if (nextProps[OPTIONS.fileAttribute] == null) {\n nextProps[OPTIONS.fileAttribute] = sourceFile;\n }\n if (OPTIONS.line && source.lineNumber != null && nextProps[OPTIONS.lineAttribute] == null) {\n nextProps[OPTIONS.lineAttribute] = String(source.lineNumber);\n }\n if (OPTIONS.column && source.columnNumber != null && nextProps[OPTIONS.columnAttribute] == null) {\n nextProps[OPTIONS.columnAttribute] = String(source.columnNumber);\n }\n\n return nextProps;\n}\n\nfunction getSourceFile(fileName) {\n const absoluteFile = normalizePath(fileName);\n const relativeFile = getRelativeFile(absoluteFile);\n\n if (OPTIONS.include.length > 0 && !matchesAny(OPTIONS.include, absoluteFile, relativeFile)) {\n return null;\n }\n if (matchesAny(OPTIONS.exclude, absoluteFile, relativeFile)) return null;\n\n return OPTIONS.filePath === 'absolute' ? absoluteFile : relativeFile;\n}\n\nfunction getRelativeFile(absoluteFile) {\n if (!OPTIONS.root) return absoluteFile;\n if (absoluteFile === OPTIONS.root) return '';\n if (absoluteFile.startsWith(OPTIONS.root + '/')) {\n return absoluteFile.slice(OPTIONS.root.length + 1);\n }\n\n return absoluteFile;\n}\n\nfunction matchesAny(patterns, absoluteFile, relativeFile) {\n return patterns.some((pattern) =>\n matchesPattern(pattern, absoluteFile, relativeFile)\n );\n}\n\nfunction matchesPattern(pattern, absoluteFile, relativeFile) {\n if (pattern.type === 'regex') {\n const regex = new RegExp(pattern.value, pattern.flags);\n return regex.test(absoluteFile) || regex.test(relativeFile);\n }\n\n const value = pattern.value;\n const target = isAbsolutePattern(value) ? absoluteFile : relativeFile;\n if (!value.includes('*')) {\n return target === value || target.startsWith(value + '/');\n }\n\n return globToRegExp(value).test(target);\n}\n\nfunction isAbsolutePattern(value) {\n return value.startsWith('/') || /^[a-zA-Z]:\\\\//.test(value);\n}\n\nfunction globToRegExp(value) {\n const source = escapeRegExp(value)\n .replace(/\\\\\\\\\\\\*\\\\\\\\\\\\*/g, '.*')\n .replace(/\\\\\\\\\\\\*/g, '[^/]*');\n\n return new RegExp('^' + source + '$');\n}\n\nfunction escapeRegExp(value) {\n return value.replace(/[|\\\\\\\\{}()[\\\\]^$+*?.]/g, '\\\\\\\\$&');\n}\n\nfunction normalizePath(value) {\n return value.replace(/\\\\\\\\/g, '/').replace(/\\\\/+$/, '');\n}\n`;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/vite/figma-image-store.ts","../src/vite/figma-image-store.server.ts","../src/vite/figma-image-store.image.ts","../src/vite.ts"],"sourcesContent":["import path from 'node:path';\nimport { loadEnv, type Plugin } from 'vite';\nimport type { ReviewFigmaImageFormat } from '../figma/image.types';\nimport {\n DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT,\n} from '../figma/image.store';\nimport {\n DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n readReviewFigmaToken,\n requireReviewFigmaToken,\n type ReviewFigmaTokenEnv,\n} from '../figma/token';\nimport {\n renderReviewFigmaImage,\n type ReviewFigmaImageRenderOptions,\n type ReviewFigmaRenderFormat,\n} from '../figma/render';\nimport {\n handleReviewFigmaImageStoreRequest,\n normalizeEndpoint,\n readJsonRequestBody,\n sendJson,\n sendReviewFigmaAsset,\n} from './figma-image-store.server';\n\nexport interface ReviewFigmaImageStorePluginOptions\n extends ReviewFigmaServerTokenOptions {\n enabled?: boolean;\n projectId?: string;\n endpoint?: string;\n dataFile?: string;\n assetDir?: string;\n assetEndpoint?: string;\n cacheAssets?: boolean;\n imageFormat?: ReviewFigmaImageFormat;\n renderFormat?: ReviewFigmaRenderFormat;\n renderScale?: number;\n useAbsoluteBounds?: boolean;\n apiBaseUrl?: string;\n fetch?: typeof fetch;\n transformAsset?: ReviewFigmaImageAssetTransformer;\n}\n\nexport interface ReviewFigmaServerTokenOptions {\n token?: string | null;\n env?: ReviewFigmaTokenEnv;\n envKey?: string;\n enabled?: boolean;\n}\n\nexport type ReviewFigmaServerImageRenderOptions =\n Omit<ReviewFigmaImageRenderOptions, 'token'> &\n ReviewFigmaServerTokenOptions;\n\nexport type ReviewFigmaImageAssetTransformInput = {\n data: Uint8Array;\n imageFormat: ReviewFigmaImageFormat;\n mimeType: string;\n targetFormat: ReviewFigmaImageFormat;\n};\n\nexport type ReviewFigmaImageAssetTransformResult = {\n data: Uint8Array | ArrayBuffer;\n imageFormat: ReviewFigmaImageFormat;\n mimeType?: string;\n};\n\nexport type ReviewFigmaImageAssetTransformer = (\n input: ReviewFigmaImageAssetTransformInput\n) =>\n | ReviewFigmaImageAssetTransformResult\n | null\n | undefined\n | Promise<ReviewFigmaImageAssetTransformResult | null | undefined>;\n\nexport {\n createReviewFigmaImageApiUrl,\n renderReviewFigmaImage,\n} from '../figma/render';\nexport {\n collectReviewFigmaReleaseSnapshot,\n createReviewFigmaImagesSnapshot,\n createReviewFigmaReleaseSnapshot,\n} from '../figma/image.snapshot';\nexport type {\n ReviewFigmaImageRenderOptions,\n ReviewFigmaRenderFormat,\n ReviewFigmaRenderedImage,\n} from '../figma/render';\nexport type {\n CollectReviewFigmaReleaseSnapshotOptions,\n CreateReviewFigmaImagesSnapshotOptions,\n CreateReviewFigmaReleaseSnapshotOptions,\n ReviewFigmaImagesSnapshot,\n ReviewFigmaReleaseSnapshot,\n} from '../figma/image.snapshot';\n\nexport const readReviewFigmaServerToken = (\n options: ReviewFigmaServerTokenOptions = {}\n) =>\n readReviewFigmaToken({\n token: options.token,\n env: options.env ?? getServerEnv(),\n envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n enabled: options.enabled,\n });\n\nexport const requireReviewFigmaServerToken = (\n options: ReviewFigmaServerTokenOptions = {}\n) =>\n requireReviewFigmaToken({\n token: options.token,\n env: options.env ?? getServerEnv(),\n envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n enabled: options.enabled,\n });\n\nexport const renderReviewFigmaServerImage = (\n options: ReviewFigmaServerImageRenderOptions\n) => {\n const { token, env, envKey, enabled, ...renderOptions } = options;\n const explicitToken = typeof token === 'string' ? token.trim() : token;\n\n return renderReviewFigmaImage({\n ...renderOptions,\n token:\n explicitToken ||\n requireReviewFigmaServerToken({ env, envKey, enabled }),\n });\n};\n\nexport const reviewFigmaImageStore = (\n options: ReviewFigmaImageStorePluginOptions = {}\n): Plugin => {\n let root = '';\n let dataFile = '';\n let assetDir = '';\n let env: ReviewFigmaTokenEnv = {};\n const enabled = options.enabled ?? true;\n const endpoint = normalizeEndpoint(\n options.endpoint ?? DEFAULT_REVIEW_FIGMA_IMAGE_STORE_ENDPOINT\n );\n const assetEndpoint = normalizeEndpoint(\n options.assetEndpoint ?? `${endpoint}/assets`\n );\n\n return {\n name: 'df-web-review-kit-figma-image-store',\n apply: 'serve',\n configResolved(config) {\n root = config.root;\n dataFile = path.resolve(\n root,\n options.dataFile ?? '.df-review/figma-images.json'\n );\n assetDir = options.assetDir\n ? path.resolve(root, options.assetDir)\n : path.join(path.dirname(dataFile), 'figma-assets');\n env = {\n ...loadEnv(config.mode, config.envDir, ''),\n ...getServerEnv(),\n ...(options.env ?? {}),\n };\n },\n configureServer(server) {\n if (!enabled) return;\n\n server.middlewares.use(async (req, res, next) => {\n const requestUrl = new URL(req.url ?? '/', 'http://localhost');\n const pathname = requestUrl.pathname;\n if (pathname.startsWith(`${assetEndpoint}/`)) {\n await sendReviewFigmaAsset(res, assetDir, assetEndpoint, pathname);\n return;\n }\n if (pathname !== endpoint && !pathname.startsWith(`${endpoint}/`)) {\n next();\n return;\n }\n\n try {\n const response = await handleReviewFigmaImageStoreRequest({\n dataFile,\n assetDir,\n assetEndpoint,\n endpoint,\n options,\n env,\n pathname,\n requestUrl,\n method: req.method ?? 'GET',\n body: await readJsonRequestBody(req),\n });\n\n sendJson(res, response.status, response.body);\n } catch (error) {\n sendJson(res, 500, {\n error:\n error instanceof Error\n ? error.message\n : 'Figma image store request failed.',\n });\n }\n });\n },\n };\n};\n\nfunction getServerEnv(): ReviewFigmaTokenEnv {\n const runtime = globalThis as typeof globalThis & {\n process?: {\n env?: ReviewFigmaTokenEnv;\n };\n };\n\n return runtime.process?.env ?? {};\n}\n","import { readFile } from 'node:fs/promises';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport path from 'node:path';\nimport type {\n ReviewFigmaImage,\n ReviewFigmaImageTarget,\n AddReviewFigmaImageInput,\n} from '../figma/image.types';\nimport {\n getReviewFigmaImageTargetKey,\n type ReorderReviewFigmaImagesInput,\n} from '../figma/image.store';\nimport type { ReviewFigmaTokenEnv } from '../figma/token';\nimport { createReviewFigmaReleaseSnapshot } from '../figma/image.snapshot';\nimport {\n getReviewFigmaAssetMimeType,\n getReviewFigmaAssetStorageKeyFromPathname,\n parseReviewFigmaImageFormat,\n} from './figma-asset';\nimport type { ReviewFigmaImageStorePluginOptions } from './figma-image-store';\nimport {\n createReviewFigmaImage,\n deleteReviewFigmaImageAsset,\n isNodeError,\n listImagesForTarget,\n normalizeOptionalText,\n readReviewFigmaImageStoreFile,\n reorderReviewFigmaImages,\n updateReviewFigmaImage,\n writeReviewFigmaImageStoreFile,\n} from './figma-image-store.image';\n\ntype ReviewFigmaImageStoreResponse = {\n status: number;\n body: unknown;\n};\n\nexport async function handleReviewFigmaImageStoreRequest({\n dataFile,\n assetDir,\n assetEndpoint,\n endpoint,\n method,\n options,\n pathname,\n requestUrl,\n body,\n env,\n}: {\n dataFile: string;\n assetDir: string;\n assetEndpoint: string;\n endpoint: string;\n method: string;\n options: ReviewFigmaImageStorePluginOptions;\n env: ReviewFigmaTokenEnv;\n pathname: string;\n requestUrl: URL;\n body: unknown;\n}): Promise<ReviewFigmaImageStoreResponse> {\n if (method === 'OPTIONS') return { status: 204, body: null };\n\n if (\n (method === 'GET' || method === 'POST') &&\n pathname === `${endpoint}/snapshot`\n ) {\n const input = parseReleaseSnapshotInput(body, requestUrl, options.projectId);\n if (!input) return jsonError(400, 'valid snapshot input is required.');\n if (options.projectId && input.projectId !== options.projectId) {\n return jsonError(403, 'snapshot project is not allowed.');\n }\n if (input.targets.some((target) => !isAllowedProjectTarget(target, options.projectId))) {\n return jsonError(403, 'snapshot target project is not allowed.');\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n return {\n status: 200,\n body: createReviewFigmaReleaseSnapshot({\n images: data.images,\n projectId: input.projectId,\n releaseId: input.releaseId,\n label: input.label,\n targets: input.targets.length > 0 ? input.targets : undefined,\n }),\n };\n }\n\n if (method === 'GET' && pathname === endpoint) {\n const target = parseTargetParam(requestUrl.searchParams.get('target'));\n if (!target) return jsonError(400, 'target query is required.');\n if (!isAllowedProjectTarget(target, options.projectId)) {\n return { status: 200, body: [] };\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n return { status: 200, body: listImagesForTarget(data.images, target) };\n }\n\n if (method === 'POST' && pathname === endpoint) {\n const input = parseAddImageInput(body);\n if (!input) return jsonError(400, 'valid add image input is required.');\n if (!isAllowedProjectTarget(input.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const image = await createReviewFigmaImage({\n assetDir,\n assetEndpoint,\n currentImages: data.images,\n env,\n input,\n options,\n });\n data.images = [image, ...data.images];\n await writeReviewFigmaImageStoreFile(dataFile, data);\n\n return { status: 201, body: image };\n }\n\n if (method === 'PATCH' && pathname === `${endpoint}/reorder`) {\n const input = parseReorderImagesInput(body);\n if (!input) return jsonError(400, 'valid reorder input is required.');\n if (!isAllowedProjectTarget(input.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const images = reorderReviewFigmaImages(data.images, input);\n data.images = images.allImages;\n await writeReviewFigmaImageStoreFile(dataFile, data);\n\n return { status: 200, body: images.targetImages };\n }\n\n const id = getEndpointItemId(pathname, endpoint);\n if (id && method === 'PATCH') {\n const patch = parseUpdateImageInput(body);\n if (!patch) return jsonError(400, 'valid update patch is required.');\n\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const result = updateReviewFigmaImage(data.images, id, patch);\n if (!result) return jsonError(404, `Figma image not found: ${id}`);\n if (!isAllowedProjectTarget(result.image.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n data.images = result.images;\n await writeReviewFigmaImageStoreFile(dataFile, data);\n\n return { status: 200, body: result.image };\n }\n\n if (id && method === 'DELETE') {\n const data = await readReviewFigmaImageStoreFile(dataFile);\n const image = data.images.find((item) => item.id === id);\n if (!image) return jsonError(404, `Figma image not found: ${id}`);\n if (!isAllowedProjectTarget(image.target, options.projectId)) {\n return jsonError(403, 'target project is not allowed.');\n }\n\n data.images = data.images.filter((item) => item.id !== id);\n await writeReviewFigmaImageStoreFile(dataFile, data);\n await deleteReviewFigmaImageAsset(assetDir, image.storageKey);\n\n return { status: 200, body: { ok: true } };\n }\n\n return jsonError(405, 'method not allowed.');\n}\n\nexport async function readJsonRequestBody(req: IncomingMessage) {\n if (req.method === 'GET' || req.method === 'DELETE') return null;\n\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);\n }\n const raw = Buffer.concat(chunks).toString('utf8').trim();\n if (!raw) return null;\n\n return JSON.parse(raw);\n}\n\nexport function sendJson(res: ServerResponse, status: number, body: unknown) {\n res.statusCode = status;\n res.setHeader('Content-Type', 'application/json; charset=utf-8');\n if (status === 204) {\n res.end();\n return;\n }\n res.end(JSON.stringify(body ?? null));\n}\n\nexport async function sendReviewFigmaAsset(\n res: ServerResponse,\n assetDir: string,\n assetEndpoint: string,\n pathname: string\n) {\n const storageKey = getReviewFigmaAssetStorageKeyFromPathname(pathname, assetEndpoint);\n if (!storageKey) {\n sendPlainText(res, 400, 'Invalid Figma image asset path.');\n return;\n }\n\n try {\n const data = await readFile(path.join(assetDir, storageKey));\n res.statusCode = 200;\n res.setHeader('Content-Type', getReviewFigmaAssetMimeType(storageKey));\n res.setHeader('Cache-Control', 'private, max-age=31536000, immutable');\n res.end(data);\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') {\n sendPlainText(res, 404, 'Figma image asset not found.');\n return;\n }\n sendPlainText(\n res,\n 500,\n error instanceof Error ? error.message : 'Figma image asset request failed.'\n );\n }\n}\n\nfunction sendPlainText(res: ServerResponse, status: number, body: string) {\n res.statusCode = status;\n res.setHeader('Content-Type', 'text/plain; charset=utf-8');\n res.end(body);\n}\n\nfunction parseTargetParam(value: string | null) {\n if (!value) return null;\n try {\n return parseReviewFigmaImageTarget(JSON.parse(value));\n } catch {\n return null;\n }\n}\n\nfunction parseAddImageInput(value: unknown): AddReviewFigmaImageInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as Partial<AddReviewFigmaImageInput>;\n const target = parseReviewFigmaImageTarget(input.target);\n if (!target || typeof input.figmaUrl !== 'string') return null;\n\n return {\n target,\n figmaUrl: input.figmaUrl,\n label: typeof input.label === 'string' ? input.label : undefined,\n order: typeof input.order === 'number' ? input.order : undefined,\n imageFormat: parseReviewFigmaImageFormat(input.imageFormat),\n asset: parseAddImageAssetInput(input.asset),\n };\n}\n\nfunction parseAddImageAssetInput(\n value: unknown\n): AddReviewFigmaImageInput['asset'] {\n if (!value || typeof value !== 'object') return undefined;\n const input = value as Partial<NonNullable<AddReviewFigmaImageInput['asset']>>;\n const imageFormat = parseReviewFigmaImageFormat(input.imageFormat);\n if (\n !imageFormat ||\n typeof input.dataUrl !== 'string' ||\n typeof input.mimeType !== 'string'\n ) {\n return undefined;\n }\n\n return {\n dataUrl: input.dataUrl,\n imageFormat,\n mimeType: input.mimeType,\n byteSize: typeof input.byteSize === 'number' ? input.byteSize : undefined,\n width: typeof input.width === 'number' ? input.width : undefined,\n height: typeof input.height === 'number' ? input.height : undefined,\n };\n}\n\nfunction parseUpdateImageInput(value: unknown) {\n if (!value || typeof value !== 'object') return null;\n const input = value as { label?: unknown; order?: unknown };\n\n return {\n label: typeof input.label === 'string' ? input.label : undefined,\n order: typeof input.order === 'number' ? input.order : undefined,\n };\n}\n\nfunction parseReorderImagesInput(\n value: unknown\n): ReorderReviewFigmaImagesInput | null {\n if (!value || typeof value !== 'object') return null;\n const input = value as ReorderReviewFigmaImagesInput;\n const target = parseReviewFigmaImageTarget(input.target);\n if (!target || !Array.isArray(input.imageIds)) return null;\n\n return {\n target,\n imageIds: input.imageIds.filter((id) => typeof id === 'string'),\n };\n}\n\nfunction parseReleaseSnapshotInput(\n value: unknown,\n requestUrl: URL,\n fallbackProjectId: string | undefined\n): {\n projectId: string;\n releaseId?: string;\n label?: string;\n targets: ReviewFigmaImageTarget[];\n} | null {\n const input = value && typeof value === 'object'\n ? (value as Partial<{ projectId: unknown; releaseId: unknown; label: unknown; targets: unknown }>)\n : null;\n const projectId = normalizeOptionalText(\n typeof input?.projectId === 'string'\n ? input.projectId\n : requestUrl.searchParams.get('projectId') ?? fallbackProjectId\n );\n if (!projectId) return null;\n\n return {\n projectId,\n releaseId: normalizeOptionalText(\n typeof input?.releaseId === 'string'\n ? input.releaseId\n : requestUrl.searchParams.get('releaseId')\n ),\n label: normalizeOptionalText(\n typeof input?.label === 'string'\n ? input.label\n : requestUrl.searchParams.get('label')\n ),\n targets: parseReleaseSnapshotTargets(input?.targets, requestUrl),\n };\n}\n\nfunction parseReleaseSnapshotTargets(value: unknown, requestUrl: URL) {\n const bodyTargets = Array.isArray(value)\n ? value.flatMap((target) => {\n const parsed = parseReviewFigmaImageTarget(target);\n return parsed ? [parsed] : [];\n })\n : [];\n const queryTargets = requestUrl.searchParams\n .getAll('target')\n .flatMap((target) => {\n const parsed = parseTargetParam(target);\n return parsed ? [parsed] : [];\n });\n const targetByKey = new Map(\n [...bodyTargets, ...queryTargets].map((target) => [\n getReviewFigmaImageTargetKey(target),\n target,\n ])\n );\n\n return Array.from(targetByKey.values());\n}\n\nfunction parseReviewFigmaImageTarget(\n value: unknown\n): ReviewFigmaImageTarget | null {\n if (!value || typeof value !== 'object') return null;\n const target = value as ReviewFigmaImageTarget;\n if (target.type === 'route') {\n if (\n typeof target.projectId !== 'string' ||\n typeof target.pageUrl !== 'string'\n ) {\n return null;\n }\n\n return {\n type: 'route',\n projectId: target.projectId,\n pageUrl: target.pageUrl,\n viewport:\n target.viewport && typeof target.viewport === 'object'\n ? target.viewport\n : undefined,\n slot: typeof target.slot === 'string' ? target.slot : undefined,\n };\n }\n\n if (target.type === 'figma-node') {\n if (\n typeof target.projectId !== 'string' ||\n typeof target.fileKey !== 'string' ||\n typeof target.nodeId !== 'string'\n ) {\n return null;\n }\n\n return {\n type: 'figma-node',\n projectId: target.projectId,\n fileKey: target.fileKey,\n nodeId: target.nodeId,\n };\n }\n\n return null;\n}\n\nexport function normalizeEndpoint(endpoint: string) {\n const normalized = endpoint.trim().replace(/\\/+$/, '');\n return normalized.startsWith('/') ? normalized : `/${normalized}`;\n}\n\nfunction getEndpointItemId(pathname: string, endpoint: string) {\n if (!pathname.startsWith(`${endpoint}/`)) return null;\n const value = pathname.slice(endpoint.length + 1);\n if (!value || value.includes('/')) return null;\n return decodeURIComponent(value);\n}\n\nfunction isAllowedProjectTarget(\n target: ReviewFigmaImageTarget,\n projectId: string | undefined\n) {\n return !projectId || target.projectId === projectId;\n}\n\nfunction jsonError(status: number, error: string): ReviewFigmaImageStoreResponse {\n return { status, body: { error } };\n}\n","import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport type {\n ReviewFigmaImage,\n ReviewFigmaImageAssetInput,\n ReviewFigmaImageFormat,\n ReviewFigmaImageTarget,\n AddReviewFigmaImageInput,\n UpdateReviewFigmaImageInput,\n} from '../figma/image.types';\nimport {\n getReviewFigmaImageMimeType,\n getReviewFigmaImageTargetKey,\n type ReorderReviewFigmaImagesInput,\n} from '../figma/image.store';\nimport { parseReviewFigmaNodeRef } from '../figma/parse';\nimport {\n requireReviewFigmaToken,\n DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n type ReviewFigmaTokenEnv,\n} from '../figma/token';\nimport {\n renderReviewFigmaImage,\n type ReviewFigmaRenderFormat,\n} from '../figma/render';\nimport {\n createReviewFigmaAssetStorageKey,\n createReviewFigmaAssetUrl,\n getReviewFigmaImageFormatFromMimeType,\n getStoreRenderFormat,\n isSafeReviewFigmaAssetStorageKey,\n normalizeImageMimeType,\n} from './figma-asset';\nimport type { ReviewFigmaImageStorePluginOptions, ReviewFigmaImageAssetTransformer } from './figma-image-store';\n\nexport type ReviewFigmaImageStoreFile = {\n version: 1;\n images: ReviewFigmaImage[];\n};\n\nasync function readReviewFigmaNodeName({\n apiBaseUrl,\n enabled,\n env,\n envKey,\n fetchOption,\n fileKey,\n nodeId,\n token,\n}: {\n apiBaseUrl?: string;\n enabled?: boolean;\n env: ReviewFigmaTokenEnv;\n envKey?: string;\n fetchOption?: typeof fetch;\n fileKey: string;\n nodeId: string;\n token?: string | null;\n}) {\n const explicitToken = typeof token === 'string' ? token.trim() : token;\n const figmaToken =\n explicitToken ||\n requireReviewFigmaToken({\n token: null,\n env,\n envKey: envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n enabled,\n });\n const fetchNode = fetchOption ?? globalThis.fetch;\n if (!fetchNode) throw new Error('Figma node name lookup requires fetch.');\n\n type FigmaNodeResponse = {\n err?: string | null;\n nodes?: Record<string, { document?: { name?: string | null } | null } | null | undefined>;\n };\n\n const response = await fetchNode(\n createReviewFigmaNodeApiUrl({ apiBaseUrl, fileKey, nodeId }),\n {\n headers: {\n 'X-Figma-Token': figmaToken,\n },\n }\n );\n const body = (await response.json().catch(() => null)) as FigmaNodeResponse | null;\n\n if (!response.ok) {\n throw new Error(body?.err || `Figma node lookup failed with ${response.status}`);\n }\n\n const nodes = body?.nodes;\n const node = nodes?.[nodeId] ?? Object.values(nodes ?? {})[0];\n return normalizeOptionalText(node?.document?.name);\n}\n\nfunction createReviewFigmaNodeApiUrl({\n apiBaseUrl = 'https://api.figma.com',\n fileKey,\n nodeId,\n}: {\n apiBaseUrl?: string;\n fileKey: string;\n nodeId: string;\n}) {\n const url = new URL(\n `/v1/files/${encodeURIComponent(fileKey)}/nodes`,\n apiBaseUrl\n );\n url.searchParams.set('ids', nodeId);\n return url.toString();\n}\n\nexport async function createReviewFigmaImage({\n assetDir,\n assetEndpoint,\n currentImages,\n env,\n input,\n options,\n}: {\n assetDir: string;\n assetEndpoint: string;\n currentImages: ReviewFigmaImage[];\n env: ReviewFigmaTokenEnv;\n input: AddReviewFigmaImageInput;\n options: ReviewFigmaImageStorePluginOptions;\n}): Promise<ReviewFigmaImage> {\n const ref = parseReviewFigmaNodeRef(input.figmaUrl);\n if (!ref) {\n throw new Error('A Figma node copy link or fileKey->nodeId value is required.');\n }\n\n const id = createReviewFigmaImageId();\n const explicitLabel = normalizeOptionalText(input.label);\n if (input.asset) {\n const cachedAsset = await cacheReviewFigmaProvidedImageAsset({\n assetDir,\n assetEndpoint,\n id,\n asset: input.asset,\n options,\n });\n const now = new Date().toISOString();\n const order =\n typeof input.order === 'number' && Number.isFinite(input.order)\n ? input.order\n : getNextImageOrder(currentImages, input.target);\n\n return {\n id,\n projectId: input.target.projectId,\n target: input.target,\n figmaUrl: input.figmaUrl,\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n imageUrl: cachedAsset.imageUrl,\n imageFormat: cachedAsset.imageFormat,\n mimeType: cachedAsset.mimeType,\n label: explicitLabel,\n order,\n storageKey: cachedAsset.storageKey,\n width: input.asset.width,\n height: input.asset.height,\n byteSize: cachedAsset.byteSize,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n const nodeLabelPromise = explicitLabel\n ? Promise.resolve(undefined)\n : readReviewFigmaNodeName({\n apiBaseUrl: options.apiBaseUrl,\n enabled: options.enabled,\n env,\n envKey: options.envKey,\n fetchOption: options.fetch,\n fileKey: ref.fileKey,\n nodeId: ref.nodeId,\n token: options.token,\n }).catch(() => undefined);\n const targetImageFormat = input.imageFormat ?? options.imageFormat ?? 'webp';\n const renderFormat = getStoreRenderFormat(options.renderFormat, targetImageFormat);\n const explicitToken = typeof options.token === 'string' ? options.token.trim() : options.token;\n const rendered = await renderReviewFigmaImage({\n figmaUrl: input.figmaUrl,\n format: renderFormat,\n scale: options.renderScale,\n useAbsoluteBounds: options.useAbsoluteBounds,\n apiBaseUrl: options.apiBaseUrl,\n fetch: options.fetch,\n token:\n explicitToken ||\n requireReviewFigmaToken({\n token: null,\n env,\n envKey: options.envKey ?? DEFAULT_REVIEW_FIGMA_TOKEN_ENV_KEY,\n enabled: options.enabled,\n }),\n });\n const cachedAsset = await cacheReviewFigmaImageAsset({\n assetDir,\n assetEndpoint,\n id,\n imageUrl: rendered.imageUrl,\n options,\n renderFormat,\n targetImageFormat,\n });\n const imageFormat =\n cachedAsset?.imageFormat ?? (renderFormat === 'jpg' ? 'jpg' : 'png');\n const now = new Date().toISOString();\n const order =\n typeof input.order === 'number' && Number.isFinite(input.order)\n ? input.order\n : getNextImageOrder(currentImages, input.target);\n const nodeLabel = await nodeLabelPromise;\n\n return {\n id,\n projectId: input.target.projectId,\n target: input.target,\n figmaUrl: input.figmaUrl,\n fileKey: rendered.fileKey,\n nodeId: rendered.nodeId,\n imageUrl: cachedAsset?.imageUrl ?? rendered.imageUrl,\n imageFormat,\n mimeType: cachedAsset?.mimeType ?? getReviewFigmaImageMimeType(imageFormat),\n label: explicitLabel ?? nodeLabel,\n order,\n storageKey: cachedAsset?.storageKey,\n byteSize: cachedAsset?.byteSize,\n createdAt: now,\n updatedAt: now,\n };\n}\n\ntype CachedReviewFigmaImageAsset = {\n imageUrl: string;\n imageFormat: ReviewFigmaImageFormat;\n mimeType: string;\n storageKey: string;\n byteSize: number;\n};\n\nasync function cacheReviewFigmaProvidedImageAsset({\n assetDir,\n assetEndpoint,\n id,\n asset,\n options,\n}: {\n assetDir: string;\n assetEndpoint: string;\n id: string;\n asset: ReviewFigmaImageAssetInput;\n options: ReviewFigmaImageStorePluginOptions;\n}): Promise<CachedReviewFigmaImageAsset> {\n const decodedAsset = decodeReviewFigmaImageAsset(asset);\n const storageKey = createReviewFigmaAssetStorageKey(\n id,\n decodedAsset.imageFormat\n );\n\n await mkdir(assetDir, { recursive: true });\n await writeFile(path.join(assetDir, storageKey), decodedAsset.data);\n\n return {\n imageUrl: createReviewFigmaAssetUrl(assetEndpoint, storageKey),\n imageFormat: decodedAsset.imageFormat,\n mimeType: decodedAsset.mimeType,\n storageKey,\n byteSize: decodedAsset.data.byteLength,\n };\n}\n\nfunction decodeReviewFigmaImageAsset(asset: ReviewFigmaImageAssetInput) {\n const mimeType = normalizeImageMimeType(asset.mimeType);\n if (!mimeType) throw new Error('Unsupported Figma image asset MIME type.');\n\n const imageFormat =\n getReviewFigmaImageFormatFromMimeType(mimeType) ?? asset.imageFormat;\n const match = /^data:([^;,]+);base64,([a-zA-Z0-9+/=\\s]+)$/.exec(\n asset.dataUrl\n );\n if (!match) throw new Error('Valid Figma image asset data URL is required.');\n\n const dataUrlMimeType = normalizeImageMimeType(match[1]);\n if (dataUrlMimeType && dataUrlMimeType !== mimeType) {\n throw new Error('Figma image asset MIME type mismatch.');\n }\n\n return {\n data: Buffer.from(match[2].replace(/\\s/g, ''), 'base64'),\n imageFormat,\n mimeType,\n };\n}\n\nasync function cacheReviewFigmaImageAsset({\n assetDir,\n assetEndpoint,\n id,\n imageUrl,\n options,\n renderFormat,\n targetImageFormat,\n}: {\n assetDir: string;\n assetEndpoint: string;\n id: string;\n imageUrl: string;\n options: ReviewFigmaImageStorePluginOptions;\n renderFormat: Extract<ReviewFigmaRenderFormat, 'png' | 'jpg'>;\n targetImageFormat: ReviewFigmaImageFormat;\n}): Promise<CachedReviewFigmaImageAsset | null> {\n if (options.cacheAssets === false) return null;\n\n const asset = await downloadReviewFigmaImageAsset({\n fetchOption: options.fetch,\n imageUrl,\n renderFormat,\n targetImageFormat,\n transformAsset: options.transformAsset,\n });\n const storageKey = createReviewFigmaAssetStorageKey(id, asset.imageFormat);\n\n await mkdir(assetDir, { recursive: true });\n await writeFile(path.join(assetDir, storageKey), asset.data);\n\n return {\n imageUrl: createReviewFigmaAssetUrl(assetEndpoint, storageKey),\n imageFormat: asset.imageFormat,\n mimeType: asset.mimeType,\n storageKey,\n byteSize: asset.data.byteLength,\n };\n}\n\nasync function downloadReviewFigmaImageAsset({\n fetchOption,\n imageUrl,\n renderFormat,\n targetImageFormat,\n transformAsset,\n}: {\n fetchOption: typeof fetch | undefined;\n imageUrl: string;\n renderFormat: Extract<ReviewFigmaRenderFormat, 'png' | 'jpg'>;\n targetImageFormat: ReviewFigmaImageFormat;\n transformAsset: ReviewFigmaImageAssetTransformer | undefined;\n}) {\n const fetchImage = fetchOption ?? globalThis.fetch;\n if (!fetchImage) throw new Error('Figma image caching requires fetch.');\n\n const response = await fetchImage(imageUrl);\n if (!response.ok) {\n throw new Error(`Figma image download failed with ${response.status}`);\n }\n\n const sourceMimeType =\n normalizeImageMimeType(response.headers.get('content-type')) ??\n getReviewFigmaImageMimeType(renderFormat === 'jpg' ? 'jpg' : 'png');\n const sourceImageFormat =\n getReviewFigmaImageFormatFromMimeType(sourceMimeType) ??\n (renderFormat === 'jpg' ? 'jpg' : 'png');\n const sourceData = new Uint8Array(await response.arrayBuffer());\n const transformed = transformAsset\n ? await transformAsset({\n data: sourceData,\n imageFormat: sourceImageFormat,\n mimeType: sourceMimeType,\n targetFormat: targetImageFormat,\n })\n : null;\n const imageFormat = transformed?.imageFormat ?? sourceImageFormat;\n const mimeType =\n normalizeImageMimeType(transformed?.mimeType) ??\n getReviewFigmaImageMimeType(imageFormat);\n const data = createBufferFromImageData(transformed?.data ?? sourceData);\n\n return { data, imageFormat, mimeType };\n}\n\nexport async function deleteReviewFigmaImageAsset(\n assetDir: string,\n storageKey: string | undefined\n) {\n if (!storageKey) return;\n if (!isSafeReviewFigmaAssetStorageKey(storageKey)) return;\n await rm(path.join(assetDir, storageKey), { force: true }).catch(() => null);\n}\n\nfunction createBufferFromImageData(data: Uint8Array | ArrayBuffer) {\n return data instanceof ArrayBuffer\n ? Buffer.from(new Uint8Array(data))\n : Buffer.from(data);\n}\n\nexport function listImagesForTarget(\n images: ReviewFigmaImage[],\n target: ReviewFigmaImageTarget\n) {\n const targetKey = getReviewFigmaImageTargetKey(target);\n return images\n .filter((image) => getReviewFigmaImageTargetKey(image.target) === targetKey)\n .sort(compareReviewFigmaImages);\n}\n\nexport function reorderReviewFigmaImages(\n images: ReviewFigmaImage[],\n input: ReorderReviewFigmaImagesInput\n) {\n const targetKey = getReviewFigmaImageTargetKey(input.target);\n const orderById = new Map(input.imageIds.map((id, index) => [id, index]));\n const targetImages = listImagesForTarget(images, input.target);\n const nextTargetImages = targetImages\n .map((image) => ({\n ...image,\n order: orderById.get(image.id) ?? input.imageIds.length + image.order,\n updatedAt: orderById.has(image.id) ? new Date().toISOString() : image.updatedAt,\n }))\n .sort(compareReviewFigmaImages);\n const nextTargetImageById = new Map(\n nextTargetImages.map((image, index) => [image.id, { ...image, order: index }])\n );\n const allImages = images.map((image) =>\n getReviewFigmaImageTargetKey(image.target) === targetKey\n ? nextTargetImageById.get(image.id) ?? image\n : image\n );\n\n return {\n allImages,\n targetImages: listImagesForTarget(allImages, input.target),\n };\n}\n\nexport function updateReviewFigmaImage(\n images: ReviewFigmaImage[],\n id: string,\n patch: UpdateReviewFigmaImageInput\n) {\n const index = images.findIndex((image) => image.id === id);\n if (index < 0) return null;\n\n const nextImage: ReviewFigmaImage = {\n ...images[index],\n label:\n patch.label === undefined\n ? images[index].label\n : normalizeOptionalText(patch.label),\n order:\n typeof patch.order === 'number' && Number.isFinite(patch.order)\n ? patch.order\n : images[index].order,\n updatedAt: new Date().toISOString(),\n };\n const nextImages = [...images];\n nextImages[index] = nextImage;\n\n return { image: nextImage, images: nextImages };\n}\n\nexport async function readReviewFigmaImageStoreFile(\n dataFile: string\n): Promise<ReviewFigmaImageStoreFile> {\n try {\n const raw = await readFile(dataFile, 'utf8');\n const parsed = JSON.parse(raw) as Partial<ReviewFigmaImageStoreFile>;\n\n return {\n version: 1,\n images: Array.isArray(parsed.images)\n ? parsed.images.flatMap((image) => (isReviewFigmaImage(image) ? [image] : []))\n : [],\n };\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') {\n return { version: 1, images: [] };\n }\n throw error;\n }\n}\n\nexport async function writeReviewFigmaImageStoreFile(\n dataFile: string,\n data: ReviewFigmaImageStoreFile\n) {\n await mkdir(path.dirname(dataFile), { recursive: true });\n await writeFile(\n dataFile,\n `${JSON.stringify({ version: 1, images: data.images }, null, 2)}\\n`,\n 'utf8'\n );\n}\n\nfunction getNextImageOrder(\n images: ReviewFigmaImage[],\n target: ReviewFigmaImageTarget\n) {\n const targetImages = listImagesForTarget(images, target);\n return targetImages.length\n ? Math.max(...targetImages.map((image) => image.order)) + 1\n : 0;\n}\n\nfunction compareReviewFigmaImages(a: ReviewFigmaImage, b: ReviewFigmaImage) {\n return a.order - b.order || a.createdAt.localeCompare(b.createdAt);\n}\n\nfunction createReviewFigmaImageId() {\n return `figma_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction isReviewFigmaImage(value: unknown): value is ReviewFigmaImage {\n if (!value || typeof value !== 'object') return false;\n const image = value as ReviewFigmaImage;\n\n return (\n typeof image.id === 'string' &&\n typeof image.projectId === 'string' &&\n typeof image.figmaUrl === 'string' &&\n typeof image.fileKey === 'string' &&\n typeof image.nodeId === 'string' &&\n typeof image.imageUrl === 'string' &&\n typeof image.order === 'number' &&\n typeof image.createdAt === 'string' &&\n typeof image.updatedAt === 'string'\n );\n}\n\nexport function isNodeError(error: unknown): error is { code?: string } {\n if (!error || typeof error !== 'object') return false;\n return 'code' in error;\n}\n\nexport function normalizeOptionalText(value: string | null | undefined) {\n if (typeof value !== 'string') return undefined;\n return value.trim() || undefined;\n}\n","import { type Plugin, type ResolvedConfig } from 'vite';\n\nexport * from './vite/figma-image-store';\n\ntype SourceLocatorPattern = string | RegExp;\n\nexport interface ReviewSourceLocatorOptions {\n enabled?: boolean;\n root?: string;\n include?: readonly SourceLocatorPattern[];\n exclude?: readonly SourceLocatorPattern[];\n filePath?: 'relative' | 'absolute';\n line?: boolean;\n column?: boolean;\n attributePrefix?: string;\n}\n\ntype RuntimeMatcher =\n | { type: 'path'; value: string }\n | { type: 'regex'; value: string; flags: string };\n\ntype RuntimeOptions = {\n enabled: boolean;\n root: string;\n include: RuntimeMatcher[];\n exclude: RuntimeMatcher[];\n filePath: 'relative' | 'absolute';\n line: boolean;\n column: boolean;\n fileAttribute: string;\n lineAttribute: string;\n columnAttribute: string;\n};\n\nconst VIRTUAL_JSX_DEV_RUNTIME_ID =\n '\\0@designfever/web-review-kit/source-locator/jsx-dev-runtime';\n\nconst REVIEW_SOURCE_ENV_DEFINE_KEYS = [\n ['__DF_WRK_REVIEW_SOURCE_ROOT__', 'VITE_REVIEW_SOURCE_ROOT'],\n ['__DF_WRK_REVIEW_SOURCE_EDITOR__', 'VITE_REVIEW_SOURCE_EDITOR'],\n [\n '__DF_WRK_REVIEW_SOURCE_URL_TEMPLATE__',\n 'VITE_REVIEW_SOURCE_URL_TEMPLATE',\n ],\n] as const;\n\ntype ReviewSourceEnvReplacements = Record<string, string>;\n\nconst createReviewSourceEnvReplacements = (\n env: ResolvedConfig['env'] = {}\n): ReviewSourceEnvReplacements => {\n return Object.fromEntries(\n REVIEW_SOURCE_ENV_DEFINE_KEYS.map(([defineKey, envKey]) => [\n defineKey,\n JSON.stringify(env[envKey] ?? ''),\n ])\n );\n};\n\nconst injectReviewSourceEnv = (\n code: string,\n replacements: ReviewSourceEnvReplacements\n) => {\n let nextCode = code;\n for (const [defineKey, value] of Object.entries(replacements)) {\n nextCode = nextCode\n .split(`typeof ${defineKey}`)\n .join(`typeof ${value}`)\n .split(`: ${defineKey}`)\n .join(`: ${value}`);\n }\n\n return nextCode === code ? null : nextCode;\n};\n\nexport const reviewSourceLocator = (\n options: ReviewSourceLocatorOptions = {}\n): Plugin => {\n let runtimeOptions = createRuntimeOptions(options);\n let sourceEnvReplacements = createReviewSourceEnvReplacements();\n\n return {\n name: 'df-web-review-kit-source-locator',\n enforce: 'pre',\n configResolved(config) {\n runtimeOptions = createRuntimeOptions(options, config);\n sourceEnvReplacements = createReviewSourceEnvReplacements(config.env);\n },\n resolveId(id, importer) {\n if (!runtimeOptions.enabled) return null;\n if (id !== 'react/jsx-dev-runtime') return null;\n if (importer === VIRTUAL_JSX_DEV_RUNTIME_ID) return null;\n\n return VIRTUAL_JSX_DEV_RUNTIME_ID;\n },\n load(id) {\n if (id !== VIRTUAL_JSX_DEV_RUNTIME_ID) return null;\n return createJsxDevRuntime(runtimeOptions);\n },\n transform(code) {\n const injectedCode = injectReviewSourceEnv(code, sourceEnvReplacements);\n return injectedCode ? { code: injectedCode, map: null } : null;\n },\n };\n};\n\nexport interface ReviewDataLocatorOptions {\n enabled?: boolean;\n root?: string;\n include?: readonly SourceLocatorPattern[];\n exclude?: readonly SourceLocatorPattern[];\n filePath?: 'relative' | 'absolute';\n /** 매칭할 component 이름 패턴. 기본은 `Section`으로 시작하는 이름. */\n componentPattern?: RegExp;\n fileAttribute?: string;\n lineAttribute?: string;\n}\n\n/**\n * page data 파일의 section 객체(`component: 'SectionXxx'`)에 출처 파일/라인을\n * `__wrkDataFile`/`__wrkDataLine` prop 으로 주입한다. 라인 보존을 위해 같은 줄에만 삽입한다.\n */\nexport const reviewDataLocator = (\n options: ReviewDataLocatorOptions = {}\n): Plugin => {\n let root = normalizePath(options.root ?? '');\n let enabled = options.enabled ?? false;\n let sourceEnvReplacements = createReviewSourceEnvReplacements();\n const include = (options.include ?? []).map(createRuntimeMatcher);\n const exclude = (options.exclude ?? ['node_modules', 'dist']).map(\n createRuntimeMatcher\n );\n const componentPattern = options.componentPattern ?? /Section[A-Za-z0-9_]*/;\n const fileKey = options.fileAttribute ?? '__wrkDataFile';\n const lineKey = options.lineAttribute ?? '__wrkDataLine';\n\n const componentSource = `(^|[\\\\n,{(\\\\[]\\\\s*)(component:\\\\s*)(['\"\\`])(${componentPattern.source})\\\\3`;\n\n return {\n name: 'df-web-review-kit-data-locator',\n enforce: 'pre',\n configResolved(config) {\n root = normalizePath(options.root ?? config.root ?? '');\n enabled = options.enabled ?? config.command === 'serve';\n sourceEnvReplacements = createReviewSourceEnvReplacements(config.env);\n },\n transform(code, id) {\n const envInjectedCode = injectReviewSourceEnv(\n code,\n sourceEnvReplacements\n );\n const inputCode = envInjectedCode ?? code;\n if (!enabled) return null;\n const file = normalizePath(id.split('?')[0]);\n const relativeFile =\n root && file.startsWith(root + '/') ? file.slice(root.length + 1) : file;\n if (\n include.length > 0 &&\n !include.some((m) => matchesPath(m, file, relativeFile))\n )\n return envInjectedCode ? { code: envInjectedCode, map: null } : null;\n if (exclude.some((m) => matchesPath(m, file, relativeFile))) {\n return envInjectedCode ? { code: envInjectedCode, map: null } : null;\n }\n\n const sourceFile =\n (options.filePath ?? 'relative') === 'absolute' ? file : relativeFile;\n const regex = new RegExp(componentSource, 'g');\n let changed = false;\n const out = inputCode.replace(\n regex,\n (\n _match,\n pre: string,\n comp: string,\n quote: string,\n name: string,\n offset: number\n ) => {\n const line = inputCode\n .slice(0, offset + pre.length)\n .split('\\n').length;\n changed = true;\n return `${pre}${JSON.stringify(fileKey)}: ${JSON.stringify(sourceFile)}, ${JSON.stringify(lineKey)}: ${line}, ${comp}${quote}${name}${quote}`;\n }\n );\n\n return changed || envInjectedCode ? { code: out, map: null } : null;\n },\n };\n};\n\nfunction matchesPath(\n matcher: RuntimeMatcher,\n absoluteFile: string,\n relativeFile: string\n) {\n if (matcher.type === 'regex') {\n const regex = new RegExp(matcher.value, matcher.flags);\n return regex.test(absoluteFile) || regex.test(relativeFile);\n }\n const target = matcher.value.startsWith('/') ? absoluteFile : relativeFile;\n return target === matcher.value || target.startsWith(matcher.value + '/') || target.includes('/' + matcher.value);\n}\n\nfunction createRuntimeOptions(\n options: ReviewSourceLocatorOptions,\n config?: ResolvedConfig\n): RuntimeOptions {\n const attributePrefix = (options.attributePrefix ?? 'data-wrk-source').replace(\n /-+$/,\n ''\n );\n const root = normalizePath(options.root ?? config?.root ?? '');\n const enabled = options.enabled ?? (config?.command === 'serve');\n\n return {\n enabled,\n root,\n include: (options.include ?? []).map(createRuntimeMatcher),\n exclude: (options.exclude ?? ['node_modules', 'dist']).map(\n createRuntimeMatcher\n ),\n filePath: options.filePath ?? 'relative',\n line: options.line ?? true,\n column: options.column ?? true,\n fileAttribute: `${attributePrefix}-file`,\n lineAttribute: `${attributePrefix}-line`,\n columnAttribute: `${attributePrefix}-column`,\n };\n}\n\nfunction createRuntimeMatcher(pattern: SourceLocatorPattern): RuntimeMatcher {\n if (pattern instanceof RegExp) {\n return { type: 'regex', value: pattern.source, flags: pattern.flags };\n }\n\n return { type: 'path', value: normalizePath(pattern).replace(/^\\.\\//, '') };\n}\n\nfunction normalizePath(value: string) {\n return value.replace(/\\\\/g, '/').replace(/\\/+$/, '');\n}\n\nfunction createJsxDevRuntime(options: RuntimeOptions) {\n return `\nimport { Fragment, jsxDEV as baseJsxDEV } from 'react/jsx-dev-runtime';\n\nconst OPTIONS = ${JSON.stringify(options)};\n\nexport { Fragment };\n\nexport function jsxDEV(type, props, key, isStaticChildren, source, self) {\n return baseJsxDEV(\n type,\n injectSourceProps(type, props, source),\n key,\n isStaticChildren,\n source,\n self\n );\n}\n\nfunction injectSourceProps(type, props, source) {\n if (typeof type !== 'string') return props;\n if (!source || typeof source.fileName !== 'string') return props;\n\n const sourceFile = getSourceFile(source.fileName);\n if (!sourceFile) return props;\n\n const nextProps = props ? { ...props } : {};\n if (nextProps[OPTIONS.fileAttribute] == null) {\n nextProps[OPTIONS.fileAttribute] = sourceFile;\n }\n if (OPTIONS.line && source.lineNumber != null && nextProps[OPTIONS.lineAttribute] == null) {\n nextProps[OPTIONS.lineAttribute] = String(source.lineNumber);\n }\n if (OPTIONS.column && source.columnNumber != null && nextProps[OPTIONS.columnAttribute] == null) {\n nextProps[OPTIONS.columnAttribute] = String(source.columnNumber);\n }\n\n return nextProps;\n}\n\nfunction getSourceFile(fileName) {\n const absoluteFile = normalizePath(fileName);\n const relativeFile = getRelativeFile(absoluteFile);\n\n if (OPTIONS.include.length > 0 && !matchesAny(OPTIONS.include, absoluteFile, relativeFile)) {\n return null;\n }\n if (matchesAny(OPTIONS.exclude, absoluteFile, relativeFile)) return null;\n\n return OPTIONS.filePath === 'absolute' ? absoluteFile : relativeFile;\n}\n\nfunction getRelativeFile(absoluteFile) {\n if (!OPTIONS.root) return absoluteFile;\n if (absoluteFile === OPTIONS.root) return '';\n if (absoluteFile.startsWith(OPTIONS.root + '/')) {\n return absoluteFile.slice(OPTIONS.root.length + 1);\n }\n\n return absoluteFile;\n}\n\nfunction matchesAny(patterns, absoluteFile, relativeFile) {\n return patterns.some((pattern) =>\n matchesPattern(pattern, absoluteFile, relativeFile)\n );\n}\n\nfunction matchesPattern(pattern, absoluteFile, relativeFile) {\n if (pattern.type === 'regex') {\n const regex = new RegExp(pattern.value, pattern.flags);\n return regex.test(absoluteFile) || regex.test(relativeFile);\n }\n\n const value = pattern.value;\n const target = isAbsolutePattern(value) ? absoluteFile : relativeFile;\n if (!value.includes('*')) {\n return target === value || target.startsWith(value + '/');\n }\n\n return globToRegExp(value).test(target);\n}\n\nfunction isAbsolutePattern(value) {\n return value.startsWith('/') || /^[a-zA-Z]:\\\\//.test(value);\n}\n\nfunction globToRegExp(value) {\n const source = escapeRegExp(value)\n .replace(/\\\\\\\\\\\\*\\\\\\\\\\\\*/g, '.*')\n .replace(/\\\\\\\\\\\\*/g, '[^/]*');\n\n return new RegExp('^' + source + '$');\n}\n\nfunction escapeRegExp(value) {\n return value.replace(/[|\\\\\\\\{}()[\\\\]^$+*?.]/g, '\\\\\\\\$&');\n}\n\nfunction normalizePath(value) {\n return value.replace(/\\\\\\\\/g, '/').replace(/\\\\/+$/, '');\n}\n`;\n}\n"],"mappings":"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","names":["path","readFile","path","cachedAsset","now","order","readFile","path","path"]}
package/docs/README.md CHANGED
@@ -5,22 +5,26 @@ Public docs are intentionally small. Keep implementation history, handoff notes,
5
5
  ## Read This Order
6
6
 
7
7
  1. [Installation](installation.md)
8
- 2. [Custom adapter sample](adaptor.sample.ts)
9
- 3. [DB setup](db-setup.md)
10
- 4. [Architecture and runtime logic](architecture.md)
11
- 5. [Figma overlay](figma-overlay.md)
12
- 6. [Grid overlay](grid-overlay.md)
13
- 7. [Release notes 0.6.0](release-notes-0.6.0.md)
8
+ 2. [Host env sample](../.env.sample)
9
+ 3. [Adapter boundaries](adapters.md)
10
+ 4. [Custom adapter sample](adaptor.sample.ts)
11
+ 5. [DB setup](db-setup.md)
12
+ 6. [Architecture and runtime logic](architecture.md)
13
+ 7. [Figma overlay](figma-overlay.md)
14
+ 8. [Grid overlay](grid-overlay.md)
15
+ 9. [Release notes 0.7.0](release-notes-0.7.0.md)
14
16
 
15
17
  ## Document Roles
16
18
 
17
19
  - `installation.md`: install the npm package, create the `/review` route, wire adapters, and run checks.
20
+ - `../.env.sample`: copyable host project env template for local, Supabase, and source opening.
21
+ - `adapters.md`: QA adapter and Figma image store responsibility boundary.
18
22
  - `adaptor.sample.ts`: copyable starting point for host-owned remote adapters.
19
23
  - `db-setup.md`: optional Supabase review item table/RPC/RLS/presence setup.
20
24
  - `architecture.md`: core/runtime, React shell, coordinate, anchor, and feature boundary notes.
21
25
  - `figma-overlay.md`: host requirements for the Figma overlay toggle.
22
26
  - `grid-overlay.md`: host requirements for the grid/helper overlay toggle.
23
- - `release-notes-0.6.0.md`: latest release changes, host notes, and validation scope.
27
+ - `release-notes-0.7.0.md`: latest release changes, host notes, and validation scope.
24
28
 
25
29
  ## Boundary
26
30
 
@@ -0,0 +1,126 @@
1
+ # Adapter Boundaries
2
+
3
+ `df-web-review-kit` has two integration surfaces that look similar but should stay separate:
4
+
5
+ - QA adapter: persists review items such as note, area, DOM QA, status, title, and assignee.
6
+ - Figma image store: persists Figma reference image metadata and cached image assets.
7
+
8
+ Use `adapter` for both concepts in conversation only when the context is clear. In code/docs, prefer the explicit names `ReviewShellAdapter` and `ReviewFigmaImageStore`.
9
+
10
+ > Note: `docs/adaptor.sample.ts` keeps the old filename spelling for compatibility with existing links. The public API name is still `adapter`.
11
+
12
+ ## Responsibility Matrix
13
+
14
+ | Concern | QA adapter | Figma image store |
15
+ | --- | --- | --- |
16
+ | Public type | `ReviewShellAdapter`, `WebReviewKitAdapter` | `ReviewFigmaImageStore` |
17
+ | Mount option | `mountReviewShell({ adapters })` | `mountReviewShell({ figmaImages: { store } })` |
18
+ | Vite plugin | Not required | `reviewFigmaImageStore()` for dev/local cache |
19
+ | Data | `ReviewItem` records | `ReviewFigmaImage` records and image assets |
20
+ | Main actions | `get`, `list`, `create`, `update`, `remove` | `listImages`, `addImage`, `updateImage`, `reorderImages`, `deleteImage` |
21
+ | UI fields | `fields.title`, `statusOptions`, `assigneeOptions` | image label, order, opacity/layer controls |
22
+ | Token/secrets | Host backend decides | `FIGMA_TOKEN` stays server-side for Vite/dev endpoint |
23
+ | Typical storage | localStorage, Supabase, df-sheet, custom QA API | `.df-review/figma-images.json`, `.df-review/figma-assets/`, future asset backend |
24
+
25
+ ## QA Adapter
26
+
27
+ QA adapters own review item persistence. They should answer one question:
28
+
29
+ > Where do QA items for this project/page live?
30
+
31
+ The shell reads and writes QA through `adapters`.
32
+
33
+ ```tsx
34
+ mountReviewShell({
35
+ projectId,
36
+ pages,
37
+ adapters: [
38
+ {
39
+ label: 'df-sheet',
40
+ get: (id) => dfSheet.get(id),
41
+ list: (query) => dfSheet.list(query),
42
+ create: (item) => dfSheet.create(item),
43
+ fields: { title: true },
44
+ statusOptions,
45
+ updateStatus: ({ id, status }) => dfSheet.updateStatus(id, status),
46
+ assigneeTitle: '담당자',
47
+ assigneeOptions,
48
+ updateAssignee: ({ id, assigneeId, assigneeName }) =>
49
+ dfSheet.updateAssignee(id, { assigneeId, assigneeName }),
50
+ remove: (id) => dfSheet.remove(id),
51
+ },
52
+ ],
53
+ });
54
+ ```
55
+
56
+ Guidelines:
57
+
58
+ - Keep backend-specific mapping inside the host adapter.
59
+ - `fields.title` only controls whether title UI appears. Omit it for comment-only QA.
60
+ - `assigneeOptions` and `updateAssignee` are optional. If omitted, assignee UI should not become a required workflow.
61
+ - `statusOptions` can be project-specific, but status remains a QA item field.
62
+ - Slow remote adapters should return promises normally; the shell owns loading and pending UI.
63
+
64
+ ## Figma Image Store
65
+
66
+ Figma image stores own reference image metadata and assets. They should answer one question:
67
+
68
+ > Which Figma reference images are attached to this route/viewport?
69
+
70
+ The shell reads and writes Figma image data through `figmaImages.store`.
71
+
72
+ ```tsx
73
+ import { createReviewFigmaImageStoreClient } from '@designfever/web-review-kit';
74
+
75
+ const figmaImageStore = createReviewFigmaImageStoreClient();
76
+
77
+ mountReviewShell({
78
+ projectId,
79
+ pages,
80
+ adapters,
81
+ figmaImages: {
82
+ store: figmaImageStore,
83
+ imageFormat: 'webp',
84
+ },
85
+ });
86
+ ```
87
+
88
+ For local/dev caching, the host Vite config wires the server endpoint:
89
+
90
+ ```ts
91
+ import { defineConfig } from 'vite';
92
+ import { reviewFigmaImageStore } from '@designfever/web-review-kit/vite';
93
+
94
+ export default defineConfig({
95
+ plugins: [
96
+ reviewFigmaImageStore({
97
+ projectId: 'my-project',
98
+ dataFile: '.df-review/figma-images.json',
99
+ }),
100
+ ],
101
+ });
102
+ ```
103
+
104
+ Guidelines:
105
+
106
+ - Do not put Figma image metadata into QA adapter metadata.
107
+ - Do not use `VITE_FIGMA_TOKEN` for server rendering. Keep `FIGMA_TOKEN` on the server/dev process.
108
+ - If no `figmaImages.store` is configured, QA should still work.
109
+ - If Figma image storage moves remote later, implement a `ReviewFigmaImageStore`-shaped client instead of extending `ReviewShellAdapter`.
110
+
111
+ ## Host Wiring Rule
112
+
113
+ Host projects can use both surfaces together, but they should be configured independently.
114
+
115
+ ```tsx
116
+ mountReviewShell({
117
+ projectId,
118
+ pages,
119
+ adapters, // QA item persistence
120
+ figmaImages: {
121
+ store: figmaImageStore, // Figma reference image persistence
122
+ },
123
+ });
124
+ ```
125
+
126
+ This keeps QA workflows, release notes, and future backend migrations from coupling unrelated data models.
@@ -7,6 +7,7 @@ import {
7
7
  type WebReviewKitAdapter,
8
8
  } from '@designfever/web-review-kit';
9
9
  import type {
10
+ ReviewShellAssigneeOption,
10
11
  ReviewShellAdapter,
11
12
  } from '@designfever/web-review-kit/react-shell';
12
13
 
@@ -15,6 +16,9 @@ type RemoteReviewAdapterOptions = {
15
16
  projectId: string;
16
17
  source?: ReviewSource;
17
18
  token?: string;
19
+ fields?: ReviewShellAdapter['fields'];
20
+ assigneeTitle?: string;
21
+ assigneeOptions?: ReviewShellAssigneeOption[];
18
22
  };
19
23
 
20
24
  type RemoteReviewItemResponse = ReviewItem | { item: ReviewItem };
@@ -98,8 +102,11 @@ export function createRemoteReviewAdapter(
98
102
  * label becomes the URL source, for example /review?source=remote.
99
103
  * create controls whether the shell can write to this adapter.
100
104
  * canWrite can be true or limited to ['dom', 'note', 'area'].
101
- * update enables comment edits in the QA panel.
105
+ * update enables title/comment edits in the QA panel.
106
+ * fields enables optional UI fields such as title. Omit title to keep comment-only UI.
102
107
  * updateStatus drives the status buttons in the QA panel.
108
+ * assigneeOptions + updateAssignee drive the assignee select next to status.
109
+ * assigneeTitle customizes the empty option/field label. Defaults to "Assignee".
103
110
  * remove enables delete actions for this source.
104
111
  */
105
112
  export function createRemoteReviewShellAdapter(
@@ -114,9 +121,14 @@ export function createRemoteReviewShellAdapter(
114
121
  create: (item) => adapter.create(item),
115
122
  update: (id, patch) => adapter.update(id, patch),
116
123
  canWrite: true,
124
+ fields: options.fields,
117
125
  statusOptions: REVIEW_WORKFLOW_STATUS_OPTIONS,
118
126
  updateStatus: ({ id, status }) =>
119
127
  adapter.update(id, { status: normalizeRemoteStatus(status) }),
128
+ assigneeTitle: options.assigneeTitle,
129
+ assigneeOptions: options.assigneeOptions ?? [],
130
+ updateAssignee: ({ id, assigneeId, assigneeName }) =>
131
+ adapter.update(id, { assigneeId, assigneeName }),
120
132
  remove: (id) => adapter.remove(id),
121
133
  };
122
134
  }
@@ -41,6 +41,7 @@ When the React shell provides a composer host, core docks DOM/area draft compose
41
41
 
42
42
  - `web.review.kit.app.ts`: controller lifecycle, state transitions, adapter calls, item creation, restore flow.
43
43
  - `web.review.kit.view.ts`: vanilla DOM renderer for core overlay UI.
44
+ - `draft.metrics.ts`: pure geometry for draft adjustment (nudge/scale) previews, kept out of the renderer.
44
45
  - `dom.anchor.ts`: selector candidate generation, anchor rebinding, text fingerprint matching.
45
46
  - `geometry.ts`: target-space and host-space coordinate conversion.
46
47
  - `review/item.ts`: marker, selection, highlight, and fallback resolution.
@@ -83,7 +84,7 @@ Core never owns persistence storage directly. It only calls the configured `WebR
83
84
  - `update`
84
85
  - `remove`
85
86
 
86
- The default local adapter is for draft/local review work. Supabase is optional host wiring, not a required backend.
87
+ The default local adapter is for draft/local review work. Supabase is optional host wiring, not a required backend. Figma reference images use a separate `ReviewFigmaImageStore`; see [Adapter boundaries](adapters.md).
87
88
 
88
89
  ## React Shell Boundary
89
90
 
@@ -0,0 +1,232 @@
1
+ # 코드 리뷰 및 정리: 0.6.0 → 0.7.0 준비
2
+
3
+ 리뷰 대상: `df-web-review-kit@0.6.0` (`origin/main` 기준)
4
+ 작업 브랜치: `chore/0.7.0-code-review-cleanup`
5
+ 범위: `src/**` 구조 정리, 죽은 코드/죽은 export 제거, dead-code 검사 설정 추가
6
+
7
+ ---
8
+
9
+ ## 요약
10
+
11
+ 0.6.0에서 기능이 늘며 가장 크게 비대한 파일은 `src/react-shell/style.ts`였다. 단일 함수 안에 3,500줄 이상 CSS 문자열이 들어 있었고, Source Tree / QA list / composer / modal / ruler 스타일이 한 파일에 섞여 있었다.
12
+
13
+ 이번 0.7.0 준비 브랜치에서는 위험도가 낮은 정리부터 적용했다.
14
+
15
+ 1. `react-shell/style.ts`를 7개 스타일 chunk로 분리
16
+ 2. 실제 미사용 함수/타입/상수와 내부 전용 helper의 잘못된 export 제거
17
+ 3. `knip` 설정과 `pnpm lint:dead-code` 스크립트 추가
18
+ 4. `typecheck`, `typecheck:dev`, `lint:dead-code`, `build` 통과 확인
19
+
20
+ ---
21
+
22
+ ## 1. 한 파일에 몰린 곳
23
+
24
+ ### 정리 전 상위 파일
25
+
26
+ | 파일 | LOC | 문제 |
27
+ |---|---:|---|
28
+ | `src/react-shell/style.ts` | 3,579 | 단일 함수가 전체 review shell CSS 문자열을 보유 |
29
+ | `src/react-shell/review/shell.tsx` | 2,306 | `ReviewShell` 단일 컴포넌트에 상태/핸들러/렌더가 많이 남음 |
30
+ | `src/core/web.review.kit.view.ts` | 1,937 | overlay, scroll, draft, anchor 책임이 한 클래스에 집중 |
31
+ | `src/core/overlay.style.ts` | 1,025 | core overlay CSS 덩어리 |
32
+ | `src/core/dom.anchor.ts` | 818 | anchor 후보/매칭 로직 집중 |
33
+ | `src/react-shell/source.open.ts` | 785 | Source Tree 기능 확장으로 커짐 |
34
+
35
+ ### 이번 브랜치에서 처리한 부분
36
+
37
+ `src/react-shell/style.ts`를 작은 조립 파일로 줄이고, CSS는 역할별 파일로 이동했다.
38
+
39
+ | 파일 | 역할 | 현재 LOC |
40
+ |---|---|---:|
41
+ | `src/react-shell/style.ts` | style tag 주입 + chunk 조립 | 26 |
42
+ | `src/react-shell/style/base.ts` | token, reset, topbar 공통 | 401 |
43
+ | `src/react-shell/style/sitemap.ts` | sitemap modal/table | 317 |
44
+ | `src/react-shell/style/modals.ts` | settings/edit/prompt modal | 606 |
45
+ | `src/react-shell/style/toolbar.ts` | tools, side rail, presence | 530 |
46
+ | `src/react-shell/style/qa-panel.ts` | QA panel/list/card/actions | 722 |
47
+ | `src/react-shell/style/stage.ts` | stage/frame/target/device | 138 |
48
+ | `src/react-shell/style/source-inspector.ts` | source outline/popover/candidate | 153 |
49
+ | `src/react-shell/style/section-outline.ts` | section outline panel | 539 |
50
+ | `src/react-shell/style/ruler.ts` | ruler + responsive media query | 262 |
51
+
52
+ 동작 변화 없이 문자열을 나눠서 `ensureReviewShellStyle()`에서 concat만 하도록 정리했다.
53
+
54
+ > 0.7.0 후속(아래 6장)에서 `stage.ts`를 `stage` / `source-inspector` / `section-outline` 3개로 더 쪼갰다.
55
+
56
+ ---
57
+
58
+ ## 2. 죽은 코드 / 죽은 export 정리
59
+
60
+ `knip` 결과를 기준으로 public entry false positive와 실제 내부 전용 helper를 구분했다.
61
+
62
+ ### 삭제한 실제 미사용 코드
63
+
64
+ | 파일 | 삭제 내용 |
65
+ |---|---|
66
+ | `src/react-shell/prompt/prompt.ts` | 미사용 `formatItemMeta`, `formatDate` 삭제 |
67
+ | `src/react-shell/source.open.ts` | 미사용 `SOURCE_SELECTOR`, `getSourceHintElement`, `getElementSourceHint` 삭제 |
68
+ | `src/core/geometry.ts` | 미사용 `getAreaPopoverPosition` 삭제 |
69
+ | `src/core/location.ts` | 미사용 alias `getNormalizedPath` 삭제 |
70
+ | `src/core/review/format.ts` | 미사용 `formatAreaDraftMeta` 삭제 |
71
+
72
+ ### export만 제거하고 내부 helper로 남긴 것
73
+
74
+ 아래 항목은 같은 파일 내부에서는 쓰이지만 외부 export가 필요 없던 helper다. 삭제하지 않고 `export`만 제거했다.
75
+
76
+ - `src/react-shell/anchor.restore.ts`
77
+ - `getReviewItemExpectedDocumentRect`
78
+ - `getReviewAnchorMatchScore`
79
+ - `getElementDocumentRect`
80
+ - `getReviewTextFingerprintScore`
81
+ - `getReviewFingerprintTokens`
82
+ - `isScrollableReviewAnchorElement`
83
+ - `src/react-shell/prompt/prompt.ts`
84
+ - `formatPromptViewport`
85
+ - `formatPromptPoint`
86
+ - `formatPromptSelection`
87
+ - `decodePromptHtmlEntities`
88
+ - `getPromptAnchorCandidates`
89
+ - `formatPromptSourceHint`
90
+ - `src/core/geometry.ts`
91
+ - `rectanglesIntersect`
92
+ - `getPopoverBounds`
93
+ - `src/react-shell/route.ts`
94
+ - `normalizeReviewPathPrefix`
95
+ - `getHashRoutePath`
96
+ - `src/react-shell/settings.ts`
97
+ - `normalizeStoredReviewSidePanel`
98
+ - `normalizeStoredReviewQaStatusFilter`
99
+ - `src/react-shell/viewport.ts`
100
+ - `getFallbackPreset`
101
+ - `getViewportPresetDistance`
102
+ - `src/react-shell/target/target.ts`
103
+ - `HIDE_SCROLLBAR_STYLE_ID`
104
+ - `FIGMA_POINTER_LOCK_STYLE_ID`
105
+ - `src/react-shell/review/shell.actions.ts`
106
+ - `listReviewItems`
107
+ - `listSitemapReviewItems`
108
+ - 내부 타입 export 축소
109
+ - `src/core/review/draft.ts`
110
+ - `src/react-shell/sitemap/tree.ts`
111
+ - `src/react-shell/source.open.ts`
112
+ - `src/react-shell/types.ts`
113
+
114
+ ---
115
+
116
+ ## 3. Dead-code 검사 설정
117
+
118
+ 추가 파일:
119
+
120
+ - `knip.json`
121
+
122
+ 추가 스크립트:
123
+
124
+ ```json
125
+ "lint:dead-code": "knip"
126
+ ```
127
+
128
+ 설정 의도:
129
+
130
+ - public entry는 `src/index.ts`, `src/react-shell.tsx`, `src/vite.ts`로 명시
131
+ - 검사 대상은 `src/**/*.{ts,tsx}`로 제한
132
+ - `dist/**`와 `docs/adaptor.sample.ts` 같은 빌드 산출물/샘플은 검사 대상 밖에 둬서 false positive를 막음
133
+
134
+ 현재 `pnpm lint:dead-code`는 이 설정으로 0 issue 통과한다.
135
+
136
+ ---
137
+
138
+ ## 4. 아직 남은 큰 덩어리
139
+
140
+ 이번 브랜치에서는 위험도가 낮은 style split + dead-code cleanup까지만 적용했다. 아래는 별도 브랜치에서 작은 단위로 나누는 게 안전하다.
141
+
142
+ ### `src/react-shell/review/shell.tsx` (2,306 LOC)
143
+
144
+ 남은 책임:
145
+
146
+ - source panel resize / delayed close
147
+ - source candidate popover state
148
+ - section outline filtering / expand state
149
+ - prompt modal copy/open 흐름
150
+ - QA list selection + card action bridge
151
+
152
+ 추천 다음 작업:
153
+
154
+ 1. `use.source.panel.ts`
155
+ 2. `use.section.outline.ts`
156
+ 3. `use.prompt.modal.ts`
157
+ 4. `use.qa.selection.ts`
158
+
159
+ `ReviewShell`은 layout assembly + child component wiring 정도로 줄이는 게 목표다.
160
+
161
+ ### `src/core/web.review.kit.view.ts` (1,937 LOC)
162
+
163
+ 남은 책임:
164
+
165
+ - overlay 렌더링
166
+ - scroll sync
167
+ - draft 작성/취소/저장
168
+ - anchor restore
169
+ - marker lifecycle
170
+
171
+ 추천 다음 작업:
172
+
173
+ 1. draft controller 분리
174
+ 2. marker renderer 분리
175
+ 3. scroll/restore orchestration 분리
176
+ 4. view class는 조립/라이프사이클만 담당
177
+
178
+ ---
179
+
180
+ ## 5. 검증
181
+
182
+ 실행한 명령:
183
+
184
+ ```bash
185
+ pnpm lint:dead-code
186
+ pnpm typecheck
187
+ pnpm typecheck:dev
188
+ pnpm build
189
+ ```
190
+
191
+ 결과:
192
+
193
+ - `pnpm lint:dead-code` 통과
194
+ - `pnpm typecheck` 통과
195
+ - `pnpm typecheck:dev` 통과
196
+ - `pnpm build` 통과
197
+
198
+ `pnpm build` 결과로 `dist/**` 산출물이 갱신됐고, 기존 chunk hash는 `chunk-IN36JHEU` → `chunk-BDP7FS4Q`로 변경됐다.
199
+
200
+ ---
201
+
202
+ ## 6. 0.7.0 후속 정리 (continued)
203
+
204
+ 같은 브랜치에서 Figma image 기능이 추가된 뒤, 위험도 낮은 정리를 한 단계 더 진행했다. 모두 동작/공개 API 변화 없는 내부 리팩터이며 단계별 커밋으로 나눴다.
205
+
206
+ ### CSS
207
+
208
+ - `shell.tsx`에 인라인으로 박혀 있던 source-select 단축키 CSS(65줄)를 `react-shell/review/source.shortcut.style.ts`로 추출 (`createSourceShortcutStyle`).
209
+ - 비대했던 `style/stage.ts`(827줄)를 도메인 기준으로 3분할: `stage.ts`(138) / `source-inspector.ts`(153) / `section-outline.ts`(539). 실제 내용이 stage가 아니라 section/source 위주였던 점을 반영.
210
+
211
+ ### 네이밍 (내부 한정, export 무변경)
212
+
213
+ - `dom.anchor.ts`: `primary` → `primaryCandidate`, `safeClosest` → `tryClosest`.
214
+ - `normalizeStoredReviewSidePanel` 등 정상 normalizer는 형제 함수와의 일관성 때문에 유지.
215
+
216
+ ### 주석
217
+
218
+ - `dom.anchor.ts`의 경로 생성(`getDomPath`/`getDomPathBetween`)·스코어링(`getTextFingerprintScore`/`getSelectionMatchScore`) 알고리즘, `view.ts`의 큰 메서드(`createNotePopover`/`createAdjustmentControls`), 매직 넘버(z-index `2147483646`, 길이 제한 160/120, `roundRatio` 정밀도) 설명 추가.
219
+
220
+ ### 파일 분할 (안전 범위)
221
+
222
+ | 원본 | 추출 | 결과 |
223
+ |---|---|---|
224
+ | `vite.ts` (1492) | `vite/figma-asset.ts` (포맷/경로 헬퍼 10개) | 1424 |
225
+ | `core/web.review.kit.view.ts` (1937) | `core/draft.metrics.ts` (draft adjustment 순수 geometry) | 1901 |
226
+ | `react-shell/figma/images.panel.tsx` (737) | `figma/image-panel.utils.ts` (순수 유틸 8개) | 669 |
227
+
228
+ §4의 `web.review.kit.view.ts` 항목 중 "draft controller 분리"의 첫 조각으로 순수 geometry부터 뽑았다. `shell.tsx`의 대규모 hook 분리와 `images.panel`의 `ImageCard` 컴포넌트 분리(drag 상태·ref 결합으로 회귀 위험 큼)는 여전히 후속 작업으로 남겨 둔다.
229
+
230
+ ### 검증
231
+
232
+ 각 단계마다 `pnpm typecheck` 통과, 추출 단계는 `pnpm lint:dead-code`(knip) 0 issue, 최종 `pnpm build` 통과 확인.