(
j: any,
root: any,
ops: Array<{ index: number; op: BatchOperation }>,
resolvedPath: string,
)
| 242 | // ── Node resolution ────────────────────────────────────────────────────── |
| 243 | |
| 244 | function resolveNodes( |
| 245 | j: any, |
| 246 | root: any, |
| 247 | ops: Array<{ index: number; op: BatchOperation }>, |
| 248 | resolvedPath: string, |
| 249 | ): ResolvedOp[] { |
| 250 | const resolved: ResolvedOp[] = []; |
| 251 | |
| 252 | for (const { index, op } of ops) { |
| 253 | if (op.op === "reorder") { |
| 254 | // Reorder uses line-based resolution (handled during mutation) |
| 255 | resolved.push({ |
| 256 | index, |
| 257 | op, |
| 258 | node: null, // not needed — mutateReorder resolves internally |
| 259 | priority: 1, // structural |
| 260 | }); |
| 261 | continue; |
| 262 | } |
| 263 | |
| 264 | // ── Step 0: Try JSX structural path (preferred) ─────────────────── |
| 265 | if ('jsxPath' in op && op.jsxPath && op.jsxPath.segments.length > 0) { |
| 266 | const pathNode = resolveJSXPath(j, root, op.jsxPath); |
| 267 | if (pathNode) { |
| 268 | // Cross-validate tag name if hint is available |
| 269 | const actualTag = getJSXTagName(pathNode.node); |
| 270 | if (!op.tagName || !actualTag || tagNameMatches(actualTag, op.tagName)) { |
| 271 | const priority = op.op === "updateClass" || op.op === "updateText" || op.op === "moveSpacing" ? 0 : 1; |
| 272 | resolved.push({ index, op, node: pathNode, priority }); |
| 273 | continue; |
| 274 | } |
| 275 | } |
| 276 | // If path resolution failed, fall through to line:col + fuzzy (existing code) |
| 277 | } |
| 278 | |
| 279 | // ── Staleness check ────────────────────────────────────────────── |
| 280 | if (op.fileMtime != null && op.fileSize != null) { |
| 281 | try { |
| 282 | const stat = fs.statSync(resolvedPath); |
| 283 | const currentMtime = Math.floor(stat.mtimeMs); |
| 284 | const expectedMtime = Math.floor(op.fileMtime); |
| 285 | const currentSize = stat.size; |
| 286 | if (currentMtime !== expectedMtime || currentSize !== op.fileSize) { |
| 287 | resolved.push({ |
| 288 | index, |
| 289 | op, |
| 290 | node: null, |
| 291 | priority: 0, |
| 292 | error: `File has been modified since the overlay captured this element (stale). ` + |
| 293 | `Expected mtime=${op.fileMtime}/size=${op.fileSize}, got mtime=${currentMtime}/size=${currentSize}`, |
| 294 | }); |
| 295 | continue; |
| 296 | } |
| 297 | } catch { |
| 298 | // stat failed — proceed without staleness check |
| 299 | } |
| 300 | } |
| 301 |
no test coverage detected