@impakers/debug 1.4.5 → 1.4.7

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/dist/next.d.mts CHANGED
@@ -10,6 +10,10 @@ interface ImpakersDebugOptions {
10
10
  * false로 설정하면 소스코드가 .map에 그대로 노출됨
11
11
  */
12
12
  stripSourceContent?: boolean;
13
+ /**
14
+ * Next App Router route -> file manifest 생성 여부 (기본: true)
15
+ */
16
+ emitRouteManifest?: boolean;
13
17
  }
14
18
  /**
15
19
  * Next.js config wrapper — 소스맵 설정 자동 구성
package/dist/next.d.ts CHANGED
@@ -10,6 +10,10 @@ interface ImpakersDebugOptions {
10
10
  * false로 설정하면 소스코드가 .map에 그대로 노출됨
11
11
  */
12
12
  stripSourceContent?: boolean;
13
+ /**
14
+ * Next App Router route -> file manifest 생성 여부 (기본: true)
15
+ */
16
+ emitRouteManifest?: boolean;
13
17
  }
14
18
  /**
15
19
  * Next.js config wrapper — 소스맵 설정 자동 구성
package/dist/next.js CHANGED
@@ -89,8 +89,127 @@ var StripSourceContentPlugin = class {
89
89
  );
90
90
  }
91
91
  };
92
+ var RouteManifestPlugin = class {
93
+ constructor(projectDir) {
94
+ this.projectDir = projectDir;
95
+ }
96
+ apply(compiler) {
97
+ compiler.hooks.afterEmit.tapAsync(
98
+ "ImpakersDebugRouteManifest",
99
+ (compilation, callback) => {
100
+ const fs = require("fs");
101
+ const path = require("path");
102
+ const outputPath = compilation.outputOptions?.path;
103
+ if (!outputPath) {
104
+ callback();
105
+ return;
106
+ }
107
+ const appDir = findAppDirectory(fs, path, this.projectDir);
108
+ if (!appDir) {
109
+ callback();
110
+ return;
111
+ }
112
+ const entries = scanRouteManifestEntries(fs, path, this.projectDir, appDir);
113
+ if (entries.length === 0) {
114
+ callback();
115
+ return;
116
+ }
117
+ const manifestPath = path.resolve(
118
+ outputPath,
119
+ "../static/chunks/impakers-debug-route-manifest.json"
120
+ );
121
+ try {
122
+ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
123
+ fs.writeFileSync(
124
+ manifestPath,
125
+ JSON.stringify({ version: 1, entries }, null, 2),
126
+ "utf-8"
127
+ );
128
+ console.log(
129
+ `[@impakers/debug] Emitted route manifest with ${entries.length} route entries`
130
+ );
131
+ } catch {
132
+ }
133
+ callback();
134
+ }
135
+ );
136
+ }
137
+ };
138
+ function findAppDirectory(fs, path, projectDir) {
139
+ const candidates = [
140
+ path.join(projectDir, "src", "app"),
141
+ path.join(projectDir, "app")
142
+ ];
143
+ for (const candidate of candidates) {
144
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
145
+ return candidate;
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+ function toPosixPath(path) {
151
+ return path.replace(/\\/g, "/");
152
+ }
153
+ function findRouteFile(fs, path, dir, baseName) {
154
+ const extensions = [".tsx", ".ts", ".jsx", ".js"];
155
+ for (const ext of extensions) {
156
+ const file = path.join(dir, `${baseName}${ext}`);
157
+ if (fs.existsSync(file) && fs.statSync(file).isFile()) {
158
+ return file;
159
+ }
160
+ }
161
+ return null;
162
+ }
163
+ function getUrlSegment(segmentName) {
164
+ if (!segmentName) return null;
165
+ if (segmentName.startsWith("(") && segmentName.endsWith(")")) return null;
166
+ return segmentName;
167
+ }
168
+ function routeFromSegments(segments) {
169
+ if (segments.length === 0) return "/";
170
+ return `/${segments.join("/")}`;
171
+ }
172
+ function scanRouteManifestEntries(fs, path, projectDir, appDir) {
173
+ const entries = [];
174
+ const walk = (currentDir, routeSegments, layoutChain) => {
175
+ const currentLayout = findRouteFile(fs, path, currentDir, "layout");
176
+ const nextLayoutChain = currentLayout ? [
177
+ ...layoutChain,
178
+ {
179
+ kind: "layout",
180
+ file: toPosixPath(path.relative(projectDir, currentLayout))
181
+ }
182
+ ] : layoutChain;
183
+ const currentPage = findRouteFile(fs, path, currentDir, "page");
184
+ if (currentPage) {
185
+ entries.push({
186
+ route: routeFromSegments(routeSegments),
187
+ segments: [...routeSegments],
188
+ files: [
189
+ {
190
+ kind: "page",
191
+ file: toPosixPath(path.relative(projectDir, currentPage))
192
+ },
193
+ ...nextLayoutChain
194
+ ]
195
+ });
196
+ }
197
+ const children = fs.readdirSync(currentDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
198
+ for (const child of children) {
199
+ if (child.name.startsWith("@")) continue;
200
+ const nextSegment = getUrlSegment(child.name);
201
+ walk(
202
+ path.join(currentDir, child.name),
203
+ nextSegment ? [...routeSegments, nextSegment] : routeSegments,
204
+ nextLayoutChain
205
+ );
206
+ }
207
+ };
208
+ walk(appDir, [], []);
209
+ return entries;
210
+ }
92
211
  function withImpakersDebug(nextConfig, options = {}) {
93
- const { stripSourceContent = true } = options;
212
+ const { stripSourceContent = true, emitRouteManifest = true } = options;
94
213
  return {
95
214
  ...nextConfig,
96
215
  // 소스맵 생성 활성화
@@ -99,9 +218,14 @@ function withImpakersDebug(nextConfig, options = {}) {
99
218
  if (typeof nextConfig.webpack === "function") {
100
219
  config = nextConfig.webpack(config, context);
101
220
  }
102
- if (!context.isServer && stripSourceContent) {
221
+ if (!context.isServer) {
103
222
  config.plugins = config.plugins || [];
104
- config.plugins.push(new StripSourceContentPlugin());
223
+ if (stripSourceContent) {
224
+ config.plugins.push(new StripSourceContentPlugin());
225
+ }
226
+ if (emitRouteManifest && context.dir) {
227
+ config.plugins.push(new RouteManifestPlugin(context.dir));
228
+ }
105
229
  }
106
230
  return config;
107
231
  }
package/dist/next.mjs CHANGED
@@ -72,8 +72,127 @@ var StripSourceContentPlugin = class {
72
72
  );
73
73
  }
74
74
  };
75
+ var RouteManifestPlugin = class {
76
+ constructor(projectDir) {
77
+ this.projectDir = projectDir;
78
+ }
79
+ apply(compiler) {
80
+ compiler.hooks.afterEmit.tapAsync(
81
+ "ImpakersDebugRouteManifest",
82
+ (compilation, callback) => {
83
+ const fs = __require("fs");
84
+ const path = __require("path");
85
+ const outputPath = compilation.outputOptions?.path;
86
+ if (!outputPath) {
87
+ callback();
88
+ return;
89
+ }
90
+ const appDir = findAppDirectory(fs, path, this.projectDir);
91
+ if (!appDir) {
92
+ callback();
93
+ return;
94
+ }
95
+ const entries = scanRouteManifestEntries(fs, path, this.projectDir, appDir);
96
+ if (entries.length === 0) {
97
+ callback();
98
+ return;
99
+ }
100
+ const manifestPath = path.resolve(
101
+ outputPath,
102
+ "../static/chunks/impakers-debug-route-manifest.json"
103
+ );
104
+ try {
105
+ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
106
+ fs.writeFileSync(
107
+ manifestPath,
108
+ JSON.stringify({ version: 1, entries }, null, 2),
109
+ "utf-8"
110
+ );
111
+ console.log(
112
+ `[@impakers/debug] Emitted route manifest with ${entries.length} route entries`
113
+ );
114
+ } catch {
115
+ }
116
+ callback();
117
+ }
118
+ );
119
+ }
120
+ };
121
+ function findAppDirectory(fs, path, projectDir) {
122
+ const candidates = [
123
+ path.join(projectDir, "src", "app"),
124
+ path.join(projectDir, "app")
125
+ ];
126
+ for (const candidate of candidates) {
127
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
128
+ return candidate;
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ function toPosixPath(path) {
134
+ return path.replace(/\\/g, "/");
135
+ }
136
+ function findRouteFile(fs, path, dir, baseName) {
137
+ const extensions = [".tsx", ".ts", ".jsx", ".js"];
138
+ for (const ext of extensions) {
139
+ const file = path.join(dir, `${baseName}${ext}`);
140
+ if (fs.existsSync(file) && fs.statSync(file).isFile()) {
141
+ return file;
142
+ }
143
+ }
144
+ return null;
145
+ }
146
+ function getUrlSegment(segmentName) {
147
+ if (!segmentName) return null;
148
+ if (segmentName.startsWith("(") && segmentName.endsWith(")")) return null;
149
+ return segmentName;
150
+ }
151
+ function routeFromSegments(segments) {
152
+ if (segments.length === 0) return "/";
153
+ return `/${segments.join("/")}`;
154
+ }
155
+ function scanRouteManifestEntries(fs, path, projectDir, appDir) {
156
+ const entries = [];
157
+ const walk = (currentDir, routeSegments, layoutChain) => {
158
+ const currentLayout = findRouteFile(fs, path, currentDir, "layout");
159
+ const nextLayoutChain = currentLayout ? [
160
+ ...layoutChain,
161
+ {
162
+ kind: "layout",
163
+ file: toPosixPath(path.relative(projectDir, currentLayout))
164
+ }
165
+ ] : layoutChain;
166
+ const currentPage = findRouteFile(fs, path, currentDir, "page");
167
+ if (currentPage) {
168
+ entries.push({
169
+ route: routeFromSegments(routeSegments),
170
+ segments: [...routeSegments],
171
+ files: [
172
+ {
173
+ kind: "page",
174
+ file: toPosixPath(path.relative(projectDir, currentPage))
175
+ },
176
+ ...nextLayoutChain
177
+ ]
178
+ });
179
+ }
180
+ const children = fs.readdirSync(currentDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
181
+ for (const child of children) {
182
+ if (child.name.startsWith("@")) continue;
183
+ const nextSegment = getUrlSegment(child.name);
184
+ walk(
185
+ path.join(currentDir, child.name),
186
+ nextSegment ? [...routeSegments, nextSegment] : routeSegments,
187
+ nextLayoutChain
188
+ );
189
+ }
190
+ };
191
+ walk(appDir, [], []);
192
+ return entries;
193
+ }
75
194
  function withImpakersDebug(nextConfig, options = {}) {
76
- const { stripSourceContent = true } = options;
195
+ const { stripSourceContent = true, emitRouteManifest = true } = options;
77
196
  return {
78
197
  ...nextConfig,
79
198
  // 소스맵 생성 활성화
@@ -82,9 +201,14 @@ function withImpakersDebug(nextConfig, options = {}) {
82
201
  if (typeof nextConfig.webpack === "function") {
83
202
  config = nextConfig.webpack(config, context);
84
203
  }
85
- if (!context.isServer && stripSourceContent) {
204
+ if (!context.isServer) {
86
205
  config.plugins = config.plugins || [];
87
- config.plugins.push(new StripSourceContentPlugin());
206
+ if (stripSourceContent) {
207
+ config.plugins.push(new StripSourceContentPlugin());
208
+ }
209
+ if (emitRouteManifest && context.dir) {
210
+ config.plugins.push(new RouteManifestPlugin(context.dir));
211
+ }
88
212
  }
89
213
  return config;
90
214
  }
package/dist/react.js CHANGED
@@ -1569,35 +1569,70 @@ function findNearestComponentSource(element, maxAncestors = 10) {
1569
1569
  }
1570
1570
 
1571
1571
  // src/utils/capture-element.ts
1572
- async function captureElement(el, options = {}) {
1573
- const { quality = 0.7, maxScale = 2 } = options;
1574
- const rect = el.getBoundingClientRect();
1575
- const scale = Math.min(window.devicePixelRatio, maxScale);
1576
- const canvas = document.createElement("canvas");
1577
- canvas.width = rect.width * scale;
1578
- canvas.height = rect.height * scale;
1579
- const ctx = canvas.getContext("2d");
1580
- ctx.scale(scale, scale);
1581
- const clone = el.cloneNode(true);
1582
- inlineComputedStyles(el, clone);
1583
- clone.style.margin = "0";
1584
- clone.style.position = "static";
1585
- const serializer = new XMLSerializer();
1586
- const html = serializer.serializeToString(clone);
1587
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${rect.width}" height="${rect.height}">
1588
- <foreignObject width="100%" height="100%">
1589
- <div xmlns="http://www.w3.org/1999/xhtml" style="width:${rect.width}px;height:${rect.height}px;overflow:hidden">${html}</div>
1590
- </foreignObject>
1591
- </svg>`;
1592
- const blob = new Blob([svg], { type: "image/svg+xml;charset=utf-8" });
1593
- const url = URL.createObjectURL(blob);
1572
+ var UNSUPPORTED_RE = /oklab|oklch|color-mix/i;
1573
+ function sanitizeUnsupportedColors(clonedDoc) {
1594
1574
  try {
1595
- const img = await loadImage(url);
1596
- ctx.drawImage(img, 0, 0, rect.width, rect.height);
1597
- return canvas.toDataURL("image/jpeg", quality);
1598
- } finally {
1599
- URL.revokeObjectURL(url);
1575
+ for (let i = 0; i < clonedDoc.styleSheets.length; i++) {
1576
+ try {
1577
+ const sheet = clonedDoc.styleSheets[i];
1578
+ const rules = sheet.cssRules || sheet.rules;
1579
+ if (!rules) continue;
1580
+ sanitizeCSSRules(sheet, rules);
1581
+ } catch {
1582
+ }
1583
+ }
1584
+ } catch {
1600
1585
  }
1586
+ clonedDoc.querySelectorAll("style").forEach((styleEl) => {
1587
+ const text = styleEl.textContent || "";
1588
+ if (UNSUPPORTED_RE.test(text)) {
1589
+ styleEl.textContent = text.replace(
1590
+ /[^{};\n]+:\s*[^;{}]*(?:oklab|oklch|color-mix|lch|lab)\([^)]*(?:\([^)]*\))*[^)]*\)[^;{}]*/gi,
1591
+ ""
1592
+ );
1593
+ }
1594
+ });
1595
+ clonedDoc.querySelectorAll("*").forEach((el) => {
1596
+ const s = el.style;
1597
+ if (!s?.cssText) return;
1598
+ if (UNSUPPORTED_RE.test(s.cssText)) {
1599
+ s.cssText = s.cssText.replace(
1600
+ /[^;]*(?:oklab|oklch|color-mix|lch|lab)\([^)]*(?:\([^)]*\))*[^)]*\)[^;]*/gi,
1601
+ ""
1602
+ );
1603
+ }
1604
+ });
1605
+ }
1606
+ function sanitizeCSSRules(sheet, rules) {
1607
+ for (let j = rules.length - 1; j >= 0; j--) {
1608
+ const rule = rules[j];
1609
+ if ("cssRules" in rule && rule.cssRules) {
1610
+ sanitizeCSSRules(sheet, rule.cssRules);
1611
+ continue;
1612
+ }
1613
+ if (rule instanceof CSSStyleRule) {
1614
+ const style = rule.style;
1615
+ for (let k = style.length - 1; k >= 0; k--) {
1616
+ const prop = style[k];
1617
+ const val = style.getPropertyValue(prop);
1618
+ if (UNSUPPORTED_RE.test(val)) {
1619
+ style.setProperty(prop, "transparent");
1620
+ }
1621
+ }
1622
+ }
1623
+ }
1624
+ }
1625
+ async function captureElement(el, options = {}) {
1626
+ const { quality = 0.7, maxScale = 2 } = options;
1627
+ const html2canvas = (await import("html2canvas")).default;
1628
+ const canvas = await html2canvas(el, {
1629
+ useCORS: true,
1630
+ allowTaint: true,
1631
+ scale: Math.min(window.devicePixelRatio, maxScale),
1632
+ logging: false,
1633
+ onclone: (clonedDoc) => sanitizeUnsupportedColors(clonedDoc)
1634
+ });
1635
+ return canvas.toDataURL("image/jpeg", quality);
1601
1636
  }
1602
1637
  async function captureFullPage(options = {}) {
1603
1638
  const { quality = 0.5, hideSelectors = [] } = options;
@@ -1614,17 +1649,7 @@ async function captureFullPage(options = {}) {
1614
1649
  logging: false,
1615
1650
  width: window.innerWidth,
1616
1651
  height: window.innerHeight,
1617
- onclone: (clonedDoc) => {
1618
- const unsupported = /oklab|oklch|color-mix/i;
1619
- clonedDoc.querySelectorAll("style").forEach((styleEl) => {
1620
- if (unsupported.test(styleEl.textContent || "")) {
1621
- styleEl.textContent = (styleEl.textContent || "").replace(
1622
- /[^{};\n]+:\s*[^;{}]*(?:oklab|oklch|color-mix|lch|lab)\([^)]*(?:\([^)]*\))*[^)]*\)[^;{}]*/gi,
1623
- ""
1624
- );
1625
- }
1626
- });
1627
- }
1652
+ onclone: (clonedDoc) => sanitizeUnsupportedColors(clonedDoc)
1628
1653
  });
1629
1654
  return canvas.toDataURL("image/jpeg", quality);
1630
1655
  } catch {
@@ -1633,23 +1658,6 @@ async function captureFullPage(options = {}) {
1633
1658
  hiddenEls.forEach((el) => el.style.visibility = "");
1634
1659
  }
1635
1660
  }
1636
- function loadImage(src) {
1637
- return new Promise((resolve, reject) => {
1638
- const img = new Image();
1639
- img.onload = () => resolve(img);
1640
- img.onerror = () => reject(new Error("\uC774\uBBF8\uC9C0 \uB85C\uB4DC \uC2E4\uD328"));
1641
- img.src = src;
1642
- });
1643
- }
1644
- function inlineComputedStyles(src, dst) {
1645
- const cs = window.getComputedStyle(src);
1646
- dst.style.cssText = cs.cssText;
1647
- const srcChildren = src.children;
1648
- const dstChildren = dst.children;
1649
- for (let i = 0; i < srcChildren.length && i < dstChildren.length; i++) {
1650
- inlineComputedStyles(srcChildren[i], dstChildren[i]);
1651
- }
1652
- }
1653
1661
 
1654
1662
  // src/core/collector.ts
1655
1663
  var MAX_ERRORS = 20;
@@ -1866,6 +1874,151 @@ function collectMetadata(getUser) {
1866
1874
  return metadata;
1867
1875
  }
1868
1876
 
1877
+ // src/core/debug-targets.ts
1878
+ var NEXT_ROUTE_MANIFEST_URL = "/_next/static/chunks/impakers-debug-route-manifest.json";
1879
+ var manifestPromise = null;
1880
+ var manifestFailed = false;
1881
+ function normalizePathname(pathname) {
1882
+ if (!pathname || pathname === "/") return "/";
1883
+ return pathname.endsWith("/") ? pathname.slice(0, -1) || "/" : pathname;
1884
+ }
1885
+ function splitPathname(pathname) {
1886
+ const normalized = normalizePathname(pathname);
1887
+ if (normalized === "/") return [];
1888
+ return normalized.slice(1).split("/").map((segment) => decodeURIComponent(segment)).filter(Boolean);
1889
+ }
1890
+ function isDynamicSegment(segment) {
1891
+ return /^\[[^\]]+\]$/.test(segment);
1892
+ }
1893
+ function isCatchAllSegment(segment) {
1894
+ return /^\[\.\.\.[^\]]+\]$/.test(segment);
1895
+ }
1896
+ function isOptionalCatchAllSegment(segment) {
1897
+ return /^\[\[\.\.\.[^\]]+\]\]$/.test(segment);
1898
+ }
1899
+ function scoreRouteMatch(routeSegments, pathnameSegments) {
1900
+ let routeIndex = 0;
1901
+ let pathIndex = 0;
1902
+ let score = 0;
1903
+ while (routeIndex < routeSegments.length) {
1904
+ const routeSegment = routeSegments[routeIndex];
1905
+ if (isOptionalCatchAllSegment(routeSegment)) {
1906
+ score += 1;
1907
+ pathIndex = pathnameSegments.length;
1908
+ routeIndex += 1;
1909
+ break;
1910
+ }
1911
+ if (isCatchAllSegment(routeSegment)) {
1912
+ if (pathIndex >= pathnameSegments.length) return null;
1913
+ score += 2;
1914
+ pathIndex = pathnameSegments.length;
1915
+ routeIndex += 1;
1916
+ break;
1917
+ }
1918
+ const pathSegment = pathnameSegments[pathIndex];
1919
+ if (pathSegment === void 0) return null;
1920
+ if (isDynamicSegment(routeSegment)) {
1921
+ score += 5;
1922
+ routeIndex += 1;
1923
+ pathIndex += 1;
1924
+ continue;
1925
+ }
1926
+ if (routeSegment !== pathSegment) return null;
1927
+ score += 20;
1928
+ routeIndex += 1;
1929
+ pathIndex += 1;
1930
+ }
1931
+ if (routeIndex !== routeSegments.length) return null;
1932
+ if (pathIndex !== pathnameSegments.length) return null;
1933
+ return score;
1934
+ }
1935
+ async function loadNextRouteManifest() {
1936
+ if (manifestFailed) return null;
1937
+ if (manifestPromise) return manifestPromise;
1938
+ manifestPromise = (async () => {
1939
+ try {
1940
+ const res = await fetch(NEXT_ROUTE_MANIFEST_URL, { cache: "force-cache" });
1941
+ if (!res.ok) {
1942
+ manifestFailed = true;
1943
+ return null;
1944
+ }
1945
+ const json = await res.json();
1946
+ if (!json || typeof json !== "object" || !Array.isArray(json.entries)) {
1947
+ manifestFailed = true;
1948
+ return null;
1949
+ }
1950
+ return json;
1951
+ } catch {
1952
+ manifestFailed = true;
1953
+ return null;
1954
+ }
1955
+ })();
1956
+ return manifestPromise;
1957
+ }
1958
+ function buildRouteTargets(entry) {
1959
+ const pageTarget = entry.files.find((file) => file.kind === "page");
1960
+ const layoutTargets = entry.files.filter((file) => file.kind === "layout");
1961
+ const targets = [];
1962
+ if (pageTarget) {
1963
+ targets.push({
1964
+ kind: "route-page",
1965
+ file: pageTarget.file,
1966
+ label: "Current page route",
1967
+ confidence: 1,
1968
+ reason: "matched-current-url"
1969
+ });
1970
+ }
1971
+ layoutTargets.forEach((layout, index) => {
1972
+ targets.push({
1973
+ kind: "route-layout",
1974
+ file: layout.file,
1975
+ label: index === layoutTargets.length - 1 ? "Nearest layout" : "Ancestor layout",
1976
+ confidence: Math.max(0.7, 0.95 - index * 0.08),
1977
+ reason: "matched-layout-chain"
1978
+ });
1979
+ });
1980
+ return targets;
1981
+ }
1982
+ async function getRouteDebugTargets(pathname) {
1983
+ if (typeof window === "undefined") return null;
1984
+ const currentPathname = pathname ?? window.location.pathname;
1985
+ const manifest = await loadNextRouteManifest();
1986
+ if (!manifest) return null;
1987
+ const pathnameSegments = splitPathname(currentPathname);
1988
+ let bestEntry = null;
1989
+ let bestScore = -1;
1990
+ for (const entry of manifest.entries) {
1991
+ const score = scoreRouteMatch(entry.segments, pathnameSegments);
1992
+ if (score === null) continue;
1993
+ if (score > bestScore) {
1994
+ bestScore = score;
1995
+ bestEntry = entry;
1996
+ }
1997
+ }
1998
+ if (!bestEntry) return null;
1999
+ return {
2000
+ targets: buildRouteTargets(bestEntry),
2001
+ context: {
2002
+ pathname: normalizePathname(currentPathname),
2003
+ matchedRoute: bestEntry.route,
2004
+ source: "next-route-manifest"
2005
+ }
2006
+ };
2007
+ }
2008
+ function mergeDebugTargets(...groups) {
2009
+ const merged = [];
2010
+ const seen = /* @__PURE__ */ new Set();
2011
+ groups.forEach((group) => {
2012
+ group?.forEach((target) => {
2013
+ const key = `${target.kind}:${target.file}:${target.line ?? ""}:${target.column ?? ""}`;
2014
+ if (seen.has(key)) return;
2015
+ seen.add(key);
2016
+ merged.push(target);
2017
+ });
2018
+ });
2019
+ return merged.sort((a, b) => b.confidence - a.confidence);
2020
+ }
2021
+
1869
2022
  // src/core/api.ts
1870
2023
  function getToken() {
1871
2024
  const token = loadToken();
@@ -3433,6 +3586,23 @@ function detectSourceFileCandidates(element) {
3433
3586
  }
3434
3587
  return [];
3435
3588
  }
3589
+ function buildComponentDebugTarget(resolvedSource, componentName) {
3590
+ const match = resolvedSource.match(/^(.+):(\d+)(?::(\d+))?$/);
3591
+ if (!match) return null;
3592
+ const [, file, lineStr, columnStr] = match;
3593
+ const line = Number.parseInt(lineStr, 10);
3594
+ const column = columnStr ? Number.parseInt(columnStr, 10) : void 0;
3595
+ if (!file || Number.isNaN(line)) return null;
3596
+ return {
3597
+ kind: "component",
3598
+ file,
3599
+ line,
3600
+ column,
3601
+ label: componentName || "Resolved component source",
3602
+ confidence: 0.98,
3603
+ reason: "resolved-from-source-map"
3604
+ };
3605
+ }
3436
3606
  var STORAGE_PREFIX = "impakers-debug-markers-";
3437
3607
  function getRouteKey() {
3438
3608
  return STORAGE_PREFIX + window.location.pathname;
@@ -3754,7 +3924,28 @@ function DebugWidget({ endpoint, getUser, onHide }) {
3754
3924
  } catch {
3755
3925
  }
3756
3926
  }
3927
+ const routeMatch = await getRouteDebugTargets(window.location.pathname);
3928
+ const componentTarget = resolvedSource && !resolvedSource.includes("/chunks/") ? buildComponentDebugTarget(resolvedSource, resolvedName) : null;
3929
+ const debugTargets = mergeDebugTargets(
3930
+ componentTarget ? [componentTarget] : void 0,
3931
+ routeMatch?.targets
3932
+ );
3933
+ if (debugTargets.length > 0) {
3934
+ metadata.debugTargets = debugTargets;
3935
+ }
3936
+ if (routeMatch?.context) {
3937
+ metadata.routeDebug = routeMatch.context;
3938
+ }
3757
3939
  const descriptionParts = [comment];
3940
+ if (debugTargets.length > 0) {
3941
+ descriptionParts.push(
3942
+ "\n\n---\n**\uB514\uBC84\uAE45 \uD0C0\uAC9F \uD30C\uC77C**:",
3943
+ ...debugTargets.map((target) => {
3944
+ const suffix = target.line ? `:${target.line}${typeof target.column === "number" ? `:${target.column}` : ""}` : "";
3945
+ return `- [${target.kind}] \`${target.file}${suffix}\` (${target.reason})`;
3946
+ })
3947
+ );
3948
+ }
3758
3949
  const elementInfo = [];
3759
3950
  if (pendingAnnotation.element) {
3760
3951
  elementInfo.push(`**\uC120\uD0DD\uB41C \uC694\uC18C**: ${pendingAnnotation.element}`);