made-refine 0.1.13 → 0.2.1-beta.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 CHANGED
@@ -31,7 +31,7 @@ This detects your framework, installs the package, previews file changes, and ap
31
31
  - Renders inside Shadow DOM for full CSS isolation from your app
32
32
  - Babel/Vite plugin adds source location metadata to every JSX element
33
33
  - Hooks into React DevTools fiber tree for component name resolution
34
- - Built-in MCP server enables hands-free agent workflows
34
+ - Integrates with the desktop app MCP broker for hands-free agent workflows
35
35
 
36
36
  ## Supported frameworks
37
37
 
package/dist/cli.cjs CHANGED
@@ -8435,185 +8435,8 @@ function installPackage(cwd) {
8435
8435
  console.log(import_picocolors.default.dim(` ${cmd}`));
8436
8436
  }
8437
8437
  }
8438
- var BABEL_JSON_CONFIG_FILES = [".babelrc", ".babelrc.json", "babel.config.json"];
8439
- var BABEL_JS_CONFIG_FILES = [
8440
- ".babelrc.js",
8441
- ".babelrc.cjs",
8442
- ".babelrc.mjs",
8443
- "babel.config.js",
8444
- "babel.config.cjs",
8445
- "babel.config.mjs"
8446
- ];
8447
- function findBabelConfigFile(cwd) {
8448
- for (const file of BABEL_JSON_CONFIG_FILES) {
8449
- const absolutePath = import_path2.default.join(cwd, file);
8450
- if (import_fs2.default.existsSync(absolutePath)) {
8451
- return {
8452
- absolutePath,
8453
- relativePath: file,
8454
- kind: "json"
8455
- };
8456
- }
8457
- }
8458
- for (const file of BABEL_JS_CONFIG_FILES) {
8459
- const absolutePath = import_path2.default.join(cwd, file);
8460
- if (import_fs2.default.existsSync(absolutePath)) {
8461
- return {
8462
- absolutePath,
8463
- relativePath: file,
8464
- kind: "js"
8465
- };
8466
- }
8467
- }
8468
- const packageJsonPath = import_path2.default.join(cwd, "package.json");
8469
- if (import_fs2.default.existsSync(packageJsonPath)) {
8470
- try {
8471
- const packageJson = JSON.parse(import_fs2.default.readFileSync(packageJsonPath, "utf-8"));
8472
- if (packageJson.babel !== void 0) {
8473
- return {
8474
- absolutePath: packageJsonPath,
8475
- relativePath: "package.json#babel",
8476
- kind: "package-json"
8477
- };
8478
- }
8479
- } catch {
8480
- }
8481
- }
8482
- return null;
8483
- }
8484
- function isMadeRefinePlugin(plugin) {
8485
- if (typeof plugin === "string") {
8486
- return plugin === "made-refine/babel";
8487
- }
8488
- if (Array.isArray(plugin) && typeof plugin[0] === "string") {
8489
- return plugin[0] === "made-refine/babel";
8490
- }
8491
- return false;
8492
- }
8493
- function hasMadeRefinePlugin(plugins) {
8494
- if (!Array.isArray(plugins)) return false;
8495
- return plugins.some((plugin) => isMadeRefinePlugin(plugin));
8496
- }
8497
- function ensureMadeRefineInDevelopmentEnv(config) {
8498
- if (hasMadeRefinePlugin(config.plugins)) {
8499
- return "already-configured";
8500
- }
8501
- const existingEnv = config.env;
8502
- if (existingEnv !== void 0 && (typeof existingEnv !== "object" || existingEnv === null || Array.isArray(existingEnv))) {
8503
- return "unsupported-shape";
8504
- }
8505
- if (!config.env) {
8506
- config.env = {};
8507
- }
8508
- const env = config.env;
8509
- const existingDevelopment = env.development;
8510
- if (existingDevelopment !== void 0 && (typeof existingDevelopment !== "object" || existingDevelopment === null || Array.isArray(existingDevelopment))) {
8511
- return "unsupported-shape";
8512
- }
8513
- if (!env.development) {
8514
- env.development = {};
8515
- }
8516
- const development = env.development;
8517
- if (hasMadeRefinePlugin(development.plugins)) {
8518
- return "already-configured";
8519
- }
8520
- if (development.plugins === void 0) {
8521
- development.plugins = ["made-refine/babel"];
8522
- return "updated";
8523
- }
8524
- if (!Array.isArray(development.plugins)) {
8525
- return "unsupported-shape";
8526
- }
8527
- development.plugins = [...development.plugins, "made-refine/babel"];
8528
- return "updated";
8529
- }
8530
- function printManualNextBabelInstructions() {
8531
- console.log(
8532
- import_picocolors.default.dim(
8533
- " env: { development: { plugins: ['made-refine/babel'] } }\n (or add 'made-refine/babel' in your existing Babel plugin list)"
8534
- )
8535
- );
8536
- }
8537
- function configureNextBabel(cwd) {
8538
- const configFile = findBabelConfigFile(cwd);
8539
- if (!configFile) {
8540
- console.log(
8541
- import_picocolors.default.yellow(" \u26A0 No existing Babel config found \u2014 skipping Babel config to preserve SWC/Turbopack defaults.")
8542
- );
8543
- console.log(import_picocolors.default.dim(" Source detection will use React fiber fallback (less precise than Babel attributes)."));
8544
- return;
8545
- }
8546
- if (configFile.kind === "js") {
8547
- console.log(import_picocolors.default.yellow(` \u26A0 Found ${configFile.relativePath} (JS Babel config) \u2014 verify/add plugin manually:`));
8548
- printManualNextBabelInstructions();
8549
- return;
8550
- }
8551
- if (configFile.kind === "package-json") {
8552
- let packageJson;
8553
- try {
8554
- packageJson = JSON.parse(import_fs2.default.readFileSync(configFile.absolutePath, "utf-8"));
8555
- } catch {
8556
- console.log(import_picocolors.default.yellow(" \u26A0 Could not parse package.json \u2014 add plugin manually:"));
8557
- printManualNextBabelInstructions();
8558
- return;
8559
- }
8560
- if (typeof packageJson !== "object" || packageJson === null || Array.isArray(packageJson)) {
8561
- console.log(import_picocolors.default.yellow(" \u26A0 package.json has unsupported shape \u2014 add plugin manually:"));
8562
- printManualNextBabelInstructions();
8563
- return;
8564
- }
8565
- const pkg = packageJson;
8566
- if (typeof pkg.babel !== "object" || pkg.babel === null || Array.isArray(pkg.babel)) {
8567
- console.log(import_picocolors.default.yellow(" \u26A0 package.json#babel has unsupported shape \u2014 add plugin manually:"));
8568
- printManualNextBabelInstructions();
8569
- return;
8570
- }
8571
- const result2 = ensureMadeRefineInDevelopmentEnv(pkg.babel);
8572
- if (result2 === "already-configured") {
8573
- console.log(import_picocolors.default.dim(" package.json#babel \u2014 already configured"));
8574
- return;
8575
- }
8576
- if (result2 === "unsupported-shape") {
8577
- console.log(import_picocolors.default.yellow(" \u26A0 package.json#babel has unsupported shape \u2014 add plugin manually:"));
8578
- printManualNextBabelInstructions();
8579
- return;
8580
- }
8581
- import_fs2.default.writeFileSync(configFile.absolutePath, `${JSON.stringify(pkg, null, 2)}
8582
- `, "utf-8");
8583
- console.log(import_picocolors.default.green(' \u2713 Updated package.json#babel (added "made-refine/babel" in development env)'));
8584
- return;
8585
- }
8586
- const content = import_fs2.default.readFileSync(configFile.absolutePath, "utf-8");
8587
- let parsed;
8588
- try {
8589
- parsed = JSON.parse(content);
8590
- } catch {
8591
- console.log(import_picocolors.default.yellow(` \u26A0 Could not parse ${configFile.relativePath} as JSON \u2014 add plugin manually:`));
8592
- printManualNextBabelInstructions();
8593
- return;
8594
- }
8595
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
8596
- console.log(import_picocolors.default.yellow(` \u26A0 ${configFile.relativePath} has unsupported shape \u2014 add plugin manually:`));
8597
- printManualNextBabelInstructions();
8598
- return;
8599
- }
8600
- const result = ensureMadeRefineInDevelopmentEnv(parsed);
8601
- if (result === "already-configured") {
8602
- console.log(import_picocolors.default.dim(` ${configFile.relativePath} \u2014 already configured`));
8603
- return;
8604
- }
8605
- if (result === "unsupported-shape") {
8606
- console.log(import_picocolors.default.yellow(` \u26A0 ${configFile.relativePath} has unsupported shape \u2014 add plugin manually:`));
8607
- printManualNextBabelInstructions();
8608
- return;
8609
- }
8610
- import_fs2.default.writeFileSync(configFile.absolutePath, `${JSON.stringify(parsed, null, 2)}
8611
- `, "utf-8");
8612
- console.log(import_picocolors.default.green(` \u2713 Updated ${configFile.relativePath} (added "made-refine/babel" in development env)`));
8613
- }
8614
8438
  async function setupNextJs(cwd) {
8615
8439
  console.log(import_picocolors.default.bold("\nConfiguring for Next.js...\n"));
8616
- configureNextBabel(cwd);
8617
8440
  const preloadSrc = import_path2.default.join(cwd, "node_modules/made-refine/dist/preload/preload.js");
8618
8441
  const publicDir = import_path2.default.join(cwd, "public");
8619
8442
  const preloadDest = import_path2.default.join(publicDir, "made-refine-preload.js");
package/dist/index.js CHANGED
@@ -2425,44 +2425,302 @@ function hslToRgb(h, s, l) {
2425
2425
  }
2426
2426
 
2427
2427
  // src/mcp-client.ts
2428
- var MCP_BASE = "http://127.0.0.1:4747";
2429
- var cachedToken = null;
2430
- async function getSessionToken(forceRefresh = false) {
2431
- if (!forceRefresh && cachedToken) return cachedToken;
2428
+ var PROTOCOL_VERSION = 1;
2429
+ var BOOTSTRAP_TIMEOUT_MS = 2500;
2430
+ var REQUEST_TIMEOUT_MS = 3e3;
2431
+ var SESSION_EXPIRY_SKEW_MS = 5e3;
2432
+ var CLIENT_NAME = "made-refine";
2433
+ var DEFAULT_CLIENT_VERSION = "unknown";
2434
+ var BOOTSTRAP_ENV_KEYS = [
2435
+ "MADE_REFINE_MCP_BOOTSTRAP_URL",
2436
+ "VITE_MADE_REFINE_MCP_BOOTSTRAP_URL",
2437
+ "NEXT_PUBLIC_MADE_REFINE_MCP_BOOTSTRAP_URL"
2438
+ ];
2439
+ var CLIENT_VERSION_ENV_KEYS = [
2440
+ "MADE_REFINE_VERSION",
2441
+ "VITE_MADE_REFINE_VERSION",
2442
+ "NEXT_PUBLIC_MADE_REFINE_VERSION"
2443
+ ];
2444
+ var cachedSession = null;
2445
+ function getTimeoutSignal(timeoutMs) {
2446
+ const timeout = AbortSignal.timeout;
2447
+ return typeof timeout === "function" ? timeout(timeoutMs) : void 0;
2448
+ }
2449
+ function getRuntimeMcpConfig() {
2450
+ if (typeof window === "undefined") return null;
2451
+ const config = window.__MADE_REFINE_CONFIG__;
2452
+ const bootstrapUrl = config?.mcp?.bootstrapUrl ?? config?.mcpBootstrapUrl ?? window.__MADE_REFINE_MCP_BOOTSTRAP_URL__;
2453
+ if (!config?.mcp && typeof bootstrapUrl !== "string") return null;
2454
+ return {
2455
+ ...config?.mcp ?? {},
2456
+ ...typeof bootstrapUrl === "string" ? { bootstrapUrl } : {}
2457
+ };
2458
+ }
2459
+ function getProcessEnvValue(keys) {
2460
+ const processLike = globalThis.process;
2461
+ const env = processLike?.env;
2462
+ if (!env) return null;
2463
+ for (const key of keys) {
2464
+ const value = env[key];
2465
+ if (typeof value === "string" && value.trim().length > 0) {
2466
+ return value.trim();
2467
+ }
2468
+ }
2469
+ return null;
2470
+ }
2471
+ function normalizeUrl(value) {
2472
+ if (typeof value !== "string") return null;
2473
+ const trimmed = value.trim();
2474
+ if (!trimmed) return null;
2475
+ if (/^https?:\/\//i.test(trimmed)) {
2476
+ return trimmed.replace(/\/+$/, "");
2477
+ }
2478
+ if (typeof window === "undefined" || !window.location?.origin) return null;
2432
2479
  try {
2433
- const res = await fetch(`${MCP_BASE}/api/health`, { signal: AbortSignal.timeout(2e3) });
2434
- if (!res.ok) return null;
2435
- const data = await res.json();
2436
- cachedToken = typeof data.sessionToken === "string" ? data.sessionToken : null;
2437
- return cachedToken;
2480
+ return new URL(trimmed, window.location.origin).toString().replace(/\/+$/, "");
2438
2481
  } catch {
2439
2482
  return null;
2440
2483
  }
2441
2484
  }
2442
- async function postWithSessionToken(path, payload) {
2443
- const send = async (token2) => {
2485
+ function normalizeBootstrapUrl(value) {
2486
+ const normalized = normalizeUrl(value);
2487
+ if (!normalized) return null;
2488
+ try {
2489
+ const url = new URL(normalized);
2490
+ const normalizedPathname = url.pathname.replace(/\/+$/, "");
2491
+ url.pathname = normalizedPathname.endsWith("/v1/bootstrap") ? normalizedPathname : `${normalizedPathname}/v1/bootstrap`;
2492
+ return url.toString();
2493
+ } catch {
2494
+ return normalized.endsWith("/v1/bootstrap") ? normalized : `${normalized}/v1/bootstrap`;
2495
+ }
2496
+ }
2497
+ function joinUrl(base, path) {
2498
+ return `${base.replace(/\/+$/, "")}${path}`;
2499
+ }
2500
+ function readString(record, key) {
2501
+ const value = record?.[key];
2502
+ if (typeof value !== "string") return null;
2503
+ const trimmed = value.trim();
2504
+ return trimmed.length > 0 ? trimmed : null;
2505
+ }
2506
+ function parseExpiresAt(value) {
2507
+ if (typeof value !== "string") return null;
2508
+ const timestamp = Date.parse(value);
2509
+ return Number.isFinite(timestamp) ? timestamp : null;
2510
+ }
2511
+ function readNumber(record, key) {
2512
+ const value = record?.[key];
2513
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
2514
+ return value;
2515
+ }
2516
+ function isLoopbackIpv4(hostname) {
2517
+ if (!/^127(?:\.\d{1,3}){3}$/.test(hostname)) return false;
2518
+ const segments = hostname.split(".");
2519
+ return segments.every((segment) => {
2520
+ const value = Number(segment);
2521
+ return Number.isInteger(value) && value >= 0 && value <= 255;
2522
+ });
2523
+ }
2524
+ function isLoopbackHostname(hostname) {
2525
+ if (hostname === "localhost" || hostname.endsWith(".localhost")) return true;
2526
+ if (hostname === "::1" || hostname === "[::1]") return true;
2527
+ return isLoopbackIpv4(hostname);
2528
+ }
2529
+ function isLoopbackHttpUrl(value) {
2530
+ try {
2531
+ const url = new URL(value);
2532
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
2533
+ return isLoopbackHostname(url.hostname);
2534
+ } catch {
2535
+ return false;
2536
+ }
2537
+ }
2538
+ function buildBootstrapRequestBody(runtimeConfig) {
2539
+ const locationPath = typeof window !== "undefined" ? window.location.pathname : "";
2540
+ const locationOrigin = typeof window !== "undefined" ? window.location.origin : null;
2541
+ return {
2542
+ protocolVersion: PROTOCOL_VERSION,
2543
+ projectFingerprint: {
2544
+ path: runtimeConfig?.projectFingerprint?.path || locationPath || "unknown",
2545
+ gitRemoteHash: runtimeConfig?.projectFingerprint?.gitRemoteHash ?? null
2546
+ },
2547
+ ...runtimeConfig?.workspaceId ? {
2548
+ workspaceId: runtimeConfig.workspaceId
2549
+ } : {},
2550
+ client: {
2551
+ name: CLIENT_NAME,
2552
+ version: runtimeConfig?.clientVersion ?? getProcessEnvValue(CLIENT_VERSION_ENV_KEYS) ?? DEFAULT_CLIENT_VERSION,
2553
+ origin: locationOrigin
2554
+ }
2555
+ };
2556
+ }
2557
+ function resolveBootstrapUrl() {
2558
+ const runtimeConfig = getRuntimeMcpConfig();
2559
+ const runtimeUrl = normalizeBootstrapUrl(runtimeConfig?.bootstrapUrl);
2560
+ if (runtimeUrl) return runtimeUrl;
2561
+ const envUrl = normalizeBootstrapUrl(getProcessEnvValue(BOOTSTRAP_ENV_KEYS));
2562
+ if (envUrl) return envUrl;
2563
+ return null;
2564
+ }
2565
+ function isSessionUsable(session, bootstrapUrl) {
2566
+ if (!session) return false;
2567
+ if (session.bootstrapUrl !== bootstrapUrl) return false;
2568
+ if (session.expiresAt == null) return true;
2569
+ return Date.now() < session.expiresAt - SESSION_EXPIRY_SKEW_MS;
2570
+ }
2571
+ async function readJsonRecord(response) {
2572
+ try {
2573
+ const data = await response.json();
2574
+ if (!data || typeof data !== "object") return null;
2575
+ return data;
2576
+ } catch {
2577
+ return null;
2578
+ }
2579
+ }
2580
+ async function bootstrapSession(force = false) {
2581
+ const bootstrapUrl = resolveBootstrapUrl();
2582
+ if (!bootstrapUrl) {
2583
+ cachedSession = null;
2584
+ return null;
2585
+ }
2586
+ if (!force && isSessionUsable(cachedSession, bootstrapUrl)) {
2587
+ return cachedSession;
2588
+ }
2589
+ const runtimeConfig = getRuntimeMcpConfig();
2590
+ try {
2591
+ const response = await fetch(bootstrapUrl, {
2592
+ method: "POST",
2593
+ headers: { "Content-Type": "application/json" },
2594
+ body: JSON.stringify(buildBootstrapRequestBody(runtimeConfig)),
2595
+ signal: getTimeoutSignal(BOOTSTRAP_TIMEOUT_MS)
2596
+ });
2597
+ if (!response.ok) {
2598
+ cachedSession = null;
2599
+ return null;
2600
+ }
2601
+ const data = await readJsonRecord(response);
2602
+ const protocolVersion = readNumber(data, "protocolVersion");
2603
+ if (protocolVersion !== PROTOCOL_VERSION) {
2604
+ cachedSession = null;
2605
+ return null;
2606
+ }
2607
+ const ingestBaseUrl = normalizeUrl(readString(data, "ingestBaseUrl"));
2608
+ if (!ingestBaseUrl || !isLoopbackHttpUrl(ingestBaseUrl)) {
2609
+ cachedSession = null;
2610
+ return null;
2611
+ }
2612
+ const nextSession = {
2613
+ bootstrapUrl,
2614
+ ingestBaseUrl,
2615
+ serverInstanceId: readString(data, "serverInstanceId"),
2616
+ projectId: readString(data, "projectId"),
2617
+ sessionId: readString(data, "sessionId"),
2618
+ accessToken: readString(data, "accessToken"),
2619
+ expiresAt: parseExpiresAt(data?.expiresAt)
2620
+ };
2621
+ cachedSession = nextSession;
2622
+ return nextSession;
2623
+ } catch {
2624
+ cachedSession = null;
2625
+ return null;
2626
+ }
2627
+ }
2628
+ async function refreshSessionToken(session) {
2629
+ try {
2444
2630
  const headers = { "Content-Type": "application/json" };
2445
- if (token2) headers["X-Session-Token"] = token2;
2446
- return fetch(`${MCP_BASE}${path}`, {
2631
+ if (session.accessToken) {
2632
+ headers.Authorization = `Bearer ${session.accessToken}`;
2633
+ }
2634
+ const response = await fetch(joinUrl(session.ingestBaseUrl, "/v1/sessions/refresh"), {
2447
2635
  method: "POST",
2448
2636
  headers,
2449
- body: JSON.stringify(payload)
2637
+ body: JSON.stringify({
2638
+ protocolVersion: PROTOCOL_VERSION,
2639
+ projectId: session.projectId,
2640
+ sessionId: session.sessionId
2641
+ }),
2642
+ signal: getTimeoutSignal(REQUEST_TIMEOUT_MS)
2450
2643
  });
2644
+ if (response.status === 404 || response.status === 405) {
2645
+ return null;
2646
+ }
2647
+ if (!response.ok) {
2648
+ return null;
2649
+ }
2650
+ const data = await readJsonRecord(response);
2651
+ const nextToken = readString(data, "accessToken");
2652
+ if (!nextToken) {
2653
+ return null;
2654
+ }
2655
+ const refreshedSession = {
2656
+ ...session,
2657
+ accessToken: nextToken,
2658
+ expiresAt: parseExpiresAt(data?.expiresAt) ?? session.expiresAt
2659
+ };
2660
+ cachedSession = refreshedSession;
2661
+ return refreshedSession;
2662
+ } catch {
2663
+ return null;
2664
+ }
2665
+ }
2666
+ function createIdempotencyKey() {
2667
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
2668
+ return crypto.randomUUID();
2669
+ }
2670
+ const timestamp = Date.now().toString(36);
2671
+ const random = Math.random().toString(36).slice(2, 12);
2672
+ return `${timestamp}-${random}`;
2673
+ }
2674
+ async function sendAnnotationRequest(session, path, payload, idempotencyKey) {
2675
+ const headers = {
2676
+ "Content-Type": "application/json",
2677
+ "X-Idempotency-Key": idempotencyKey
2678
+ };
2679
+ if (session.accessToken) {
2680
+ headers.Authorization = `Bearer ${session.accessToken}`;
2681
+ }
2682
+ return fetch(joinUrl(session.ingestBaseUrl, path), {
2683
+ method: "POST",
2684
+ headers,
2685
+ body: JSON.stringify(payload),
2686
+ signal: getTimeoutSignal(REQUEST_TIMEOUT_MS)
2687
+ });
2688
+ }
2689
+ async function toClientResponse(response) {
2690
+ const data = await readJsonRecord(response);
2691
+ const bodyOk = data?.ok;
2692
+ const parsedOk = typeof bodyOk === "boolean" ? bodyOk : response.ok;
2693
+ return {
2694
+ ok: parsedOk && response.ok,
2695
+ id: readString(data, "id") ?? ""
2451
2696
  };
2452
- let token = await getSessionToken();
2453
- let res = await send(token);
2454
- if (res.status === 403) {
2455
- cachedToken = null;
2456
- token = await getSessionToken(true);
2457
- res = await send(token);
2697
+ }
2698
+ async function postWithSessionToken(path, payload) {
2699
+ const idempotencyKey = createIdempotencyKey();
2700
+ let session = await bootstrapSession();
2701
+ if (!session) return { ok: false, id: "" };
2702
+ let response;
2703
+ try {
2704
+ response = await sendAnnotationRequest(session, path, payload, idempotencyKey);
2705
+ } catch {
2706
+ return { ok: false, id: "" };
2707
+ }
2708
+ if (response.status === 401 || response.status === 403) {
2709
+ session = await refreshSessionToken(session) ?? await bootstrapSession(true);
2710
+ if (!session) return { ok: false, id: "" };
2711
+ try {
2712
+ response = await sendAnnotationRequest(session, path, payload, idempotencyKey);
2713
+ } catch {
2714
+ return { ok: false, id: "" };
2715
+ }
2458
2716
  }
2459
- return res.json();
2717
+ return toClientResponse(response);
2460
2718
  }
2461
2719
  async function sendEditToAgent(edit) {
2462
- return postWithSessionToken("/api/edit", edit);
2720
+ return postWithSessionToken("/v1/annotations/edit", edit);
2463
2721
  }
2464
2722
  async function sendCommentToAgent(comment) {
2465
- return postWithSessionToken("/api/comment", comment);
2723
+ return postWithSessionToken("/v1/annotations/comment", comment);
2466
2724
  }
2467
2725
 
2468
2726
  // src/provider.tsx
@@ -4128,7 +4386,7 @@ function MeasurementOverlay({
4128
4386
  children: [
4129
4387
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ElementHighlight, { element: selectedElement, color: BLUE }),
4130
4388
  hoveredElement && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ElementHighlight, { element: hoveredElement, color: TOMATO, isDashed: true }),
4131
- measurements.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MeasurementLineComponent, { line }, i))
4389
+ measurements.map((line) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MeasurementLineComponent, { line }, `${line.direction}-${line.x1}-${line.y1}-${line.x2}-${line.y2}`))
4132
4390
  ]
4133
4391
  }
4134
4392
  );
@@ -4699,7 +4957,7 @@ function SelectionOverlay({
4699
4957
  onDoubleClick: handleDoubleClick,
4700
4958
  onMouseMove: handleMouseMove,
4701
4959
  onMouseLeave: handleMouseLeave,
4702
- children: moveHandleRects.map((targetRect, idx) => {
4960
+ children: moveHandleRects.map((targetRect) => {
4703
4961
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
4704
4962
  "button",
4705
4963
  {
@@ -4726,7 +4984,7 @@ function SelectionOverlay({
4726
4984
  },
4727
4985
  onPointerDown: handleMoveHandlePointerDown(targetRect.target)
4728
4986
  },
4729
- `${idx}-${targetRect.left}-${targetRect.top}`
4987
+ `${targetRect.left}-${targetRect.top}-${targetRect.width}-${targetRect.height}`
4730
4988
  );
4731
4989
  })
4732
4990
  }
@@ -4869,10 +5127,12 @@ function CommentPin({
4869
5127
  }
4870
5128
  ),
4871
5129
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4872
- "div",
5130
+ "button",
4873
5131
  {
5132
+ type: "button",
4874
5133
  "data-direct-edit": "comment-pin",
4875
- className: "group/pin fixed z-[99998] flex size-3 cursor-pointer items-center justify-center rounded-full bg-blue-500 shadow-md ring-2 ring-white transition-transform hover:scale-[1.67] hover:shadow-lg",
5134
+ "aria-label": `Comment ${index}`,
5135
+ className: "group/pin fixed z-[99998] flex size-3 cursor-pointer items-center justify-center rounded-full border-none bg-blue-500 p-0 shadow-md ring-2 ring-white transition-transform hover:scale-[1.67] hover:shadow-lg",
4876
5136
  style: {
4877
5137
  left: position.x - 6,
4878
5138
  top: position.y - 6,
@@ -4952,6 +5212,7 @@ function NewCommentInput({
4952
5212
  "div",
4953
5213
  {
4954
5214
  ref: cardRef,
5215
+ role: "presentation",
4955
5216
  "data-direct-edit": "comment-card",
4956
5217
  className: cn(
4957
5218
  "fixed z-[99999] flex items-center gap-1.5 rounded-xl outline outline-1 outline-foreground/10 bg-background p-1.5 shadow-lg",
@@ -5069,6 +5330,7 @@ function CommentThread({
5069
5330
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
5070
5331
  "div",
5071
5332
  {
5333
+ role: "presentation",
5072
5334
  "data-direct-edit": "comment-card",
5073
5335
  className: "fixed z-[99999] w-[280px] overflow-hidden rounded-xl outline outline-1 outline-foreground/10 bg-background shadow-lg",
5074
5336
  style: {
@@ -5166,13 +5428,13 @@ function CommentThread({
5166
5428
  ] }),
5167
5429
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-xs leading-relaxed text-foreground", children: comment.text })
5168
5430
  ] }),
5169
- comment.replies.map((reply, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "border-t border-border/30 px-3 py-2.5", children: [
5431
+ comment.replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "border-t border-border/30 px-3 py-2.5", children: [
5170
5432
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mb-1 flex items-center gap-2", children: [
5171
5433
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex size-5 shrink-0 items-center justify-center rounded-full bg-blue-500 text-[10px] font-bold text-white", children: index }),
5172
5434
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-[10px] text-muted-foreground", children: formatRelativeTime(reply.createdAt) })
5173
5435
  ] }),
5174
5436
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-xs leading-relaxed text-foreground", children: reply.text })
5175
- ] }, i))
5437
+ ] }, reply.createdAt))
5176
5438
  ] }),
5177
5439
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-1.5 border-t border-border/50 px-2 py-1.5", children: [
5178
5440
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
@@ -7675,6 +7937,7 @@ function DirectEditPanelContent() {
7675
7937
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
7676
7938
  "div",
7677
7939
  {
7940
+ role: "presentation",
7678
7941
  "data-direct-edit": "overlay",
7679
7942
  className: cn("fixed inset-0 z-[99990] cursor-default"),
7680
7943
  style: { pointerEvents: textEditingElement ? "none" : "auto" },
@@ -7756,7 +8019,7 @@ function DirectEditPanelContent() {
7756
8019
  strokeWidth: 1,
7757
8020
  strokeDasharray: "4 2"
7758
8021
  },
7759
- i
8022
+ `${r.left}-${r.top}-${r.width}-${r.height}`
7760
8023
  );
7761
8024
  })
7762
8025
  ]
@@ -8998,10 +9261,19 @@ ${text}`);
8998
9261
  return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
8999
9262
  "div",
9000
9263
  {
9264
+ role: "button",
9265
+ tabIndex: 0,
9001
9266
  className: "group flex cursor-pointer items-start justify-between rounded-md px-1.5 py-1.5 text-xs transition-colors hover:bg-muted/50",
9002
9267
  onClick: () => {
9003
9268
  void handleCopyItem(item);
9004
9269
  },
9270
+ onKeyDown: (e) => {
9271
+ if (e.target !== e.currentTarget) return;
9272
+ if (e.key === "Enter" || e.key === " ") {
9273
+ e.preventDefault();
9274
+ void handleCopyItem(item);
9275
+ }
9276
+ },
9005
9277
  children: [
9006
9278
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "min-w-0 flex flex-1 flex-col items-start gap-[4px]", children: [
9007
9279
  /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(Badge, { variant: "secondary", className: "h-6 shrink-0 px-1.5 text-xs", children: [
@@ -9033,7 +9305,7 @@ ${text}`);
9033
9305
  )
9034
9306
  ]
9035
9307
  },
9036
- i
9308
+ item.type === "comment" ? item.comment.id : `edit-${i}`
9037
9309
  );
9038
9310
  }) })
9039
9311
  ]
@@ -9121,7 +9393,7 @@ ${text}`);
9121
9393
  { label: "Back / Exit", keys: ["Esc"] }
9122
9394
  ].map(({ label, keys }) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex h-8 w-full items-center justify-between rounded-md px-2 text-xs text-muted-foreground", children: [
9123
9395
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { children: label }),
9124
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "flex items-center gap-0.5", children: keys.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("kbd", { className: popupKbdClass, children: k }, i)) })
9396
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "flex items-center gap-0.5", children: keys.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("kbd", { className: popupKbdClass, children: k }, typeof k === "string" ? k : i)) })
9125
9397
  ] }, label)) })
9126
9398
  ]
9127
9399
  }