@designfever/web-review-kit 0.4.1 → 0.6.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/README.md +20 -4
- package/dist/{chunk-6L2KJ7XL.js → chunk-IN36JHEU.js} +309 -59
- package/dist/chunk-IN36JHEU.js.map +1 -0
- package/dist/index.cjs +306 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/react-shell.cjs +3329 -907
- package/dist/react-shell.cjs.map +1 -1
- package/dist/react-shell.d.cts +19 -1
- package/dist/react-shell.d.ts +19 -1
- package/dist/react-shell.js +2771 -595
- package/dist/react-shell.js.map +1 -1
- package/dist/{types-D_qYtwTs.d.cts → types-DFHHVRBc.d.cts} +2 -0
- package/dist/{types-D_qYtwTs.d.ts → types-DFHHVRBc.d.ts} +2 -0
- package/dist/vite.cjs +50 -0
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.d.cts +17 -1
- package/dist/vite.d.ts +17 -1
- package/dist/vite.js +49 -0
- package/dist/vite.js.map +1 -1
- package/docs/README.md +2 -0
- package/docs/architecture.md +4 -0
- package/docs/installation.md +21 -3
- package/docs/release-notes-0.5.0.md +134 -0
- package/docs/release-notes-0.6.0.md +108 -0
- package/package.json +1 -1
- package/dist/chunk-6L2KJ7XL.js.map +0 -1
|
@@ -163,6 +163,7 @@ interface WebReviewKitController {
|
|
|
163
163
|
close(): void;
|
|
164
164
|
toggle(): void;
|
|
165
165
|
setMode(mode: ReviewMode): void;
|
|
166
|
+
startElementReview(element: Element, comment?: string): Promise<void>;
|
|
166
167
|
getMode(): ReviewMode;
|
|
167
168
|
highlightItem(itemId?: string): void;
|
|
168
169
|
setHiddenItemIds(itemIds?: string[]): void;
|
|
@@ -175,6 +176,7 @@ interface WebReviewKitTarget {
|
|
|
175
176
|
document: Document;
|
|
176
177
|
getViewportRect?: () => Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>;
|
|
177
178
|
getOverlayRect?: () => Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>;
|
|
179
|
+
getComposerHost?: () => HTMLElement | null | undefined;
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
export type { DomAnchor as D, LocalAdapterOptions as L, NumberedReviewItem as N, ReviewWorkflowStatus as R, SupabaseReviewAdapterOptions as S, ViewportSize as V, WebReviewKitAdapter as W, ReviewItemStatus as a, WebReviewKitOptions as b, WebReviewKitController as c, ReviewViewportPreset as d, ReviewItem as e, ReviewItemScope as f, DomAnchorCandidate as g, DomAnchorStrategy as h, DomSourceHint as i, RelativeSelection as j, ReviewItemKind as k, ReviewItemQuery as l, ReviewMarker as m, ReviewMode as n, ReviewPoint as o, ReviewRulerConfig as p, ReviewSelection as q, ReviewSource as r, ReviewSubmitStatus as s, ReviewViewportScope as t, SupabaseReviewClient as u, WebReviewKitTarget as v };
|
|
@@ -163,6 +163,7 @@ interface WebReviewKitController {
|
|
|
163
163
|
close(): void;
|
|
164
164
|
toggle(): void;
|
|
165
165
|
setMode(mode: ReviewMode): void;
|
|
166
|
+
startElementReview(element: Element, comment?: string): Promise<void>;
|
|
166
167
|
getMode(): ReviewMode;
|
|
167
168
|
highlightItem(itemId?: string): void;
|
|
168
169
|
setHiddenItemIds(itemIds?: string[]): void;
|
|
@@ -175,6 +176,7 @@ interface WebReviewKitTarget {
|
|
|
175
176
|
document: Document;
|
|
176
177
|
getViewportRect?: () => Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>;
|
|
177
178
|
getOverlayRect?: () => Pick<DOMRect, 'left' | 'top' | 'width' | 'height'>;
|
|
179
|
+
getComposerHost?: () => HTMLElement | null | undefined;
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
export type { DomAnchor as D, LocalAdapterOptions as L, NumberedReviewItem as N, ReviewWorkflowStatus as R, SupabaseReviewAdapterOptions as S, ViewportSize as V, WebReviewKitAdapter as W, ReviewItemStatus as a, WebReviewKitOptions as b, WebReviewKitController as c, ReviewViewportPreset as d, ReviewItem as e, ReviewItemScope as f, DomAnchorCandidate as g, DomAnchorStrategy as h, DomSourceHint as i, RelativeSelection as j, ReviewItemKind as k, ReviewItemQuery as l, ReviewMarker as m, ReviewMode as n, ReviewPoint as o, ReviewRulerConfig as p, ReviewSelection as q, ReviewSource as r, ReviewSubmitStatus as s, ReviewViewportScope as t, SupabaseReviewClient as u, WebReviewKitTarget as v };
|
package/dist/vite.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/vite.ts
|
|
21
21
|
var vite_exports = {};
|
|
22
22
|
__export(vite_exports, {
|
|
23
|
+
reviewDataLocator: () => reviewDataLocator,
|
|
23
24
|
reviewSourceLocator: () => reviewSourceLocator
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(vite_exports);
|
|
@@ -44,6 +45,54 @@ var reviewSourceLocator = (options = {}) => {
|
|
|
44
45
|
}
|
|
45
46
|
};
|
|
46
47
|
};
|
|
48
|
+
var reviewDataLocator = (options = {}) => {
|
|
49
|
+
let root = normalizePath(options.root ?? "");
|
|
50
|
+
let enabled = options.enabled ?? false;
|
|
51
|
+
const include = (options.include ?? []).map(createRuntimeMatcher);
|
|
52
|
+
const exclude = (options.exclude ?? ["node_modules", "dist"]).map(
|
|
53
|
+
createRuntimeMatcher
|
|
54
|
+
);
|
|
55
|
+
const componentPattern = options.componentPattern ?? /Section[A-Za-z0-9_]*/;
|
|
56
|
+
const fileKey = options.fileAttribute ?? "__wrkDataFile";
|
|
57
|
+
const lineKey = options.lineAttribute ?? "__wrkDataLine";
|
|
58
|
+
const componentSource = `(^|[\\n,{(\\[]\\s*)(component:\\s*)(['"\`])(${componentPattern.source})\\3`;
|
|
59
|
+
return {
|
|
60
|
+
name: "df-web-review-kit-data-locator",
|
|
61
|
+
enforce: "pre",
|
|
62
|
+
configResolved(config) {
|
|
63
|
+
root = normalizePath(options.root ?? config.root ?? "");
|
|
64
|
+
enabled = options.enabled ?? config.command === "serve";
|
|
65
|
+
},
|
|
66
|
+
transform(code, id) {
|
|
67
|
+
if (!enabled) return null;
|
|
68
|
+
const file = normalizePath(id.split("?")[0]);
|
|
69
|
+
const relativeFile = root && file.startsWith(root + "/") ? file.slice(root.length + 1) : file;
|
|
70
|
+
if (include.length > 0 && !include.some((m) => matchesPath(m, file, relativeFile)))
|
|
71
|
+
return null;
|
|
72
|
+
if (exclude.some((m) => matchesPath(m, file, relativeFile))) return null;
|
|
73
|
+
const sourceFile = (options.filePath ?? "relative") === "absolute" ? file : relativeFile;
|
|
74
|
+
const regex = new RegExp(componentSource, "g");
|
|
75
|
+
let changed = false;
|
|
76
|
+
const out = code.replace(
|
|
77
|
+
regex,
|
|
78
|
+
(_match, pre, comp, quote, name, offset) => {
|
|
79
|
+
const line = code.slice(0, offset + pre.length).split("\n").length;
|
|
80
|
+
changed = true;
|
|
81
|
+
return `${pre}${JSON.stringify(fileKey)}: ${JSON.stringify(sourceFile)}, ${JSON.stringify(lineKey)}: ${line}, ${comp}${quote}${name}${quote}`;
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
return changed ? { code: out, map: null } : null;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
function matchesPath(matcher, absoluteFile, relativeFile) {
|
|
89
|
+
if (matcher.type === "regex") {
|
|
90
|
+
const regex = new RegExp(matcher.value, matcher.flags);
|
|
91
|
+
return regex.test(absoluteFile) || regex.test(relativeFile);
|
|
92
|
+
}
|
|
93
|
+
const target = matcher.value.startsWith("/") ? absoluteFile : relativeFile;
|
|
94
|
+
return target === matcher.value || target.startsWith(matcher.value + "/") || target.includes("/" + matcher.value);
|
|
95
|
+
}
|
|
47
96
|
function createRuntimeOptions(options, config) {
|
|
48
97
|
const attributePrefix = (options.attributePrefix ?? "data-wrk-source").replace(
|
|
49
98
|
/-+$/,
|
|
@@ -181,6 +230,7 @@ function normalizePath(value) {
|
|
|
181
230
|
}
|
|
182
231
|
// Annotate the CommonJS export names for ESM import in node:
|
|
183
232
|
0 && (module.exports = {
|
|
233
|
+
reviewDataLocator,
|
|
184
234
|
reviewSourceLocator
|
|
185
235
|
});
|
|
186
236
|
//# sourceMappingURL=vite.cjs.map
|
package/dist/vite.cjs.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\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCA,IAAM,6BACJ;AAEK,IAAM,sBAAsB,CACjC,UAAsC,CAAC,MAC5B;AACX,MAAI,iBAAiB,qBAAqB,OAAO;AAEjD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAQ;AACrB,uBAAiB,qBAAqB,SAAS,MAAM;AAAA,IACvD;AAAA,IACA,UAAU,IAAI,UAAU;AACtB,UAAI,CAAC,eAAe,QAAS,QAAO;AACpC,UAAI,OAAO,wBAAyB,QAAO;AAC3C,UAAI,aAAa,2BAA4B,QAAO;AAEpD,aAAO;AAAA,IACT;AAAA,IACA,KAAK,IAAI;AACP,UAAI,OAAO,2BAA4B,QAAO;AAC9C,aAAO,oBAAoB,cAAc;AAAA,IAC3C;AAAA,EACF;AACF;AAEA,SAAS,qBACP,SACA,QACgB;AAChB,QAAM,mBAAmB,QAAQ,mBAAmB,mBAAmB;AAAA,IACrE;AAAA,IACA;AAAA,EACF;AACA,QAAM,OAAO,cAAc,QAAQ,QAAQ,QAAQ,QAAQ,EAAE;AAC7D,QAAM,UAAU,QAAQ,WAAY,QAAQ,YAAY;AAExD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,QAAQ,WAAW,CAAC,GAAG,IAAI,oBAAoB;AAAA,IACzD,UAAU,QAAQ,WAAW,CAAC,gBAAgB,MAAM,GAAG;AAAA,MACrD;AAAA,IACF;AAAA,IACA,UAAU,QAAQ,YAAY;AAAA,IAC9B,MAAM,QAAQ,QAAQ;AAAA,IACtB,QAAQ,QAAQ,UAAU;AAAA,IAC1B,eAAe,GAAG,eAAe;AAAA,IACjC,eAAe,GAAG,eAAe;AAAA,IACjC,iBAAiB,GAAG,eAAe;AAAA,EACrC;AACF;AAEA,SAAS,qBAAqB,SAA+C;AAC3E,MAAI,mBAAmB,QAAQ;AAC7B,WAAO,EAAE,MAAM,SAAS,OAAO,QAAQ,QAAQ,OAAO,QAAQ,MAAM;AAAA,EACtE;AAEA,SAAO,EAAE,MAAM,QAAQ,OAAO,cAAc,OAAO,EAAE,QAAQ,SAAS,EAAE,EAAE;AAC5E;AAEA,SAAS,cAAc,OAAe;AACpC,SAAO,MAAM,QAAQ,OAAO,GAAG,EAAE,QAAQ,QAAQ,EAAE;AACrD;AAEA,SAAS,oBAAoB,SAAyB;AACpD,SAAO;AAAA;AAAA;AAAA,kBAGS,KAAK,UAAU,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmGzC;","names":[]}
|
|
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":[]}
|
package/dist/vite.d.cts
CHANGED
|
@@ -12,5 +12,21 @@ interface ReviewSourceLocatorOptions {
|
|
|
12
12
|
attributePrefix?: string;
|
|
13
13
|
}
|
|
14
14
|
declare const reviewSourceLocator: (options?: ReviewSourceLocatorOptions) => Plugin;
|
|
15
|
+
interface ReviewDataLocatorOptions {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
root?: string;
|
|
18
|
+
include?: readonly SourceLocatorPattern[];
|
|
19
|
+
exclude?: readonly SourceLocatorPattern[];
|
|
20
|
+
filePath?: 'relative' | 'absolute';
|
|
21
|
+
/** 매칭할 component 이름 패턴. 기본은 `Section`으로 시작하는 이름. */
|
|
22
|
+
componentPattern?: RegExp;
|
|
23
|
+
fileAttribute?: string;
|
|
24
|
+
lineAttribute?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* page data 파일의 section 객체(`component: 'SectionXxx'`)에 출처 파일/라인을
|
|
28
|
+
* `__wrkDataFile`/`__wrkDataLine` prop 으로 주입한다. 라인 보존을 위해 같은 줄에만 삽입한다.
|
|
29
|
+
*/
|
|
30
|
+
declare const reviewDataLocator: (options?: ReviewDataLocatorOptions) => Plugin;
|
|
15
31
|
|
|
16
|
-
export { type ReviewSourceLocatorOptions, reviewSourceLocator };
|
|
32
|
+
export { type ReviewDataLocatorOptions, type ReviewSourceLocatorOptions, reviewDataLocator, reviewSourceLocator };
|
package/dist/vite.d.ts
CHANGED
|
@@ -12,5 +12,21 @@ interface ReviewSourceLocatorOptions {
|
|
|
12
12
|
attributePrefix?: string;
|
|
13
13
|
}
|
|
14
14
|
declare const reviewSourceLocator: (options?: ReviewSourceLocatorOptions) => Plugin;
|
|
15
|
+
interface ReviewDataLocatorOptions {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
root?: string;
|
|
18
|
+
include?: readonly SourceLocatorPattern[];
|
|
19
|
+
exclude?: readonly SourceLocatorPattern[];
|
|
20
|
+
filePath?: 'relative' | 'absolute';
|
|
21
|
+
/** 매칭할 component 이름 패턴. 기본은 `Section`으로 시작하는 이름. */
|
|
22
|
+
componentPattern?: RegExp;
|
|
23
|
+
fileAttribute?: string;
|
|
24
|
+
lineAttribute?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* page data 파일의 section 객체(`component: 'SectionXxx'`)에 출처 파일/라인을
|
|
28
|
+
* `__wrkDataFile`/`__wrkDataLine` prop 으로 주입한다. 라인 보존을 위해 같은 줄에만 삽입한다.
|
|
29
|
+
*/
|
|
30
|
+
declare const reviewDataLocator: (options?: ReviewDataLocatorOptions) => Plugin;
|
|
15
31
|
|
|
16
|
-
export { type ReviewSourceLocatorOptions, reviewSourceLocator };
|
|
32
|
+
export { type ReviewDataLocatorOptions, type ReviewSourceLocatorOptions, reviewDataLocator, reviewSourceLocator };
|
package/dist/vite.js
CHANGED
|
@@ -20,6 +20,54 @@ var reviewSourceLocator = (options = {}) => {
|
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
22
|
};
|
|
23
|
+
var reviewDataLocator = (options = {}) => {
|
|
24
|
+
let root = normalizePath(options.root ?? "");
|
|
25
|
+
let enabled = options.enabled ?? false;
|
|
26
|
+
const include = (options.include ?? []).map(createRuntimeMatcher);
|
|
27
|
+
const exclude = (options.exclude ?? ["node_modules", "dist"]).map(
|
|
28
|
+
createRuntimeMatcher
|
|
29
|
+
);
|
|
30
|
+
const componentPattern = options.componentPattern ?? /Section[A-Za-z0-9_]*/;
|
|
31
|
+
const fileKey = options.fileAttribute ?? "__wrkDataFile";
|
|
32
|
+
const lineKey = options.lineAttribute ?? "__wrkDataLine";
|
|
33
|
+
const componentSource = `(^|[\\n,{(\\[]\\s*)(component:\\s*)(['"\`])(${componentPattern.source})\\3`;
|
|
34
|
+
return {
|
|
35
|
+
name: "df-web-review-kit-data-locator",
|
|
36
|
+
enforce: "pre",
|
|
37
|
+
configResolved(config) {
|
|
38
|
+
root = normalizePath(options.root ?? config.root ?? "");
|
|
39
|
+
enabled = options.enabled ?? config.command === "serve";
|
|
40
|
+
},
|
|
41
|
+
transform(code, id) {
|
|
42
|
+
if (!enabled) return null;
|
|
43
|
+
const file = normalizePath(id.split("?")[0]);
|
|
44
|
+
const relativeFile = root && file.startsWith(root + "/") ? file.slice(root.length + 1) : file;
|
|
45
|
+
if (include.length > 0 && !include.some((m) => matchesPath(m, file, relativeFile)))
|
|
46
|
+
return null;
|
|
47
|
+
if (exclude.some((m) => matchesPath(m, file, relativeFile))) return null;
|
|
48
|
+
const sourceFile = (options.filePath ?? "relative") === "absolute" ? file : relativeFile;
|
|
49
|
+
const regex = new RegExp(componentSource, "g");
|
|
50
|
+
let changed = false;
|
|
51
|
+
const out = code.replace(
|
|
52
|
+
regex,
|
|
53
|
+
(_match, pre, comp, quote, name, offset) => {
|
|
54
|
+
const line = code.slice(0, offset + pre.length).split("\n").length;
|
|
55
|
+
changed = true;
|
|
56
|
+
return `${pre}${JSON.stringify(fileKey)}: ${JSON.stringify(sourceFile)}, ${JSON.stringify(lineKey)}: ${line}, ${comp}${quote}${name}${quote}`;
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
return changed ? { code: out, map: null } : null;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
function matchesPath(matcher, absoluteFile, relativeFile) {
|
|
64
|
+
if (matcher.type === "regex") {
|
|
65
|
+
const regex = new RegExp(matcher.value, matcher.flags);
|
|
66
|
+
return regex.test(absoluteFile) || regex.test(relativeFile);
|
|
67
|
+
}
|
|
68
|
+
const target = matcher.value.startsWith("/") ? absoluteFile : relativeFile;
|
|
69
|
+
return target === matcher.value || target.startsWith(matcher.value + "/") || target.includes("/" + matcher.value);
|
|
70
|
+
}
|
|
23
71
|
function createRuntimeOptions(options, config) {
|
|
24
72
|
const attributePrefix = (options.attributePrefix ?? "data-wrk-source").replace(
|
|
25
73
|
/-+$/,
|
|
@@ -156,6 +204,7 @@ function normalizePath(value) {
|
|
|
156
204
|
`;
|
|
157
205
|
}
|
|
158
206
|
export {
|
|
207
|
+
reviewDataLocator,
|
|
159
208
|
reviewSourceLocator
|
|
160
209
|
};
|
|
161
210
|
//# sourceMappingURL=vite.js.map
|
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\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":";AAgCA,IAAM,6BACJ;AAEK,IAAM,sBAAsB,CACjC,UAAsC,CAAC,MAC5B;AACX,MAAI,iBAAiB,qBAAqB,OAAO;AAEjD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAQ;AACrB,uBAAiB,qBAAqB,SAAS,MAAM;AAAA,IACvD;AAAA,IACA,UAAU,IAAI,UAAU;AACtB,UAAI,CAAC,eAAe,QAAS,QAAO;AACpC,UAAI,OAAO,wBAAyB,QAAO;AAC3C,UAAI,aAAa,2BAA4B,QAAO;AAEpD,aAAO;AAAA,IACT;AAAA,IACA,KAAK,IAAI;AACP,UAAI,OAAO,2BAA4B,QAAO;AAC9C,aAAO,oBAAoB,cAAc;AAAA,IAC3C;AAAA,EACF;AACF;AAEA,SAAS,qBACP,SACA,QACgB;AAChB,QAAM,mBAAmB,QAAQ,mBAAmB,mBAAmB;AAAA,IACrE;AAAA,IACA;AAAA,EACF;AACA,QAAM,OAAO,cAAc,QAAQ,QAAQ,QAAQ,QAAQ,EAAE;AAC7D,QAAM,UAAU,QAAQ,WAAY,QAAQ,YAAY;AAExD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,QAAQ,WAAW,CAAC,GAAG,IAAI,oBAAoB;AAAA,IACzD,UAAU,QAAQ,WAAW,CAAC,gBAAgB,MAAM,GAAG;AAAA,MACrD;AAAA,IACF;AAAA,IACA,UAAU,QAAQ,YAAY;AAAA,IAC9B,MAAM,QAAQ,QAAQ;AAAA,IACtB,QAAQ,QAAQ,UAAU;AAAA,IAC1B,eAAe,GAAG,eAAe;AAAA,IACjC,eAAe,GAAG,eAAe;AAAA,IACjC,iBAAiB,GAAG,eAAe;AAAA,EACrC;AACF;AAEA,SAAS,qBAAqB,SAA+C;AAC3E,MAAI,mBAAmB,QAAQ;AAC7B,WAAO,EAAE,MAAM,SAAS,OAAO,QAAQ,QAAQ,OAAO,QAAQ,MAAM;AAAA,EACtE;AAEA,SAAO,EAAE,MAAM,QAAQ,OAAO,cAAc,OAAO,EAAE,QAAQ,SAAS,EAAE,EAAE;AAC5E;AAEA,SAAS,cAAc,OAAe;AACpC,SAAO,MAAM,QAAQ,OAAO,GAAG,EAAE,QAAQ,QAAQ,EAAE;AACrD;AAEA,SAAS,oBAAoB,SAAyB;AACpD,SAAO;AAAA;AAAA;AAAA,kBAGS,KAAK,UAAU,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmGzC;","names":[]}
|
|
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":[]}
|
package/docs/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Public docs are intentionally small. Keep implementation history, handoff notes,
|
|
|
10
10
|
4. [Architecture and runtime logic](architecture.md)
|
|
11
11
|
5. [Figma overlay](figma-overlay.md)
|
|
12
12
|
6. [Grid overlay](grid-overlay.md)
|
|
13
|
+
7. [Release notes 0.6.0](release-notes-0.6.0.md)
|
|
13
14
|
|
|
14
15
|
## Document Roles
|
|
15
16
|
|
|
@@ -19,6 +20,7 @@ Public docs are intentionally small. Keep implementation history, handoff notes,
|
|
|
19
20
|
- `architecture.md`: core/runtime, React shell, coordinate, anchor, and feature boundary notes.
|
|
20
21
|
- `figma-overlay.md`: host requirements for the Figma overlay toggle.
|
|
21
22
|
- `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.
|
|
22
24
|
|
|
23
25
|
## Boundary
|
|
24
26
|
|
package/docs/architecture.md
CHANGED
|
@@ -35,6 +35,8 @@ createWebReviewKit({
|
|
|
35
35
|
|
|
36
36
|
`ui.panel: false` means the React shell owns the side panel and toolbar. Core still owns target overlays such as note pins, area selection boxes, DOM hover outlines, saved item markers, and highlights.
|
|
37
37
|
|
|
38
|
+
When the React shell provides a composer host, core docks DOM/area draft composer UI into the QA panel instead of rendering it as a floating target overlay. Core still owns draft creation, anchor capture, geometry, and adapter submission; React shell only provides the stable panel host.
|
|
39
|
+
|
|
38
40
|
## Core Modules
|
|
39
41
|
|
|
40
42
|
- `web.review.kit.app.ts`: controller lifecycle, state transitions, adapter calls, item creation, restore flow.
|
|
@@ -95,6 +97,8 @@ The default local adapter is for draft/local review work. Supabase is optional h
|
|
|
95
97
|
- ruler UI
|
|
96
98
|
- presence UI
|
|
97
99
|
- host overlay toggles such as grid and Figma
|
|
100
|
+
- Source Tree UI, metadata toggles, and browser-local UI state persistence
|
|
101
|
+
- QA panel composer host for shell-owned layout
|
|
98
102
|
|
|
99
103
|
React shell should call the core controller instead of duplicating target overlay logic.
|
|
100
104
|
|
package/docs/installation.md
CHANGED
|
@@ -195,7 +195,10 @@ Host projects can opt into source file hints for local QA. The Vite plugin wraps
|
|
|
195
195
|
|
|
196
196
|
```ts
|
|
197
197
|
import { defineConfig } from 'vite';
|
|
198
|
-
import {
|
|
198
|
+
import {
|
|
199
|
+
reviewDataLocator,
|
|
200
|
+
reviewSourceLocator,
|
|
201
|
+
} from '@designfever/web-review-kit/vite';
|
|
199
202
|
|
|
200
203
|
export default defineConfig({
|
|
201
204
|
plugins: [
|
|
@@ -204,11 +207,18 @@ export default defineConfig({
|
|
|
204
207
|
include: ['src'],
|
|
205
208
|
filePath: 'absolute',
|
|
206
209
|
}),
|
|
210
|
+
reviewDataLocator({
|
|
211
|
+
enabled: true,
|
|
212
|
+
include: ['src/data'],
|
|
213
|
+
filePath: 'absolute',
|
|
214
|
+
}),
|
|
207
215
|
],
|
|
208
216
|
});
|
|
209
217
|
```
|
|
210
218
|
|
|
211
|
-
Captured DOM nodes will include `data-wrk-source-file`, `data-wrk-source-line`, and `data-wrk-source-column`.
|
|
219
|
+
Captured DOM nodes will include `data-wrk-source-file`, `data-wrk-source-line`, and `data-wrk-source-column`. Data locator also injects `__wrkDataFile` and `__wrkDataLine` props into page data section objects so the shell can expose matching `data-wrk-data-*` hints when host components forward those props to section wrappers.
|
|
220
|
+
|
|
221
|
+
In the review shell, hold `Option` over the target iframe to show source candidates from the DOM ancestry. Click the target to pin the candidate list, then choose which file to open. The side rail Source Tree panel lists section/source/data candidates and can scroll to a section or open its source/data file. It can also show live box metrics, text/font metadata, media URLs, and class tags for each node. If the file path is absolute, it opens directly. If the plugin stores relative paths, pass `sourceRoot` when mounting the shell.
|
|
212
222
|
|
|
213
223
|
```tsx
|
|
214
224
|
mountReviewShell({
|
|
@@ -219,15 +229,23 @@ mountReviewShell({
|
|
|
219
229
|
sourceRoot: import.meta.env.VITE_REVIEW_SOURCE_ROOT,
|
|
220
230
|
sourceInspector: {
|
|
221
231
|
editor: 'vscode', // 'vscode' | 'cursor' | 'webstorm' | 'custom'
|
|
232
|
+
maxDepth: 9,
|
|
233
|
+
hoverOutline: true,
|
|
234
|
+
includePlacer: false,
|
|
235
|
+
ignore: ['core.section', 'control.render'],
|
|
222
236
|
// urlTemplate: 'my-editor://open?file={encodedPath}&line={line}&column={column}',
|
|
223
237
|
},
|
|
224
238
|
});
|
|
225
239
|
```
|
|
226
240
|
|
|
227
|
-
Set `sourceInspector.enabled` to `false` when source code opening should be unavailable. The `data-font` overlay still belongs to the target project markup and is not required for source opening.
|
|
241
|
+
Set `sourceInspector.enabled` to `false` when source code opening should be unavailable. Use `sourceInspector.ignore` to hide infrastructure files from source candidates and the Source Tree. Use `sourceInspector.maxDepth` to cap Source Tree traversal depth. Set `sourceInspector.hoverOutline` to `false` to disable iframe target outlines while hovering Source Tree items. Set `sourceInspector.includePlacer` to `true` when primitive Placer nodes should appear in Source Tree. The `data-font` overlay still belongs to the target project markup and is not required for source opening.
|
|
228
242
|
|
|
229
243
|
Use this only in dev/review builds. Source paths are written into the browser DOM and can be persisted with QA items.
|
|
230
244
|
|
|
245
|
+
Source Tree filter/options, side panel state, and QA status filter are browser-local UI preferences. They are stored in localStorage and are not sent through the adapter.
|
|
246
|
+
|
|
247
|
+
In React shell mode, DOM/area draft composers dock into the QA panel when the shell provides a composer host. This keeps mobile Safari keyboard/viewport resize from moving a floating composer layer over the target frame.
|
|
248
|
+
|
|
231
249
|
## Custom Adapter
|
|
232
250
|
|
|
233
251
|
If a team or host project owns its own QA backend, keep that adapter in the host project or in a separate package. Start from [adaptor.sample.ts](adaptor.sample.ts) and map its `WebReviewKitAdapter` methods to your backend API.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# 릴리즈 노트: 0.5.0
|
|
2
|
+
|
|
3
|
+
0.4.1 release 이후 변경 사항 정리.
|
|
4
|
+
|
|
5
|
+
비교 기준: `a80ca35` (`release: 0.4.1`)
|
|
6
|
+
검토 기준: 0.5.0 dogfood working tree (`Source Tree`, QA UI polish, Lexus host integration 포함)
|
|
7
|
+
|
|
8
|
+
## 주요 변경
|
|
9
|
+
|
|
10
|
+
- Review shell side rail에 Source Tree panel을 추가했다.
|
|
11
|
+
- Source inspector 후보에서 infrastructure file을 숨길 수 있게 했다.
|
|
12
|
+
- Source Tree 탐색 깊이를 `sourceInspector.maxDepth`로 제한할 수 있게 했다.
|
|
13
|
+
- Vite dev plugin에 page data 출처를 주입하는 `reviewDataLocator()`를 추가했다.
|
|
14
|
+
- Source candidate가 page data file hint를 함께 노출하도록 확장했다.
|
|
15
|
+
- QA status filter가 iframe overlay 표시에도 반영되도록 했다.
|
|
16
|
+
- 모바일 comment textarea focus 시 viewport zoom으로 popup 위치가 틀어지는 현상을 줄였다.
|
|
17
|
+
- Lexus dogfood 기준 init, review route, Vite locator 설정을 0.5.0 host integration 기준으로 정리했다.
|
|
18
|
+
|
|
19
|
+
## 추가
|
|
20
|
+
|
|
21
|
+
### Source Tree Panel
|
|
22
|
+
|
|
23
|
+
QA side rail에 `SOURCE` panel을 추가했다. Source inspector가 활성화된 상태에서 target iframe의 section/source 구조를 tree로 볼 수 있다.
|
|
24
|
+
|
|
25
|
+
- section/source 후보 tree 표시
|
|
26
|
+
- filter input으로 label, source file, data file 검색
|
|
27
|
+
- section row 클릭 시 target section으로 scroll
|
|
28
|
+
- source file open action
|
|
29
|
+
- data file open action
|
|
30
|
+
- DOM QA 작성 action
|
|
31
|
+
- collapsible tree row
|
|
32
|
+
|
|
33
|
+
Tree root는 source hint가 있는 section wrapper와 header/footer landmark를 기준으로 잡는다. Placer 계열 primitive는 tree noise를 줄이기 위해 기본 제외하고, 필요하면 `sourceInspector.includePlacer`로 표시할 수 있다.
|
|
34
|
+
|
|
35
|
+
기본 펼침 정책은 Source Tree가 너무 길어지는 것을 막기 위해 2depth까지만 열고, 그 아래 subtree는 접는다. `FrameHeader`, `FrameFooter`는 root(1depth)여도 기본 접힘 상태로 시작한다.
|
|
36
|
+
|
|
37
|
+
### Source Inspector 옵션
|
|
38
|
+
|
|
39
|
+
`sourceInspector` mount option을 확장했다.
|
|
40
|
+
|
|
41
|
+
- `sourceInspector.maxDepth`: Source Tree traversal depth 제한
|
|
42
|
+
- `sourceInspector.ignore`: source candidate와 Source Tree에서 숨길 file pattern
|
|
43
|
+
- `sourceInspector.hoverOutline`: Source Tree item hover 시 iframe target outline 표시 여부
|
|
44
|
+
- `sourceInspector.includePlacer`: Source Tree에서 Placer primitive node 표시 여부
|
|
45
|
+
|
|
46
|
+
문자열 pattern은 normalized path 부분 일치로 처리하고, `RegExp` pattern도 지원한다. Ignore 결과로 후보가 모두 사라지면 빈 list 대신 원본 후보를 보여준다.
|
|
47
|
+
|
|
48
|
+
동일 source file 비교는 absolute/relative/suffix 경로를 정규화해서 같은 파일이 root와 child에 중복 표시되지 않게 했다. 단, 같은 이름의 sibling node는 branch-local 기준으로 유지해서 실제 sibling 구조가 사라지지 않게 했다.
|
|
49
|
+
|
|
50
|
+
### Data Locator
|
|
51
|
+
|
|
52
|
+
`@designfever/web-review-kit/vite`에 `reviewDataLocator()`를 추가했다. Page data 파일의 section object에서 `component: 'SectionXxx'` 패턴을 찾아 data file/line hint prop을 주입한다.
|
|
53
|
+
|
|
54
|
+
기본 주입 prop:
|
|
55
|
+
|
|
56
|
+
- `__wrkDataFile`
|
|
57
|
+
- `__wrkDataLine`
|
|
58
|
+
|
|
59
|
+
Host component가 이 prop을 section wrapper의 `data-wrk-data-file`, `data-wrk-data-line`으로 넘기면 Source Tree에서 data file open action을 사용할 수 있다.
|
|
60
|
+
|
|
61
|
+
## 변경
|
|
62
|
+
|
|
63
|
+
### Source Candidate 수집
|
|
64
|
+
|
|
65
|
+
Source 후보 수집이 DOM source hint와 data hint를 함께 본다.
|
|
66
|
+
|
|
67
|
+
- 클릭 지점에서 부모로 올라가며 source hint 수집
|
|
68
|
+
- 같은 source file은 클릭 지점에 가까운 후보 1개만 유지
|
|
69
|
+
- data hint는 별도 후보로 추가
|
|
70
|
+
- `sourceInspector.ignore` 적용
|
|
71
|
+
|
|
72
|
+
Option source candidate popup은 후보 row 사이 divider를 추가하고, row 높이를 고정값이 아니라 content 기반 padding/line-height로 정리했다. 위치가 없는 후보는 `file only` 대신 `-:-`로 표시해 `line:column` 양식과 맞췄다.
|
|
73
|
+
|
|
74
|
+
### Source Open 표시
|
|
75
|
+
|
|
76
|
+
`pages/` 경로도 display path 축약 규칙에 포함했다. 기존 `page/`, `app/`, `components/`, `src/` 계열 규칙은 유지한다.
|
|
77
|
+
|
|
78
|
+
### Review Shell UI
|
|
79
|
+
|
|
80
|
+
- side rail을 아이콘 중심 UI로 바꾸고 active 상태는 우측 line indicator로 표시한다.
|
|
81
|
+
- Source Tree/QA 아이콘 weight와 전체 font weight를 낮춰 panel 밀도를 줄였다.
|
|
82
|
+
- Source Tree filter clear button의 클릭 영역과 색상을 정리했다.
|
|
83
|
+
- Source Tree 1depth item 사이 divider를 추가했다.
|
|
84
|
+
- Source Tree hover 시 iframe target outline을 표시하고, row action에서 DOM QA 작성을 시작할 수 있게 했다.
|
|
85
|
+
- Source Tree row click scroll은 smooth behavior를 제거하고 target element가 iframe center 쪽에 오도록 조정했다.
|
|
86
|
+
- QA header와 Source Tree header/filter 높이를 34px control 기준으로 맞췄다.
|
|
87
|
+
- 좁은 viewport에서는 해상도 선택을 dropdown으로, overlay tool group을 `...` menu로 줄여 2줄 layout을 유지한다.
|
|
88
|
+
- 설정 modal에 Dark / Light / System theme icon option을 추가했다.
|
|
89
|
+
- 사용자 presence는 side rail 하단 icon badge로 표시하고, 클릭 시 사용자 이름 list를 세로로 보여준다.
|
|
90
|
+
|
|
91
|
+
### QA Filter / Overlay
|
|
92
|
+
|
|
93
|
+
QA list의 status filter(`Todo`, `Doing`, `Done` 등)를 선택하면 해당 status item만 iframe overlay에 표시되도록 했다.
|
|
94
|
+
|
|
95
|
+
### Mobile 입력
|
|
96
|
+
|
|
97
|
+
모바일 Safari에서 comment textarea focus 시 16px 미만 input zoom이 popup layer 위치를 흔드는 문제를 줄이기 위해 coarse pointer 환경의 textarea/select font-size를 16px로 보정했다.
|
|
98
|
+
|
|
99
|
+
## Host 적용 메모
|
|
100
|
+
|
|
101
|
+
### Lexus dogfood integration
|
|
102
|
+
|
|
103
|
+
`lexus_official_v2026` dogfood 적용 기준으로 아래 host 설정을 확인했다.
|
|
104
|
+
|
|
105
|
+
- `vite.config.js`에서 `reviewSourceLocator()`와 `reviewDataLocator()`를 dev 전용으로 함께 사용한다.
|
|
106
|
+
- `reviewSourceLocator({ include: ['src', 'page'], filePath: 'absolute' })`로 component/source hint를 주입한다.
|
|
107
|
+
- `reviewDataLocator({ include: ['page', 'src/data'], filePath: 'absolute' })`로 page data object의 file/line hint를 주입한다.
|
|
108
|
+
- Vite `define.__DFWR_SOURCE_ROOT__`로 review shell이 상대 source path를 editor open용 project root와 매칭할 수 있게 한다.
|
|
109
|
+
- review route init(`page/review/index.tsx`)에서 `sourceRoot`, `sourceInspector.maxDepth`, `sourceInspector.includePlacer`, `sourceInspector.ignore`를 host별로 설정한다.
|
|
110
|
+
- app-level dev init(`src/helper/helper.review-kit.ts`)은 `import.meta.env.DEV`에서만 동작하고, `__dfwr_target` query가 있으면 중복 mount를 피한다.
|
|
111
|
+
- host section base interface는 `__wrkDataFile`, `__wrkDataLine` dev hint prop을 허용해야 `reviewDataLocator()` 주입값을 section wrapper까지 전달할 수 있다.
|
|
112
|
+
- dogfood 중에는 `@designfever/web-review-kit`을 `file:../df-web-review-kit`로 연결해 0.5.0 dist를 Lexus review page에서 직접 검증했다.
|
|
113
|
+
|
|
114
|
+
## 문서
|
|
115
|
+
|
|
116
|
+
- README에 `reviewDataLocator()`와 Source Tree 설명을 추가했다.
|
|
117
|
+
- Installation docs에 data locator, Source Tree, `ignore`, `maxDepth` 설정을 추가했다.
|
|
118
|
+
|
|
119
|
+
## 검증
|
|
120
|
+
|
|
121
|
+
검토한 main branch 기준으로 아래 명령을 확인했다.
|
|
122
|
+
|
|
123
|
+
- `pnpm typecheck`
|
|
124
|
+
- `pnpm typecheck:dev`
|
|
125
|
+
- `pnpm build`
|
|
126
|
+
- `pnpm build:dev`
|
|
127
|
+
- `git diff --check`
|
|
128
|
+
- `npm pack --dry-run`
|
|
129
|
+
- Lexus dogfood review page에서 Source Tree, Option source popup, QA status filter, side rail, mobile textarea 보정을 수동 확인했다.
|
|
130
|
+
|
|
131
|
+
## 메모
|
|
132
|
+
|
|
133
|
+
- `package.json` version은 `0.5.0`이다.
|
|
134
|
+
- npm publish 전에는 registry login 상태를 확인해야 한다.
|