@archie/devtools 0.0.12-beta → 0.0.13

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.
@@ -37,7 +37,7 @@ __export(client_exports, {
37
37
  module.exports = __toCommonJS(client_exports);
38
38
 
39
39
  // src/client/dnd/DndProvider.tsx
40
- var import_react18 = require("react");
40
+ var import_react19 = require("react");
41
41
 
42
42
  // node_modules/@dnd-kit/core/dist/core.esm.js
43
43
  var import_react3 = __toESM(require("react"));
@@ -4104,19 +4104,498 @@ function mergeInspectorTheme(theme) {
4104
4104
  };
4105
4105
  }
4106
4106
 
4107
+ // src/client/dnd/design-mode/structural-policy.ts
4108
+ var STRUCTURAL_COMPONENT_RE = /(?:Page|Provider|RootLayout)$/;
4109
+ var STRUCTURAL_TAGS = /* @__PURE__ */ new Set(["html", "body"]);
4110
+ function isStructuralComponentName(componentName) {
4111
+ return !!componentName && STRUCTURAL_COMPONENT_RE.test(componentName);
4112
+ }
4113
+ function isHighLevelStructuralElement(el) {
4114
+ if (!el) return false;
4115
+ if (el.hasAttribute("data-dnd-structural")) return true;
4116
+ const tag = el.tagName.toLowerCase();
4117
+ if (STRUCTURAL_TAGS.has(tag)) return true;
4118
+ if (el.id === "root") return true;
4119
+ return tag === "main" && (el.parentElement === document.body || el.parentElement?.id === "root");
4120
+ }
4121
+ function isStructuralScannedElement(scanned) {
4122
+ if (!scanned) return false;
4123
+ if (isHighLevelStructuralElement(scanned.element)) return true;
4124
+ return isStructuralComponentName(scanned.editorMeta?.componentName);
4125
+ }
4126
+ function isStructuralContainer(containerId, nodeMap) {
4127
+ return isStructuralScannedElement(nodeMap.get(containerId));
4128
+ }
4129
+ function isValidLaneContainer(containerId, projection, nodeMap) {
4130
+ const container = projection.containerIndex.get(containerId);
4131
+ if (!container) return false;
4132
+ if (container.children.length === 0) return false;
4133
+ return !isStructuralContainer(containerId, nodeMap);
4134
+ }
4135
+ function buildBlockedStructuralContainerIds(projection, nodeMap, sourceContainerId) {
4136
+ const blocked = /* @__PURE__ */ new Set();
4137
+ for (const containerId of projection.containerIndex.keys()) {
4138
+ if (containerId === sourceContainerId) continue;
4139
+ if (isStructuralContainer(containerId, nodeMap)) blocked.add(containerId);
4140
+ }
4141
+ return blocked;
4142
+ }
4143
+
4144
+ // src/client/dnd/design-mode/fiber-bridge.ts
4145
+ var FIBER_KEY_PREFIX = "__reactFiber$";
4146
+ var PROPS_KEY_PREFIX = "__reactProps$";
4147
+ var metaCache = /* @__PURE__ */ new WeakMap();
4148
+ var directMetaCache = /* @__PURE__ */ new WeakMap();
4149
+ function hasEditorMetaInFiberChain(fiber) {
4150
+ let current = fiber;
4151
+ while (current) {
4152
+ if (current.memoizedProps?.__editorMeta) return true;
4153
+ current = current.return;
4154
+ }
4155
+ return false;
4156
+ }
4157
+ function compareFiberCandidates(candidate, currentBest) {
4158
+ if (!currentBest) return 1;
4159
+ const candidateScore = [
4160
+ candidate.hasMatchingProps ? 1 : 0,
4161
+ candidate.hasDirectEditorMeta ? 1 : 0,
4162
+ candidate.hasCompanionPropsKey ? 1 : 0,
4163
+ candidate.hasEditorMetaInReturnChain ? 1 : 0,
4164
+ candidate.order
4165
+ ];
4166
+ const currentScore = [
4167
+ currentBest.hasMatchingProps ? 1 : 0,
4168
+ currentBest.hasDirectEditorMeta ? 1 : 0,
4169
+ currentBest.hasCompanionPropsKey ? 1 : 0,
4170
+ currentBest.hasEditorMetaInReturnChain ? 1 : 0,
4171
+ currentBest.order
4172
+ ];
4173
+ for (let i = 0; i < candidateScore.length; i += 1) {
4174
+ if (candidateScore[i] === currentScore[i]) continue;
4175
+ return candidateScore[i] > currentScore[i] ? 1 : -1;
4176
+ }
4177
+ return 0;
4178
+ }
4179
+ function getFiberSelectionFromElement(el) {
4180
+ const record = el;
4181
+ const keys = Object.keys(record);
4182
+ let bestCandidate = null;
4183
+ for (let i = 0; i < keys.length; i += 1) {
4184
+ const fiberKey = keys[i];
4185
+ if (!fiberKey.startsWith(FIBER_KEY_PREFIX)) continue;
4186
+ const fiber = record[fiberKey];
4187
+ if (!fiber) continue;
4188
+ if (fiber.stateNode && fiber.stateNode !== el) continue;
4189
+ const suffix = fiberKey.slice(FIBER_KEY_PREFIX.length);
4190
+ const propsKey = `${PROPS_KEY_PREFIX}${suffix}`;
4191
+ const hasCompanionPropsKey = Object.prototype.hasOwnProperty.call(record, propsKey);
4192
+ const companionProps = hasCompanionPropsKey ? record[propsKey] : void 0;
4193
+ const candidate = {
4194
+ fiber,
4195
+ fiberKey,
4196
+ hasMatchingProps: hasCompanionPropsKey && fiber.memoizedProps === companionProps,
4197
+ hasDirectEditorMeta: !!fiber.memoizedProps?.__editorMeta,
4198
+ hasCompanionPropsKey,
4199
+ hasEditorMetaInReturnChain: hasEditorMetaInFiberChain(fiber),
4200
+ // Tiebreaker: higher index in Object.keys = later React fiber attachment.
4201
+ // Relies on spec-guaranteed insertion-order for string keys (ES2015+).
4202
+ order: i
4203
+ };
4204
+ if (compareFiberCandidates(candidate, bestCandidate) > 0) {
4205
+ bestCandidate = candidate;
4206
+ }
4207
+ }
4208
+ if (!bestCandidate) {
4209
+ return { fiber: null, fiberKey: null };
4210
+ }
4211
+ return { fiber: bestCandidate.fiber, fiberKey: bestCandidate.fiberKey };
4212
+ }
4213
+ function readFiberAwareCache(cache, el, selection) {
4214
+ const cached = cache.get(el);
4215
+ if (!cached) return void 0;
4216
+ if (cached.fiber !== selection.fiber) return void 0;
4217
+ if (cached.fiberKey !== selection.fiberKey) return void 0;
4218
+ return cached.value;
4219
+ }
4220
+ function writeFiberAwareCache(cache, el, selection, value) {
4221
+ cache.set(el, {
4222
+ fiber: selection.fiber,
4223
+ fiberKey: selection.fiberKey,
4224
+ value
4225
+ });
4226
+ return value;
4227
+ }
4228
+ function isRootSvg(el) {
4229
+ return el instanceof SVGElement && el.tagName.toLowerCase() === "svg";
4230
+ }
4231
+ function isPassthroughMeta(meta) {
4232
+ return !!meta?.staticProps && meta.staticProps.asChild === true;
4233
+ }
4234
+ function isWrapperMeta(meta) {
4235
+ if (!meta?.componentName) return false;
4236
+ return meta.componentName[0] !== meta.componentName[0].toLowerCase();
4237
+ }
4238
+ function hasOwnTextContent(el) {
4239
+ for (const node of Array.from(el.childNodes)) {
4240
+ if (node.nodeType !== Node.TEXT_NODE) continue;
4241
+ if (node.textContent?.trim()) return true;
4242
+ }
4243
+ return false;
4244
+ }
4245
+ function getCandidateChildren(el) {
4246
+ return Array.from(el.children).filter((child) => {
4247
+ if (!(child instanceof HTMLElement)) return false;
4248
+ if (child.hasAttribute("data-design-mode-ui")) return false;
4249
+ if (child.hasAttribute("data-design-mode-allow")) return false;
4250
+ return !shouldIgnoreAsVirtualRuntime(child);
4251
+ });
4252
+ }
4253
+ function shouldIgnoreAsVirtualRuntime(el) {
4254
+ const tag = el.tagName.toLowerCase();
4255
+ if (tag === "canvas") return true;
4256
+ if (tag === "script" || tag === "style" || tag === "noscript") return true;
4257
+ if (el instanceof SVGElement && !isRootSvg(el)) return true;
4258
+ return false;
4259
+ }
4260
+ function getDirectEditorMeta(el) {
4261
+ const selection = getFiberSelectionFromElement(el);
4262
+ const cached = readFiberAwareCache(directMetaCache, el, selection);
4263
+ if (cached !== void 0) return cached;
4264
+ const meta = selection.fiber?.memoizedProps?.__editorMeta;
4265
+ return writeFiberAwareCache(directMetaCache, el, selection, meta ?? null);
4266
+ }
4267
+ function getEditorMeta(el) {
4268
+ const selection = getFiberSelectionFromElement(el);
4269
+ const cached = readFiberAwareCache(metaCache, el, selection);
4270
+ if (cached !== void 0) return cached;
4271
+ let fiber = selection.fiber;
4272
+ while (fiber) {
4273
+ const meta = fiber.memoizedProps?.__editorMeta;
4274
+ if (meta) {
4275
+ return writeFiberAwareCache(metaCache, el, selection, meta);
4276
+ }
4277
+ fiber = fiber.return;
4278
+ }
4279
+ return writeFiberAwareCache(metaCache, el, selection, null);
4280
+ }
4281
+ function findMetaOwnerElement(el) {
4282
+ let current = el;
4283
+ while (current) {
4284
+ if (getEditorMeta(current)) return current;
4285
+ current = current.parentElement;
4286
+ }
4287
+ return null;
4288
+ }
4289
+ function resolveHostElementFromFiberChain(el) {
4290
+ if (shouldIgnoreAsVirtualRuntime(el)) return null;
4291
+ const directMeta = getDirectEditorMeta(el);
4292
+ const ownerMeta = directMeta ?? getEditorMeta(el);
4293
+ if (isPassthroughMeta(ownerMeta)) {
4294
+ const children = getCandidateChildren(el);
4295
+ if (children.length !== 1) return null;
4296
+ return children[0];
4297
+ }
4298
+ if (directMeta && !isWrapperMeta(directMeta)) return el;
4299
+ const MAX_STEPS = 10;
4300
+ const ownerNodeId = ownerMeta?.nodeId ?? null;
4301
+ let outermost = el;
4302
+ let parent = el.parentElement;
4303
+ for (let i = 0; i < MAX_STEPS; i += 1) {
4304
+ if (!parent || parent === document.body) break;
4305
+ if (shouldIgnoreAsVirtualRuntime(parent)) break;
4306
+ const parentMeta = getEditorMeta(parent);
4307
+ if (!parentMeta) break;
4308
+ if (ownerNodeId && parentMeta.nodeId !== ownerNodeId) break;
4309
+ if (isStructuralComponentName(parentMeta.componentName)) break;
4310
+ if (!isWrapperMeta(parentMeta)) break;
4311
+ if (isPassthroughMeta(parentMeta)) break;
4312
+ if (hasOwnTextContent(parent)) break;
4313
+ const children = getCandidateChildren(parent);
4314
+ if (children.length !== 1) break;
4315
+ outermost = parent;
4316
+ parent = parent.parentElement;
4317
+ }
4318
+ return outermost;
4319
+ }
4320
+ function resolveDragSurface(el) {
4321
+ const host = resolveHostElementFromFiberChain(el);
4322
+ if (!host) {
4323
+ return { host: null, metaOwner: null, kind: "direct-host" };
4324
+ }
4325
+ const metaOwner = findMetaOwnerElement(host) ?? findMetaOwnerElement(el);
4326
+ const ownerMeta = metaOwner ? getEditorMeta(metaOwner) : null;
4327
+ const kind = isPassthroughMeta(ownerMeta) ? "passthrough-slot" : metaOwner && metaOwner !== host && isWrapperMeta(ownerMeta) ? "wrapper-to-host" : "direct-host";
4328
+ return {
4329
+ host,
4330
+ metaOwner,
4331
+ kind
4332
+ };
4333
+ }
4334
+
4335
+ // src/client/dnd/design-mode/node-key.ts
4336
+ var _runtimeKeyMap = /* @__PURE__ */ new WeakMap();
4337
+ var _runtimeKeyCounter = 0;
4338
+ function getRuntimeKey(el) {
4339
+ const existing = _runtimeKeyMap.get(el);
4340
+ if (existing) return existing;
4341
+ const key2 = `runtime:${++_runtimeKeyCounter}`;
4342
+ _runtimeKeyMap.set(el, key2);
4343
+ return key2;
4344
+ }
4345
+ function getNodeKeyFromElement(el) {
4346
+ const resolved = resolveDragSurface(el);
4347
+ const host = resolved.host ?? el;
4348
+ const meta = getEditorMeta(host);
4349
+ if (meta) return meta.nodeId;
4350
+ const persisted = host.getAttribute("data-dnd-node-id");
4351
+ if (persisted) return persisted;
4352
+ return getRuntimeKey(host);
4353
+ }
4354
+ function getInspectorRefFromElement(el) {
4355
+ const resolved = resolveDragSurface(el);
4356
+ const host = resolved.host ?? el;
4357
+ const meta = getEditorMeta(host);
4358
+ if (!meta) return null;
4359
+ return { relativePath: meta.file, line: meta.line, column: meta.col };
4360
+ }
4361
+
4362
+ // src/client/dnd/design-mode/arrange-persistence-diagnostics.ts
4363
+ function isSourceBackedRuntimeNodeKey(nodeKey) {
4364
+ return typeof nodeKey === "string" && nodeKey.trim().length > 0 && !nodeKey.startsWith("runtime:");
4365
+ }
4366
+ function mergeRuntimeArrangeDiagnostics(diagnosticsEntries) {
4367
+ const blockedReasons = /* @__PURE__ */ new Set();
4368
+ const notes = /* @__PURE__ */ new Set();
4369
+ let structurallyUnsafe = false;
4370
+ for (const diagnostics of diagnosticsEntries) {
4371
+ if (!diagnostics) continue;
4372
+ if (diagnostics.structurallyUnsafe === true) {
4373
+ structurallyUnsafe = true;
4374
+ }
4375
+ for (const reason of diagnostics.blockedReasons ?? []) {
4376
+ if (typeof reason === "string" && reason.trim().length > 0) {
4377
+ blockedReasons.add(reason);
4378
+ }
4379
+ }
4380
+ for (const note of diagnostics.notes ?? []) {
4381
+ if (typeof note === "string" && note.trim().length > 0) {
4382
+ notes.add(note);
4383
+ }
4384
+ }
4385
+ }
4386
+ if (!structurallyUnsafe && blockedReasons.size === 0 && notes.size === 0) {
4387
+ return null;
4388
+ }
4389
+ return {
4390
+ ...structurallyUnsafe ? { structurallyUnsafe: true } : {},
4391
+ ...blockedReasons.size > 0 ? { blockedReasons: Array.from(blockedReasons) } : {},
4392
+ ...notes.size > 0 ? { notes: Array.from(notes) } : {}
4393
+ };
4394
+ }
4395
+ function detectDuplicateSourceBackedNodeKeys(nodeKeys) {
4396
+ const seenKeys = /* @__PURE__ */ new Set();
4397
+ const duplicateKeys = /* @__PURE__ */ new Set();
4398
+ for (const nodeKey of nodeKeys) {
4399
+ if (!isSourceBackedRuntimeNodeKey(nodeKey)) {
4400
+ continue;
4401
+ }
4402
+ if (seenKeys.has(nodeKey)) {
4403
+ duplicateKeys.add(nodeKey);
4404
+ continue;
4405
+ }
4406
+ seenKeys.add(nodeKey);
4407
+ }
4408
+ return Array.from(duplicateKeys);
4409
+ }
4410
+ function buildOccurrenceNodeId(nodeKey, occurrenceIndex) {
4411
+ return `${nodeKey}::${occurrenceIndex}`;
4412
+ }
4413
+ function buildEffectiveNodeIdsByCommitNodeId(commitNodeIds, nodeKeysByCommitNodeId) {
4414
+ const countsByNodeKey = /* @__PURE__ */ new Map();
4415
+ for (const commitNodeId of commitNodeIds) {
4416
+ const nodeKey = nodeKeysByCommitNodeId.get(commitNodeId);
4417
+ if (!isSourceBackedRuntimeNodeKey(nodeKey)) {
4418
+ continue;
4419
+ }
4420
+ countsByNodeKey.set(nodeKey, (countsByNodeKey.get(nodeKey) ?? 0) + 1);
4421
+ }
4422
+ const seenByNodeKey = /* @__PURE__ */ new Map();
4423
+ const effectiveNodeIdsByCommitNodeId = /* @__PURE__ */ new Map();
4424
+ for (const commitNodeId of commitNodeIds) {
4425
+ const nodeKey = nodeKeysByCommitNodeId.get(commitNodeId);
4426
+ if (!isSourceBackedRuntimeNodeKey(nodeKey) || (countsByNodeKey.get(nodeKey) ?? 0) < 2) {
4427
+ effectiveNodeIdsByCommitNodeId.set(commitNodeId, null);
4428
+ continue;
4429
+ }
4430
+ const occurrenceIndex = seenByNodeKey.get(nodeKey) ?? 0;
4431
+ seenByNodeKey.set(nodeKey, occurrenceIndex + 1);
4432
+ effectiveNodeIdsByCommitNodeId.set(
4433
+ commitNodeId,
4434
+ buildOccurrenceNodeId(nodeKey, occurrenceIndex)
4435
+ );
4436
+ }
4437
+ return effectiveNodeIdsByCommitNodeId;
4438
+ }
4439
+ function createCommitLaneIdentitySnapshot(params) {
4440
+ return {
4441
+ laneContainerId: params.laneContainerId,
4442
+ commitNodeIds: [...params.commitNodeIds],
4443
+ nodeKeysByCommitNodeId: new Map(params.nodeKeysByCommitNodeId),
4444
+ effectiveNodeIdsByCommitNodeId: buildEffectiveNodeIdsByCommitNodeId(
4445
+ params.commitNodeIds,
4446
+ params.nodeKeysByCommitNodeId
4447
+ ),
4448
+ committable: true
4449
+ };
4450
+ }
4451
+ function hasSafeOccurrenceIdentities(lane, duplicateNodeKey) {
4452
+ const effectiveNodeIds = lane.commitNodeIds.filter(
4453
+ (commitNodeId) => lane.nodeKeysByCommitNodeId.get(commitNodeId) === duplicateNodeKey
4454
+ ).map(
4455
+ (commitNodeId) => lane.effectiveNodeIdsByCommitNodeId.get(commitNodeId) ?? null
4456
+ );
4457
+ return effectiveNodeIds.length > 1 && effectiveNodeIds.every(
4458
+ (effectiveNodeId) => typeof effectiveNodeId === "string" && effectiveNodeId.length > 0
4459
+ ) && new Set(effectiveNodeIds).size === effectiveNodeIds.length;
4460
+ }
4461
+ function buildLanePersistenceDiagnostics(params) {
4462
+ const duplicateNodeKeys = detectDuplicateSourceBackedNodeKeys(
4463
+ buildNodeKeysFromCommitIds(
4464
+ params.lane.commitNodeIds,
4465
+ params.lane.nodeKeysByCommitNodeId
4466
+ )
4467
+ );
4468
+ if (duplicateNodeKeys.length === 0) {
4469
+ return null;
4470
+ }
4471
+ const safeDuplicateNodeKeys = duplicateNodeKeys.filter(
4472
+ (nodeKey) => hasSafeOccurrenceIdentities(params.lane, nodeKey)
4473
+ );
4474
+ if (safeDuplicateNodeKeys.length === duplicateNodeKeys.length) {
4475
+ return {
4476
+ notes: safeDuplicateNodeKeys.map(
4477
+ (nodeKey) => `Arrange lane ${params.lane.laneContainerId} repeats source-backed nodeKey ${nodeKey}, but runtime assigned explicit occurrence identities for each commit root.`
4478
+ )
4479
+ };
4480
+ }
4481
+ return {
4482
+ structurallyUnsafe: true,
4483
+ blockedReasons: duplicateNodeKeys.map(
4484
+ (nodeKey) => `Arrange lane ${params.lane.laneContainerId} contains multiple commit roots with the same source-backed nodeKey (${nodeKey}), so local save is unsafe.`
4485
+ ),
4486
+ notes: [
4487
+ `Lane ${params.lane.laneContainerId} duplicates source-backed nodeKeys: ${duplicateNodeKeys.join(", ")}`
4488
+ ]
4489
+ };
4490
+ }
4491
+ function removeCommitNodeId(commitNodeIds, commitNodeId) {
4492
+ return commitNodeIds.filter((candidate) => candidate !== commitNodeId);
4493
+ }
4494
+ function buildNodeKeysFromCommitIds(commitNodeIds, nodeKeysByCommitNodeId) {
4495
+ return commitNodeIds.map(
4496
+ (commitNodeId) => nodeKeysByCommitNodeId.get(commitNodeId) ?? null
4497
+ );
4498
+ }
4499
+ function captureCommitLaneIdentitySnapshot(lane, resolveNodeElement) {
4500
+ if (!lane?.committable) return null;
4501
+ const nodeKeysByCommitNodeId = /* @__PURE__ */ new Map();
4502
+ for (const commitNodeId of lane.orderedCommitNodeIds) {
4503
+ const element = resolveNodeElement(commitNodeId);
4504
+ if (!element) return null;
4505
+ nodeKeysByCommitNodeId.set(commitNodeId, getNodeKeyFromElement(element));
4506
+ }
4507
+ return createCommitLaneIdentitySnapshot({
4508
+ laneContainerId: lane.laneContainerId,
4509
+ nodeKeysByCommitNodeId,
4510
+ commitNodeIds: lane.orderedCommitNodeIds
4511
+ });
4512
+ }
4513
+ function projectCommitNodeIdsForMove(params) {
4514
+ const withoutMovedCommit = removeCommitNodeId(
4515
+ params.commitNodeIds,
4516
+ params.movedCommitNodeId
4517
+ );
4518
+ if (typeof params.targetIndex !== "number" || !Number.isFinite(params.targetIndex)) {
4519
+ return [...withoutMovedCommit, params.movedCommitNodeId];
4520
+ }
4521
+ const insertionIndex = Math.max(
4522
+ 0,
4523
+ Math.min(params.targetIndex, withoutMovedCommit.length)
4524
+ );
4525
+ return [
4526
+ ...withoutMovedCommit.slice(0, insertionIndex),
4527
+ params.movedCommitNodeId,
4528
+ ...withoutMovedCommit.slice(insertionIndex)
4529
+ ];
4530
+ }
4531
+ function buildMovePersistenceDiagnostics(params) {
4532
+ const {
4533
+ sourceLane,
4534
+ targetLane,
4535
+ movedCommitNodeId,
4536
+ sourceLaneContainerId,
4537
+ targetLaneContainerId,
4538
+ targetIndex
4539
+ } = params;
4540
+ const sameLane = !!sourceLaneContainerId && !!targetLaneContainerId && sourceLaneContainerId === targetLaneContainerId;
4541
+ const targetSnapshot = sameLane ? targetLane ?? sourceLane : targetLane;
4542
+ const sourceLaneAfter = !sameLane && sourceLane?.committable ? createCommitLaneIdentitySnapshot({
4543
+ laneContainerId: sourceLane.laneContainerId,
4544
+ commitNodeIds: removeCommitNodeId(
4545
+ sourceLane.commitNodeIds,
4546
+ movedCommitNodeId
4547
+ ),
4548
+ nodeKeysByCommitNodeId: sourceLane.nodeKeysByCommitNodeId
4549
+ }) : null;
4550
+ const targetLaneAfter = targetSnapshot?.committable ? createCommitLaneIdentitySnapshot({
4551
+ laneContainerId: targetSnapshot.laneContainerId,
4552
+ commitNodeIds: projectCommitNodeIdsForMove({
4553
+ commitNodeIds: targetSnapshot.commitNodeIds,
4554
+ movedCommitNodeId,
4555
+ targetIndex
4556
+ }),
4557
+ nodeKeysByCommitNodeId: new Map([
4558
+ ...targetSnapshot.nodeKeysByCommitNodeId,
4559
+ ...sourceLane?.nodeKeysByCommitNodeId.has(movedCommitNodeId) ? [
4560
+ [
4561
+ movedCommitNodeId,
4562
+ sourceLane.nodeKeysByCommitNodeId.get(movedCommitNodeId) ?? null
4563
+ ]
4564
+ ] : []
4565
+ ])
4566
+ }) : null;
4567
+ return mergeRuntimeArrangeDiagnostics([
4568
+ sourceLaneAfter ? buildLanePersistenceDiagnostics({ lane: sourceLaneAfter }) : null,
4569
+ targetLaneAfter ? buildLanePersistenceDiagnostics({ lane: targetLaneAfter }) : null
4570
+ ]);
4571
+ }
4572
+
4107
4573
  // src/client/dnd/design-mode/store.ts
4108
4574
  function summarizeEditorChangeEvent(event) {
4575
+ if (event.kind === "insert") {
4576
+ return `insert ${event.displayName} at ${event.containerId}[${event.index}]`;
4577
+ }
4109
4578
  if (event.kind === "move") {
4110
4579
  const selectedHint = event.selectedNodeKey && event.selectedNodeKey !== event.nodeKey ? ` (selected ${event.selectedNodeKey})` : "";
4111
4580
  const anchorHint = event.anchorNodeKey && event.anchorNodeKey !== event.nodeKey ? ` (anchor ${event.anchorNodeKey})` : "";
4112
- const commandHint = event.commandType ? ` ${event.commandType}` : "";
4113
- return `move${commandHint} ${event.nodeKey}: parent ${event.payload.before.parentNodeId}[${event.payload.before.index}] \u2192 ${event.payload.after.parentNodeId}[${event.payload.after.index}]${event.payload.after.commitNodeId ? ` (commit ${event.payload.after.commitNodeId}` : ""}${event.payload.after.beforeNodeId ? ` before ${event.payload.after.beforeNodeId}` : ""}${event.payload.after.afterNodeId ? ` after ${event.payload.after.afterNodeId}` : ""}${event.payload.after.commitNodeId ? ")" : ""}${selectedHint}${anchorHint}`;
4581
+ const beforeParentNodeId = event.payload.before.effectiveContainerNodeId ?? event.payload.before.parentNodeId;
4582
+ const afterParentNodeId = event.payload.after.effectiveContainerNodeId ?? event.payload.after.parentNodeId;
4583
+ const commitNodeId = event.payload.after.effectiveCommitNodeId ?? event.payload.after.commitNodeId;
4584
+ const beforeNodeId = event.payload.after.effectiveBeforeNodeId ?? event.payload.after.beforeNodeId;
4585
+ const afterNodeId = event.payload.after.effectiveAfterNodeId ?? event.payload.after.afterNodeId;
4586
+ return `move ${event.nodeKey}: parent ${beforeParentNodeId}[${event.payload.before.index}] \u2192 ${afterParentNodeId}[${event.payload.after.index}]${commitNodeId ? ` (commit ${commitNodeId}` : ""}${beforeNodeId ? ` before ${beforeNodeId}` : ""}${afterNodeId ? ` after ${afterNodeId}` : ""}${commitNodeId ? ")" : ""}${selectedHint}${anchorHint}`;
4114
4587
  }
4115
4588
  if (event.payload.after.mode === "grid-span") {
4116
4589
  return `resize(grid-span) ${event.nodeKey}: col ${event.payload.before.colSpan ?? 1}\u2192${event.payload.after.colSpan ?? 1}, row ${event.payload.before.rowSpan ?? 1}\u2192${event.payload.after.rowSpan ?? 1}`;
4117
4590
  }
4118
4591
  return `resize(px) ${event.nodeKey}: ${event.payload.before.width}\xD7${event.payload.before.height} \u2192 ${event.payload.after.width}\xD7${event.payload.after.height}`;
4119
4592
  }
4593
+ function buildFallbackOperationId(now, nodeId) {
4594
+ return `${now}:${nodeId}`;
4595
+ }
4596
+ function inferMoveCommandType(before, after) {
4597
+ return before.parentNodeId === after.parentNodeId ? "reorder" : "moveCrossContainer";
4598
+ }
4120
4599
  function rebuildNodeEdit(orderedChanges, nodeKey) {
4121
4600
  let latestMove;
4122
4601
  let latestResize;
@@ -4131,11 +4610,36 @@ function rebuildNodeEdit(orderedChanges, nodeKey) {
4131
4610
  nodeId = ev.nodeId;
4132
4611
  inspectorRef = ev.inspectorRef;
4133
4612
  if (ev.kind === "move") latestMove = ev.payload;
4134
- else latestResize = ev.payload;
4613
+ else if (ev.kind === "resize") latestResize = ev.payload;
4135
4614
  }
4136
4615
  if (count === 0) return null;
4137
4616
  return { nodeKey, nodeId, inspectorRef, latestMove, latestResize, changeCount: count, lastUpdatedAt: lastAt };
4138
4617
  }
4618
+ function coalesceOrderedChanges(changes) {
4619
+ if (changes.length <= 1) return changes;
4620
+ const result = [];
4621
+ for (const event of changes) {
4622
+ const prev = result[result.length - 1];
4623
+ if (prev && prev.kind === "resize" && event.kind === "resize" && prev.nodeKey === event.nodeKey) {
4624
+ result[result.length - 1] = {
4625
+ ...event,
4626
+ payload: { before: prev.payload.before, after: event.payload.after }
4627
+ };
4628
+ } else {
4629
+ result.push(event);
4630
+ }
4631
+ }
4632
+ return result;
4633
+ }
4634
+ function buildEmptyArrangeSessionState() {
4635
+ return {
4636
+ nodeEditsByKey: {},
4637
+ orderedChanges: [],
4638
+ undoneChanges: [],
4639
+ canUndo: false,
4640
+ canRedo: false
4641
+ };
4642
+ }
4139
4643
  var useDesignModeStore = create((set, get) => ({
4140
4644
  // UI state
4141
4645
  enabled: false,
@@ -4151,8 +4655,10 @@ var useDesignModeStore = create((set, get) => ({
4151
4655
  nodeEditsByKey: {},
4152
4656
  orderedChanges: [],
4153
4657
  undoneChanges: [],
4658
+ // External drag
4659
+ externalDrag: null,
4154
4660
  // UI actions
4155
- toggle: () => set((s) => ({ enabled: !s.enabled })),
4661
+ toggle: () => set((s) => ({ enabled: !s.enabled, ...buildEmptyArrangeSessionState() })),
4156
4662
  setEnabled: (enabled) => set({ enabled }),
4157
4663
  setHovered: (selector) => set({ hoveredSelector: selector }),
4158
4664
  setSelected: (selector) => set({ selectedSelector: selector }),
@@ -4167,6 +4673,7 @@ var useDesignModeStore = create((set, get) => ({
4167
4673
  canUndo: false,
4168
4674
  canRedo: false
4169
4675
  }),
4676
+ resetArrangeSession: () => set(buildEmptyArrangeSessionState()),
4170
4677
  // Change tracking actions
4171
4678
  recordMoveChange: ({
4172
4679
  operationId,
@@ -4179,18 +4686,24 @@ var useDesignModeStore = create((set, get) => ({
4179
4686
  selectedInspectorRef,
4180
4687
  anchorNodeKey,
4181
4688
  anchorNodeId,
4689
+ sourceContainer,
4690
+ sourceBeforeSibling,
4691
+ sourceAfterSibling,
4182
4692
  container,
4183
4693
  beforeSibling,
4184
4694
  afterSibling,
4695
+ diagnostics,
4185
4696
  componentName,
4186
4697
  before,
4187
4698
  after
4188
4699
  }) => {
4189
4700
  const now = Date.now();
4701
+ const normalizedOperationId = operationId ?? buildFallbackOperationId(now, nodeId);
4702
+ const normalizedCommandType = commandType ?? inferMoveCommandType(before, after);
4190
4703
  const event = {
4191
4704
  kind: "move",
4192
- operationId: operationId ?? `${now}:${nodeId}`,
4193
- commandType: commandType ?? "reorder",
4705
+ operationId: normalizedOperationId,
4706
+ commandType: normalizedCommandType,
4194
4707
  nodeKey,
4195
4708
  nodeId,
4196
4709
  inspectorRef,
@@ -4199,9 +4712,13 @@ var useDesignModeStore = create((set, get) => ({
4199
4712
  selectedInspectorRef,
4200
4713
  anchorNodeKey,
4201
4714
  anchorNodeId,
4715
+ sourceContainer: sourceContainer ?? null,
4716
+ sourceBeforeSibling: sourceBeforeSibling ?? null,
4717
+ sourceAfterSibling: sourceAfterSibling ?? null,
4202
4718
  container: container ?? null,
4203
4719
  beforeSibling: beforeSibling ?? null,
4204
4720
  afterSibling: afterSibling ?? null,
4721
+ diagnostics: diagnostics ?? null,
4205
4722
  payload: { before, after },
4206
4723
  at: now
4207
4724
  };
@@ -4224,11 +4741,20 @@ var useDesignModeStore = create((set, get) => ({
4224
4741
  };
4225
4742
  });
4226
4743
  },
4227
- recordResizeChange: ({ operationId, nodeKey, nodeId, inspectorRef, componentName, before, after }) => {
4744
+ recordResizeChange: ({
4745
+ operationId,
4746
+ nodeKey,
4747
+ nodeId,
4748
+ inspectorRef,
4749
+ componentName,
4750
+ before,
4751
+ after
4752
+ }) => {
4228
4753
  const now = Date.now();
4754
+ const normalizedOperationId = operationId ?? buildFallbackOperationId(now, nodeId);
4229
4755
  const event = {
4230
4756
  kind: "resize",
4231
- operationId: operationId ?? `${now}:${nodeId}`,
4757
+ operationId: normalizedOperationId,
4232
4758
  nodeKey,
4233
4759
  nodeId,
4234
4760
  inspectorRef,
@@ -4254,6 +4780,52 @@ var useDesignModeStore = create((set, get) => ({
4254
4780
  };
4255
4781
  });
4256
4782
  },
4783
+ recordInsertChange: ({
4784
+ operationId,
4785
+ nodeKey,
4786
+ nodeId,
4787
+ inspectorRef,
4788
+ displayName,
4789
+ html,
4790
+ containerId,
4791
+ containerNodeKey,
4792
+ index
4793
+ }) => {
4794
+ const now = Date.now();
4795
+ const normalizedOperationId = operationId ?? buildFallbackOperationId(now, nodeId);
4796
+ const event = {
4797
+ kind: "insert",
4798
+ operationId: normalizedOperationId,
4799
+ nodeKey,
4800
+ nodeId,
4801
+ inspectorRef,
4802
+ displayName,
4803
+ html,
4804
+ containerId,
4805
+ containerNodeKey,
4806
+ index,
4807
+ at: now
4808
+ };
4809
+ set((s) => {
4810
+ const prev = s.nodeEditsByKey[nodeKey];
4811
+ const meta = {
4812
+ nodeKey,
4813
+ nodeId,
4814
+ inspectorRef,
4815
+ latestMove: prev?.latestMove,
4816
+ latestResize: prev?.latestResize,
4817
+ changeCount: (prev?.changeCount ?? 0) + 1,
4818
+ lastUpdatedAt: now
4819
+ };
4820
+ return {
4821
+ orderedChanges: [...s.orderedChanges, event],
4822
+ nodeEditsByKey: { ...s.nodeEditsByKey, [nodeKey]: meta },
4823
+ undoneChanges: []
4824
+ };
4825
+ });
4826
+ },
4827
+ startExternalDrag: (component) => set({ externalDrag: component }),
4828
+ cancelExternalDrag: () => set({ externalDrag: null }),
4257
4829
  undoLastChange: () => {
4258
4830
  const { orderedChanges, undoneChanges } = get();
4259
4831
  if (orderedChanges.length === 0) return null;
@@ -4279,7 +4851,8 @@ var useDesignModeStore = create((set, get) => ({
4279
4851
  },
4280
4852
  clearChanges: () => set({ orderedChanges: [], undoneChanges: [], nodeEditsByKey: {} }),
4281
4853
  exportChangesForAI: () => {
4282
- const { orderedChanges, nodeEditsByKey } = get();
4854
+ const { orderedChanges: rawChanges, nodeEditsByKey } = get();
4855
+ const orderedChanges = coalesceOrderedChanges(rawChanges);
4283
4856
  const changesByFile = {};
4284
4857
  for (const event of orderedChanges) {
4285
4858
  const fileKey = event.inspectorRef?.relativePath ?? "__runtime__";
@@ -4293,180 +4866,127 @@ var useDesignModeStore = create((set, get) => ({
4293
4866
  lines.push(` ${summarizeEditorChangeEvent(ev)}`);
4294
4867
  }
4295
4868
  }
4869
+ const diagnostics = mergeRuntimeArrangeDiagnostics(
4870
+ orderedChanges.map(
4871
+ (event) => event.kind === "move" ? event.diagnostics : null
4872
+ )
4873
+ );
4296
4874
  return {
4297
4875
  changesByFile,
4298
4876
  finalSnapshot: { ...nodeEditsByKey },
4299
- aiPromptContext: lines.join("\n")
4877
+ aiPromptContext: lines.join("\n"),
4878
+ ...diagnostics ? { diagnostics } : {}
4300
4879
  };
4301
4880
  }
4302
4881
  }));
4303
4882
 
4304
4883
  // src/client/dnd/design-mode/DesignModeOverlay.tsx
4305
- var import_react16 = require("react");
4884
+ var import_react17 = require("react");
4306
4885
 
4307
4886
  // src/client/dnd/design-mode/useElementScanner.ts
4308
4887
  var import_react6 = require("react");
4309
4888
 
4310
- // src/client/dnd/design-mode/structural-policy.ts
4311
- var STRUCTURAL_COMPONENT_RE = /(?:Layout|Page|Provider|App)$/;
4312
- var STRUCTURAL_TAGS = /* @__PURE__ */ new Set(["html", "body"]);
4313
- function isStructuralComponentName(componentName) {
4314
- return !!componentName && STRUCTURAL_COMPONENT_RE.test(componentName);
4889
+ // src/client/dnd/design-mode/helpers.ts
4890
+ var DND_NODE_ID_ATTR = "data-dnd-node-id";
4891
+ var PAGE_EDGE_GUARD = 10;
4892
+ var HARD_PAGE_EDGE_GUARD = 4;
4893
+ var SAFE_ZONE_INSET = 8;
4894
+ function clamp(value, min, max) {
4895
+ return Math.max(min, Math.min(max, value));
4315
4896
  }
4316
- function isHighLevelStructuralElement(el) {
4317
- if (!el) return false;
4318
- const tag = el.tagName.toLowerCase();
4319
- if (STRUCTURAL_TAGS.has(tag)) return true;
4320
- if (el.id === "root") return true;
4321
- return tag === "main" && (el.parentElement === document.body || el.parentElement?.id === "root");
4897
+ function buildNodeSelector(nodeId) {
4898
+ return `[${DND_NODE_ID_ATTR}="${escapeCssAttrValue(nodeId)}"]`;
4322
4899
  }
4323
- function isStructuralScannedElement(scanned) {
4324
- if (!scanned) return false;
4325
- if (isHighLevelStructuralElement(scanned.element)) return true;
4326
- return isStructuralComponentName(scanned.editorMeta?.componentName);
4900
+ var LARGE_NODE_NEST_BLOCK_MIN_WIDTH = 520;
4901
+ var LARGE_NODE_NEST_BLOCK_MIN_HEIGHT = 260;
4902
+ var LARGE_NODE_NEST_BLOCK_MIN_AREA = 18e4;
4903
+ function isEligibleForDrag(sc) {
4904
+ const source = sc.eligibilitySource ?? "editor-meta";
4905
+ return source === "editor-meta" || source === "force-allow" || source === "inferred-sortable-descendant";
4327
4906
  }
4328
- function isStructuralContainer(containerId, nodeMap) {
4329
- return isStructuralScannedElement(nodeMap.get(containerId));
4907
+ function isPointerInEdgeZone(px, py, guard) {
4908
+ return px <= guard || py <= guard || px >= window.innerWidth - guard || py >= window.innerHeight - guard;
4330
4909
  }
4331
- function isValidLaneContainer(containerId, projection, nodeMap) {
4332
- const container = projection.containerIndex.get(containerId);
4333
- if (!container) return false;
4334
- if (container.children.length < 2) return false;
4335
- return !isStructuralContainer(containerId, nodeMap);
4910
+ function isPointerInPageEdgeZone(px, py) {
4911
+ return isPointerInEdgeZone(px, py, PAGE_EDGE_GUARD);
4336
4912
  }
4337
- function buildBlockedStructuralContainerIds(projection, nodeMap, sourceContainerId) {
4338
- const blocked = /* @__PURE__ */ new Set();
4339
- for (const containerId of projection.containerIndex.keys()) {
4340
- if (containerId === sourceContainerId) continue;
4341
- if (isStructuralContainer(containerId, nodeMap)) blocked.add(containerId);
4342
- }
4343
- return blocked;
4913
+ function isPointerInHardPageEdgeZone(px, py) {
4914
+ return isPointerInEdgeZone(px, py, HARD_PAGE_EDGE_GUARD);
4344
4915
  }
4345
-
4346
- // src/client/dnd/design-mode/fiber-bridge.ts
4347
- var metaCache = /* @__PURE__ */ new WeakMap();
4348
- var directMetaCache = /* @__PURE__ */ new WeakMap();
4349
- function getFiberFromElement(el) {
4350
- const keys = Object.keys(el);
4351
- for (let i = 0; i < keys.length; i++) {
4352
- if (keys[i].startsWith("__reactFiber$")) {
4353
- return el[keys[i]];
4354
- }
4355
- }
4356
- return null;
4916
+ function resolveSafeZoneRect() {
4917
+ const root = document.getElementById("root");
4918
+ if (!root) return null;
4919
+ const rect = root.getBoundingClientRect();
4920
+ const left = rect.left + SAFE_ZONE_INSET;
4921
+ const top = rect.top + SAFE_ZONE_INSET;
4922
+ const right = rect.right - SAFE_ZONE_INSET;
4923
+ const bottom = rect.bottom - SAFE_ZONE_INSET;
4924
+ if (right <= left || bottom <= top) return null;
4925
+ return { left, top, right, bottom };
4357
4926
  }
4358
- function isRootSvg(el) {
4359
- return el instanceof SVGElement && el.tagName.toLowerCase() === "svg";
4927
+ function isPointerInSafeZone(px, py, safeZone) {
4928
+ if (!safeZone) return true;
4929
+ return px >= safeZone.left && px <= safeZone.right && py >= safeZone.top && py <= safeZone.bottom;
4360
4930
  }
4361
- function isPassthroughMeta(meta) {
4362
- return !!meta?.staticProps && meta.staticProps.asChild === true;
4931
+ function isPointerOutsideSafeZone(px, py, safeZone) {
4932
+ return !isPointerInSafeZone(px, py, safeZone);
4363
4933
  }
4364
- function isWrapperMeta(meta) {
4365
- if (!meta?.componentName) return false;
4366
- return meta.componentName[0] !== meta.componentName[0].toLowerCase();
4934
+ function isPointerBlocked(px, py, safeZone) {
4935
+ return isPointerInHardPageEdgeZone(px, py) || isPointerOutsideSafeZone(px, py, safeZone);
4367
4936
  }
4368
- function hasOwnTextContent(el) {
4369
- for (const node of Array.from(el.childNodes)) {
4370
- if (node.nodeType !== Node.TEXT_NODE) continue;
4371
- if (node.textContent?.trim()) return true;
4937
+ function shouldBlockNestForLargeNode(w, h) {
4938
+ return w >= LARGE_NODE_NEST_BLOCK_MIN_WIDTH || h >= LARGE_NODE_NEST_BLOCK_MIN_HEIGHT || w * h >= LARGE_NODE_NEST_BLOCK_MIN_AREA;
4939
+ }
4940
+ function findScrollableAncestor(start, dx, dy) {
4941
+ let el = start;
4942
+ while (el && el !== document.body) {
4943
+ const cs = getComputedStyle(el);
4944
+ if (dy !== 0 && (cs.overflowY === "auto" || cs.overflowY === "scroll") && el.scrollHeight > el.clientHeight + 1) return el;
4945
+ if (dx !== 0 && (cs.overflowX === "auto" || cs.overflowX === "scroll") && el.scrollWidth > el.clientWidth + 1) return el;
4946
+ el = el.parentElement;
4372
4947
  }
4373
- return false;
4948
+ return null;
4374
4949
  }
4375
- function getCandidateChildren(el) {
4376
- return Array.from(el.children).filter((child) => {
4377
- if (!(child instanceof HTMLElement)) return false;
4378
- if (child.hasAttribute("data-design-mode-ui")) return false;
4379
- if (child.hasAttribute("data-design-mode-allow")) return false;
4380
- return !shouldIgnoreAsVirtualRuntime(child);
4381
- });
4950
+ function escapeCssAttrValue(value) {
4951
+ const esc = globalThis.CSS?.escape;
4952
+ return esc ? esc(value) : value.replace(/["\\]/g, "\\$&");
4382
4953
  }
4383
- function shouldIgnoreAsVirtualRuntime(el) {
4384
- const tag = el.tagName.toLowerCase();
4385
- if (tag === "canvas") return true;
4386
- if (tag === "script" || tag === "style" || tag === "noscript") return true;
4387
- if (el instanceof SVGElement && !isRootSvg(el)) return true;
4388
- return false;
4954
+ function isDesignModeUiElement(el) {
4955
+ if (!(el instanceof HTMLElement)) return false;
4956
+ return el.hasAttribute("data-design-mode-ui") || !!el.closest("[data-design-mode-ui]");
4389
4957
  }
4390
- function getDirectEditorMeta(el) {
4391
- const cached = directMetaCache.get(el);
4392
- if (cached !== void 0) return cached;
4393
- const fiber = getFiberFromElement(el);
4394
- const meta = fiber?.memoizedProps?.__editorMeta;
4395
- const resolved = meta ?? null;
4396
- directMetaCache.set(el, resolved);
4397
- return resolved;
4958
+ function isElementScrollable(el) {
4959
+ if (!(el instanceof HTMLElement)) return false;
4960
+ const cs = getComputedStyle(el);
4961
+ const overY = cs.overflowY;
4962
+ const overX = cs.overflowX;
4963
+ return (overY === "auto" || overY === "scroll") && el.scrollHeight > el.clientHeight || (overX === "auto" || overX === "scroll") && el.scrollWidth > el.clientWidth;
4398
4964
  }
4399
- function getEditorMeta(el) {
4400
- const cached = metaCache.get(el);
4401
- if (cached !== void 0) return cached;
4402
- let fiber = getFiberFromElement(el);
4403
- while (fiber) {
4404
- const meta = fiber.memoizedProps?.__editorMeta;
4405
- if (meta) {
4406
- metaCache.set(el, meta);
4407
- return meta;
4965
+ function resolveScrollableFromHitStack(stack) {
4966
+ for (const hit of stack) {
4967
+ if (isDesignModeUiElement(hit)) continue;
4968
+ let el = hit;
4969
+ while (el) {
4970
+ if (isElementScrollable(el)) return el;
4971
+ el = el.parentElement;
4408
4972
  }
4409
- fiber = fiber.return;
4410
4973
  }
4411
- metaCache.set(el, null);
4412
4974
  return null;
4413
4975
  }
4414
- function findMetaOwnerElement(el) {
4415
- let current = el;
4416
- while (current) {
4417
- if (getEditorMeta(current)) return current;
4418
- current = current.parentElement;
4419
- }
4420
- return null;
4421
- }
4422
- function resolveHostElementFromFiberChain(el) {
4423
- if (shouldIgnoreAsVirtualRuntime(el)) return null;
4424
- const directMeta = getDirectEditorMeta(el);
4425
- const ownerMeta = directMeta ?? getEditorMeta(el);
4426
- if (isPassthroughMeta(ownerMeta)) {
4427
- const children = getCandidateChildren(el);
4428
- if (children.length !== 1) return null;
4429
- return children[0];
4430
- }
4431
- if (directMeta && !isWrapperMeta(directMeta)) return el;
4432
- const MAX_STEPS = 10;
4433
- const ownerNodeId = ownerMeta?.nodeId ?? null;
4434
- let outermost = el;
4435
- let parent = el.parentElement;
4436
- for (let i = 0; i < MAX_STEPS; i += 1) {
4437
- if (!parent || parent === document.body) break;
4438
- if (shouldIgnoreAsVirtualRuntime(parent)) break;
4439
- const parentMeta = getEditorMeta(parent);
4440
- if (!parentMeta) break;
4441
- if (ownerNodeId && parentMeta.nodeId !== ownerNodeId) break;
4442
- if (isStructuralComponentName(parentMeta.componentName)) break;
4443
- if (!isWrapperMeta(parentMeta)) break;
4444
- if (isPassthroughMeta(parentMeta)) break;
4445
- if (hasOwnTextContent(parent)) break;
4446
- const children = getCandidateChildren(parent);
4447
- if (children.length !== 1) break;
4448
- outermost = parent;
4449
- parent = parent.parentElement;
4450
- }
4451
- return outermost;
4452
- }
4453
- function resolveDragSurface(el) {
4454
- const host = resolveHostElementFromFiberChain(el);
4455
- if (!host) {
4456
- return { host: null, metaOwner: null, kind: "direct-host" };
4976
+ var _undoParentNodeIdMap = /* @__PURE__ */ new WeakMap();
4977
+ var _undoParentNodeIdCounter = 0;
4978
+ function ensureParentNodeId(el) {
4979
+ const existing = el.getAttribute(DND_NODE_ID_ATTR);
4980
+ if (existing) return existing;
4981
+ if (!_undoParentNodeIdMap.has(el)) {
4982
+ _undoParentNodeIdMap.set(el, `dnd-parent:${++_undoParentNodeIdCounter}`);
4457
4983
  }
4458
- const metaOwner = findMetaOwnerElement(host) ?? findMetaOwnerElement(el);
4459
- const ownerMeta = metaOwner ? getEditorMeta(metaOwner) : null;
4460
- const kind = isPassthroughMeta(ownerMeta) ? "passthrough-slot" : metaOwner && metaOwner !== host && isWrapperMeta(ownerMeta) ? "wrapper-to-host" : "direct-host";
4461
- return {
4462
- host,
4463
- metaOwner,
4464
- kind
4465
- };
4984
+ const id = _undoParentNodeIdMap.get(el);
4985
+ el.setAttribute(DND_NODE_ID_ATTR, id);
4986
+ return id;
4466
4987
  }
4467
4988
 
4468
4989
  // src/client/dnd/design-mode/useElementScanner.ts
4469
- var DND_NODE_ID_ATTR = "data-dnd-node-id";
4470
4990
  function resolveContainerStrategy(el) {
4471
4991
  const cs = getComputedStyle(el);
4472
4992
  const d = cs.display;
@@ -4497,7 +5017,7 @@ function getRuntimeNodeId(el) {
4497
5017
  }
4498
5018
  function getEditorMetaNodeId(el) {
4499
5019
  if (!(el instanceof HTMLElement)) return null;
4500
- return getEditorMeta(el)?.nodeId ?? null;
5020
+ return getDirectEditorMeta(el)?.nodeId ?? null;
4501
5021
  }
4502
5022
  function isInspectorEligible(el) {
4503
5023
  if (getEditorMetaNodeId(el) !== null) return "editor-meta";
@@ -4521,7 +5041,7 @@ function inferEligibilityFromSortableParent(el) {
4521
5041
  if (child.hasAttribute("data-design-mode-ui")) return false;
4522
5042
  return !isRuntimeVisualInternal(child);
4523
5043
  });
4524
- if (siblingElements.length < 2) return null;
5044
+ if (siblingElements.length < 1) return null;
4525
5045
  return "inferred-sortable-descendant";
4526
5046
  }
4527
5047
  function ensureUniqueNodeId(base, used) {
@@ -5075,7 +5595,15 @@ function buildProjection(elements, rectOverrides) {
5075
5595
  }
5076
5596
  const containerIndex = /* @__PURE__ */ new Map();
5077
5597
  for (const node of nodeMap.values()) {
5078
- if (node.children.length === 0) continue;
5598
+ if (node.children.length === 0) {
5599
+ const scanInfo = elementIndex.get(node.id);
5600
+ const eligible = scanInfo?.editorMeta || scanInfo?.eligibilitySource === "force-allow";
5601
+ if (!eligible) continue;
5602
+ node.sortable = true;
5603
+ node.strategy = "vertical";
5604
+ containerIndex.set(node.id, node);
5605
+ continue;
5606
+ }
5079
5607
  node.sortable = true;
5080
5608
  const firstChild = elementIndex.get(node.children[0]);
5081
5609
  node.strategy = firstChild?.containerStrategy ?? "vertical";
@@ -5144,33 +5672,27 @@ function buildSlotIndex(containerIndex, nodeMap) {
5144
5672
  }
5145
5673
  function buildVerticalSlotRect(children, nodeMap, cRect, i, n) {
5146
5674
  const top = i === 0 ? cRect.top : nodeMap.get(children[i - 1])?.rect.bottom ?? cRect.top;
5147
- const bottom = i === n ? cRect.bottom : nodeMap.get(children[i])?.rect.top ?? cRect.bottom;
5148
- const height = Math.max(0, bottom - top);
5149
- return {
5150
- x: cRect.left,
5151
- y: top,
5152
- left: cRect.left,
5153
- top,
5154
- right: cRect.right,
5155
- bottom: top + height,
5156
- width: cRect.width,
5157
- height
5158
- };
5675
+ const bottom = i === n ? cRect.bottom : nodeMap.get(children[i])?.rect.top ?? cRect.bottom;
5676
+ const height = Math.max(0, bottom - top);
5677
+ const prevRect = i > 0 ? nodeMap.get(children[i - 1])?.rect : null;
5678
+ const nextRect = i < n ? nodeMap.get(children[i])?.rect : null;
5679
+ const refRect = prevRect ?? nextRect;
5680
+ const left = refRect ? refRect.left : cRect.left;
5681
+ const right = refRect ? refRect.right : cRect.right;
5682
+ const width = Math.max(0, right - left);
5683
+ return { x: left, y: top, left, top, right, bottom: top + height, width, height };
5159
5684
  }
5160
5685
  function buildHorizontalSlotRect(children, nodeMap, cRect, i, n) {
5161
5686
  const left = i === 0 ? cRect.left : nodeMap.get(children[i - 1])?.rect.right ?? cRect.left;
5162
5687
  const right = i === n ? cRect.right : nodeMap.get(children[i])?.rect.left ?? cRect.right;
5163
5688
  const width = Math.max(0, right - left);
5164
- return {
5165
- x: left,
5166
- y: cRect.top,
5167
- left,
5168
- top: cRect.top,
5169
- right: left + width,
5170
- bottom: cRect.bottom,
5171
- width,
5172
- height: cRect.height
5173
- };
5689
+ const prevRect = i > 0 ? nodeMap.get(children[i - 1])?.rect : null;
5690
+ const nextRect = i < n ? nodeMap.get(children[i])?.rect : null;
5691
+ const refRect = prevRect ?? nextRect;
5692
+ const top = refRect ? refRect.top : cRect.top;
5693
+ const bottom = refRect ? refRect.bottom : cRect.bottom;
5694
+ const height = Math.max(0, bottom - top);
5695
+ return { x: left, y: top, left, top, right: left + width, bottom, width, height };
5174
5696
  }
5175
5697
  function buildRectSlotRect(children, nodeMap, cRect, i, n, rows) {
5176
5698
  const findRow = (childIdx) => rows.find((r) => childIdx >= r.startIdx && childIdx <= r.endIdx) ?? null;
@@ -5234,6 +5756,79 @@ function buildRectSlotRect(children, nodeMap, cRect, i, n, rows) {
5234
5756
  };
5235
5757
  }
5236
5758
 
5759
+ // src/client/dnd/design-mode/history.ts
5760
+ function applyInsertCommand(command, direction, resolveNodeElement) {
5761
+ if (direction === "undo") {
5762
+ const el = resolveNodeElement(command.nodeId);
5763
+ if (el?.isConnected) el.remove();
5764
+ return true;
5765
+ }
5766
+ const container = resolveNodeElement(command.containerId);
5767
+ if (!container) return false;
5768
+ const existing = resolveNodeElement(command.nodeId);
5769
+ if (existing?.isConnected) return true;
5770
+ const template = document.createElement("template");
5771
+ template.innerHTML = command.html.trim();
5772
+ const newEl = template.content.firstElementChild;
5773
+ if (!newEl) return false;
5774
+ newEl.setAttribute("data-dnd-node-id", command.nodeId);
5775
+ newEl.setAttribute("data-dnd-force-allow", "");
5776
+ newEl.setAttribute("data-dnd-moved", "");
5777
+ const kids = Array.from(container.children).filter(
5778
+ (c) => c instanceof HTMLElement
5779
+ );
5780
+ container.insertBefore(newEl, kids[command.index] ?? null);
5781
+ return true;
5782
+ }
5783
+ var DEFAULT_LIMIT = 300;
5784
+ var DndCommandHistory = class {
5785
+ constructor(limit = DEFAULT_LIMIT) {
5786
+ this.limit = limit;
5787
+ __publicField(this, "undoStack", []);
5788
+ __publicField(this, "redoStack", []);
5789
+ }
5790
+ execute(command, apply) {
5791
+ const applied = apply(command, "redo");
5792
+ if (!applied) return false;
5793
+ this.undoStack.push(command);
5794
+ if (this.undoStack.length > this.limit) this.undoStack.shift();
5795
+ this.redoStack = [];
5796
+ return true;
5797
+ }
5798
+ undo(apply) {
5799
+ const command = this.undoStack.pop();
5800
+ if (!command) return false;
5801
+ const applied = apply(command, "undo");
5802
+ if (!applied) {
5803
+ this.undoStack.push(command);
5804
+ return false;
5805
+ }
5806
+ this.redoStack.push(command);
5807
+ return true;
5808
+ }
5809
+ redo(apply) {
5810
+ const command = this.redoStack.pop();
5811
+ if (!command) return false;
5812
+ const applied = apply(command, "redo");
5813
+ if (!applied) {
5814
+ this.redoStack.push(command);
5815
+ return false;
5816
+ }
5817
+ this.undoStack.push(command);
5818
+ return true;
5819
+ }
5820
+ clear() {
5821
+ this.undoStack = [];
5822
+ this.redoStack = [];
5823
+ }
5824
+ canUndo() {
5825
+ return this.undoStack.length > 0;
5826
+ }
5827
+ canRedo() {
5828
+ return this.redoStack.length > 0;
5829
+ }
5830
+ };
5831
+
5237
5832
  // src/client/dnd/design-mode/resize.ts
5238
5833
  function splitTopLevelSpaceSeparated(value) {
5239
5834
  const tokens = [];
@@ -5265,9 +5860,6 @@ function parseSpanValue(value) {
5265
5860
  const parsed = Number.parseInt(match[1], 10);
5266
5861
  return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
5267
5862
  }
5268
- function clamp(value, min, max) {
5269
- return Math.max(min, Math.min(max, value));
5270
- }
5271
5863
  function resolveResizeTargetElement(selectedEl) {
5272
5864
  const elementChildren = Array.from(selectedEl.children).filter(
5273
5865
  (child2) => child2 instanceof HTMLElement
@@ -5348,100 +5940,6 @@ function applyStyleSnapshot(el, snapshot) {
5348
5940
  }
5349
5941
  }
5350
5942
 
5351
- // src/client/dnd/design-mode/helpers.ts
5352
- var DND_NODE_ID_ATTR2 = "data-dnd-node-id";
5353
- var PAGE_EDGE_GUARD = 10;
5354
- var HARD_PAGE_EDGE_GUARD = 4;
5355
- var SAFE_ZONE_INSET = 8;
5356
- var LARGE_NODE_NEST_BLOCK_MIN_WIDTH = 520;
5357
- var LARGE_NODE_NEST_BLOCK_MIN_HEIGHT = 260;
5358
- var LARGE_NODE_NEST_BLOCK_MIN_AREA = 18e4;
5359
- function isEligibleForDrag(sc) {
5360
- const source = sc.eligibilitySource ?? "editor-meta";
5361
- return source === "editor-meta" || source === "force-allow" || source === "inferred-sortable-descendant";
5362
- }
5363
- function isPointerInPageEdgeZone(px, py) {
5364
- return px <= PAGE_EDGE_GUARD || py <= PAGE_EDGE_GUARD || px >= window.innerWidth - PAGE_EDGE_GUARD || py >= window.innerHeight - PAGE_EDGE_GUARD;
5365
- }
5366
- function isPointerInHardPageEdgeZone(px, py) {
5367
- return px <= HARD_PAGE_EDGE_GUARD || py <= HARD_PAGE_EDGE_GUARD || px >= window.innerWidth - HARD_PAGE_EDGE_GUARD || py >= window.innerHeight - HARD_PAGE_EDGE_GUARD;
5368
- }
5369
- function resolveSafeZoneRect() {
5370
- const root = document.getElementById("root");
5371
- if (!root) return null;
5372
- const rect = root.getBoundingClientRect();
5373
- const left = rect.left + SAFE_ZONE_INSET;
5374
- const top = rect.top + SAFE_ZONE_INSET;
5375
- const right = rect.right - SAFE_ZONE_INSET;
5376
- const bottom = rect.bottom - SAFE_ZONE_INSET;
5377
- if (right <= left || bottom <= top) return null;
5378
- return { left, top, right, bottom };
5379
- }
5380
- function isPointerInSafeZone(px, py, safeZone) {
5381
- if (!safeZone) return true;
5382
- return px >= safeZone.left && px <= safeZone.right && py >= safeZone.top && py <= safeZone.bottom;
5383
- }
5384
- function isPointerOutsideSafeZone(px, py, safeZone) {
5385
- return !isPointerInSafeZone(px, py, safeZone);
5386
- }
5387
- function isPointerBlocked(px, py, safeZone) {
5388
- return isPointerInHardPageEdgeZone(px, py) || isPointerOutsideSafeZone(px, py, safeZone);
5389
- }
5390
- function shouldBlockNestForLargeNode(w, h) {
5391
- return w >= LARGE_NODE_NEST_BLOCK_MIN_WIDTH || h >= LARGE_NODE_NEST_BLOCK_MIN_HEIGHT || w * h >= LARGE_NODE_NEST_BLOCK_MIN_AREA;
5392
- }
5393
- function findScrollableAncestor(start, dx, dy) {
5394
- let el = start;
5395
- while (el && el !== document.body) {
5396
- const cs = getComputedStyle(el);
5397
- if (dy !== 0 && (cs.overflowY === "auto" || cs.overflowY === "scroll") && el.scrollHeight > el.clientHeight + 1) return el;
5398
- if (dx !== 0 && (cs.overflowX === "auto" || cs.overflowX === "scroll") && el.scrollWidth > el.clientWidth + 1) return el;
5399
- el = el.parentElement;
5400
- }
5401
- return null;
5402
- }
5403
- function escapeCssAttrValue(value) {
5404
- const esc = globalThis.CSS?.escape;
5405
- return esc ? esc(value) : value.replace(/["\\]/g, "\\$&");
5406
- }
5407
- function isDesignModeUiElement(el) {
5408
- if (!(el instanceof HTMLElement)) return false;
5409
- return el.hasAttribute("data-design-mode-ui") || !!el.closest("[data-design-mode-ui]");
5410
- }
5411
- function isElementScrollable(el) {
5412
- if (!(el instanceof HTMLElement)) return false;
5413
- const cs = getComputedStyle(el);
5414
- const overY = cs.overflowY;
5415
- const overX = cs.overflowX;
5416
- return (overY === "auto" || overY === "scroll") && el.scrollHeight > el.clientHeight || (overX === "auto" || overX === "scroll") && el.scrollWidth > el.clientWidth;
5417
- }
5418
- function resolveScrollableFromHitStack(stack) {
5419
- for (const hit of stack) {
5420
- if (isDesignModeUiElement(hit)) continue;
5421
- let el = hit;
5422
- while (el) {
5423
- if (isElementScrollable(el)) return el;
5424
- el = el.parentElement;
5425
- }
5426
- }
5427
- return null;
5428
- }
5429
- var _undoParentNodeIdMap = /* @__PURE__ */ new WeakMap();
5430
- var _undoParentNodeIdCounter = 0;
5431
- function ensureParentNodeId(el) {
5432
- const existing = el.getAttribute(DND_NODE_ID_ATTR2);
5433
- if (existing) return existing;
5434
- if (!_undoParentNodeIdMap.has(el)) {
5435
- _undoParentNodeIdMap.set(el, `dnd-parent:${++_undoParentNodeIdCounter}`);
5436
- }
5437
- const id = _undoParentNodeIdMap.get(el);
5438
- el.setAttribute(DND_NODE_ID_ATTR2, id);
5439
- return id;
5440
- }
5441
- function ensureElementNodeId(el) {
5442
- return ensureParentNodeId(el);
5443
- }
5444
-
5445
5943
  // src/client/dnd/design-mode/drag-preview.tsx
5446
5944
  var import_react9 = require("react");
5447
5945
  var import_jsx_runtime3 = require("react/jsx-runtime");
@@ -5542,7 +6040,7 @@ function findNearestContainerId(nodeId, projection, nodeMap, allowStructuralFall
5542
6040
  const parentId = node?.parentId ?? null;
5543
6041
  if (!parentId) break;
5544
6042
  const parentContainer = projection.containerIndex.get(parentId);
5545
- if (parentContainer && parentContainer.children.length >= 2) {
6043
+ if (parentContainer && parentContainer.children.length >= 1) {
5546
6044
  if (isValidLaneContainer(parentId, projection, nodeMap)) return parentId;
5547
6045
  if (allowStructuralFallback && !structuralFallback) structuralFallback = parentId;
5548
6046
  }
@@ -5627,12 +6125,6 @@ function findDirectChildUnderParent(child, parent) {
5627
6125
  }
5628
6126
  return null;
5629
6127
  }
5630
- function isContiguous(indices) {
5631
- if (indices.length === 0) return false;
5632
- const min = Math.min(...indices);
5633
- const max = Math.max(...indices);
5634
- return max - min + 1 === indices.length;
5635
- }
5636
6128
  function createPlacement(parentNodeId, laneContainerId, commitNodeId, beforeNodeId, afterNodeId, index) {
5637
6129
  return {
5638
6130
  parentNodeId,
@@ -5646,7 +6138,16 @@ function createPlacement(parentNodeId, laneContainerId, commitNodeId, beforeNode
5646
6138
  function buildCommitLaneSnapshot(laneContainerId, projection, nodeMap) {
5647
6139
  const lane = projection.containerIndex.get(laneContainerId);
5648
6140
  const laneElement = nodeMap.get(laneContainerId)?.element ?? null;
5649
- if (!lane || lane.children.length === 0 || !laneElement) return null;
6141
+ if (!lane || !laneElement) return null;
6142
+ if (lane.children.length === 0) {
6143
+ return {
6144
+ laneContainerId,
6145
+ commitParentNodeId: ensureParentNodeId(laneElement),
6146
+ orderedCommitNodeIds: [],
6147
+ commitNodeByAnchorId: /* @__PURE__ */ new Map(),
6148
+ committable: true
6149
+ };
6150
+ }
5650
6151
  const childElements = lane.children.map((childId) => nodeMap.get(childId)?.element ?? null).filter((el) => !!el);
5651
6152
  if (childElements.length !== lane.children.length) return null;
5652
6153
  const commitParent = findLowestCommonAncestor(childElements, laneElement);
@@ -5663,19 +6164,18 @@ function buildCommitLaneSnapshot(laneContainerId, projection, nodeMap) {
5663
6164
  if (seenRoots.has(commitRoot)) return null;
5664
6165
  seenRoots.add(commitRoot);
5665
6166
  commitRoots.push(commitRoot);
5666
- commitNodeByAnchorId.set(anchorNodeId, ensureElementNodeId(commitRoot));
6167
+ commitNodeByAnchorId.set(anchorNodeId, ensureParentNodeId(commitRoot));
5667
6168
  }
5668
6169
  const childList = Array.from(commitParent.children).filter(
5669
6170
  (child) => child instanceof HTMLElement
5670
6171
  );
5671
6172
  const commitRootIndices = commitRoots.map((root) => childList.indexOf(root));
5672
6173
  if (commitRootIndices.some((index) => index < 0)) return null;
5673
- if (!isContiguous(commitRootIndices)) return null;
5674
6174
  const domOrderedRoots = [...commitRoots].sort(
5675
6175
  (a, b) => childList.indexOf(a) - childList.indexOf(b)
5676
6176
  );
5677
- const projectionOrderedIds = commitRoots.map((root) => ensureElementNodeId(root));
5678
- const domOrderedIds = domOrderedRoots.map((root) => ensureElementNodeId(root));
6177
+ const projectionOrderedIds = commitRoots.map((root) => ensureParentNodeId(root));
6178
+ const domOrderedIds = domOrderedRoots.map((root) => ensureParentNodeId(root));
5679
6179
  if (projectionOrderedIds.length !== domOrderedIds.length) return null;
5680
6180
  if (projectionOrderedIds.some((id, index) => id !== domOrderedIds[index])) return null;
5681
6181
  return {
@@ -5720,7 +6220,7 @@ function resolveAppendCommitPlacement(parentEl, commitNodeId, laneContainerId) {
5720
6220
  const children = Array.from(parentEl.children).filter(
5721
6221
  (child) => child instanceof HTMLElement
5722
6222
  );
5723
- const lastChild = children.length > 0 ? ensureElementNodeId(children[children.length - 1]) : null;
6223
+ const lastChild = children.length > 0 ? ensureParentNodeId(children[children.length - 1]) : null;
5724
6224
  return createPlacement(
5725
6225
  ensureParentNodeId(parentEl),
5726
6226
  laneContainerId,
@@ -5775,6 +6275,58 @@ function createDesignModeCollisionDetection(blockedContainerIds) {
5775
6275
  };
5776
6276
  }
5777
6277
 
6278
+ // src/client/dnd/design-mode/arrange-session.ts
6279
+ function serializeArrangeChange(event, index) {
6280
+ return {
6281
+ id: `${event.kind}:${event.operationId}:${index}`,
6282
+ kind: event.kind,
6283
+ operationId: event.operationId,
6284
+ commandType: event.kind === "move" ? event.commandType : void 0,
6285
+ nodeKey: event.nodeKey,
6286
+ nodeId: event.nodeId,
6287
+ inspectorRef: event.inspectorRef,
6288
+ selectedNodeKey: event.kind === "move" ? event.selectedNodeKey : void 0,
6289
+ selectedNodeId: event.kind === "move" ? event.selectedNodeId : void 0,
6290
+ selectedInspectorRef: event.kind === "move" ? event.selectedInspectorRef : void 0,
6291
+ anchorNodeKey: event.kind === "move" ? event.anchorNodeKey : void 0,
6292
+ anchorNodeId: event.kind === "move" ? event.anchorNodeId : void 0,
6293
+ container: event.kind === "move" ? event.container : void 0,
6294
+ beforeSibling: event.kind === "move" ? event.beforeSibling : void 0,
6295
+ afterSibling: event.kind === "move" ? event.afterSibling : void 0,
6296
+ sourceContainer: event.kind === "move" ? event.sourceContainer : void 0,
6297
+ sourceBeforeSibling: event.kind === "move" ? event.sourceBeforeSibling : void 0,
6298
+ sourceAfterSibling: event.kind === "move" ? event.sourceAfterSibling : void 0,
6299
+ diagnostics: event.kind === "move" ? event.diagnostics : void 0,
6300
+ // Insert-specific fields
6301
+ displayName: event.kind === "insert" ? event.displayName : void 0,
6302
+ html: event.kind === "insert" ? event.html : void 0,
6303
+ containerId: event.kind === "insert" ? event.containerId : void 0,
6304
+ containerNodeKey: event.kind === "insert" ? event.containerNodeKey : void 0,
6305
+ insertIndex: event.kind === "insert" ? event.index : void 0,
6306
+ summary: summarizeEditorChangeEvent(event),
6307
+ at: event.at
6308
+ };
6309
+ }
6310
+ function buildArrangeSessionSnapshot(params) {
6311
+ const { enabled, aiOutput } = params;
6312
+ const coalescedChanges = coalesceOrderedChanges(params.orderedChanges);
6313
+ const updatedAt = typeof params.updatedAt === "number" ? params.updatedAt : Date.now();
6314
+ return {
6315
+ mode: enabled ? "arrange" : "off",
6316
+ canUndo: params.orderedChanges.length > 0,
6317
+ changes: coalescedChanges.map(
6318
+ (event, index) => serializeArrangeChange(event, index)
6319
+ ),
6320
+ diagnostics: aiOutput.diagnostics ?? null,
6321
+ output: {
6322
+ json: aiOutput,
6323
+ prompt: aiOutput.aiPromptContext,
6324
+ summary: coalescedChanges.length === 0 ? "No arrange changes recorded yet." : `${coalescedChanges.length} arrange change${coalescedChanges.length === 1 ? "" : "s"} recorded.`
6325
+ },
6326
+ updatedAt
6327
+ };
6328
+ }
6329
+
5778
6330
  // src/client/dnd/design-mode/useDesignModeAutoScroll.ts
5779
6331
  var import_react10 = require("react");
5780
6332
  function edgeVelocity(distanceToEdge, zone, maxSpeed) {
@@ -5790,11 +6342,16 @@ function findScrollableAtPoint(x, y) {
5790
6342
  return resolveScrollableFromHitStack(stack) ?? (document.scrollingElement ?? document.documentElement);
5791
6343
  }
5792
6344
  function getScrollerKey(el) {
5793
- return el.getAttribute?.(DND_NODE_ID_ATTR2) ?? el.id ?? el.tagName.toLowerCase();
6345
+ return el.getAttribute?.(DND_NODE_ID_ATTR) ?? el.id ?? el.tagName.toLowerCase();
5794
6346
  }
5795
6347
  function pointerInsideRect(x, y, rect, inset = 0) {
5796
6348
  return x >= rect.left - inset && x <= rect.right + inset && y >= rect.top - inset && y <= rect.bottom + inset;
5797
6349
  }
6350
+ var AUTO_SCROLL_ZONE_PX = 120;
6351
+ var AUTO_SCROLL_MAX_SPEED = 24;
6352
+ var SCROLLER_RECT_RECALC_INTERVAL = 8;
6353
+ var SCROLLER_SWITCH_FRAMES = 3;
6354
+ var SCROLLER_EDGE_HYSTERESIS_PX = 18;
5798
6355
  function useDesignModeAutoScroll({
5799
6356
  active,
5800
6357
  mouseRef,
@@ -5803,11 +6360,6 @@ function useDesignModeAutoScroll({
5803
6360
  }) {
5804
6361
  (0, import_react10.useEffect)(() => {
5805
6362
  if (!active) return;
5806
- const ZONE = 120;
5807
- const MAX = 24;
5808
- const RECALC_INTERVAL = 8;
5809
- const SWITCH_FRAMES = 3;
5810
- const SCROLLER_EDGE_HYSTERESIS = 18;
5811
6363
  let scrollEl = findScrollableAtPoint(mouseRef.current.x, mouseRef.current.y);
5812
6364
  let cachedRect = scrollEl.getBoundingClientRect();
5813
6365
  let rectAge = 0;
@@ -5815,17 +6367,17 @@ function useDesignModeAutoScroll({
5815
6367
  const tick = () => {
5816
6368
  const { x, y } = mouseRef.current;
5817
6369
  const safeZone = safeZoneRef.current;
5818
- if (++rectAge > RECALC_INTERVAL) {
6370
+ if (++rectAge > SCROLLER_RECT_RECALC_INTERVAL) {
5819
6371
  const candidate = findScrollableAtPoint(x, y);
5820
6372
  const candidateKey = getScrollerKey(candidate);
5821
6373
  const currentKey = getScrollerKey(scrollEl);
5822
6374
  const currentRect = scrollEl.getBoundingClientRect();
5823
- if (candidateKey !== currentKey && !pointerInsideRect(x, y, currentRect, SCROLLER_EDGE_HYSTERESIS)) {
6375
+ if (candidateKey !== currentKey && !pointerInsideRect(x, y, currentRect, SCROLLER_EDGE_HYSTERESIS_PX)) {
5824
6376
  const pendingKey = autoScrollStateRef.current.pendingScrollerKey;
5825
6377
  const pendingFrames = pendingKey === candidateKey ? (autoScrollStateRef.current.pendingFrames ?? 0) + 1 : 1;
5826
6378
  autoScrollStateRef.current.pendingScrollerKey = candidateKey;
5827
6379
  autoScrollStateRef.current.pendingFrames = pendingFrames;
5828
- if (pendingFrames >= SWITCH_FRAMES) {
6380
+ if (pendingFrames >= SCROLLER_SWITCH_FRAMES) {
5829
6381
  scrollEl = candidate;
5830
6382
  cachedRect = candidate.getBoundingClientRect();
5831
6383
  autoScrollStateRef.current.pendingScrollerKey = null;
@@ -5834,98 +6386,52 @@ function useDesignModeAutoScroll({
5834
6386
  } else {
5835
6387
  scrollEl = candidateKey === currentKey ? scrollEl : candidate;
5836
6388
  cachedRect = scrollEl.getBoundingClientRect();
5837
- autoScrollStateRef.current.pendingScrollerKey = null;
5838
- autoScrollStateRef.current.pendingFrames = 0;
5839
- }
5840
- rectAge = 0;
5841
- }
5842
- if (isPointerInPageEdgeZone(x, y) || isPointerOutsideSafeZone(x, y, safeZone)) {
5843
- autoScrollStateRef.current = {
5844
- ...autoScrollStateRef.current,
5845
- scrollerKey: null,
5846
- vx: 0,
5847
- vy: 0
5848
- };
5849
- raf = requestAnimationFrame(tick);
5850
- return;
5851
- }
5852
- const dTop = y - cachedRect.top;
5853
- const dBot = cachedRect.bottom - y;
5854
- let dy = 0;
5855
- if (dTop > 0 && dTop < ZONE) dy = -edgeVelocity(dTop, ZONE, MAX);
5856
- else if (dBot > 0 && dBot < ZONE) dy = edgeVelocity(dBot, ZONE, MAX);
5857
- const dLeft = x - cachedRect.left;
5858
- const dRight = cachedRect.right - x;
5859
- let dx = 0;
5860
- if (dLeft > 0 && dLeft < ZONE) dx = -edgeVelocity(dLeft, ZONE, MAX);
5861
- else if (dRight > 0 && dRight < ZONE) dx = edgeVelocity(dRight, ZONE, MAX);
5862
- if (dy !== 0) scrollEl.scrollTop += dy;
5863
- if (dx !== 0) scrollEl.scrollLeft += dx;
5864
- autoScrollStateRef.current = {
5865
- ...autoScrollStateRef.current,
5866
- scrollerKey: getScrollerKey(scrollEl),
5867
- vx: dx,
5868
- vy: dy
5869
- };
5870
- raf = requestAnimationFrame(tick);
5871
- };
5872
- raf = requestAnimationFrame(tick);
5873
- return () => cancelAnimationFrame(raf);
5874
- }, [active, autoScrollStateRef, mouseRef, safeZoneRef]);
5875
- }
5876
-
5877
- // src/client/dnd/design-mode/useOverlayRefs.ts
5878
- var import_react11 = require("react");
5879
-
5880
- // src/client/dnd/design-mode/history.ts
5881
- var DEFAULT_LIMIT = 300;
5882
- var DndCommandHistory = class {
5883
- constructor(limit = DEFAULT_LIMIT) {
5884
- this.limit = limit;
5885
- __publicField(this, "undoStack", []);
5886
- __publicField(this, "redoStack", []);
5887
- }
5888
- execute(command, apply) {
5889
- const applied = apply(command, "redo");
5890
- if (!applied) return false;
5891
- this.undoStack.push(command);
5892
- if (this.undoStack.length > this.limit) this.undoStack.shift();
5893
- this.redoStack = [];
5894
- return true;
5895
- }
5896
- undo(apply) {
5897
- const command = this.undoStack.pop();
5898
- if (!command) return false;
5899
- const applied = apply(command, "undo");
5900
- if (!applied) {
5901
- this.undoStack.push(command);
5902
- return false;
5903
- }
5904
- this.redoStack.push(command);
5905
- return true;
5906
- }
5907
- redo(apply) {
5908
- const command = this.redoStack.pop();
5909
- if (!command) return false;
5910
- const applied = apply(command, "redo");
5911
- if (!applied) {
5912
- this.redoStack.push(command);
5913
- return false;
5914
- }
5915
- this.undoStack.push(command);
5916
- return true;
5917
- }
5918
- clear() {
5919
- this.undoStack = [];
5920
- this.redoStack = [];
5921
- }
5922
- canUndo() {
5923
- return this.undoStack.length > 0;
5924
- }
5925
- canRedo() {
5926
- return this.redoStack.length > 0;
5927
- }
5928
- };
6389
+ autoScrollStateRef.current.pendingScrollerKey = null;
6390
+ autoScrollStateRef.current.pendingFrames = 0;
6391
+ }
6392
+ rectAge = 0;
6393
+ }
6394
+ if (isPointerInPageEdgeZone(x, y) || isPointerOutsideSafeZone(x, y, safeZone)) {
6395
+ autoScrollStateRef.current = {
6396
+ ...autoScrollStateRef.current,
6397
+ scrollerKey: null,
6398
+ vx: 0,
6399
+ vy: 0
6400
+ };
6401
+ raf = requestAnimationFrame(tick);
6402
+ return;
6403
+ }
6404
+ const dTop = y - cachedRect.top;
6405
+ const dBot = cachedRect.bottom - y;
6406
+ let dy = 0;
6407
+ if (dTop > 0 && dTop < AUTO_SCROLL_ZONE_PX)
6408
+ dy = -edgeVelocity(dTop, AUTO_SCROLL_ZONE_PX, AUTO_SCROLL_MAX_SPEED);
6409
+ else if (dBot > 0 && dBot < AUTO_SCROLL_ZONE_PX)
6410
+ dy = edgeVelocity(dBot, AUTO_SCROLL_ZONE_PX, AUTO_SCROLL_MAX_SPEED);
6411
+ const dLeft = x - cachedRect.left;
6412
+ const dRight = cachedRect.right - x;
6413
+ let dx = 0;
6414
+ if (dLeft > 0 && dLeft < AUTO_SCROLL_ZONE_PX)
6415
+ dx = -edgeVelocity(dLeft, AUTO_SCROLL_ZONE_PX, AUTO_SCROLL_MAX_SPEED);
6416
+ else if (dRight > 0 && dRight < AUTO_SCROLL_ZONE_PX)
6417
+ dx = edgeVelocity(dRight, AUTO_SCROLL_ZONE_PX, AUTO_SCROLL_MAX_SPEED);
6418
+ if (dy !== 0) scrollEl.scrollTop += dy;
6419
+ if (dx !== 0) scrollEl.scrollLeft += dx;
6420
+ autoScrollStateRef.current = {
6421
+ ...autoScrollStateRef.current,
6422
+ scrollerKey: getScrollerKey(scrollEl),
6423
+ vx: dx,
6424
+ vy: dy
6425
+ };
6426
+ raf = requestAnimationFrame(tick);
6427
+ };
6428
+ raf = requestAnimationFrame(tick);
6429
+ return () => cancelAnimationFrame(raf);
6430
+ }, [active, autoScrollStateRef, mouseRef, safeZoneRef]);
6431
+ }
6432
+
6433
+ // src/client/dnd/design-mode/useOverlayRefs.ts
6434
+ var import_react11 = require("react");
5929
6435
 
5930
6436
  // src/client/dnd/design-mode/sortable-preview-animator.ts
5931
6437
  function isIdentityTransform(transform) {
@@ -6139,43 +6645,6 @@ function useHoverDetection({
6139
6645
  // src/client/dnd/design-mode/useDragVisualLoop.ts
6140
6646
  var import_react13 = require("react");
6141
6647
 
6142
- // src/client/dnd/design-mode/drag-lock.ts
6143
- var MIN_UNLOCK_MARGIN = 32;
6144
- var MAX_UNLOCK_MARGIN = 72;
6145
- var MIN_UNLOCK_DWELL_MS = 170;
6146
- var MAX_UNLOCK_DWELL_MS = 300;
6147
- function clamp2(value, min, max) {
6148
- return Math.min(max, Math.max(min, value));
6149
- }
6150
- function resolveCrossUnlockParams(rect) {
6151
- const width = Math.max(0, rect.right - rect.left);
6152
- const height = Math.max(0, rect.bottom - rect.top);
6153
- const shortest = Math.max(1, Math.min(width, height));
6154
- const margin = Math.round(clamp2(shortest * 0.14, MIN_UNLOCK_MARGIN, MAX_UNLOCK_MARGIN));
6155
- const dwellMs = Math.round(clamp2(shortest * 0.7, MIN_UNLOCK_DWELL_MS, MAX_UNLOCK_DWELL_MS));
6156
- return { margin, dwellMs };
6157
- }
6158
- function checkCrossUnlock(lock, adjustedPx, adjustedPy, lockRect, now, margin, dwellMs, graceMs) {
6159
- if (lock.isCrossUnlocked) {
6160
- if (lock.unlockUntilTs !== null && now < lock.unlockUntilTs) return lock;
6161
- return lock;
6162
- }
6163
- const outsideBand = adjustedPx < lockRect.left - margin || adjustedPx > lockRect.right + margin || adjustedPy < lockRect.top - margin || adjustedPy > lockRect.bottom + margin;
6164
- if (!outsideBand) {
6165
- return lock.exitSince !== null ? { ...lock, exitSince: null } : lock;
6166
- }
6167
- const exitSince = lock.exitSince ?? now;
6168
- if (now - exitSince >= dwellMs) {
6169
- return {
6170
- containerId: lock.containerId,
6171
- isCrossUnlocked: true,
6172
- exitSince,
6173
- unlockUntilTs: now + graceMs
6174
- };
6175
- }
6176
- return lock.exitSince !== null ? lock : { ...lock, exitSince: now };
6177
- }
6178
-
6179
6648
  // src/client/dnd/design-mode/quadtree.ts
6180
6649
  function overlaps(a, b) {
6181
6650
  return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
@@ -6185,6 +6654,7 @@ function fitsInside(outer, inner) {
6185
6654
  }
6186
6655
  var CAPACITY = 8;
6187
6656
  var MAX_DEPTH2 = 8;
6657
+ var ROOT_BOUNDS_PAD = 100;
6188
6658
  var QTNode = class _QTNode {
6189
6659
  constructor(bounds, depth = 0) {
6190
6660
  __publicField(this, "bounds");
@@ -6282,12 +6752,11 @@ var Quadtree = class _Quadtree {
6282
6752
  if (r.right > maxX) maxX = r.right;
6283
6753
  if (r.bottom > maxY) maxY = r.bottom;
6284
6754
  }
6285
- const PAD = 100;
6286
6755
  const root = new QTNode({
6287
- x: minX - PAD,
6288
- y: minY - PAD,
6289
- w: maxX - minX + 2 * PAD,
6290
- h: maxY - minY + 2 * PAD
6756
+ x: minX - ROOT_BOUNDS_PAD,
6757
+ y: minY - ROOT_BOUNDS_PAD,
6758
+ w: maxX - minX + 2 * ROOT_BOUNDS_PAD,
6759
+ h: maxY - minY + 2 * ROOT_BOUNDS_PAD
6291
6760
  });
6292
6761
  for (const [id, r] of rects) {
6293
6762
  if (r.width > 0 && r.height > 0) {
@@ -6389,9 +6858,9 @@ function getSlotQueryRadius() {
6389
6858
  const vh = window.innerHeight;
6390
6859
  return Math.min(500, Math.max(100, Math.max(vw, vh) * 0.2));
6391
6860
  }
6392
- var NEST_EDGE_FRACTION = 0.28;
6393
- var ORIGIN_RETURN_BAND_PX = 56;
6394
- var LAST_CONTAINER_REENTRY_SLOP_PX = 40;
6861
+ var NEST_EDGE_FRACTION = 0.2;
6862
+ var ORIGIN_RETURN_BAND_PX = 24;
6863
+ var LAST_CONTAINER_REENTRY_SLOP_PX = 16;
6395
6864
  var ROW_OVERLAP_TOLERANCE_PX = 1;
6396
6865
  function normalizeContainerSet(containers) {
6397
6866
  if (!containers) return null;
@@ -6469,8 +6938,6 @@ var DragIntentEngine = class {
6469
6938
  // Pre-computed set of slot IDs that would produce no movement for the active item.
6470
6939
  __publicField(this, "noOpSlotIds");
6471
6940
  __publicField(this, "originContainerId");
6472
- __publicField(this, "laneRootContainerId");
6473
- __publicField(this, "laneParentId");
6474
6941
  __publicField(this, "laneAllowedContainers");
6475
6942
  __publicField(this, "blockedContainerIds");
6476
6943
  __publicField(this, "targetPlane", "lane");
@@ -6482,8 +6949,6 @@ var DragIntentEngine = class {
6482
6949
  this.queryRadius = getSlotQueryRadius();
6483
6950
  this.slotsByContainer = buildSlotsByContainer(projection.slotIndex);
6484
6951
  this.originContainerId = this.getNodeParentContainerId(activeId);
6485
- this.laneRootContainerId = options?.laneRootContainerId ?? this.originContainerId;
6486
- this.laneParentId = this.laneRootContainerId ? this.containerIndex.get(this.laneRootContainerId)?.parentId ?? null : null;
6487
6952
  this.laneAllowedContainers = normalizeContainerSet(options?.laneAllowedContainers);
6488
6953
  this.blockedContainerIds = normalizeContainerSet(options?.blockedContainerIds);
6489
6954
  const noOpSlotIds = /* @__PURE__ */ new Set();
@@ -6515,10 +6980,6 @@ var DragIntentEngine = class {
6515
6980
  setTargetPlane(plane) {
6516
6981
  this.targetPlane = plane;
6517
6982
  }
6518
- setLaneAllowedContainers(containers) {
6519
- this.laneAllowedContainers = normalizeContainerSet(containers);
6520
- this.reset();
6521
- }
6522
6983
  getTargetPlane() {
6523
6984
  return this.targetPlane;
6524
6985
  }
@@ -6617,7 +7078,7 @@ var DragIntentEngine = class {
6617
7078
  if (this.isInvalidDropContainer(pointerContainerId)) return null;
6618
7079
  for (const childId of container.children) {
6619
7080
  if (childId === this.activeId) continue;
6620
- if (this.containerIndex.has(childId)) continue;
7081
+ if (this.isInvalidDropContainer(childId)) continue;
6621
7082
  const childNode = this.getNodeRect(childId);
6622
7083
  if (!childNode) continue;
6623
7084
  if (!pointInRect(px, py, childNode)) continue;
@@ -6642,9 +7103,16 @@ var DragIntentEngine = class {
6642
7103
  if (laneLock && !this.isLaneContainer(id)) continue;
6643
7104
  if (!pointInRect(px, py, node.rect)) continue;
6644
7105
  if (this.hasUsableSlots(id) === false) continue;
6645
- pointerContainers.push({ id, depth: node.depth, area: node.rect.width * node.rect.height });
7106
+ const r = node.rect;
7107
+ const edgeDistX = Math.min(px - r.left, r.right - px);
7108
+ const edgeDistY = Math.min(py - r.top, r.bottom - py);
7109
+ const minDim = Math.min(r.width, r.height);
7110
+ const edgeFraction = Math.min(edgeDistX, edgeDistY) / Math.max(1, minDim);
7111
+ const edgePenalty = edgeFraction < 0.1 ? 1 : 0;
7112
+ pointerContainers.push({ id, depth: node.depth, area: node.rect.width * node.rect.height, edgePenalty });
6646
7113
  }
6647
7114
  pointerContainers.sort((a, b) => {
7115
+ if (a.edgePenalty !== b.edgePenalty) return a.edgePenalty - b.edgePenalty;
6648
7116
  if (b.depth !== a.depth) return b.depth - a.depth;
6649
7117
  return a.area - b.area;
6650
7118
  });
@@ -6657,7 +7125,9 @@ var DragIntentEngine = class {
6657
7125
  }
6658
7126
  if (this.originContainerId && this.originContainerId !== pointerContainerId && !this.isInvalidDropContainer(this.originContainerId) && !this.isBlockedContainer(this.originContainerId) && (!laneLock || this.isLaneContainer(this.originContainerId)) && this.hasUsableSlots(this.originContainerId)) {
6659
7127
  const originNode = this.containerIndex.get(this.originContainerId);
6660
- if (originNode && pointInExpandedRect(px, py, originNode.rect, ORIGIN_RETURN_BAND_PX)) {
7128
+ const currentNode = pointerContainerId ? this.containerIndex.get(pointerContainerId) : null;
7129
+ const originIsDeeper = originNode && (!currentNode || originNode.depth >= currentNode.depth);
7130
+ if (originIsDeeper && originNode && pointInExpandedRect(px, py, originNode.rect, ORIGIN_RETURN_BAND_PX)) {
6661
7131
  pointerContainerId = this.originContainerId;
6662
7132
  }
6663
7133
  }
@@ -6724,8 +7194,13 @@ var DragIntentEngine = class {
6724
7194
  }
6725
7195
  return n;
6726
7196
  }
7197
+ const SLOT_ZONE_PX = 30;
6727
7198
  for (const child of childRects) {
6728
7199
  const midY = child.rect.top + child.rect.height / 2;
7200
+ const clampedTop = Math.max(child.rect.top, midY - SLOT_ZONE_PX);
7201
+ if (py < clampedTop) return child.index;
7202
+ const clampedBottom = Math.min(child.rect.bottom, midY + SLOT_ZONE_PX);
7203
+ if (py > clampedBottom) continue;
6729
7204
  if (py < midY) return child.index;
6730
7205
  }
6731
7206
  return n;
@@ -6794,15 +7269,14 @@ var DragIntentEngine = class {
6794
7269
  hasUsableSlots(containerId) {
6795
7270
  const filtered = this.getContainerSlots(containerId);
6796
7271
  if (filtered.length > 0) return true;
6797
- return (this.slotsByContainer.get(containerId)?.length ?? 0) > 0;
7272
+ const raw = this.slotsByContainer.get(containerId) ?? [];
7273
+ if (raw.length === 0) return false;
7274
+ const container = this.containerIndex.get(containerId);
7275
+ return !container || container.children.length <= 1;
6798
7276
  }
6799
7277
  isLaneContainer(containerId) {
6800
7278
  if (this.laneAllowedContainers) return this.laneAllowedContainers.has(containerId);
6801
- if (!this.laneRootContainerId) return true;
6802
- if (containerId === this.laneRootContainerId) return true;
6803
- const node = this.containerIndex.get(containerId);
6804
- if (!node) return false;
6805
- return node.parentId === this.laneParentId;
7279
+ return true;
6806
7280
  }
6807
7281
  isBlockedContainer(containerId) {
6808
7282
  return this.blockedContainerIds?.has(containerId) ?? false;
@@ -6813,9 +7287,11 @@ function createDragIntentEngine(projection, activeId, options) {
6813
7287
  }
6814
7288
 
6815
7289
  // src/client/dnd/design-mode/drop-line.ts
7290
+ var DROP_LINE_THICKNESS_PX = 2;
7291
+ var DROP_LINE_MIN_LENGTH_PX = 40;
6816
7292
  function slotDropLine(slot, container, projectionTree, sdx, sdy) {
6817
- const T = 2;
6818
- const MIN = 40;
7293
+ const T = DROP_LINE_THICKNESS_PX;
7294
+ const MIN = DROP_LINE_MIN_LENGTH_PX;
6819
7295
  const bp = computeSlotBoundaryPoint(slot, container, projectionTree);
6820
7296
  if (container.strategy === "horizontal") {
6821
7297
  return {
@@ -6855,9 +7331,6 @@ function slotDropLine(slot, container, projectionTree, sdx, sdy) {
6855
7331
  }
6856
7332
 
6857
7333
  // src/client/dnd/design-mode/preview-layout.ts
6858
- function clampIndex(index, min, max) {
6859
- return Math.max(min, Math.min(index, max));
6860
- }
6861
7334
  function getContainerChildren(projection, containerId) {
6862
7335
  return [...projection.containerIndex.get(containerId)?.children ?? []];
6863
7336
  }
@@ -6977,7 +7450,7 @@ function buildPreviewLayout({
6977
7450
  const originalItemIds = getContainerChildren(projection, containerId);
6978
7451
  const activeIndex = originalItemIds.indexOf(anchorNodeId);
6979
7452
  if (activeIndex < 0) return null;
6980
- const overIndex = clampIndex(targetIndex, 0, Math.max(0, originalItemIds.length - 1));
7453
+ const overIndex = clamp(targetIndex, 0, Math.max(0, originalItemIds.length - 1));
6981
7454
  const previewItemIds = arrayMove(originalItemIds, activeIndex, overIndex);
6982
7455
  const strategy = getContainerStrategy(projection, containerId);
6983
7456
  containers.set(containerId, {
@@ -7017,7 +7490,7 @@ function buildPreviewLayout({
7017
7490
  activePreviewContainers.add(sourceContainerId);
7018
7491
  const targetOriginalItemIds = targetChildren.filter((id) => id !== anchorNodeId);
7019
7492
  const targetPreviewItemIds = [...targetOriginalItemIds];
7020
- const targetOverIndex = clampIndex(targetIndex, 0, targetOriginalItemIds.length);
7493
+ const targetOverIndex = clamp(targetIndex, 0, targetOriginalItemIds.length);
7021
7494
  targetPreviewItemIds.splice(targetOverIndex, 0, anchorNodeId);
7022
7495
  const targetStrategy = getContainerStrategy(projection, targetContainerId);
7023
7496
  const targetStrategyItemIds = [...targetOriginalItemIds, anchorNodeId];
@@ -7049,9 +7522,8 @@ function buildPreviewLayout({
7049
7522
  }
7050
7523
 
7051
7524
  // src/client/dnd/design-mode/useDragVisualLoop.ts
7052
- var CONTAINER_SWITCH_HYSTERESIS = 32;
7053
- var CROSS_UNLOCK_GRACE_MS = 320;
7054
- var SMALL_NODE_NEST_DWELL_MS = 180;
7525
+ var CONTAINER_SWITCH_HYSTERESIS = 12;
7526
+ var SMALL_NODE_NEST_DWELL_MS = 100;
7055
7527
  var LARGE_NODE_NEST_DWELL_MS = 320;
7056
7528
  function useDragVisualLoop({
7057
7529
  activeDrag,
@@ -7100,33 +7572,12 @@ function useDragVisualLoop({
7100
7572
  const scrollDy = window.scrollY - snapshot.scroll.y;
7101
7573
  const adjustedPx = px + scrollDx;
7102
7574
  const adjustedPy = py + scrollDy;
7103
- const lockContainer = refs.lockRef.current.containerId ? refs.projectionRef.current.containerIndex.get(refs.lockRef.current.containerId) ?? null : null;
7104
- if (!refs.lockRef.current.isCrossUnlocked && lockContainer) {
7105
- const { margin, dwellMs } = resolveCrossUnlockParams(lockContainer.rect);
7106
- const nextLock = checkCrossUnlock(
7107
- refs.lockRef.current,
7108
- adjustedPx,
7109
- adjustedPy,
7110
- lockContainer.rect,
7111
- performance.now(),
7112
- margin,
7113
- dwellMs,
7114
- CROSS_UNLOCK_GRACE_MS
7115
- );
7116
- if (nextLock !== refs.lockRef.current) {
7117
- refs.lockRef.current = nextLock;
7118
- if (nextLock.isCrossUnlocked) engine.setLaneAllowedContainers(null);
7119
- }
7120
- }
7121
7575
  const nestPreview = engine.peekNestIntent(px, py, window.scrollX, window.scrollY, snapshot.scroll.x, snapshot.scroll.y);
7122
7576
  const isLargeDraggedNode = shouldBlockNestForLargeNode(activeDrag.w, activeDrag.h);
7123
7577
  let allowNest = false;
7124
7578
  if (nestPreview) {
7125
7579
  const childId = nestPreview.slotId.split("::nest::")[1] ?? "";
7126
- const siblings = engine.getContainerChildren(nestPreview.containerId);
7127
- const isSiblingList = siblings.length >= 2;
7128
- const isSourceContainerNest = nestPreview.containerId === refs.sourceContainerIdRef.current;
7129
- allowNest = !!childId && !isSiblingList && !isLargeDraggedNode && !isSourceContainerNest;
7580
+ allowNest = !!childId && !isLargeDraggedNode;
7130
7581
  }
7131
7582
  if (!allowNest) {
7132
7583
  refs.nestCandidateRef.current = null;
@@ -7136,7 +7587,12 @@ function useDragVisualLoop({
7136
7587
  const now = performance.now();
7137
7588
  if (!refs.nestCandidateRef.current || refs.nestCandidateRef.current.id !== nestPreview.slotId) {
7138
7589
  refs.nestCandidateRef.current = { id: nestPreview.slotId, since: now };
7139
- refs.nestTargetRef.current = null;
7590
+ const hintTargetId = nestPreview.slotId.split("::nest::")[1] ?? "";
7591
+ refs.nestTargetRef.current = refs.nodeMapRef.current.get(hintTargetId) ?? null;
7592
+ const hintRect = snapshot.rects.get(hintTargetId);
7593
+ if (hintRect) {
7594
+ setInsideRect(new DOMRect(hintRect.left - scrollDx, hintRect.top - scrollDy, hintRect.width, hintRect.height));
7595
+ }
7140
7596
  setTargetPlane("lane");
7141
7597
  } else if (now - refs.nestCandidateRef.current.since >= (isLargeDraggedNode ? LARGE_NODE_NEST_DWELL_MS : SMALL_NODE_NEST_DWELL_MS)) {
7142
7598
  setTargetPlane("nest");
@@ -7195,18 +7651,6 @@ function useDragVisualLoop({
7195
7651
  setTargetPlane("lane");
7196
7652
  refs.nestCandidateRef.current = null;
7197
7653
  refs.nestTargetRef.current = null;
7198
- const lockedParentId = refs.lockRef.current.containerId;
7199
- if (!refs.lockRef.current.isCrossUnlocked && (!lockedParentId || intent.containerId !== lockedParentId)) {
7200
- refs.lastIntentRef.current = null;
7201
- refs.visualIntentRef.current = null;
7202
- refs.resolvedReorderRef.current = null;
7203
- refs.finalVisualKeyRef.current = null;
7204
- setInsideRect(null);
7205
- setDropLine(null);
7206
- animator.clear();
7207
- rafId = requestAnimationFrame(tick);
7208
- return;
7209
- }
7210
7654
  refs.lastIntentRef.current = intent;
7211
7655
  refs.visualIntentRef.current = intent;
7212
7656
  setInsideRect(null);
@@ -7238,19 +7682,43 @@ function useDragVisualLoop({
7238
7682
  }
7239
7683
  const sourceCommitLane = refs.sourceCommitLaneRef.current ?? getCommitLaneSnapshot(sourceContainerId);
7240
7684
  const targetCommitLane = getCommitLaneSnapshot(intent.containerId);
7241
- const commitPlacement = resolveCommitPlacementFromVisualTarget(
7685
+ let commitPlacement = resolveCommitPlacementFromVisualTarget(
7242
7686
  targetCommitLane,
7243
7687
  sourceCommitLane,
7244
7688
  activeDrag.anchorNodeId,
7245
7689
  targetInsertIndex
7246
7690
  );
7247
7691
  if (!commitPlacement) {
7248
- refs.resolvedReorderRef.current = null;
7249
- refs.finalVisualKeyRef.current = null;
7250
- setDropLine(null);
7251
- animator.clear();
7252
- rafId = requestAnimationFrame(tick);
7253
- return;
7692
+ const containerEl = refs.nodeMapRef.current.get(intent.containerId)?.element;
7693
+ const sourceCommitNodeId = refs.sourceCommitLaneRef.current?.commitNodeByAnchorId.get(activeDrag.anchorNodeId) ?? activeDrag.anchorNodeId;
7694
+ if (!containerEl) {
7695
+ refs.resolvedReorderRef.current = null;
7696
+ refs.finalVisualKeyRef.current = null;
7697
+ setDropLine(null);
7698
+ animator.clear();
7699
+ rafId = requestAnimationFrame(tick);
7700
+ return;
7701
+ }
7702
+ const targetChildren = Array.from(containerEl.children).filter(
7703
+ (child) => child instanceof HTMLElement
7704
+ );
7705
+ const filteredChildren = targetChildren.filter(
7706
+ (child) => child.getAttribute(DND_NODE_ID_ATTR) !== sourceCommitNodeId
7707
+ );
7708
+ const idx = Math.max(0, Math.min(targetInsertIndex, filteredChildren.length));
7709
+ const beforeChild = filteredChildren[idx] ?? null;
7710
+ const afterChild = idx > 0 ? filteredChildren[idx - 1] ?? null : null;
7711
+ const parentNodeId = ensureParentNodeId(containerEl);
7712
+ const beforeNodeId = beforeChild?.getAttribute(DND_NODE_ID_ATTR) ? ensureParentNodeId(beforeChild) : null;
7713
+ const afterNodeId = afterChild?.getAttribute(DND_NODE_ID_ATTR) ? ensureParentNodeId(afterChild) : null;
7714
+ commitPlacement = {
7715
+ parentNodeId,
7716
+ laneContainerId: intent.containerId,
7717
+ commitNodeId: sourceCommitNodeId,
7718
+ beforeNodeId,
7719
+ afterNodeId,
7720
+ index: idx
7721
+ };
7254
7722
  }
7255
7723
  setDropLine(slotDropLine(slot, container, proj.projectionTree, scrollDx, scrollDy));
7256
7724
  refs.resolvedReorderRef.current = {
@@ -7295,56 +7763,175 @@ function useDragVisualLoop({
7295
7763
 
7296
7764
  // src/client/dnd/design-mode/useDragLifecycle.ts
7297
7765
  var import_react14 = require("react");
7298
-
7299
- // src/client/dnd/design-mode/node-key.ts
7300
- var _runtimeKeyMap = /* @__PURE__ */ new WeakMap();
7301
- var _runtimeKeyCounter = 0;
7302
- function getRuntimeKey(el) {
7303
- const existing = _runtimeKeyMap.get(el);
7304
- if (existing) return existing;
7305
- const key2 = `runtime:${++_runtimeKeyCounter}`;
7306
- _runtimeKeyMap.set(el, key2);
7307
- return key2;
7308
- }
7309
- function getNodeKeyFromElement(el) {
7310
- const resolved = resolveDragSurface(el);
7311
- const host = resolved.host ?? el;
7312
- const meta = getEditorMeta(host);
7313
- if (meta) return meta.nodeId;
7314
- const persisted = host.getAttribute("data-dnd-node-id");
7315
- if (persisted) return persisted;
7316
- return getRuntimeKey(host);
7317
- }
7318
- function getInspectorRefFromElement(el) {
7319
- const resolved = resolveDragSurface(el);
7320
- const host = resolved.host ?? el;
7321
- const meta = getEditorMeta(host);
7322
- if (!meta) return null;
7323
- return { relativePath: meta.file, line: meta.line, column: meta.col };
7324
- }
7325
-
7326
- // src/client/dnd/design-mode/useDragLifecycle.ts
7327
- var SOFT_COMMIT_DISTANCE_PX = 14;
7328
- function isLaneCommitValid(intent, resolvedReorder, lockedParentId) {
7329
- if (!intent || intent.mode !== "reorder") return false;
7330
- if (!resolvedReorder) return false;
7331
- if (!lockedParentId) return false;
7332
- if (intent.containerId !== lockedParentId) return false;
7333
- if (resolvedReorder.visual.slotId !== intent.slotId) return false;
7334
- if (resolvedReorder.visual.parentNodeId !== intent.containerId) return false;
7335
- return true;
7336
- }
7337
- function buildRuntimeIdentityLocal(nodeId, resolveNodeElement) {
7766
+ function buildRuntimeIdentityLocal(nodeId, resolveNodeElement, effectiveLaneSnapshot) {
7338
7767
  if (!nodeId) return null;
7339
7768
  const element = resolveNodeElement(nodeId);
7340
7769
  if (!element) return null;
7770
+ const nodeKey = getNodeKeyFromElement(element);
7771
+ const inspectorRef = getInspectorRefFromElement(element);
7772
+ const laneEffectiveNodeId = effectiveLaneSnapshot?.effectiveNodeIdsByCommitNodeId.get(nodeId) ?? null;
7773
+ const effectiveNodeId = typeof laneEffectiveNodeId === "string" && laneEffectiveNodeId.length > 0 ? laneEffectiveNodeId : isSourceBackedRuntimeNodeKey(nodeKey) ? nodeKey : null;
7774
+ const shouldExposeEffectiveIdentity = typeof effectiveNodeId === "string" && effectiveNodeId.length > 0;
7341
7775
  return {
7342
7776
  nodeId,
7343
- nodeKey: getNodeKeyFromElement(element),
7344
- selector: element.getAttribute(DND_NODE_ID_ATTR2) ? `[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(nodeId)}"]` : null,
7345
- inspectorRef: getInspectorRefFromElement(element)
7777
+ nodeKey,
7778
+ selector: element.getAttribute(DND_NODE_ID_ATTR) ? buildNodeSelector(nodeId) : null,
7779
+ inspectorRef,
7780
+ ...shouldExposeEffectiveIdentity ? {
7781
+ effectiveNodeId,
7782
+ effectiveNodeKey: effectiveNodeId,
7783
+ effectiveInspectorRef: inspectorRef
7784
+ } : {}
7785
+ };
7786
+ }
7787
+ function buildMovePayloadWithEffectiveIdentities(params) {
7788
+ const {
7789
+ placement,
7790
+ containerIdentity,
7791
+ beforeSiblingIdentity,
7792
+ afterSiblingIdentity,
7793
+ commitIdentity
7794
+ } = params;
7795
+ return {
7796
+ ...placement,
7797
+ effectiveContainerNodeId: containerIdentity?.effectiveNodeId ?? null,
7798
+ effectiveContainerNodeKey: containerIdentity?.effectiveNodeKey ?? null,
7799
+ effectiveContainerInspectorRef: containerIdentity?.effectiveInspectorRef ?? null,
7800
+ effectiveBeforeNodeId: beforeSiblingIdentity?.effectiveNodeId ?? null,
7801
+ effectiveBeforeNodeKey: beforeSiblingIdentity?.effectiveNodeKey ?? null,
7802
+ effectiveBeforeInspectorRef: beforeSiblingIdentity?.effectiveInspectorRef ?? null,
7803
+ effectiveAfterNodeId: afterSiblingIdentity?.effectiveNodeId ?? null,
7804
+ effectiveAfterNodeKey: afterSiblingIdentity?.effectiveNodeKey ?? null,
7805
+ effectiveAfterInspectorRef: afterSiblingIdentity?.effectiveInspectorRef ?? null,
7806
+ effectiveCommitNodeId: commitIdentity?.effectiveNodeId ?? null,
7807
+ effectiveCommitNodeKey: commitIdentity?.effectiveNodeKey ?? null,
7808
+ effectiveCommitInspectorRef: commitIdentity?.effectiveInspectorRef ?? null
7346
7809
  };
7347
7810
  }
7811
+ function recordCommittedMove(args) {
7812
+ const {
7813
+ command,
7814
+ anchorNodeId,
7815
+ selectedNodeId,
7816
+ anchorEl,
7817
+ selectedEl,
7818
+ nodeKey,
7819
+ inspRef,
7820
+ selectedNodeKey,
7821
+ selectedInspectorRef,
7822
+ sourceLaneSnapshot,
7823
+ targetLaneSnapshot,
7824
+ resolveNodeElement,
7825
+ recordMoveChange
7826
+ } = args;
7827
+ const sourceLaneIdentitySnapshot = captureCommitLaneIdentitySnapshot(
7828
+ sourceLaneSnapshot,
7829
+ resolveNodeElement
7830
+ );
7831
+ const targetLaneIdentitySnapshot = command.to.laneContainerId === command.from.laneContainerId ? sourceLaneIdentitySnapshot : captureCommitLaneIdentitySnapshot(targetLaneSnapshot, resolveNodeElement);
7832
+ const targetLaneAfterIdentitySnapshot = targetLaneIdentitySnapshot ? createCommitLaneIdentitySnapshot({
7833
+ laneContainerId: targetLaneIdentitySnapshot.laneContainerId,
7834
+ commitNodeIds: projectCommitNodeIdsForMove({
7835
+ commitNodeIds: targetLaneIdentitySnapshot.commitNodeIds,
7836
+ movedCommitNodeId: command.commitNodeId,
7837
+ targetIndex: command.to.index
7838
+ }),
7839
+ nodeKeysByCommitNodeId: new Map([
7840
+ ...targetLaneIdentitySnapshot.nodeKeysByCommitNodeId,
7841
+ ...sourceLaneIdentitySnapshot?.nodeKeysByCommitNodeId.has(command.commitNodeId) ? [[command.commitNodeId, sourceLaneIdentitySnapshot.nodeKeysByCommitNodeId.get(command.commitNodeId) ?? null]] : []
7842
+ ])
7843
+ }) : null;
7844
+ const sourceContainerIdentity = buildRuntimeIdentityLocal(
7845
+ command.from.parentNodeId ?? command.from.laneContainerId,
7846
+ resolveNodeElement,
7847
+ sourceLaneIdentitySnapshot
7848
+ );
7849
+ const sourceBeforeSiblingIdentity = buildRuntimeIdentityLocal(command.from.beforeNodeId, resolveNodeElement, sourceLaneIdentitySnapshot);
7850
+ const sourceAfterSiblingIdentity = buildRuntimeIdentityLocal(command.from.afterNodeId, resolveNodeElement, sourceLaneIdentitySnapshot);
7851
+ const sourceCommitIdentity = buildRuntimeIdentityLocal(command.commitNodeId, resolveNodeElement, sourceLaneIdentitySnapshot);
7852
+ const containerIdentity = buildRuntimeIdentityLocal(
7853
+ command.to.parentNodeId ?? command.to.laneContainerId,
7854
+ resolveNodeElement,
7855
+ targetLaneAfterIdentitySnapshot
7856
+ );
7857
+ const beforeSiblingIdentity = buildRuntimeIdentityLocal(command.to.beforeNodeId, resolveNodeElement, targetLaneAfterIdentitySnapshot);
7858
+ const afterSiblingIdentity = buildRuntimeIdentityLocal(command.to.afterNodeId, resolveNodeElement, targetLaneAfterIdentitySnapshot);
7859
+ const targetCommitIdentity = buildRuntimeIdentityLocal(command.commitNodeId, resolveNodeElement, targetLaneAfterIdentitySnapshot);
7860
+ const diagnostics = buildMovePersistenceDiagnostics({
7861
+ sourceLane: sourceLaneIdentitySnapshot,
7862
+ targetLane: targetLaneIdentitySnapshot,
7863
+ movedCommitNodeId: command.commitNodeId,
7864
+ sourceLaneContainerId: command.from.laneContainerId,
7865
+ targetLaneContainerId: command.to.laneContainerId,
7866
+ targetIndex: command.to.index
7867
+ });
7868
+ const componentName = getEditorMeta(anchorEl)?.componentName;
7869
+ const operationId = `${command.at}:${anchorNodeId}`;
7870
+ const beforePayload = buildMovePayloadWithEffectiveIdentities({
7871
+ placement: command.from,
7872
+ containerIdentity: sourceContainerIdentity,
7873
+ beforeSiblingIdentity: sourceBeforeSiblingIdentity,
7874
+ afterSiblingIdentity: sourceAfterSiblingIdentity,
7875
+ commitIdentity: sourceCommitIdentity
7876
+ });
7877
+ const afterPayload = buildMovePayloadWithEffectiveIdentities({
7878
+ placement: command.to,
7879
+ containerIdentity,
7880
+ beforeSiblingIdentity,
7881
+ afterSiblingIdentity,
7882
+ commitIdentity: targetCommitIdentity
7883
+ });
7884
+ recordMoveChange({
7885
+ operationId,
7886
+ commandType: command.type,
7887
+ nodeKey,
7888
+ nodeId: anchorNodeId,
7889
+ inspectorRef: inspRef,
7890
+ selectedNodeKey,
7891
+ selectedNodeId,
7892
+ selectedInspectorRef,
7893
+ anchorNodeKey: nodeKey,
7894
+ anchorNodeId,
7895
+ sourceContainer: sourceContainerIdentity,
7896
+ sourceBeforeSibling: sourceBeforeSiblingIdentity,
7897
+ sourceAfterSibling: sourceAfterSiblingIdentity,
7898
+ diagnostics,
7899
+ container: containerIdentity,
7900
+ beforeSibling: beforeSiblingIdentity,
7901
+ afterSibling: afterSiblingIdentity,
7902
+ componentName,
7903
+ before: beforePayload,
7904
+ after: afterPayload
7905
+ });
7906
+ dispatchRuntimeEvent({
7907
+ type: "element-moved",
7908
+ operationId,
7909
+ commandType: command.type,
7910
+ selected: {
7911
+ nodeId: selectedNodeId,
7912
+ nodeKey: selectedNodeKey,
7913
+ selector: selectedEl.getAttribute(DND_NODE_ID_ATTR) ? buildNodeSelector(selectedNodeId) : null,
7914
+ inspectorRef: selectedInspectorRef,
7915
+ ...targetCommitIdentity?.effectiveNodeId ? { effectiveNodeId: targetCommitIdentity.effectiveNodeId, effectiveNodeKey: targetCommitIdentity.effectiveNodeKey } : {}
7916
+ },
7917
+ anchor: {
7918
+ nodeId: anchorNodeId,
7919
+ nodeKey,
7920
+ selector: anchorEl.getAttribute(DND_NODE_ID_ATTR) ? buildNodeSelector(anchorNodeId) : null,
7921
+ inspectorRef: inspRef,
7922
+ ...targetCommitIdentity?.effectiveNodeId ? { effectiveNodeId: targetCommitIdentity.effectiveNodeId, effectiveNodeKey: targetCommitIdentity.effectiveNodeKey } : {}
7923
+ },
7924
+ sourceContainer: sourceContainerIdentity,
7925
+ sourceBeforeSibling: sourceBeforeSiblingIdentity,
7926
+ sourceAfterSibling: sourceAfterSiblingIdentity,
7927
+ container: containerIdentity,
7928
+ beforeSibling: beforeSiblingIdentity,
7929
+ afterSibling: afterSiblingIdentity,
7930
+ diagnostics,
7931
+ before: beforePayload,
7932
+ after: afterPayload
7933
+ });
7934
+ }
7348
7935
  function useDragLifecycle({
7349
7936
  refs,
7350
7937
  pause,
@@ -7426,12 +8013,10 @@ function useDragLifecycle({
7426
8013
  const srcContainerId = anchor.sourceContainerId;
7427
8014
  const sourceCommitLane = getCommitLaneSnapshot(srcContainerId);
7428
8015
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
7429
- const laneAllowed = srcContainerId ? /* @__PURE__ */ new Set([srcContainerId]) : null;
7430
8016
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
7431
8017
  refs.blockedContainerIdsRef.current = blockedContainerIds;
7432
8018
  refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, {
7433
8019
  laneRootContainerId: srcContainerId,
7434
- laneAllowedContainers: laneAllowed,
7435
8020
  blockedContainerIds
7436
8021
  });
7437
8022
  refs.engineRef.current.setTargetPlane("lane");
@@ -7439,7 +8024,7 @@ function useDragLifecycle({
7439
8024
  refs.sourceContainerIdRef.current = srcContainerId;
7440
8025
  refs.sourceCommitLaneRef.current = sourceCommitLane;
7441
8026
  refs.dragSessionAnchorRef.current = anchor;
7442
- refs.lockRef.current = { containerId: srcContainerId, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null };
8027
+ refs.lockRef.current = { containerId: srcContainerId, isCrossUnlocked: true, exitSince: null, unlockUntilTs: null };
7443
8028
  refs.dragStartPositionRef.current = startPosition;
7444
8029
  refs.safeZoneRef.current = resolveSafeZoneRect();
7445
8030
  pause();
@@ -7465,22 +8050,34 @@ function useDragLifecycle({
7465
8050
  const pointer = refs.mouseRef.current;
7466
8051
  const safeZone = refs.safeZoneRef.current;
7467
8052
  const blockedByGuard = isPointerOutsideSafeZone(pointer.x, pointer.y, safeZone) || isPointerInHardPageEdgeZone(pointer.x, pointer.y);
7468
- const sourcePos = refs.dragStartPositionRef.current ?? getCurrentCommitPlacement(anchorNodeId, refs.sourceContainerIdRef.current);
8053
+ let sourcePos = refs.dragStartPositionRef.current ?? getCurrentCommitPlacement(anchorNodeId, refs.sourceContainerIdRef.current);
8054
+ if (!sourcePos && activeEl && activeEl.parentElement) {
8055
+ const parentEl = activeEl.parentElement;
8056
+ const commitNodeId = activeEl.getAttribute(DND_NODE_ID_ATTR) ?? anchorNodeId;
8057
+ const siblings = Array.from(parentEl.children).filter((c) => c instanceof HTMLElement);
8058
+ const idx = siblings.indexOf(activeEl);
8059
+ if (idx >= 0) {
8060
+ sourcePos = {
8061
+ parentNodeId: ensureParentNodeId(parentEl),
8062
+ laneContainerId: refs.sourceContainerIdRef.current,
8063
+ commitNodeId,
8064
+ beforeNodeId: idx < siblings.length - 1 ? siblings[idx + 1].getAttribute(DND_NODE_ID_ATTR) ?? ensureParentNodeId(siblings[idx + 1]) : null,
8065
+ afterNodeId: idx > 0 ? siblings[idx - 1].getAttribute(DND_NODE_ID_ATTR) ?? ensureParentNodeId(siblings[idx - 1]) : null,
8066
+ index: idx
8067
+ };
8068
+ }
8069
+ }
7469
8070
  const intent = refs.visualIntentRef.current;
7470
- const finalVisual = refs.finalVisualKeyRef.current;
7471
8071
  const nestTarget = refs.nestTargetRef.current;
7472
8072
  const intentHasContainer = intent ? refs.projectionRef.current.containerIndex.has(intent.containerId) : false;
7473
8073
  const intentHasSlot = intent ? intent.mode === "nest" || refs.projectionRef.current.slotIndex.has(intent.slotId) : false;
7474
8074
  const intentValid = !!intent && intentHasContainer && intentHasSlot;
7475
8075
  if (!blockedByGuard && activeEl && sourcePos && (nestTarget || intentValid)) {
7476
8076
  const dropTargetId = nestTarget?.nodeId ?? intent?.containerId;
7477
- if (dropTargetId) {
7478
- const targetSc = refs.nodeMapRef.current.get(dropTargetId);
7479
- if (!targetSc || !isEligibleForDrag(targetSc)) {
7480
- clearDragState();
7481
- syncHistoryAvailability();
7482
- return;
7483
- }
8077
+ if (dropTargetId && !refs.nodeMapRef.current.has(dropTargetId)) {
8078
+ clearDragState();
8079
+ syncHistoryAvailability();
8080
+ return;
7484
8081
  }
7485
8082
  let command = null;
7486
8083
  if (nestTarget) {
@@ -7500,46 +8097,8 @@ function useDragLifecycle({
7500
8097
  };
7501
8098
  }
7502
8099
  } else if (intentValid && intent.mode === "reorder") {
7503
- const lockedParentId = refs.lockRef.current.containerId;
7504
- if (!refs.lockRef.current.isCrossUnlocked && (!lockedParentId || intent.containerId !== lockedParentId)) {
7505
- clearDragState();
7506
- syncHistoryAvailability();
7507
- return;
7508
- }
7509
- const snapshot = refs.snapshotRef.current;
7510
- const targetContainer = refs.projectionRef.current.containerIndex.get(intent.containerId);
7511
- if (targetContainer) {
7512
- const scrollDx = window.scrollX - (snapshot?.scroll.x ?? 0);
7513
- const scrollDy = window.scrollY - (snapshot?.scroll.y ?? 0);
7514
- const adjPx = pointer.x + scrollDx;
7515
- const adjPy = pointer.y + scrollDy;
7516
- const r = targetContainer.rect;
7517
- const inContainer = adjPx >= r.left - 40 && adjPx <= r.right + 40 && adjPy >= r.top - 40 && adjPy <= r.bottom + 40;
7518
- if (!inContainer) {
7519
- const softFallbackAllowed = finalVisual?.mode === "reorder" && finalVisual.containerId === intent.containerId && (() => {
7520
- const slot = refs.projectionRef.current.slotIndex.get(finalVisual.slotId);
7521
- const container = refs.projectionRef.current.containerIndex.get(finalVisual.containerId);
7522
- if (!slot || !container) return false;
7523
- const bp = computeSlotBoundaryPoint(slot, container, refs.projectionRef.current.projectionTree);
7524
- const dist = Math.hypot(adjPx - bp.x, adjPy - bp.y);
7525
- return dist <= SOFT_COMMIT_DISTANCE_PX;
7526
- })();
7527
- if (!softFallbackAllowed) {
7528
- clearDragState();
7529
- syncHistoryAvailability();
7530
- return;
7531
- }
7532
- }
7533
- }
7534
8100
  const resolvedReorder = refs.resolvedReorderRef.current;
7535
- const laneValid = refs.lockRef.current.isCrossUnlocked ? !!resolvedReorder && resolvedReorder.visual.slotId === intent.slotId && resolvedReorder.visual.parentNodeId === intent.containerId : isLaneCommitValid(intent, resolvedReorder, lockedParentId);
7536
- if (!laneValid) {
7537
- clearDragState();
7538
- syncHistoryAvailability();
7539
- return;
7540
- }
7541
- const visualParity = !!finalVisual && finalVisual.mode === "reorder" && finalVisual.anchorNodeId === anchorNodeId && finalVisual.slotId === resolvedReorder.visual.slotId && finalVisual.parentNodeId === resolvedReorder.visual.parentNodeId && finalVisual.index === resolvedReorder.visual.index && finalVisual.containerId === intent.containerId;
7542
- if (!visualParity) {
8101
+ if (!resolvedReorder || resolvedReorder.visual.containerId !== intent.containerId) {
7543
8102
  clearDragState();
7544
8103
  syncHistoryAvailability();
7545
8104
  return;
@@ -7555,62 +8114,32 @@ function useDragLifecycle({
7555
8114
  };
7556
8115
  }
7557
8116
  if (command) {
8117
+ const anchorEl = resolveNodeElement(anchorNodeId) ?? activeEl;
8118
+ const selectedEl = resolveNodeElement(selectedNodeId) ?? anchorEl;
8119
+ const nodeKey = getNodeKeyFromElement(anchorEl);
8120
+ const inspRef = getInspectorRefFromElement(anchorEl);
8121
+ const selectedNodeKey = getNodeKeyFromElement(selectedEl);
8122
+ const selectedInspectorRef = getInspectorRefFromElement(selectedEl);
8123
+ const sourceLaneSnapshot = refs.sourceCommitLaneRef.current ?? getCommitLaneSnapshot(command.from.laneContainerId);
8124
+ const targetLaneSnapshot = command.to.laneContainerId === command.from.laneContainerId ? sourceLaneSnapshot : getCommitLaneSnapshot(command.to.laneContainerId);
7558
8125
  const committed = refs.historyRef.current.execute(command, applyCommand);
7559
8126
  if (committed) {
7560
8127
  activeEl.setAttribute("data-dnd-moved", "");
7561
8128
  selectElement(null);
7562
- const anchorEl = resolveNodeElement(anchorNodeId) ?? activeEl;
7563
- const selectedEl = resolveNodeElement(selectedNodeId) ?? anchorEl;
7564
- const nodeKey = getNodeKeyFromElement(anchorEl);
7565
- const inspRef = getInspectorRefFromElement(anchorEl);
7566
- const selectedNodeKey = getNodeKeyFromElement(selectedEl);
7567
- const selectedInspectorRef = getInspectorRefFromElement(selectedEl);
7568
- const containerIdentity = buildRuntimeIdentityLocal(
7569
- command.to.parentNodeId ?? command.to.laneContainerId,
7570
- resolveNodeElement
7571
- );
7572
- const beforeSiblingIdentity = buildRuntimeIdentityLocal(command.to.beforeNodeId, resolveNodeElement);
7573
- const afterSiblingIdentity = buildRuntimeIdentityLocal(command.to.afterNodeId, resolveNodeElement);
7574
- const operationId = `${command.at}:${anchorNodeId}`;
7575
- recordMoveChange({
7576
- operationId,
7577
- commandType: command.type,
8129
+ recordCommittedMove({
8130
+ command,
8131
+ anchorNodeId,
8132
+ selectedNodeId,
8133
+ anchorEl,
8134
+ selectedEl,
7578
8135
  nodeKey,
7579
- nodeId: anchorNodeId,
7580
- inspectorRef: inspRef,
8136
+ inspRef,
7581
8137
  selectedNodeKey,
7582
- selectedNodeId,
7583
8138
  selectedInspectorRef,
7584
- anchorNodeKey: nodeKey,
7585
- anchorNodeId,
7586
- container: containerIdentity,
7587
- beforeSibling: beforeSiblingIdentity,
7588
- afterSibling: afterSiblingIdentity,
7589
- componentName: getEditorMeta(anchorEl)?.componentName,
7590
- before: command.from,
7591
- after: command.to
7592
- });
7593
- dispatchRuntimeEvent({
7594
- type: "element-moved",
7595
- operationId,
7596
- commandType: command.type,
7597
- selected: {
7598
- nodeId: selectedNodeId,
7599
- nodeKey: selectedNodeKey,
7600
- selector: selectedEl.getAttribute(DND_NODE_ID_ATTR2) ? `[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(selectedNodeId)}"]` : null,
7601
- inspectorRef: selectedInspectorRef
7602
- },
7603
- anchor: {
7604
- nodeId: anchorNodeId,
7605
- nodeKey,
7606
- selector: anchorEl.getAttribute(DND_NODE_ID_ATTR2) ? `[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(anchorNodeId)}"]` : null,
7607
- inspectorRef: inspRef
7608
- },
7609
- container: containerIdentity,
7610
- beforeSibling: beforeSiblingIdentity,
7611
- afterSibling: afterSiblingIdentity,
7612
- before: command.from,
7613
- after: command.to
8139
+ sourceLaneSnapshot,
8140
+ targetLaneSnapshot,
8141
+ resolveNodeElement,
8142
+ recordMoveChange
7614
8143
  });
7615
8144
  }
7616
8145
  }
@@ -7649,7 +8178,7 @@ function useResizeSession({
7649
8178
  const rect = resizeTargetEl.getBoundingClientRect();
7650
8179
  const selectedNodeKey = getNodeKeyFromElement(selectedEl);
7651
8180
  const targetNodeKey = getNodeKeyFromElement(resizeTargetEl);
7652
- const targetNodeId = ensureElementNodeId(resizeTargetEl);
8181
+ const targetNodeId = ensureParentNodeId(resizeTargetEl);
7653
8182
  const startColSpan = parseSpanValue(getComputedStyle(resizeTargetEl).gridColumnEnd || resizeTargetEl.style.gridColumnEnd);
7654
8183
  const startRowSpan = parseSpanValue(getComputedStyle(resizeTargetEl).gridRowEnd || resizeTargetEl.style.gridRowEnd);
7655
8184
  const columnTrackCount = getGridTrackCount(resizeTargetParent ? getComputedStyle(resizeTargetParent).gridTemplateColumns : null);
@@ -7774,13 +8303,13 @@ function useResizeSession({
7774
8303
  selected: {
7775
8304
  nodeId: selectedNodeId,
7776
8305
  nodeKey: selectedNodeKey,
7777
- selector: `[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(selectedNodeId)}"]`,
8306
+ selector: buildNodeSelector(selectedNodeId),
7778
8307
  inspectorRef
7779
8308
  },
7780
8309
  anchor: {
7781
8310
  nodeId: targetNodeId,
7782
8311
  nodeKey: targetNodeKey,
7783
- selector: `[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(targetNodeId)}"]`,
8312
+ selector: buildNodeSelector(targetNodeId),
7784
8313
  inspectorRef: getInspectorRefFromElement(element)
7785
8314
  },
7786
8315
  resize: {
@@ -7821,6 +8350,169 @@ function useResizeSession({
7821
8350
  return { handleResizeHandlePointerDown };
7822
8351
  }
7823
8352
 
8353
+ // src/client/dnd/design-mode/useExternalDragLoop.ts
8354
+ var import_react16 = require("react");
8355
+ var EXTERNAL_DRAG_ACTIVE_ID = "__external-drag-placeholder__";
8356
+ function useExternalDragLoop({
8357
+ externalDrag,
8358
+ refs,
8359
+ elements,
8360
+ resolveNodeElement,
8361
+ setDropLine
8362
+ }) {
8363
+ const engineRef = (0, import_react16.useRef)(null);
8364
+ const lastIntentRef = (0, import_react16.useRef)(null);
8365
+ (0, import_react16.useEffect)(() => {
8366
+ if (!externalDrag) {
8367
+ engineRef.current = null;
8368
+ lastIntentRef.current = null;
8369
+ setDropLine(null);
8370
+ externalDragMouse.commitRequested = false;
8371
+ return;
8372
+ }
8373
+ const projection = buildProjection(elements);
8374
+ if (projection.containerIndex.size === 0) return;
8375
+ const rects = /* @__PURE__ */ new Map();
8376
+ for (const el of elements) {
8377
+ rects.set(el.nodeId, el.rect);
8378
+ }
8379
+ const nodeMap = /* @__PURE__ */ new Map();
8380
+ for (const el of elements) nodeMap.set(el.nodeId, el);
8381
+ const blockedContainerIds = buildBlockedStructuralContainerIds(projection, nodeMap, null);
8382
+ const engine = new DragIntentEngine(projection, EXTERNAL_DRAG_ACTIVE_ID, {
8383
+ blockedContainerIds
8384
+ });
8385
+ engineRef.current = engine;
8386
+ const scrollX = window.scrollX;
8387
+ const scrollY = window.scrollY;
8388
+ let rafId;
8389
+ const tick = () => {
8390
+ if (externalDragMouse.commitRequested) {
8391
+ externalDragMouse.commitRequested = false;
8392
+ commitExternalDrop(
8393
+ externalDragMouse.x,
8394
+ externalDragMouse.y,
8395
+ engine,
8396
+ externalDrag,
8397
+ refs,
8398
+ resolveNodeElement,
8399
+ setDropLine
8400
+ );
8401
+ return;
8402
+ }
8403
+ const intent = engine.resolve(
8404
+ externalDragMouse.x,
8405
+ externalDragMouse.y,
8406
+ window.scrollX,
8407
+ window.scrollY,
8408
+ scrollX,
8409
+ scrollY
8410
+ );
8411
+ if (intent && intent.mode === "reorder") {
8412
+ lastIntentRef.current = intent;
8413
+ const slot = projection.slotIndex.get(intent.slotId);
8414
+ const container = projection.containerIndex.get(intent.containerId);
8415
+ if (slot && container) {
8416
+ const scrollDx = window.scrollX - scrollX;
8417
+ const scrollDy = window.scrollY - scrollY;
8418
+ setDropLine(slotDropLine(slot, container, projection.projectionTree, scrollDx, scrollDy));
8419
+ } else {
8420
+ setDropLine(null);
8421
+ }
8422
+ } else {
8423
+ lastIntentRef.current = null;
8424
+ setDropLine(null);
8425
+ }
8426
+ rafId = requestAnimationFrame(tick);
8427
+ };
8428
+ rafId = requestAnimationFrame(tick);
8429
+ return () => cancelAnimationFrame(rafId);
8430
+ }, [externalDrag, elements, refs, resolveNodeElement, setDropLine]);
8431
+ }
8432
+ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, setDropLine) {
8433
+ const scrollX = window.scrollX;
8434
+ const scrollY = window.scrollY;
8435
+ const intent = engine.resolve(x, y, scrollX, scrollY, scrollX, scrollY);
8436
+ if (!intent || intent.mode !== "reorder") {
8437
+ useDesignModeStore.getState().cancelExternalDrag();
8438
+ setDropLine(null);
8439
+ return;
8440
+ }
8441
+ const containerEl = resolveNodeElement(intent.containerId);
8442
+ if (!containerEl) {
8443
+ useDesignModeStore.getState().cancelExternalDrag();
8444
+ setDropLine(null);
8445
+ return;
8446
+ }
8447
+ const template = document.createElement("template");
8448
+ template.innerHTML = component.html.trim();
8449
+ const newElement = template.content.firstElementChild;
8450
+ if (!newElement) {
8451
+ useDesignModeStore.getState().cancelExternalDrag();
8452
+ setDropLine(null);
8453
+ return;
8454
+ }
8455
+ const nodeId = `insert:${Date.now()}`;
8456
+ newElement.setAttribute(DND_NODE_ID_ATTR, nodeId);
8457
+ newElement.setAttribute("data-dnd-force-allow", "");
8458
+ newElement.setAttribute("data-dnd-moved", "");
8459
+ const children = Array.from(containerEl.children).filter(
8460
+ (child) => child instanceof HTMLElement
8461
+ );
8462
+ const beforeChild = children[intent.index] ?? null;
8463
+ containerEl.insertBefore(newElement, beforeChild);
8464
+ const insertCommand = {
8465
+ type: "insert",
8466
+ nodeId,
8467
+ html: component.html,
8468
+ containerId: intent.containerId,
8469
+ index: intent.index,
8470
+ at: Date.now()
8471
+ };
8472
+ const applied = refs.historyRef.current.execute(insertCommand, (command, direction) => {
8473
+ if (command.type !== "insert") return false;
8474
+ return applyInsertCommand(command, direction, resolveNodeElement);
8475
+ });
8476
+ if (applied) {
8477
+ const nodeKey = getNodeKeyFromElement(newElement);
8478
+ const inspectorRef = getInspectorRefFromElement(newElement);
8479
+ const containerNodeKey = getNodeKeyFromElement(containerEl);
8480
+ let containerIdentity = null;
8481
+ if (containerEl) {
8482
+ containerIdentity = {
8483
+ nodeId: intent.containerId,
8484
+ nodeKey: getNodeKeyFromElement(containerEl),
8485
+ selector: containerEl.getAttribute(DND_NODE_ID_ATTR) ? buildNodeSelector(intent.containerId) : null,
8486
+ inspectorRef: getInspectorRefFromElement(containerEl)
8487
+ };
8488
+ }
8489
+ const operationId = `${insertCommand.at}:${nodeId}`;
8490
+ useDesignModeStore.getState().recordInsertChange({
8491
+ operationId,
8492
+ nodeKey,
8493
+ nodeId,
8494
+ inspectorRef,
8495
+ displayName: component.displayName,
8496
+ html: component.html,
8497
+ containerId: intent.containerId,
8498
+ containerNodeKey,
8499
+ index: intent.index
8500
+ });
8501
+ dispatchRuntimeEvent({
8502
+ type: "element-inserted",
8503
+ operationId,
8504
+ nodeId,
8505
+ nodeKey,
8506
+ displayName: component.displayName,
8507
+ html: component.html,
8508
+ container: containerIdentity,
8509
+ index: intent.index
8510
+ });
8511
+ }
8512
+ setDropLine(null);
8513
+ useDesignModeStore.getState().cancelExternalDrag();
8514
+ }
8515
+
7824
8516
  // src/client/dnd/design-mode/DesignModeOverlay.tsx
7825
8517
  var import_jsx_runtime4 = require("react/jsx-runtime");
7826
8518
  var DROP_ANIMATION = { duration: DURATION_MS, easing: EASING };
@@ -7866,11 +8558,11 @@ function HitArea({
7866
8558
  onResizeHandlePointerDown
7867
8559
  }) {
7868
8560
  const { setNodeRef, listeners, attributes, isDragging } = useDraggable({ id: scanned.nodeId });
7869
- (0, import_react16.useEffect)(() => {
8561
+ (0, import_react17.useEffect)(() => {
7870
8562
  setNodeRef(scanned.element);
7871
8563
  return () => setNodeRef(null);
7872
8564
  }, [setNodeRef, scanned.element]);
7873
- (0, import_react16.useLayoutEffect)(() => {
8565
+ (0, import_react17.useLayoutEffect)(() => {
7874
8566
  const el = scanned.element;
7875
8567
  if (isDragging) {
7876
8568
  el.style.opacity = "0";
@@ -7878,16 +8570,16 @@ function HitArea({
7878
8570
  el.style.removeProperty("opacity");
7879
8571
  }
7880
8572
  }, [isDragging, scanned.element]);
7881
- (0, import_react16.useEffect)(() => () => {
8573
+ (0, import_react17.useEffect)(() => () => {
7882
8574
  scanned.element.style.removeProperty("opacity");
7883
8575
  scanned.element.style.removeProperty("transform");
7884
8576
  scanned.element.style.removeProperty("transition");
7885
8577
  scanned.element.style.removeProperty("will-change");
7886
8578
  }, [scanned.element]);
7887
- const divRef = (0, import_react16.useRef)(null);
7888
- const [hoverResizeHandle, setHoverResizeHandle] = (0, import_react16.useState)(null);
8579
+ const divRef = (0, import_react17.useRef)(null);
8580
+ const [hoverResizeHandle, setHoverResizeHandle] = (0, import_react17.useState)(null);
7889
8581
  const isSelected = scanned.nodeId === selectedNodeId;
7890
- (0, import_react16.useEffect)(() => {
8582
+ (0, import_react17.useEffect)(() => {
7891
8583
  const div = divRef.current;
7892
8584
  if (!div) return;
7893
8585
  const onWheel = (e) => {
@@ -7958,13 +8650,14 @@ function DesignModeOverlay() {
7958
8650
  const redoLastChange = useDesignModeStore((s) => s.redoLastChange);
7959
8651
  const orderedChanges = useDesignModeStore((s) => s.orderedChanges);
7960
8652
  const exportChangesForAI = useDesignModeStore((s) => s.exportChangesForAI);
8653
+ const externalDrag = useDesignModeStore((s) => s.externalDrag);
7961
8654
  const { elements, pause, resume } = useElementScanner(enabled);
7962
- const nodeMap = (0, import_react16.useMemo)(() => {
8655
+ const nodeMap = (0, import_react17.useMemo)(() => {
7963
8656
  const m = /* @__PURE__ */ new Map();
7964
8657
  for (const el of elements) m.set(el.nodeId, el);
7965
8658
  return m;
7966
8659
  }, [elements]);
7967
- const draggableElements = (0, import_react16.useMemo)(() => {
8660
+ const draggableElements = (0, import_react17.useMemo)(() => {
7968
8661
  if (elements.length === 0) return [];
7969
8662
  const projection = buildProjection(elements);
7970
8663
  return collectDraggableAnchors(elements, projection, nodeMap);
@@ -7972,21 +8665,21 @@ function DesignModeOverlay() {
7972
8665
  const refs = useOverlayRefs(undoRequestId, redoRequestId);
7973
8666
  refs.elementsRef.current = elements;
7974
8667
  refs.nodeMapRef.current = nodeMap;
7975
- (0, import_react16.useEffect)(() => {
8668
+ (0, import_react17.useEffect)(() => {
7976
8669
  const m = /* @__PURE__ */ new Map();
7977
8670
  for (const el of draggableElements) m.set(el.nodeId, el);
7978
8671
  refs.draggableNodeMapRef.current = m;
7979
8672
  }, [draggableElements]);
7980
- const resolveNodeElement = (0, import_react16.useCallback)((nodeId) => {
8673
+ const resolveNodeElement = (0, import_react17.useCallback)((nodeId) => {
7981
8674
  const known = refs.nodeMapRef.current.get(nodeId)?.element;
7982
8675
  if (known) return known;
7983
- return document.querySelector(`[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(nodeId)}"]`);
8676
+ return document.querySelector(buildNodeSelector(nodeId));
7984
8677
  }, []);
7985
- const clearCommitLaneCache = (0, import_react16.useCallback)(() => {
8678
+ const clearCommitLaneCache = (0, import_react17.useCallback)(() => {
7986
8679
  refs.commitLaneCacheRef.current.clear();
7987
8680
  refs.sourceCommitLaneRef.current = null;
7988
8681
  }, []);
7989
- const getCommitLaneSnapshot = (0, import_react16.useCallback)((laneContainerId) => {
8682
+ const getCommitLaneSnapshot = (0, import_react17.useCallback)((laneContainerId) => {
7990
8683
  if (!laneContainerId) return null;
7991
8684
  if (refs.commitLaneCacheRef.current.has(laneContainerId)) {
7992
8685
  return refs.commitLaneCacheRef.current.get(laneContainerId) ?? null;
@@ -7995,15 +8688,15 @@ function DesignModeOverlay() {
7995
8688
  refs.commitLaneCacheRef.current.set(laneContainerId, snapshot);
7996
8689
  return snapshot;
7997
8690
  }, []);
7998
- const getCurrentCommitPlacement = (0, import_react16.useCallback)((anchorNodeId, laneContainerId) => {
8691
+ const getCurrentCommitPlacement = (0, import_react17.useCallback)((anchorNodeId, laneContainerId) => {
7999
8692
  const lane = getCommitLaneSnapshot(laneContainerId);
8000
8693
  return resolveCurrentCommitPlacement(lane, anchorNodeId);
8001
8694
  }, [getCommitLaneSnapshot]);
8002
- const applyCommand = (0, import_react16.useCallback)((command, direction) => {
8695
+ const applyCommand = (0, import_react17.useCallback)((command, direction) => {
8003
8696
  if (command.type === "resize") {
8004
8697
  const resCmd = command;
8005
8698
  const targetId = resCmd.targetNodeId ?? resCmd.nodeId;
8006
- const targetEl = document.querySelector(`[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(targetId)}"]`);
8699
+ const targetEl = document.querySelector(buildNodeSelector(targetId));
8007
8700
  if (!targetEl) return false;
8008
8701
  const snapshot = direction === "redo" ? resCmd.after : resCmd.before;
8009
8702
  if (snapshot.styles) {
@@ -8014,29 +8707,36 @@ function DesignModeOverlay() {
8014
8707
  }
8015
8708
  return true;
8016
8709
  }
8710
+ if (command.type === "insert") {
8711
+ return applyInsertCommand(command, direction, resolveNodeElement);
8712
+ }
8017
8713
  const applied = applyDndCommandPlacement(command, direction, resolveNodeElement);
8018
8714
  if (!applied) return false;
8019
8715
  clearCommitLaneCache();
8020
8716
  return true;
8021
8717
  }, [clearCommitLaneCache, resolveNodeElement]);
8022
- const syncHistoryAvailability = (0, import_react16.useCallback)(() => {
8718
+ const syncHistoryAvailability = (0, import_react17.useCallback)(() => {
8023
8719
  if (historyMode === "host") {
8024
8720
  setHistoryAvailability(false, false);
8025
8721
  return;
8026
8722
  }
8027
8723
  setHistoryAvailability(refs.historyRef.current.canUndo(), refs.historyRef.current.canRedo());
8028
8724
  }, [historyMode, setHistoryAvailability]);
8725
+ (0, import_react17.useEffect)(() => {
8726
+ refs.historyRef.current.clear();
8727
+ syncHistoryAvailability();
8728
+ }, [enabled, syncHistoryAvailability]);
8029
8729
  const sensors = useSensors(
8030
8730
  useSensor(PointerSensor, { activationConstraint: { distance: 8 } })
8031
8731
  );
8032
- const collisionDetection = (0, import_react16.useCallback)(
8732
+ const collisionDetection = (0, import_react17.useCallback)(
8033
8733
  (args) => createDesignModeCollisionDetection(refs.blockedContainerIdsRef.current)(args),
8034
8734
  []
8035
8735
  // eslint-disable-line react-hooks/exhaustive-deps -- refs are stable
8036
8736
  );
8037
- const [insideRect, setInsideRect] = (0, import_react16.useState)(null);
8038
- const [dropLine, setDropLine] = (0, import_react16.useState)(null);
8039
- (0, import_react16.useEffect)(() => {
8737
+ const [insideRect, setInsideRect] = (0, import_react17.useState)(null);
8738
+ const [dropLine, setDropLine] = (0, import_react17.useState)(null);
8739
+ (0, import_react17.useEffect)(() => {
8040
8740
  if (!enabled) return;
8041
8741
  const fn = (e) => {
8042
8742
  refs.mouseRef.current = { x: e.clientX, y: e.clientY };
@@ -8044,7 +8744,7 @@ function DesignModeOverlay() {
8044
8744
  window.addEventListener("pointermove", fn, { capture: true, passive: true });
8045
8745
  return () => window.removeEventListener("pointermove", fn, { capture: true });
8046
8746
  }, [enabled]);
8047
- (0, import_react16.useEffect)(() => {
8747
+ (0, import_react17.useEffect)(() => {
8048
8748
  if (!enabled) return;
8049
8749
  if (historyMode !== "local") return;
8050
8750
  const onKeyDown = (e) => {
@@ -8073,7 +8773,7 @@ function DesignModeOverlay() {
8073
8773
  setInsideRect,
8074
8774
  setDropLine
8075
8775
  });
8076
- (0, import_react16.useEffect)(() => {
8776
+ (0, import_react17.useEffect)(() => {
8077
8777
  if (!enabled || activeDrag) return;
8078
8778
  if (undoRequestId === refs.processedUndoIdRef.current) return;
8079
8779
  refs.processedUndoIdRef.current = undoRequestId;
@@ -8090,7 +8790,7 @@ function DesignModeOverlay() {
8090
8790
  });
8091
8791
  syncHistoryAvailability();
8092
8792
  }, [undoRequestId, enabled, activeDrag, applyCommand, resume, selectElement, syncHistoryAvailability, undoLastChange]);
8093
- (0, import_react16.useEffect)(() => {
8793
+ (0, import_react17.useEffect)(() => {
8094
8794
  if (!enabled || activeDrag || historyMode !== "local") return;
8095
8795
  if (redoRequestId === refs.processedRedoIdRef.current) return;
8096
8796
  refs.processedRedoIdRef.current = redoRequestId;
@@ -8102,49 +8802,29 @@ function DesignModeOverlay() {
8102
8802
  }
8103
8803
  syncHistoryAvailability();
8104
8804
  }, [redoRequestId, enabled, activeDrag, applyCommand, historyMode, resume, selectElement, syncHistoryAvailability, redoLastChange]);
8105
- (0, import_react16.useEffect)(() => {
8805
+ (0, import_react17.useEffect)(() => {
8106
8806
  syncHistoryAvailability();
8107
8807
  }, [syncHistoryAvailability]);
8108
- (0, import_react16.useEffect)(() => {
8808
+ (0, import_react17.useEffect)(() => {
8109
8809
  dispatchRuntimeEvent({
8110
8810
  type: "design-mode-changed",
8111
8811
  mode: enabled ? "design" : "off"
8112
8812
  });
8113
8813
  }, [enabled]);
8114
- (0, import_react16.useEffect)(() => {
8814
+ (0, import_react17.useEffect)(() => {
8115
8815
  const aiOutput = exportChangesForAI();
8116
8816
  const updatedAt = orderedChanges.length > 0 ? orderedChanges[orderedChanges.length - 1]?.at ?? Date.now() : Date.now();
8117
8817
  dispatchRuntimeEvent({
8118
8818
  type: "arrange-session-changed",
8119
- session: {
8120
- mode: enabled ? "arrange" : "off",
8121
- canUndo: orderedChanges.length > 0,
8122
- changes: orderedChanges.map((event, index) => ({
8123
- id: `${event.kind}:${event.operationId}:${index}`,
8124
- kind: event.kind,
8125
- operationId: event.operationId,
8126
- commandType: event.kind === "move" ? event.commandType : void 0,
8127
- nodeKey: event.nodeKey,
8128
- nodeId: event.nodeId,
8129
- inspectorRef: event.inspectorRef,
8130
- selectedNodeKey: event.kind === "move" ? event.selectedNodeKey : void 0,
8131
- selectedNodeId: event.kind === "move" ? event.selectedNodeId : void 0,
8132
- selectedInspectorRef: event.kind === "move" ? event.selectedInspectorRef : void 0,
8133
- anchorNodeKey: event.kind === "move" ? event.anchorNodeKey : void 0,
8134
- anchorNodeId: event.kind === "move" ? event.anchorNodeId : void 0,
8135
- summary: summarizeEditorChangeEvent(event),
8136
- at: event.at
8137
- })),
8138
- output: {
8139
- json: aiOutput,
8140
- prompt: aiOutput.aiPromptContext,
8141
- summary: orderedChanges.length === 0 ? "No arrange changes recorded yet." : `${orderedChanges.length} arrange change${orderedChanges.length === 1 ? "" : "s"} recorded.`
8142
- },
8819
+ session: buildArrangeSessionSnapshot({
8820
+ enabled,
8821
+ orderedChanges,
8822
+ aiOutput,
8143
8823
  updatedAt
8144
- }
8824
+ })
8145
8825
  });
8146
8826
  }, [enabled, exportChangesForAI, orderedChanges]);
8147
- (0, import_react16.useEffect)(() => {
8827
+ (0, import_react17.useEffect)(() => {
8148
8828
  if (!enabled) return;
8149
8829
  document.body.classList.add("design-mode-active");
8150
8830
  return () => document.body.classList.remove("design-mode-active");
@@ -8157,6 +8837,13 @@ function DesignModeOverlay() {
8157
8837
  autoScrollStateRef: refs.autoScrollStateRef
8158
8838
  });
8159
8839
  useDragVisualLoop({ activeDrag, refs, getCommitLaneSnapshot, setInsideRect, setDropLine });
8840
+ useExternalDragLoop({
8841
+ externalDrag,
8842
+ refs,
8843
+ elements,
8844
+ resolveNodeElement,
8845
+ setDropLine
8846
+ });
8160
8847
  const { handleResizeHandlePointerDown } = useResizeSession({
8161
8848
  enabled,
8162
8849
  refs,
@@ -8212,7 +8899,7 @@ function DesignModeOverlay() {
8212
8899
  }
8213
8900
  }
8214
8901
  ),
8215
- dropLine && activeDrag && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
8902
+ dropLine && (activeDrag ?? externalDrag) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
8216
8903
  "div",
8217
8904
  {
8218
8905
  "data-design-mode-ui": true,
@@ -8237,8 +8924,8 @@ function DesignModeOverlay() {
8237
8924
  }
8238
8925
 
8239
8926
  // src/client/dnd/design-mode/DesignModeErrorBoundary.tsx
8240
- var import_react17 = __toESM(require("react"));
8241
- var DesignModeErrorBoundary = class extends import_react17.default.Component {
8927
+ var import_react18 = __toESM(require("react"));
8928
+ var DesignModeErrorBoundary = class extends import_react18.default.Component {
8242
8929
  constructor(props) {
8243
8930
  super(props);
8244
8931
  this.state = { hasError: false };
@@ -8271,35 +8958,40 @@ var combinedCollisionDetection = (args) => {
8271
8958
 
8272
8959
  // src/client/dnd/DndProvider.tsx
8273
8960
  var import_jsx_runtime5 = require("react/jsx-runtime");
8961
+ var externalDragMouse = { x: 0, y: 0, commitRequested: false };
8274
8962
  function DndProvider({ children }) {
8275
8963
  const setActive = useDndStore((s) => s.setActive);
8276
8964
  const dragEndHandlers = useDndStore((s) => s.dragEndHandlers);
8277
- const toggle = useDesignModeStore((s) => s.toggle);
8278
8965
  const designModeEnabled = useDesignModeStore((s) => s.enabled);
8279
- const setDesignModeEnabled = useDesignModeStore((s) => s.setEnabled);
8280
8966
  const setHistoryMode = useDesignModeStore((s) => s.setHistoryMode);
8281
8967
  const setInspectorTheme = useDesignModeStore((s) => s.setInspectorTheme);
8282
8968
  const requestUndo = useDesignModeStore((s) => s.requestUndo);
8283
- (0, import_react18.useEffect)(() => {
8969
+ const transitionDesignMode = (0, import_react19.useCallback)((nextEnabled) => {
8970
+ const designModeState = useDesignModeStore.getState();
8971
+ if (designModeState.enabled === nextEnabled) return;
8972
+ designModeState.resetArrangeSession();
8973
+ designModeState.setEnabled(nextEnabled);
8974
+ }, []);
8975
+ (0, import_react19.useEffect)(() => {
8284
8976
  const handleKeyDown = (e) => {
8285
8977
  if (e.ctrlKey && e.shiftKey && e.key === "D") {
8286
8978
  e.preventDefault();
8287
- toggle();
8979
+ transitionDesignMode(!useDesignModeStore.getState().enabled);
8288
8980
  }
8289
8981
  };
8290
8982
  window.addEventListener("keydown", handleKeyDown);
8291
8983
  return () => window.removeEventListener("keydown", handleKeyDown);
8292
- }, [toggle]);
8293
- (0, import_react18.useEffect)(() => {
8984
+ }, [transitionDesignMode]);
8985
+ (0, import_react19.useEffect)(() => {
8294
8986
  const handleHostCommand = (event) => {
8295
8987
  const detail = event.detail;
8296
8988
  if (!detail || typeof detail !== "object") return;
8297
8989
  switch (detail.type) {
8298
8990
  case "sync-editor-state":
8299
8991
  if (detail.mode === "design") {
8300
- setDesignModeEnabled(true);
8992
+ transitionDesignMode(true);
8301
8993
  } else if (detail.mode === "inspect" || detail.mode === "off") {
8302
- setDesignModeEnabled(false);
8994
+ transitionDesignMode(false);
8303
8995
  }
8304
8996
  if (detail.historyMode) {
8305
8997
  setHistoryMode(detail.historyMode);
@@ -8309,7 +9001,7 @@ function DndProvider({ children }) {
8309
9001
  }
8310
9002
  break;
8311
9003
  case "set-design-mode":
8312
- setDesignModeEnabled(detail.enabled);
9004
+ transitionDesignMode(detail.enabled);
8313
9005
  break;
8314
9006
  case "set-history-mode":
8315
9007
  setHistoryMode(detail.historyMode);
@@ -8320,6 +9012,21 @@ function DndProvider({ children }) {
8320
9012
  case "request-undo":
8321
9013
  requestUndo();
8322
9014
  break;
9015
+ case "external-drag-start":
9016
+ useDesignModeStore.getState().startExternalDrag(detail.component);
9017
+ break;
9018
+ case "external-drag-move":
9019
+ externalDragMouse.x = detail.clientX;
9020
+ externalDragMouse.y = detail.clientY;
9021
+ break;
9022
+ case "external-drag-end":
9023
+ externalDragMouse.x = detail.clientX;
9024
+ externalDragMouse.y = detail.clientY;
9025
+ externalDragMouse.commitRequested = true;
9026
+ break;
9027
+ case "external-drag-cancel":
9028
+ useDesignModeStore.getState().cancelExternalDrag();
9029
+ break;
8323
9030
  default:
8324
9031
  break;
8325
9032
  }
@@ -8329,7 +9036,7 @@ function DndProvider({ children }) {
8329
9036
  window.removeEventListener(HOST_COMMAND_EVENT, handleHostCommand);
8330
9037
  };
8331
9038
  }, [
8332
- setDesignModeEnabled,
9039
+ transitionDesignMode,
8333
9040
  setHistoryMode,
8334
9041
  setInspectorTheme,
8335
9042
  requestUndo
@@ -8337,7 +9044,7 @@ function DndProvider({ children }) {
8337
9044
  const pointerSensor = useSensor(PointerSensor, { activationConstraint: { distance: 8 } });
8338
9045
  const keyboardSensor = useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates });
8339
9046
  const sensors = useSensors(pointerSensor, keyboardSensor);
8340
- const handleDragStart = (0, import_react18.useCallback)(
9047
+ const handleDragStart = (0, import_react19.useCallback)(
8341
9048
  (event) => {
8342
9049
  const { active } = event;
8343
9050
  const element = document.querySelector(`[data-canvas-node="${active.id}"]`);
@@ -8345,7 +9052,7 @@ function DndProvider({ children }) {
8345
9052
  },
8346
9053
  [setActive]
8347
9054
  );
8348
- const handleDragEnd = (0, import_react18.useCallback)(
9055
+ const handleDragEnd = (0, import_react19.useCallback)(
8349
9056
  (event) => {
8350
9057
  const { active, over } = event;
8351
9058
  const dragEndEvent = {
@@ -8361,9 +9068,10 @@ function DndProvider({ children }) {
8361
9068
  },
8362
9069
  [dragEndHandlers, setActive]
8363
9070
  );
8364
- const handleDesignModeCrash = (0, import_react18.useCallback)(() => {
9071
+ const handleDesignModeCrash = (0, import_react19.useCallback)(() => {
8365
9072
  const designModeState = useDesignModeStore.getState();
8366
9073
  designModeState.resetAll();
9074
+ designModeState.resetArrangeSession();
8367
9075
  designModeState.setEnabled(false);
8368
9076
  }, []);
8369
9077
  if (designModeEnabled) {
@@ -8404,7 +9112,7 @@ function DndProvider({ children }) {
8404
9112
  }
8405
9113
 
8406
9114
  // src/client/useArchieDevToolsRuntime.ts
8407
- var import_react20 = require("react");
9115
+ var import_react21 = require("react");
8408
9116
 
8409
9117
  // src/client/inject-inspector/archieOrigins.ts
8410
9118
  var ARCHIE_HOST_ORIGINS = [
@@ -8510,10 +9218,9 @@ function injectInspector(opts = {}) {
8510
9218
  document.head.appendChild(script);
8511
9219
  return null;
8512
9220
  }
8513
- const localInspectorModulePath = "./inspector.js";
8514
9221
  return import(
8515
9222
  /* @vite-ignore */
8516
- localInspectorModulePath
9223
+ "./inspector.js"
8517
9224
  ).then((m) => {
8518
9225
  try {
8519
9226
  m.default();
@@ -8599,13 +9306,13 @@ function setupArchieRouteListener(router) {
8599
9306
  }
8600
9307
 
8601
9308
  // src/client/runtime/domMetaSanitizer.ts
8602
- var import_react19 = __toESM(require("react"));
9309
+ var import_react20 = __toESM(require("react"));
8603
9310
  var _installed = false;
8604
9311
  function installDomMetaSanitizer() {
8605
9312
  if (_installed) return;
8606
9313
  _installed = true;
8607
- const _origCE = import_react19.default.createElement.bind(import_react19.default);
8608
- import_react19.default.createElement = function patchedCreateElement(type, props, ...children) {
9314
+ const _origCE = import_react20.default.createElement.bind(import_react20.default);
9315
+ import_react20.default.createElement = function patchedCreateElement(type, props, ...children) {
8609
9316
  if (typeof type === "string" && props != null && "__editorMeta" in props) {
8610
9317
  const { __editorMeta: _stripped, ...rest } = props;
8611
9318
  return _origCE(type, rest, ...children);
@@ -8629,18 +9336,26 @@ installDomMetaSanitizer();
8629
9336
  installDomMetaSanitizer();
8630
9337
  function useArchieDevToolsRuntime({
8631
9338
  router,
8632
- inspector = true
9339
+ inspector,
9340
+ inspectorOptions
8633
9341
  }) {
8634
- (0, import_react20.useEffect)(() => {
9342
+ const stableOptionsKey = JSON.stringify(inspectorOptions ?? null);
9343
+ const inspectorOptionsRef = (0, import_react21.useRef)(inspectorOptions);
9344
+ inspectorOptionsRef.current = inspectorOptions;
9345
+ (0, import_react21.useEffect)(() => {
8635
9346
  if (process.env.NODE_ENV !== "development") {
8636
9347
  return;
8637
9348
  }
8638
9349
  const unsubscribe = setupArchieRouteListener(router);
8639
- if (inspector) {
8640
- injectInspector({ scriptSource: "remote" });
9350
+ if (inspector !== false) {
9351
+ const isLocalHost = typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
9352
+ injectInspector({
9353
+ scriptSource: isLocalHost ? "local" : "remote",
9354
+ ...inspectorOptionsRef.current
9355
+ });
8641
9356
  }
8642
9357
  return unsubscribe;
8643
- }, [router, inspector]);
9358
+ }, [router, inspector, stableOptionsKey]);
8644
9359
  }
8645
9360
 
8646
9361
  // src/client/ArchieDevToolProvider.tsx
@@ -8648,9 +9363,10 @@ var import_jsx_runtime6 = require("react/jsx-runtime");
8648
9363
  function ArchieDevToolProvider({
8649
9364
  children,
8650
9365
  router,
8651
- inspector = true
9366
+ inspector,
9367
+ inspectorOptions
8652
9368
  }) {
8653
- useArchieDevToolsRuntime({ router, inspector });
9369
+ useArchieDevToolsRuntime({ router, inspector, inspectorOptions });
8654
9370
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DndProvider, { children });
8655
9371
  }
8656
9372
  // Annotate the CommonJS export names for ESM import in node: