@archie/devtools 0.0.12 → 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.
@@ -3,7 +3,7 @@ import {
3
3
  } from "../chunk-QZ7TP4HQ.mjs";
4
4
 
5
5
  // src/client/dnd/DndProvider.tsx
6
- import { useCallback as useCallback9, useEffect as useEffect12 } from "react";
6
+ import { useCallback as useCallback9, useEffect as useEffect13 } from "react";
7
7
 
8
8
  // node_modules/@dnd-kit/core/dist/core.esm.js
9
9
  import React2, { createContext, useContext, useEffect as useEffect2, useState as useState2, useCallback as useCallback3, useMemo as useMemo2, useRef as useRef2, memo, useReducer, cloneElement, forwardRef } from "react";
@@ -4070,18 +4070,498 @@ function mergeInspectorTheme(theme) {
4070
4070
  };
4071
4071
  }
4072
4072
 
4073
+ // src/client/dnd/design-mode/structural-policy.ts
4074
+ var STRUCTURAL_COMPONENT_RE = /(?:Page|Provider|RootLayout)$/;
4075
+ var STRUCTURAL_TAGS = /* @__PURE__ */ new Set(["html", "body"]);
4076
+ function isStructuralComponentName(componentName) {
4077
+ return !!componentName && STRUCTURAL_COMPONENT_RE.test(componentName);
4078
+ }
4079
+ function isHighLevelStructuralElement(el) {
4080
+ if (!el) return false;
4081
+ if (el.hasAttribute("data-dnd-structural")) return true;
4082
+ const tag = el.tagName.toLowerCase();
4083
+ if (STRUCTURAL_TAGS.has(tag)) return true;
4084
+ if (el.id === "root") return true;
4085
+ return tag === "main" && (el.parentElement === document.body || el.parentElement?.id === "root");
4086
+ }
4087
+ function isStructuralScannedElement(scanned) {
4088
+ if (!scanned) return false;
4089
+ if (isHighLevelStructuralElement(scanned.element)) return true;
4090
+ return isStructuralComponentName(scanned.editorMeta?.componentName);
4091
+ }
4092
+ function isStructuralContainer(containerId, nodeMap) {
4093
+ return isStructuralScannedElement(nodeMap.get(containerId));
4094
+ }
4095
+ function isValidLaneContainer(containerId, projection, nodeMap) {
4096
+ const container = projection.containerIndex.get(containerId);
4097
+ if (!container) return false;
4098
+ if (container.children.length === 0) return false;
4099
+ return !isStructuralContainer(containerId, nodeMap);
4100
+ }
4101
+ function buildBlockedStructuralContainerIds(projection, nodeMap, sourceContainerId) {
4102
+ const blocked = /* @__PURE__ */ new Set();
4103
+ for (const containerId of projection.containerIndex.keys()) {
4104
+ if (containerId === sourceContainerId) continue;
4105
+ if (isStructuralContainer(containerId, nodeMap)) blocked.add(containerId);
4106
+ }
4107
+ return blocked;
4108
+ }
4109
+
4110
+ // src/client/dnd/design-mode/fiber-bridge.ts
4111
+ var FIBER_KEY_PREFIX = "__reactFiber$";
4112
+ var PROPS_KEY_PREFIX = "__reactProps$";
4113
+ var metaCache = /* @__PURE__ */ new WeakMap();
4114
+ var directMetaCache = /* @__PURE__ */ new WeakMap();
4115
+ function hasEditorMetaInFiberChain(fiber) {
4116
+ let current = fiber;
4117
+ while (current) {
4118
+ if (current.memoizedProps?.__editorMeta) return true;
4119
+ current = current.return;
4120
+ }
4121
+ return false;
4122
+ }
4123
+ function compareFiberCandidates(candidate, currentBest) {
4124
+ if (!currentBest) return 1;
4125
+ const candidateScore = [
4126
+ candidate.hasMatchingProps ? 1 : 0,
4127
+ candidate.hasDirectEditorMeta ? 1 : 0,
4128
+ candidate.hasCompanionPropsKey ? 1 : 0,
4129
+ candidate.hasEditorMetaInReturnChain ? 1 : 0,
4130
+ candidate.order
4131
+ ];
4132
+ const currentScore = [
4133
+ currentBest.hasMatchingProps ? 1 : 0,
4134
+ currentBest.hasDirectEditorMeta ? 1 : 0,
4135
+ currentBest.hasCompanionPropsKey ? 1 : 0,
4136
+ currentBest.hasEditorMetaInReturnChain ? 1 : 0,
4137
+ currentBest.order
4138
+ ];
4139
+ for (let i = 0; i < candidateScore.length; i += 1) {
4140
+ if (candidateScore[i] === currentScore[i]) continue;
4141
+ return candidateScore[i] > currentScore[i] ? 1 : -1;
4142
+ }
4143
+ return 0;
4144
+ }
4145
+ function getFiberSelectionFromElement(el) {
4146
+ const record = el;
4147
+ const keys = Object.keys(record);
4148
+ let bestCandidate = null;
4149
+ for (let i = 0; i < keys.length; i += 1) {
4150
+ const fiberKey = keys[i];
4151
+ if (!fiberKey.startsWith(FIBER_KEY_PREFIX)) continue;
4152
+ const fiber = record[fiberKey];
4153
+ if (!fiber) continue;
4154
+ if (fiber.stateNode && fiber.stateNode !== el) continue;
4155
+ const suffix = fiberKey.slice(FIBER_KEY_PREFIX.length);
4156
+ const propsKey = `${PROPS_KEY_PREFIX}${suffix}`;
4157
+ const hasCompanionPropsKey = Object.prototype.hasOwnProperty.call(record, propsKey);
4158
+ const companionProps = hasCompanionPropsKey ? record[propsKey] : void 0;
4159
+ const candidate = {
4160
+ fiber,
4161
+ fiberKey,
4162
+ hasMatchingProps: hasCompanionPropsKey && fiber.memoizedProps === companionProps,
4163
+ hasDirectEditorMeta: !!fiber.memoizedProps?.__editorMeta,
4164
+ hasCompanionPropsKey,
4165
+ hasEditorMetaInReturnChain: hasEditorMetaInFiberChain(fiber),
4166
+ // Tiebreaker: higher index in Object.keys = later React fiber attachment.
4167
+ // Relies on spec-guaranteed insertion-order for string keys (ES2015+).
4168
+ order: i
4169
+ };
4170
+ if (compareFiberCandidates(candidate, bestCandidate) > 0) {
4171
+ bestCandidate = candidate;
4172
+ }
4173
+ }
4174
+ if (!bestCandidate) {
4175
+ return { fiber: null, fiberKey: null };
4176
+ }
4177
+ return { fiber: bestCandidate.fiber, fiberKey: bestCandidate.fiberKey };
4178
+ }
4179
+ function readFiberAwareCache(cache, el, selection) {
4180
+ const cached = cache.get(el);
4181
+ if (!cached) return void 0;
4182
+ if (cached.fiber !== selection.fiber) return void 0;
4183
+ if (cached.fiberKey !== selection.fiberKey) return void 0;
4184
+ return cached.value;
4185
+ }
4186
+ function writeFiberAwareCache(cache, el, selection, value) {
4187
+ cache.set(el, {
4188
+ fiber: selection.fiber,
4189
+ fiberKey: selection.fiberKey,
4190
+ value
4191
+ });
4192
+ return value;
4193
+ }
4194
+ function isRootSvg(el) {
4195
+ return el instanceof SVGElement && el.tagName.toLowerCase() === "svg";
4196
+ }
4197
+ function isPassthroughMeta(meta) {
4198
+ return !!meta?.staticProps && meta.staticProps.asChild === true;
4199
+ }
4200
+ function isWrapperMeta(meta) {
4201
+ if (!meta?.componentName) return false;
4202
+ return meta.componentName[0] !== meta.componentName[0].toLowerCase();
4203
+ }
4204
+ function hasOwnTextContent(el) {
4205
+ for (const node of Array.from(el.childNodes)) {
4206
+ if (node.nodeType !== Node.TEXT_NODE) continue;
4207
+ if (node.textContent?.trim()) return true;
4208
+ }
4209
+ return false;
4210
+ }
4211
+ function getCandidateChildren(el) {
4212
+ return Array.from(el.children).filter((child) => {
4213
+ if (!(child instanceof HTMLElement)) return false;
4214
+ if (child.hasAttribute("data-design-mode-ui")) return false;
4215
+ if (child.hasAttribute("data-design-mode-allow")) return false;
4216
+ return !shouldIgnoreAsVirtualRuntime(child);
4217
+ });
4218
+ }
4219
+ function shouldIgnoreAsVirtualRuntime(el) {
4220
+ const tag = el.tagName.toLowerCase();
4221
+ if (tag === "canvas") return true;
4222
+ if (tag === "script" || tag === "style" || tag === "noscript") return true;
4223
+ if (el instanceof SVGElement && !isRootSvg(el)) return true;
4224
+ return false;
4225
+ }
4226
+ function getDirectEditorMeta(el) {
4227
+ const selection = getFiberSelectionFromElement(el);
4228
+ const cached = readFiberAwareCache(directMetaCache, el, selection);
4229
+ if (cached !== void 0) return cached;
4230
+ const meta = selection.fiber?.memoizedProps?.__editorMeta;
4231
+ return writeFiberAwareCache(directMetaCache, el, selection, meta ?? null);
4232
+ }
4233
+ function getEditorMeta(el) {
4234
+ const selection = getFiberSelectionFromElement(el);
4235
+ const cached = readFiberAwareCache(metaCache, el, selection);
4236
+ if (cached !== void 0) return cached;
4237
+ let fiber = selection.fiber;
4238
+ while (fiber) {
4239
+ const meta = fiber.memoizedProps?.__editorMeta;
4240
+ if (meta) {
4241
+ return writeFiberAwareCache(metaCache, el, selection, meta);
4242
+ }
4243
+ fiber = fiber.return;
4244
+ }
4245
+ return writeFiberAwareCache(metaCache, el, selection, null);
4246
+ }
4247
+ function findMetaOwnerElement(el) {
4248
+ let current = el;
4249
+ while (current) {
4250
+ if (getEditorMeta(current)) return current;
4251
+ current = current.parentElement;
4252
+ }
4253
+ return null;
4254
+ }
4255
+ function resolveHostElementFromFiberChain(el) {
4256
+ if (shouldIgnoreAsVirtualRuntime(el)) return null;
4257
+ const directMeta = getDirectEditorMeta(el);
4258
+ const ownerMeta = directMeta ?? getEditorMeta(el);
4259
+ if (isPassthroughMeta(ownerMeta)) {
4260
+ const children = getCandidateChildren(el);
4261
+ if (children.length !== 1) return null;
4262
+ return children[0];
4263
+ }
4264
+ if (directMeta && !isWrapperMeta(directMeta)) return el;
4265
+ const MAX_STEPS = 10;
4266
+ const ownerNodeId = ownerMeta?.nodeId ?? null;
4267
+ let outermost = el;
4268
+ let parent = el.parentElement;
4269
+ for (let i = 0; i < MAX_STEPS; i += 1) {
4270
+ if (!parent || parent === document.body) break;
4271
+ if (shouldIgnoreAsVirtualRuntime(parent)) break;
4272
+ const parentMeta = getEditorMeta(parent);
4273
+ if (!parentMeta) break;
4274
+ if (ownerNodeId && parentMeta.nodeId !== ownerNodeId) break;
4275
+ if (isStructuralComponentName(parentMeta.componentName)) break;
4276
+ if (!isWrapperMeta(parentMeta)) break;
4277
+ if (isPassthroughMeta(parentMeta)) break;
4278
+ if (hasOwnTextContent(parent)) break;
4279
+ const children = getCandidateChildren(parent);
4280
+ if (children.length !== 1) break;
4281
+ outermost = parent;
4282
+ parent = parent.parentElement;
4283
+ }
4284
+ return outermost;
4285
+ }
4286
+ function resolveDragSurface(el) {
4287
+ const host = resolveHostElementFromFiberChain(el);
4288
+ if (!host) {
4289
+ return { host: null, metaOwner: null, kind: "direct-host" };
4290
+ }
4291
+ const metaOwner = findMetaOwnerElement(host) ?? findMetaOwnerElement(el);
4292
+ const ownerMeta = metaOwner ? getEditorMeta(metaOwner) : null;
4293
+ const kind = isPassthroughMeta(ownerMeta) ? "passthrough-slot" : metaOwner && metaOwner !== host && isWrapperMeta(ownerMeta) ? "wrapper-to-host" : "direct-host";
4294
+ return {
4295
+ host,
4296
+ metaOwner,
4297
+ kind
4298
+ };
4299
+ }
4300
+
4301
+ // src/client/dnd/design-mode/node-key.ts
4302
+ var _runtimeKeyMap = /* @__PURE__ */ new WeakMap();
4303
+ var _runtimeKeyCounter = 0;
4304
+ function getRuntimeKey(el) {
4305
+ const existing = _runtimeKeyMap.get(el);
4306
+ if (existing) return existing;
4307
+ const key2 = `runtime:${++_runtimeKeyCounter}`;
4308
+ _runtimeKeyMap.set(el, key2);
4309
+ return key2;
4310
+ }
4311
+ function getNodeKeyFromElement(el) {
4312
+ const resolved = resolveDragSurface(el);
4313
+ const host = resolved.host ?? el;
4314
+ const meta = getEditorMeta(host);
4315
+ if (meta) return meta.nodeId;
4316
+ const persisted = host.getAttribute("data-dnd-node-id");
4317
+ if (persisted) return persisted;
4318
+ return getRuntimeKey(host);
4319
+ }
4320
+ function getInspectorRefFromElement(el) {
4321
+ const resolved = resolveDragSurface(el);
4322
+ const host = resolved.host ?? el;
4323
+ const meta = getEditorMeta(host);
4324
+ if (!meta) return null;
4325
+ return { relativePath: meta.file, line: meta.line, column: meta.col };
4326
+ }
4327
+
4328
+ // src/client/dnd/design-mode/arrange-persistence-diagnostics.ts
4329
+ function isSourceBackedRuntimeNodeKey(nodeKey) {
4330
+ return typeof nodeKey === "string" && nodeKey.trim().length > 0 && !nodeKey.startsWith("runtime:");
4331
+ }
4332
+ function mergeRuntimeArrangeDiagnostics(diagnosticsEntries) {
4333
+ const blockedReasons = /* @__PURE__ */ new Set();
4334
+ const notes = /* @__PURE__ */ new Set();
4335
+ let structurallyUnsafe = false;
4336
+ for (const diagnostics of diagnosticsEntries) {
4337
+ if (!diagnostics) continue;
4338
+ if (diagnostics.structurallyUnsafe === true) {
4339
+ structurallyUnsafe = true;
4340
+ }
4341
+ for (const reason of diagnostics.blockedReasons ?? []) {
4342
+ if (typeof reason === "string" && reason.trim().length > 0) {
4343
+ blockedReasons.add(reason);
4344
+ }
4345
+ }
4346
+ for (const note of diagnostics.notes ?? []) {
4347
+ if (typeof note === "string" && note.trim().length > 0) {
4348
+ notes.add(note);
4349
+ }
4350
+ }
4351
+ }
4352
+ if (!structurallyUnsafe && blockedReasons.size === 0 && notes.size === 0) {
4353
+ return null;
4354
+ }
4355
+ return {
4356
+ ...structurallyUnsafe ? { structurallyUnsafe: true } : {},
4357
+ ...blockedReasons.size > 0 ? { blockedReasons: Array.from(blockedReasons) } : {},
4358
+ ...notes.size > 0 ? { notes: Array.from(notes) } : {}
4359
+ };
4360
+ }
4361
+ function detectDuplicateSourceBackedNodeKeys(nodeKeys) {
4362
+ const seenKeys = /* @__PURE__ */ new Set();
4363
+ const duplicateKeys = /* @__PURE__ */ new Set();
4364
+ for (const nodeKey of nodeKeys) {
4365
+ if (!isSourceBackedRuntimeNodeKey(nodeKey)) {
4366
+ continue;
4367
+ }
4368
+ if (seenKeys.has(nodeKey)) {
4369
+ duplicateKeys.add(nodeKey);
4370
+ continue;
4371
+ }
4372
+ seenKeys.add(nodeKey);
4373
+ }
4374
+ return Array.from(duplicateKeys);
4375
+ }
4376
+ function buildOccurrenceNodeId(nodeKey, occurrenceIndex) {
4377
+ return `${nodeKey}::${occurrenceIndex}`;
4378
+ }
4379
+ function buildEffectiveNodeIdsByCommitNodeId(commitNodeIds, nodeKeysByCommitNodeId) {
4380
+ const countsByNodeKey = /* @__PURE__ */ new Map();
4381
+ for (const commitNodeId of commitNodeIds) {
4382
+ const nodeKey = nodeKeysByCommitNodeId.get(commitNodeId);
4383
+ if (!isSourceBackedRuntimeNodeKey(nodeKey)) {
4384
+ continue;
4385
+ }
4386
+ countsByNodeKey.set(nodeKey, (countsByNodeKey.get(nodeKey) ?? 0) + 1);
4387
+ }
4388
+ const seenByNodeKey = /* @__PURE__ */ new Map();
4389
+ const effectiveNodeIdsByCommitNodeId = /* @__PURE__ */ new Map();
4390
+ for (const commitNodeId of commitNodeIds) {
4391
+ const nodeKey = nodeKeysByCommitNodeId.get(commitNodeId);
4392
+ if (!isSourceBackedRuntimeNodeKey(nodeKey) || (countsByNodeKey.get(nodeKey) ?? 0) < 2) {
4393
+ effectiveNodeIdsByCommitNodeId.set(commitNodeId, null);
4394
+ continue;
4395
+ }
4396
+ const occurrenceIndex = seenByNodeKey.get(nodeKey) ?? 0;
4397
+ seenByNodeKey.set(nodeKey, occurrenceIndex + 1);
4398
+ effectiveNodeIdsByCommitNodeId.set(
4399
+ commitNodeId,
4400
+ buildOccurrenceNodeId(nodeKey, occurrenceIndex)
4401
+ );
4402
+ }
4403
+ return effectiveNodeIdsByCommitNodeId;
4404
+ }
4405
+ function createCommitLaneIdentitySnapshot(params) {
4406
+ return {
4407
+ laneContainerId: params.laneContainerId,
4408
+ commitNodeIds: [...params.commitNodeIds],
4409
+ nodeKeysByCommitNodeId: new Map(params.nodeKeysByCommitNodeId),
4410
+ effectiveNodeIdsByCommitNodeId: buildEffectiveNodeIdsByCommitNodeId(
4411
+ params.commitNodeIds,
4412
+ params.nodeKeysByCommitNodeId
4413
+ ),
4414
+ committable: true
4415
+ };
4416
+ }
4417
+ function hasSafeOccurrenceIdentities(lane, duplicateNodeKey) {
4418
+ const effectiveNodeIds = lane.commitNodeIds.filter(
4419
+ (commitNodeId) => lane.nodeKeysByCommitNodeId.get(commitNodeId) === duplicateNodeKey
4420
+ ).map(
4421
+ (commitNodeId) => lane.effectiveNodeIdsByCommitNodeId.get(commitNodeId) ?? null
4422
+ );
4423
+ return effectiveNodeIds.length > 1 && effectiveNodeIds.every(
4424
+ (effectiveNodeId) => typeof effectiveNodeId === "string" && effectiveNodeId.length > 0
4425
+ ) && new Set(effectiveNodeIds).size === effectiveNodeIds.length;
4426
+ }
4427
+ function buildLanePersistenceDiagnostics(params) {
4428
+ const duplicateNodeKeys = detectDuplicateSourceBackedNodeKeys(
4429
+ buildNodeKeysFromCommitIds(
4430
+ params.lane.commitNodeIds,
4431
+ params.lane.nodeKeysByCommitNodeId
4432
+ )
4433
+ );
4434
+ if (duplicateNodeKeys.length === 0) {
4435
+ return null;
4436
+ }
4437
+ const safeDuplicateNodeKeys = duplicateNodeKeys.filter(
4438
+ (nodeKey) => hasSafeOccurrenceIdentities(params.lane, nodeKey)
4439
+ );
4440
+ if (safeDuplicateNodeKeys.length === duplicateNodeKeys.length) {
4441
+ return {
4442
+ notes: safeDuplicateNodeKeys.map(
4443
+ (nodeKey) => `Arrange lane ${params.lane.laneContainerId} repeats source-backed nodeKey ${nodeKey}, but runtime assigned explicit occurrence identities for each commit root.`
4444
+ )
4445
+ };
4446
+ }
4447
+ return {
4448
+ structurallyUnsafe: true,
4449
+ blockedReasons: duplicateNodeKeys.map(
4450
+ (nodeKey) => `Arrange lane ${params.lane.laneContainerId} contains multiple commit roots with the same source-backed nodeKey (${nodeKey}), so local save is unsafe.`
4451
+ ),
4452
+ notes: [
4453
+ `Lane ${params.lane.laneContainerId} duplicates source-backed nodeKeys: ${duplicateNodeKeys.join(", ")}`
4454
+ ]
4455
+ };
4456
+ }
4457
+ function removeCommitNodeId(commitNodeIds, commitNodeId) {
4458
+ return commitNodeIds.filter((candidate) => candidate !== commitNodeId);
4459
+ }
4460
+ function buildNodeKeysFromCommitIds(commitNodeIds, nodeKeysByCommitNodeId) {
4461
+ return commitNodeIds.map(
4462
+ (commitNodeId) => nodeKeysByCommitNodeId.get(commitNodeId) ?? null
4463
+ );
4464
+ }
4465
+ function captureCommitLaneIdentitySnapshot(lane, resolveNodeElement) {
4466
+ if (!lane?.committable) return null;
4467
+ const nodeKeysByCommitNodeId = /* @__PURE__ */ new Map();
4468
+ for (const commitNodeId of lane.orderedCommitNodeIds) {
4469
+ const element = resolveNodeElement(commitNodeId);
4470
+ if (!element) return null;
4471
+ nodeKeysByCommitNodeId.set(commitNodeId, getNodeKeyFromElement(element));
4472
+ }
4473
+ return createCommitLaneIdentitySnapshot({
4474
+ laneContainerId: lane.laneContainerId,
4475
+ nodeKeysByCommitNodeId,
4476
+ commitNodeIds: lane.orderedCommitNodeIds
4477
+ });
4478
+ }
4479
+ function projectCommitNodeIdsForMove(params) {
4480
+ const withoutMovedCommit = removeCommitNodeId(
4481
+ params.commitNodeIds,
4482
+ params.movedCommitNodeId
4483
+ );
4484
+ if (typeof params.targetIndex !== "number" || !Number.isFinite(params.targetIndex)) {
4485
+ return [...withoutMovedCommit, params.movedCommitNodeId];
4486
+ }
4487
+ const insertionIndex = Math.max(
4488
+ 0,
4489
+ Math.min(params.targetIndex, withoutMovedCommit.length)
4490
+ );
4491
+ return [
4492
+ ...withoutMovedCommit.slice(0, insertionIndex),
4493
+ params.movedCommitNodeId,
4494
+ ...withoutMovedCommit.slice(insertionIndex)
4495
+ ];
4496
+ }
4497
+ function buildMovePersistenceDiagnostics(params) {
4498
+ const {
4499
+ sourceLane,
4500
+ targetLane,
4501
+ movedCommitNodeId,
4502
+ sourceLaneContainerId,
4503
+ targetLaneContainerId,
4504
+ targetIndex
4505
+ } = params;
4506
+ const sameLane = !!sourceLaneContainerId && !!targetLaneContainerId && sourceLaneContainerId === targetLaneContainerId;
4507
+ const targetSnapshot = sameLane ? targetLane ?? sourceLane : targetLane;
4508
+ const sourceLaneAfter = !sameLane && sourceLane?.committable ? createCommitLaneIdentitySnapshot({
4509
+ laneContainerId: sourceLane.laneContainerId,
4510
+ commitNodeIds: removeCommitNodeId(
4511
+ sourceLane.commitNodeIds,
4512
+ movedCommitNodeId
4513
+ ),
4514
+ nodeKeysByCommitNodeId: sourceLane.nodeKeysByCommitNodeId
4515
+ }) : null;
4516
+ const targetLaneAfter = targetSnapshot?.committable ? createCommitLaneIdentitySnapshot({
4517
+ laneContainerId: targetSnapshot.laneContainerId,
4518
+ commitNodeIds: projectCommitNodeIdsForMove({
4519
+ commitNodeIds: targetSnapshot.commitNodeIds,
4520
+ movedCommitNodeId,
4521
+ targetIndex
4522
+ }),
4523
+ nodeKeysByCommitNodeId: new Map([
4524
+ ...targetSnapshot.nodeKeysByCommitNodeId,
4525
+ ...sourceLane?.nodeKeysByCommitNodeId.has(movedCommitNodeId) ? [
4526
+ [
4527
+ movedCommitNodeId,
4528
+ sourceLane.nodeKeysByCommitNodeId.get(movedCommitNodeId) ?? null
4529
+ ]
4530
+ ] : []
4531
+ ])
4532
+ }) : null;
4533
+ return mergeRuntimeArrangeDiagnostics([
4534
+ sourceLaneAfter ? buildLanePersistenceDiagnostics({ lane: sourceLaneAfter }) : null,
4535
+ targetLaneAfter ? buildLanePersistenceDiagnostics({ lane: targetLaneAfter }) : null
4536
+ ]);
4537
+ }
4538
+
4073
4539
  // src/client/dnd/design-mode/store.ts
4074
4540
  function summarizeEditorChangeEvent(event) {
4541
+ if (event.kind === "insert") {
4542
+ return `insert ${event.displayName} at ${event.containerId}[${event.index}]`;
4543
+ }
4075
4544
  if (event.kind === "move") {
4076
4545
  const selectedHint = event.selectedNodeKey && event.selectedNodeKey !== event.nodeKey ? ` (selected ${event.selectedNodeKey})` : "";
4077
4546
  const anchorHint = event.anchorNodeKey && event.anchorNodeKey !== event.nodeKey ? ` (anchor ${event.anchorNodeKey})` : "";
4078
- return `move ${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}`;
4547
+ const beforeParentNodeId = event.payload.before.effectiveContainerNodeId ?? event.payload.before.parentNodeId;
4548
+ const afterParentNodeId = event.payload.after.effectiveContainerNodeId ?? event.payload.after.parentNodeId;
4549
+ const commitNodeId = event.payload.after.effectiveCommitNodeId ?? event.payload.after.commitNodeId;
4550
+ const beforeNodeId = event.payload.after.effectiveBeforeNodeId ?? event.payload.after.beforeNodeId;
4551
+ const afterNodeId = event.payload.after.effectiveAfterNodeId ?? event.payload.after.afterNodeId;
4552
+ 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}`;
4079
4553
  }
4080
4554
  if (event.payload.after.mode === "grid-span") {
4081
4555
  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}`;
4082
4556
  }
4083
4557
  return `resize(px) ${event.nodeKey}: ${event.payload.before.width}\xD7${event.payload.before.height} \u2192 ${event.payload.after.width}\xD7${event.payload.after.height}`;
4084
4558
  }
4559
+ function buildFallbackOperationId(now, nodeId) {
4560
+ return `${now}:${nodeId}`;
4561
+ }
4562
+ function inferMoveCommandType(before, after) {
4563
+ return before.parentNodeId === after.parentNodeId ? "reorder" : "moveCrossContainer";
4564
+ }
4085
4565
  function rebuildNodeEdit(orderedChanges, nodeKey) {
4086
4566
  let latestMove;
4087
4567
  let latestResize;
@@ -4096,11 +4576,36 @@ function rebuildNodeEdit(orderedChanges, nodeKey) {
4096
4576
  nodeId = ev.nodeId;
4097
4577
  inspectorRef = ev.inspectorRef;
4098
4578
  if (ev.kind === "move") latestMove = ev.payload;
4099
- else latestResize = ev.payload;
4579
+ else if (ev.kind === "resize") latestResize = ev.payload;
4100
4580
  }
4101
4581
  if (count === 0) return null;
4102
4582
  return { nodeKey, nodeId, inspectorRef, latestMove, latestResize, changeCount: count, lastUpdatedAt: lastAt };
4103
4583
  }
4584
+ function coalesceOrderedChanges(changes) {
4585
+ if (changes.length <= 1) return changes;
4586
+ const result = [];
4587
+ for (const event of changes) {
4588
+ const prev = result[result.length - 1];
4589
+ if (prev && prev.kind === "resize" && event.kind === "resize" && prev.nodeKey === event.nodeKey) {
4590
+ result[result.length - 1] = {
4591
+ ...event,
4592
+ payload: { before: prev.payload.before, after: event.payload.after }
4593
+ };
4594
+ } else {
4595
+ result.push(event);
4596
+ }
4597
+ }
4598
+ return result;
4599
+ }
4600
+ function buildEmptyArrangeSessionState() {
4601
+ return {
4602
+ nodeEditsByKey: {},
4603
+ orderedChanges: [],
4604
+ undoneChanges: [],
4605
+ canUndo: false,
4606
+ canRedo: false
4607
+ };
4608
+ }
4104
4609
  var useDesignModeStore = create((set, get) => ({
4105
4610
  // UI state
4106
4611
  enabled: false,
@@ -4116,8 +4621,10 @@ var useDesignModeStore = create((set, get) => ({
4116
4621
  nodeEditsByKey: {},
4117
4622
  orderedChanges: [],
4118
4623
  undoneChanges: [],
4624
+ // External drag
4625
+ externalDrag: null,
4119
4626
  // UI actions
4120
- toggle: () => set((s) => ({ enabled: !s.enabled })),
4627
+ toggle: () => set((s) => ({ enabled: !s.enabled, ...buildEmptyArrangeSessionState() })),
4121
4628
  setEnabled: (enabled) => set({ enabled }),
4122
4629
  setHovered: (selector) => set({ hoveredSelector: selector }),
4123
4630
  setSelected: (selector) => set({ selectedSelector: selector }),
@@ -4132,8 +4639,11 @@ var useDesignModeStore = create((set, get) => ({
4132
4639
  canUndo: false,
4133
4640
  canRedo: false
4134
4641
  }),
4642
+ resetArrangeSession: () => set(buildEmptyArrangeSessionState()),
4135
4643
  // Change tracking actions
4136
4644
  recordMoveChange: ({
4645
+ operationId,
4646
+ commandType,
4137
4647
  nodeKey,
4138
4648
  nodeId,
4139
4649
  inspectorRef,
@@ -4142,13 +4652,24 @@ var useDesignModeStore = create((set, get) => ({
4142
4652
  selectedInspectorRef,
4143
4653
  anchorNodeKey,
4144
4654
  anchorNodeId,
4655
+ sourceContainer,
4656
+ sourceBeforeSibling,
4657
+ sourceAfterSibling,
4658
+ container,
4659
+ beforeSibling,
4660
+ afterSibling,
4661
+ diagnostics,
4145
4662
  componentName,
4146
4663
  before,
4147
4664
  after
4148
4665
  }) => {
4149
4666
  const now = Date.now();
4667
+ const normalizedOperationId = operationId ?? buildFallbackOperationId(now, nodeId);
4668
+ const normalizedCommandType = commandType ?? inferMoveCommandType(before, after);
4150
4669
  const event = {
4151
4670
  kind: "move",
4671
+ operationId: normalizedOperationId,
4672
+ commandType: normalizedCommandType,
4152
4673
  nodeKey,
4153
4674
  nodeId,
4154
4675
  inspectorRef,
@@ -4157,6 +4678,13 @@ var useDesignModeStore = create((set, get) => ({
4157
4678
  selectedInspectorRef,
4158
4679
  anchorNodeKey,
4159
4680
  anchorNodeId,
4681
+ sourceContainer: sourceContainer ?? null,
4682
+ sourceBeforeSibling: sourceBeforeSibling ?? null,
4683
+ sourceAfterSibling: sourceAfterSibling ?? null,
4684
+ container: container ?? null,
4685
+ beforeSibling: beforeSibling ?? null,
4686
+ afterSibling: afterSibling ?? null,
4687
+ diagnostics: diagnostics ?? null,
4160
4688
  payload: { before, after },
4161
4689
  at: now
4162
4690
  };
@@ -4179,10 +4707,20 @@ var useDesignModeStore = create((set, get) => ({
4179
4707
  };
4180
4708
  });
4181
4709
  },
4182
- recordResizeChange: ({ nodeKey, nodeId, inspectorRef, componentName, before, after }) => {
4710
+ recordResizeChange: ({
4711
+ operationId,
4712
+ nodeKey,
4713
+ nodeId,
4714
+ inspectorRef,
4715
+ componentName,
4716
+ before,
4717
+ after
4718
+ }) => {
4183
4719
  const now = Date.now();
4720
+ const normalizedOperationId = operationId ?? buildFallbackOperationId(now, nodeId);
4184
4721
  const event = {
4185
4722
  kind: "resize",
4723
+ operationId: normalizedOperationId,
4186
4724
  nodeKey,
4187
4725
  nodeId,
4188
4726
  inspectorRef,
@@ -4208,6 +4746,52 @@ var useDesignModeStore = create((set, get) => ({
4208
4746
  };
4209
4747
  });
4210
4748
  },
4749
+ recordInsertChange: ({
4750
+ operationId,
4751
+ nodeKey,
4752
+ nodeId,
4753
+ inspectorRef,
4754
+ displayName,
4755
+ html,
4756
+ containerId,
4757
+ containerNodeKey,
4758
+ index
4759
+ }) => {
4760
+ const now = Date.now();
4761
+ const normalizedOperationId = operationId ?? buildFallbackOperationId(now, nodeId);
4762
+ const event = {
4763
+ kind: "insert",
4764
+ operationId: normalizedOperationId,
4765
+ nodeKey,
4766
+ nodeId,
4767
+ inspectorRef,
4768
+ displayName,
4769
+ html,
4770
+ containerId,
4771
+ containerNodeKey,
4772
+ index,
4773
+ at: now
4774
+ };
4775
+ set((s) => {
4776
+ const prev = s.nodeEditsByKey[nodeKey];
4777
+ const meta = {
4778
+ nodeKey,
4779
+ nodeId,
4780
+ inspectorRef,
4781
+ latestMove: prev?.latestMove,
4782
+ latestResize: prev?.latestResize,
4783
+ changeCount: (prev?.changeCount ?? 0) + 1,
4784
+ lastUpdatedAt: now
4785
+ };
4786
+ return {
4787
+ orderedChanges: [...s.orderedChanges, event],
4788
+ nodeEditsByKey: { ...s.nodeEditsByKey, [nodeKey]: meta },
4789
+ undoneChanges: []
4790
+ };
4791
+ });
4792
+ },
4793
+ startExternalDrag: (component) => set({ externalDrag: component }),
4794
+ cancelExternalDrag: () => set({ externalDrag: null }),
4211
4795
  undoLastChange: () => {
4212
4796
  const { orderedChanges, undoneChanges } = get();
4213
4797
  if (orderedChanges.length === 0) return null;
@@ -4233,7 +4817,8 @@ var useDesignModeStore = create((set, get) => ({
4233
4817
  },
4234
4818
  clearChanges: () => set({ orderedChanges: [], undoneChanges: [], nodeEditsByKey: {} }),
4235
4819
  exportChangesForAI: () => {
4236
- const { orderedChanges, nodeEditsByKey } = get();
4820
+ const { orderedChanges: rawChanges, nodeEditsByKey } = get();
4821
+ const orderedChanges = coalesceOrderedChanges(rawChanges);
4237
4822
  const changesByFile = {};
4238
4823
  for (const event of orderedChanges) {
4239
4824
  const fileKey = event.inspectorRef?.relativePath ?? "__runtime__";
@@ -4247,180 +4832,127 @@ var useDesignModeStore = create((set, get) => ({
4247
4832
  lines.push(` ${summarizeEditorChangeEvent(ev)}`);
4248
4833
  }
4249
4834
  }
4835
+ const diagnostics = mergeRuntimeArrangeDiagnostics(
4836
+ orderedChanges.map(
4837
+ (event) => event.kind === "move" ? event.diagnostics : null
4838
+ )
4839
+ );
4250
4840
  return {
4251
4841
  changesByFile,
4252
4842
  finalSnapshot: { ...nodeEditsByKey },
4253
- aiPromptContext: lines.join("\n")
4843
+ aiPromptContext: lines.join("\n"),
4844
+ ...diagnostics ? { diagnostics } : {}
4254
4845
  };
4255
4846
  }
4256
4847
  }));
4257
4848
 
4258
4849
  // src/client/dnd/design-mode/DesignModeOverlay.tsx
4259
- import { useRef as useRef9, useCallback as useCallback8, useEffect as useEffect11, useLayoutEffect as useLayoutEffect2, useState as useState6, useMemo as useMemo6 } from "react";
4850
+ import { useRef as useRef10, useCallback as useCallback8, useEffect as useEffect12, useLayoutEffect as useLayoutEffect2, useState as useState6, useMemo as useMemo6 } from "react";
4260
4851
 
4261
4852
  // src/client/dnd/design-mode/useElementScanner.ts
4262
4853
  import { useState as useState4, useEffect as useEffect4, useCallback as useCallback4, useRef as useRef4 } from "react";
4263
4854
 
4264
- // src/client/dnd/design-mode/structural-policy.ts
4265
- var STRUCTURAL_COMPONENT_RE = /(?:Layout|Page|Provider|App)$/;
4266
- var STRUCTURAL_TAGS = /* @__PURE__ */ new Set(["html", "body"]);
4267
- function isStructuralComponentName(componentName) {
4268
- return !!componentName && STRUCTURAL_COMPONENT_RE.test(componentName);
4855
+ // src/client/dnd/design-mode/helpers.ts
4856
+ var DND_NODE_ID_ATTR = "data-dnd-node-id";
4857
+ var PAGE_EDGE_GUARD = 10;
4858
+ var HARD_PAGE_EDGE_GUARD = 4;
4859
+ var SAFE_ZONE_INSET = 8;
4860
+ function clamp(value, min, max) {
4861
+ return Math.max(min, Math.min(max, value));
4269
4862
  }
4270
- function isHighLevelStructuralElement(el) {
4271
- if (!el) return false;
4272
- const tag = el.tagName.toLowerCase();
4273
- if (STRUCTURAL_TAGS.has(tag)) return true;
4274
- if (el.id === "root") return true;
4275
- return tag === "main" && (el.parentElement === document.body || el.parentElement?.id === "root");
4863
+ function buildNodeSelector(nodeId) {
4864
+ return `[${DND_NODE_ID_ATTR}="${escapeCssAttrValue(nodeId)}"]`;
4276
4865
  }
4277
- function isStructuralScannedElement(scanned) {
4278
- if (!scanned) return false;
4279
- if (isHighLevelStructuralElement(scanned.element)) return true;
4280
- return isStructuralComponentName(scanned.editorMeta?.componentName);
4866
+ var LARGE_NODE_NEST_BLOCK_MIN_WIDTH = 520;
4867
+ var LARGE_NODE_NEST_BLOCK_MIN_HEIGHT = 260;
4868
+ var LARGE_NODE_NEST_BLOCK_MIN_AREA = 18e4;
4869
+ function isEligibleForDrag(sc) {
4870
+ const source = sc.eligibilitySource ?? "editor-meta";
4871
+ return source === "editor-meta" || source === "force-allow" || source === "inferred-sortable-descendant";
4281
4872
  }
4282
- function isStructuralContainer(containerId, nodeMap) {
4283
- return isStructuralScannedElement(nodeMap.get(containerId));
4873
+ function isPointerInEdgeZone(px, py, guard) {
4874
+ return px <= guard || py <= guard || px >= window.innerWidth - guard || py >= window.innerHeight - guard;
4284
4875
  }
4285
- function isValidLaneContainer(containerId, projection, nodeMap) {
4286
- const container = projection.containerIndex.get(containerId);
4287
- if (!container) return false;
4288
- if (container.children.length < 2) return false;
4289
- return !isStructuralContainer(containerId, nodeMap);
4876
+ function isPointerInPageEdgeZone(px, py) {
4877
+ return isPointerInEdgeZone(px, py, PAGE_EDGE_GUARD);
4290
4878
  }
4291
- function buildBlockedStructuralContainerIds(projection, nodeMap, sourceContainerId) {
4292
- const blocked = /* @__PURE__ */ new Set();
4293
- for (const containerId of projection.containerIndex.keys()) {
4294
- if (containerId === sourceContainerId) continue;
4295
- if (isStructuralContainer(containerId, nodeMap)) blocked.add(containerId);
4296
- }
4297
- return blocked;
4879
+ function isPointerInHardPageEdgeZone(px, py) {
4880
+ return isPointerInEdgeZone(px, py, HARD_PAGE_EDGE_GUARD);
4298
4881
  }
4299
-
4300
- // src/client/dnd/design-mode/fiber-bridge.ts
4301
- var metaCache = /* @__PURE__ */ new WeakMap();
4302
- var directMetaCache = /* @__PURE__ */ new WeakMap();
4303
- function getFiberFromElement(el) {
4304
- const keys = Object.keys(el);
4305
- for (let i = 0; i < keys.length; i++) {
4306
- if (keys[i].startsWith("__reactFiber$")) {
4307
- return el[keys[i]];
4308
- }
4309
- }
4310
- return null;
4882
+ function resolveSafeZoneRect() {
4883
+ const root = document.getElementById("root");
4884
+ if (!root) return null;
4885
+ const rect = root.getBoundingClientRect();
4886
+ const left = rect.left + SAFE_ZONE_INSET;
4887
+ const top = rect.top + SAFE_ZONE_INSET;
4888
+ const right = rect.right - SAFE_ZONE_INSET;
4889
+ const bottom = rect.bottom - SAFE_ZONE_INSET;
4890
+ if (right <= left || bottom <= top) return null;
4891
+ return { left, top, right, bottom };
4311
4892
  }
4312
- function isRootSvg(el) {
4313
- return el instanceof SVGElement && el.tagName.toLowerCase() === "svg";
4893
+ function isPointerInSafeZone(px, py, safeZone) {
4894
+ if (!safeZone) return true;
4895
+ return px >= safeZone.left && px <= safeZone.right && py >= safeZone.top && py <= safeZone.bottom;
4314
4896
  }
4315
- function isPassthroughMeta(meta) {
4316
- return !!meta?.staticProps && meta.staticProps.asChild === true;
4897
+ function isPointerOutsideSafeZone(px, py, safeZone) {
4898
+ return !isPointerInSafeZone(px, py, safeZone);
4317
4899
  }
4318
- function isWrapperMeta(meta) {
4319
- if (!meta?.componentName) return false;
4320
- return meta.componentName[0] !== meta.componentName[0].toLowerCase();
4900
+ function isPointerBlocked(px, py, safeZone) {
4901
+ return isPointerInHardPageEdgeZone(px, py) || isPointerOutsideSafeZone(px, py, safeZone);
4321
4902
  }
4322
- function hasOwnTextContent(el) {
4323
- for (const node of Array.from(el.childNodes)) {
4324
- if (node.nodeType !== Node.TEXT_NODE) continue;
4325
- if (node.textContent?.trim()) return true;
4903
+ function shouldBlockNestForLargeNode(w, h) {
4904
+ return w >= LARGE_NODE_NEST_BLOCK_MIN_WIDTH || h >= LARGE_NODE_NEST_BLOCK_MIN_HEIGHT || w * h >= LARGE_NODE_NEST_BLOCK_MIN_AREA;
4905
+ }
4906
+ function findScrollableAncestor(start, dx, dy) {
4907
+ let el = start;
4908
+ while (el && el !== document.body) {
4909
+ const cs = getComputedStyle(el);
4910
+ if (dy !== 0 && (cs.overflowY === "auto" || cs.overflowY === "scroll") && el.scrollHeight > el.clientHeight + 1) return el;
4911
+ if (dx !== 0 && (cs.overflowX === "auto" || cs.overflowX === "scroll") && el.scrollWidth > el.clientWidth + 1) return el;
4912
+ el = el.parentElement;
4326
4913
  }
4327
- return false;
4914
+ return null;
4328
4915
  }
4329
- function getCandidateChildren(el) {
4330
- return Array.from(el.children).filter((child) => {
4331
- if (!(child instanceof HTMLElement)) return false;
4332
- if (child.hasAttribute("data-design-mode-ui")) return false;
4333
- if (child.hasAttribute("data-design-mode-allow")) return false;
4334
- return !shouldIgnoreAsVirtualRuntime(child);
4335
- });
4916
+ function escapeCssAttrValue(value) {
4917
+ const esc = globalThis.CSS?.escape;
4918
+ return esc ? esc(value) : value.replace(/["\\]/g, "\\$&");
4336
4919
  }
4337
- function shouldIgnoreAsVirtualRuntime(el) {
4338
- const tag = el.tagName.toLowerCase();
4339
- if (tag === "canvas") return true;
4340
- if (tag === "script" || tag === "style" || tag === "noscript") return true;
4341
- if (el instanceof SVGElement && !isRootSvg(el)) return true;
4342
- return false;
4920
+ function isDesignModeUiElement(el) {
4921
+ if (!(el instanceof HTMLElement)) return false;
4922
+ return el.hasAttribute("data-design-mode-ui") || !!el.closest("[data-design-mode-ui]");
4343
4923
  }
4344
- function getDirectEditorMeta(el) {
4345
- const cached = directMetaCache.get(el);
4346
- if (cached !== void 0) return cached;
4347
- const fiber = getFiberFromElement(el);
4348
- const meta = fiber?.memoizedProps?.__editorMeta;
4349
- const resolved = meta ?? null;
4350
- directMetaCache.set(el, resolved);
4351
- return resolved;
4924
+ function isElementScrollable(el) {
4925
+ if (!(el instanceof HTMLElement)) return false;
4926
+ const cs = getComputedStyle(el);
4927
+ const overY = cs.overflowY;
4928
+ const overX = cs.overflowX;
4929
+ return (overY === "auto" || overY === "scroll") && el.scrollHeight > el.clientHeight || (overX === "auto" || overX === "scroll") && el.scrollWidth > el.clientWidth;
4352
4930
  }
4353
- function getEditorMeta(el) {
4354
- const cached = metaCache.get(el);
4355
- if (cached !== void 0) return cached;
4356
- let fiber = getFiberFromElement(el);
4357
- while (fiber) {
4358
- const meta = fiber.memoizedProps?.__editorMeta;
4359
- if (meta) {
4360
- metaCache.set(el, meta);
4361
- return meta;
4931
+ function resolveScrollableFromHitStack(stack) {
4932
+ for (const hit of stack) {
4933
+ if (isDesignModeUiElement(hit)) continue;
4934
+ let el = hit;
4935
+ while (el) {
4936
+ if (isElementScrollable(el)) return el;
4937
+ el = el.parentElement;
4362
4938
  }
4363
- fiber = fiber.return;
4364
4939
  }
4365
- metaCache.set(el, null);
4366
4940
  return null;
4367
4941
  }
4368
- function findMetaOwnerElement(el) {
4369
- let current = el;
4370
- while (current) {
4371
- if (getEditorMeta(current)) return current;
4372
- current = current.parentElement;
4373
- }
4374
- return null;
4375
- }
4376
- function resolveHostElementFromFiberChain(el) {
4377
- if (shouldIgnoreAsVirtualRuntime(el)) return null;
4378
- const directMeta = getDirectEditorMeta(el);
4379
- const ownerMeta = directMeta ?? getEditorMeta(el);
4380
- if (isPassthroughMeta(ownerMeta)) {
4381
- const children = getCandidateChildren(el);
4382
- if (children.length !== 1) return null;
4383
- return children[0];
4384
- }
4385
- if (directMeta && !isWrapperMeta(directMeta)) return el;
4386
- const MAX_STEPS = 10;
4387
- const ownerNodeId = ownerMeta?.nodeId ?? null;
4388
- let outermost = el;
4389
- let parent = el.parentElement;
4390
- for (let i = 0; i < MAX_STEPS; i += 1) {
4391
- if (!parent || parent === document.body) break;
4392
- if (shouldIgnoreAsVirtualRuntime(parent)) break;
4393
- const parentMeta = getEditorMeta(parent);
4394
- if (!parentMeta) break;
4395
- if (ownerNodeId && parentMeta.nodeId !== ownerNodeId) break;
4396
- if (isStructuralComponentName(parentMeta.componentName)) break;
4397
- if (!isWrapperMeta(parentMeta)) break;
4398
- if (isPassthroughMeta(parentMeta)) break;
4399
- if (hasOwnTextContent(parent)) break;
4400
- const children = getCandidateChildren(parent);
4401
- if (children.length !== 1) break;
4402
- outermost = parent;
4403
- parent = parent.parentElement;
4404
- }
4405
- return outermost;
4406
- }
4407
- function resolveDragSurface(el) {
4408
- const host = resolveHostElementFromFiberChain(el);
4409
- if (!host) {
4410
- return { host: null, metaOwner: null, kind: "direct-host" };
4411
- }
4412
- const metaOwner = findMetaOwnerElement(host) ?? findMetaOwnerElement(el);
4413
- const ownerMeta = metaOwner ? getEditorMeta(metaOwner) : null;
4414
- const kind = isPassthroughMeta(ownerMeta) ? "passthrough-slot" : metaOwner && metaOwner !== host && isWrapperMeta(ownerMeta) ? "wrapper-to-host" : "direct-host";
4415
- return {
4416
- host,
4417
- metaOwner,
4418
- kind
4419
- };
4942
+ var _undoParentNodeIdMap = /* @__PURE__ */ new WeakMap();
4943
+ var _undoParentNodeIdCounter = 0;
4944
+ function ensureParentNodeId(el) {
4945
+ const existing = el.getAttribute(DND_NODE_ID_ATTR);
4946
+ if (existing) return existing;
4947
+ if (!_undoParentNodeIdMap.has(el)) {
4948
+ _undoParentNodeIdMap.set(el, `dnd-parent:${++_undoParentNodeIdCounter}`);
4949
+ }
4950
+ const id = _undoParentNodeIdMap.get(el);
4951
+ el.setAttribute(DND_NODE_ID_ATTR, id);
4952
+ return id;
4420
4953
  }
4421
4954
 
4422
4955
  // src/client/dnd/design-mode/useElementScanner.ts
4423
- var DND_NODE_ID_ATTR = "data-dnd-node-id";
4424
4956
  function resolveContainerStrategy(el) {
4425
4957
  const cs = getComputedStyle(el);
4426
4958
  const d = cs.display;
@@ -4451,7 +4983,7 @@ function getRuntimeNodeId(el) {
4451
4983
  }
4452
4984
  function getEditorMetaNodeId(el) {
4453
4985
  if (!(el instanceof HTMLElement)) return null;
4454
- return getEditorMeta(el)?.nodeId ?? null;
4986
+ return getDirectEditorMeta(el)?.nodeId ?? null;
4455
4987
  }
4456
4988
  function isInspectorEligible(el) {
4457
4989
  if (getEditorMetaNodeId(el) !== null) return "editor-meta";
@@ -4475,7 +5007,7 @@ function inferEligibilityFromSortableParent(el) {
4475
5007
  if (child.hasAttribute("data-design-mode-ui")) return false;
4476
5008
  return !isRuntimeVisualInternal(child);
4477
5009
  });
4478
- if (siblingElements.length < 2) return null;
5010
+ if (siblingElements.length < 1) return null;
4479
5011
  return "inferred-sortable-descendant";
4480
5012
  }
4481
5013
  function ensureUniqueNodeId(base, used) {
@@ -5029,7 +5561,15 @@ function buildProjection(elements, rectOverrides) {
5029
5561
  }
5030
5562
  const containerIndex = /* @__PURE__ */ new Map();
5031
5563
  for (const node of nodeMap.values()) {
5032
- if (node.children.length === 0) continue;
5564
+ if (node.children.length === 0) {
5565
+ const scanInfo = elementIndex.get(node.id);
5566
+ const eligible = scanInfo?.editorMeta || scanInfo?.eligibilitySource === "force-allow";
5567
+ if (!eligible) continue;
5568
+ node.sortable = true;
5569
+ node.strategy = "vertical";
5570
+ containerIndex.set(node.id, node);
5571
+ continue;
5572
+ }
5033
5573
  node.sortable = true;
5034
5574
  const firstChild = elementIndex.get(node.children[0]);
5035
5575
  node.strategy = firstChild?.containerStrategy ?? "vertical";
@@ -5100,31 +5640,25 @@ function buildVerticalSlotRect(children, nodeMap, cRect, i, n) {
5100
5640
  const top = i === 0 ? cRect.top : nodeMap.get(children[i - 1])?.rect.bottom ?? cRect.top;
5101
5641
  const bottom = i === n ? cRect.bottom : nodeMap.get(children[i])?.rect.top ?? cRect.bottom;
5102
5642
  const height = Math.max(0, bottom - top);
5103
- return {
5104
- x: cRect.left,
5105
- y: top,
5106
- left: cRect.left,
5107
- top,
5108
- right: cRect.right,
5109
- bottom: top + height,
5110
- width: cRect.width,
5111
- height
5112
- };
5643
+ const prevRect = i > 0 ? nodeMap.get(children[i - 1])?.rect : null;
5644
+ const nextRect = i < n ? nodeMap.get(children[i])?.rect : null;
5645
+ const refRect = prevRect ?? nextRect;
5646
+ const left = refRect ? refRect.left : cRect.left;
5647
+ const right = refRect ? refRect.right : cRect.right;
5648
+ const width = Math.max(0, right - left);
5649
+ return { x: left, y: top, left, top, right, bottom: top + height, width, height };
5113
5650
  }
5114
5651
  function buildHorizontalSlotRect(children, nodeMap, cRect, i, n) {
5115
5652
  const left = i === 0 ? cRect.left : nodeMap.get(children[i - 1])?.rect.right ?? cRect.left;
5116
5653
  const right = i === n ? cRect.right : nodeMap.get(children[i])?.rect.left ?? cRect.right;
5117
5654
  const width = Math.max(0, right - left);
5118
- return {
5119
- x: left,
5120
- y: cRect.top,
5121
- left,
5122
- top: cRect.top,
5123
- right: left + width,
5124
- bottom: cRect.bottom,
5125
- width,
5126
- height: cRect.height
5127
- };
5655
+ const prevRect = i > 0 ? nodeMap.get(children[i - 1])?.rect : null;
5656
+ const nextRect = i < n ? nodeMap.get(children[i])?.rect : null;
5657
+ const refRect = prevRect ?? nextRect;
5658
+ const top = refRect ? refRect.top : cRect.top;
5659
+ const bottom = refRect ? refRect.bottom : cRect.bottom;
5660
+ const height = Math.max(0, bottom - top);
5661
+ return { x: left, y: top, left, top, right: left + width, bottom, width, height };
5128
5662
  }
5129
5663
  function buildRectSlotRect(children, nodeMap, cRect, i, n, rows) {
5130
5664
  const findRow = (childIdx) => rows.find((r) => childIdx >= r.startIdx && childIdx <= r.endIdx) ?? null;
@@ -5188,6 +5722,79 @@ function buildRectSlotRect(children, nodeMap, cRect, i, n, rows) {
5188
5722
  };
5189
5723
  }
5190
5724
 
5725
+ // src/client/dnd/design-mode/history.ts
5726
+ function applyInsertCommand(command, direction, resolveNodeElement) {
5727
+ if (direction === "undo") {
5728
+ const el = resolveNodeElement(command.nodeId);
5729
+ if (el?.isConnected) el.remove();
5730
+ return true;
5731
+ }
5732
+ const container = resolveNodeElement(command.containerId);
5733
+ if (!container) return false;
5734
+ const existing = resolveNodeElement(command.nodeId);
5735
+ if (existing?.isConnected) return true;
5736
+ const template = document.createElement("template");
5737
+ template.innerHTML = command.html.trim();
5738
+ const newEl = template.content.firstElementChild;
5739
+ if (!newEl) return false;
5740
+ newEl.setAttribute("data-dnd-node-id", command.nodeId);
5741
+ newEl.setAttribute("data-dnd-force-allow", "");
5742
+ newEl.setAttribute("data-dnd-moved", "");
5743
+ const kids = Array.from(container.children).filter(
5744
+ (c) => c instanceof HTMLElement
5745
+ );
5746
+ container.insertBefore(newEl, kids[command.index] ?? null);
5747
+ return true;
5748
+ }
5749
+ var DEFAULT_LIMIT = 300;
5750
+ var DndCommandHistory = class {
5751
+ constructor(limit = DEFAULT_LIMIT) {
5752
+ this.limit = limit;
5753
+ __publicField(this, "undoStack", []);
5754
+ __publicField(this, "redoStack", []);
5755
+ }
5756
+ execute(command, apply) {
5757
+ const applied = apply(command, "redo");
5758
+ if (!applied) return false;
5759
+ this.undoStack.push(command);
5760
+ if (this.undoStack.length > this.limit) this.undoStack.shift();
5761
+ this.redoStack = [];
5762
+ return true;
5763
+ }
5764
+ undo(apply) {
5765
+ const command = this.undoStack.pop();
5766
+ if (!command) return false;
5767
+ const applied = apply(command, "undo");
5768
+ if (!applied) {
5769
+ this.undoStack.push(command);
5770
+ return false;
5771
+ }
5772
+ this.redoStack.push(command);
5773
+ return true;
5774
+ }
5775
+ redo(apply) {
5776
+ const command = this.redoStack.pop();
5777
+ if (!command) return false;
5778
+ const applied = apply(command, "redo");
5779
+ if (!applied) {
5780
+ this.redoStack.push(command);
5781
+ return false;
5782
+ }
5783
+ this.undoStack.push(command);
5784
+ return true;
5785
+ }
5786
+ clear() {
5787
+ this.undoStack = [];
5788
+ this.redoStack = [];
5789
+ }
5790
+ canUndo() {
5791
+ return this.undoStack.length > 0;
5792
+ }
5793
+ canRedo() {
5794
+ return this.redoStack.length > 0;
5795
+ }
5796
+ };
5797
+
5191
5798
  // src/client/dnd/design-mode/resize.ts
5192
5799
  function splitTopLevelSpaceSeparated(value) {
5193
5800
  const tokens = [];
@@ -5219,9 +5826,6 @@ function parseSpanValue(value) {
5219
5826
  const parsed = Number.parseInt(match[1], 10);
5220
5827
  return Number.isFinite(parsed) && parsed > 0 ? parsed : 1;
5221
5828
  }
5222
- function clamp(value, min, max) {
5223
- return Math.max(min, Math.min(max, value));
5224
- }
5225
5829
  function resolveResizeTargetElement(selectedEl) {
5226
5830
  const elementChildren = Array.from(selectedEl.children).filter(
5227
5831
  (child2) => child2 instanceof HTMLElement
@@ -5302,100 +5906,6 @@ function applyStyleSnapshot(el, snapshot) {
5302
5906
  }
5303
5907
  }
5304
5908
 
5305
- // src/client/dnd/design-mode/helpers.ts
5306
- var DND_NODE_ID_ATTR2 = "data-dnd-node-id";
5307
- var PAGE_EDGE_GUARD = 10;
5308
- var HARD_PAGE_EDGE_GUARD = 4;
5309
- var SAFE_ZONE_INSET = 8;
5310
- var LARGE_NODE_NEST_BLOCK_MIN_WIDTH = 520;
5311
- var LARGE_NODE_NEST_BLOCK_MIN_HEIGHT = 260;
5312
- var LARGE_NODE_NEST_BLOCK_MIN_AREA = 18e4;
5313
- function isEligibleForDrag(sc) {
5314
- const source = sc.eligibilitySource ?? "editor-meta";
5315
- return source === "editor-meta" || source === "force-allow" || source === "inferred-sortable-descendant";
5316
- }
5317
- function isPointerInPageEdgeZone(px, py) {
5318
- return px <= PAGE_EDGE_GUARD || py <= PAGE_EDGE_GUARD || px >= window.innerWidth - PAGE_EDGE_GUARD || py >= window.innerHeight - PAGE_EDGE_GUARD;
5319
- }
5320
- function isPointerInHardPageEdgeZone(px, py) {
5321
- 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;
5322
- }
5323
- function resolveSafeZoneRect() {
5324
- const root = document.getElementById("root");
5325
- if (!root) return null;
5326
- const rect = root.getBoundingClientRect();
5327
- const left = rect.left + SAFE_ZONE_INSET;
5328
- const top = rect.top + SAFE_ZONE_INSET;
5329
- const right = rect.right - SAFE_ZONE_INSET;
5330
- const bottom = rect.bottom - SAFE_ZONE_INSET;
5331
- if (right <= left || bottom <= top) return null;
5332
- return { left, top, right, bottom };
5333
- }
5334
- function isPointerInSafeZone(px, py, safeZone) {
5335
- if (!safeZone) return true;
5336
- return px >= safeZone.left && px <= safeZone.right && py >= safeZone.top && py <= safeZone.bottom;
5337
- }
5338
- function isPointerOutsideSafeZone(px, py, safeZone) {
5339
- return !isPointerInSafeZone(px, py, safeZone);
5340
- }
5341
- function isPointerBlocked(px, py, safeZone) {
5342
- return isPointerInHardPageEdgeZone(px, py) || isPointerOutsideSafeZone(px, py, safeZone);
5343
- }
5344
- function shouldBlockNestForLargeNode(w, h) {
5345
- return w >= LARGE_NODE_NEST_BLOCK_MIN_WIDTH || h >= LARGE_NODE_NEST_BLOCK_MIN_HEIGHT || w * h >= LARGE_NODE_NEST_BLOCK_MIN_AREA;
5346
- }
5347
- function findScrollableAncestor(start, dx, dy) {
5348
- let el = start;
5349
- while (el && el !== document.body) {
5350
- const cs = getComputedStyle(el);
5351
- if (dy !== 0 && (cs.overflowY === "auto" || cs.overflowY === "scroll") && el.scrollHeight > el.clientHeight + 1) return el;
5352
- if (dx !== 0 && (cs.overflowX === "auto" || cs.overflowX === "scroll") && el.scrollWidth > el.clientWidth + 1) return el;
5353
- el = el.parentElement;
5354
- }
5355
- return null;
5356
- }
5357
- function escapeCssAttrValue(value) {
5358
- const esc = globalThis.CSS?.escape;
5359
- return esc ? esc(value) : value.replace(/["\\]/g, "\\$&");
5360
- }
5361
- function isDesignModeUiElement(el) {
5362
- if (!(el instanceof HTMLElement)) return false;
5363
- return el.hasAttribute("data-design-mode-ui") || !!el.closest("[data-design-mode-ui]");
5364
- }
5365
- function isElementScrollable(el) {
5366
- if (!(el instanceof HTMLElement)) return false;
5367
- const cs = getComputedStyle(el);
5368
- const overY = cs.overflowY;
5369
- const overX = cs.overflowX;
5370
- return (overY === "auto" || overY === "scroll") && el.scrollHeight > el.clientHeight || (overX === "auto" || overX === "scroll") && el.scrollWidth > el.clientWidth;
5371
- }
5372
- function resolveScrollableFromHitStack(stack) {
5373
- for (const hit of stack) {
5374
- if (isDesignModeUiElement(hit)) continue;
5375
- let el = hit;
5376
- while (el) {
5377
- if (isElementScrollable(el)) return el;
5378
- el = el.parentElement;
5379
- }
5380
- }
5381
- return null;
5382
- }
5383
- var _undoParentNodeIdMap = /* @__PURE__ */ new WeakMap();
5384
- var _undoParentNodeIdCounter = 0;
5385
- function ensureParentNodeId(el) {
5386
- const existing = el.getAttribute(DND_NODE_ID_ATTR2);
5387
- if (existing) return existing;
5388
- if (!_undoParentNodeIdMap.has(el)) {
5389
- _undoParentNodeIdMap.set(el, `dnd-parent:${++_undoParentNodeIdCounter}`);
5390
- }
5391
- const id = _undoParentNodeIdMap.get(el);
5392
- el.setAttribute(DND_NODE_ID_ATTR2, id);
5393
- return id;
5394
- }
5395
- function ensureElementNodeId(el) {
5396
- return ensureParentNodeId(el);
5397
- }
5398
-
5399
5909
  // src/client/dnd/design-mode/drag-preview.tsx
5400
5910
  import { useRef as useRef7, useEffect as useEffect6 } from "react";
5401
5911
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -5496,7 +6006,7 @@ function findNearestContainerId(nodeId, projection, nodeMap, allowStructuralFall
5496
6006
  const parentId = node?.parentId ?? null;
5497
6007
  if (!parentId) break;
5498
6008
  const parentContainer = projection.containerIndex.get(parentId);
5499
- if (parentContainer && parentContainer.children.length >= 2) {
6009
+ if (parentContainer && parentContainer.children.length >= 1) {
5500
6010
  if (isValidLaneContainer(parentId, projection, nodeMap)) return parentId;
5501
6011
  if (allowStructuralFallback && !structuralFallback) structuralFallback = parentId;
5502
6012
  }
@@ -5581,12 +6091,6 @@ function findDirectChildUnderParent(child, parent) {
5581
6091
  }
5582
6092
  return null;
5583
6093
  }
5584
- function isContiguous(indices) {
5585
- if (indices.length === 0) return false;
5586
- const min = Math.min(...indices);
5587
- const max = Math.max(...indices);
5588
- return max - min + 1 === indices.length;
5589
- }
5590
6094
  function createPlacement(parentNodeId, laneContainerId, commitNodeId, beforeNodeId, afterNodeId, index) {
5591
6095
  return {
5592
6096
  parentNodeId,
@@ -5600,7 +6104,16 @@ function createPlacement(parentNodeId, laneContainerId, commitNodeId, beforeNode
5600
6104
  function buildCommitLaneSnapshot(laneContainerId, projection, nodeMap) {
5601
6105
  const lane = projection.containerIndex.get(laneContainerId);
5602
6106
  const laneElement = nodeMap.get(laneContainerId)?.element ?? null;
5603
- if (!lane || lane.children.length === 0 || !laneElement) return null;
6107
+ if (!lane || !laneElement) return null;
6108
+ if (lane.children.length === 0) {
6109
+ return {
6110
+ laneContainerId,
6111
+ commitParentNodeId: ensureParentNodeId(laneElement),
6112
+ orderedCommitNodeIds: [],
6113
+ commitNodeByAnchorId: /* @__PURE__ */ new Map(),
6114
+ committable: true
6115
+ };
6116
+ }
5604
6117
  const childElements = lane.children.map((childId) => nodeMap.get(childId)?.element ?? null).filter((el) => !!el);
5605
6118
  if (childElements.length !== lane.children.length) return null;
5606
6119
  const commitParent = findLowestCommonAncestor(childElements, laneElement);
@@ -5617,19 +6130,18 @@ function buildCommitLaneSnapshot(laneContainerId, projection, nodeMap) {
5617
6130
  if (seenRoots.has(commitRoot)) return null;
5618
6131
  seenRoots.add(commitRoot);
5619
6132
  commitRoots.push(commitRoot);
5620
- commitNodeByAnchorId.set(anchorNodeId, ensureElementNodeId(commitRoot));
6133
+ commitNodeByAnchorId.set(anchorNodeId, ensureParentNodeId(commitRoot));
5621
6134
  }
5622
6135
  const childList = Array.from(commitParent.children).filter(
5623
6136
  (child) => child instanceof HTMLElement
5624
6137
  );
5625
6138
  const commitRootIndices = commitRoots.map((root) => childList.indexOf(root));
5626
6139
  if (commitRootIndices.some((index) => index < 0)) return null;
5627
- if (!isContiguous(commitRootIndices)) return null;
5628
6140
  const domOrderedRoots = [...commitRoots].sort(
5629
6141
  (a, b) => childList.indexOf(a) - childList.indexOf(b)
5630
6142
  );
5631
- const projectionOrderedIds = commitRoots.map((root) => ensureElementNodeId(root));
5632
- const domOrderedIds = domOrderedRoots.map((root) => ensureElementNodeId(root));
6143
+ const projectionOrderedIds = commitRoots.map((root) => ensureParentNodeId(root));
6144
+ const domOrderedIds = domOrderedRoots.map((root) => ensureParentNodeId(root));
5633
6145
  if (projectionOrderedIds.length !== domOrderedIds.length) return null;
5634
6146
  if (projectionOrderedIds.some((id, index) => id !== domOrderedIds[index])) return null;
5635
6147
  return {
@@ -5674,7 +6186,7 @@ function resolveAppendCommitPlacement(parentEl, commitNodeId, laneContainerId) {
5674
6186
  const children = Array.from(parentEl.children).filter(
5675
6187
  (child) => child instanceof HTMLElement
5676
6188
  );
5677
- const lastChild = children.length > 0 ? ensureElementNodeId(children[children.length - 1]) : null;
6189
+ const lastChild = children.length > 0 ? ensureParentNodeId(children[children.length - 1]) : null;
5678
6190
  return createPlacement(
5679
6191
  ensureParentNodeId(parentEl),
5680
6192
  laneContainerId,
@@ -5729,6 +6241,58 @@ function createDesignModeCollisionDetection(blockedContainerIds) {
5729
6241
  };
5730
6242
  }
5731
6243
 
6244
+ // src/client/dnd/design-mode/arrange-session.ts
6245
+ function serializeArrangeChange(event, index) {
6246
+ return {
6247
+ id: `${event.kind}:${event.operationId}:${index}`,
6248
+ kind: event.kind,
6249
+ operationId: event.operationId,
6250
+ commandType: event.kind === "move" ? event.commandType : void 0,
6251
+ nodeKey: event.nodeKey,
6252
+ nodeId: event.nodeId,
6253
+ inspectorRef: event.inspectorRef,
6254
+ selectedNodeKey: event.kind === "move" ? event.selectedNodeKey : void 0,
6255
+ selectedNodeId: event.kind === "move" ? event.selectedNodeId : void 0,
6256
+ selectedInspectorRef: event.kind === "move" ? event.selectedInspectorRef : void 0,
6257
+ anchorNodeKey: event.kind === "move" ? event.anchorNodeKey : void 0,
6258
+ anchorNodeId: event.kind === "move" ? event.anchorNodeId : void 0,
6259
+ container: event.kind === "move" ? event.container : void 0,
6260
+ beforeSibling: event.kind === "move" ? event.beforeSibling : void 0,
6261
+ afterSibling: event.kind === "move" ? event.afterSibling : void 0,
6262
+ sourceContainer: event.kind === "move" ? event.sourceContainer : void 0,
6263
+ sourceBeforeSibling: event.kind === "move" ? event.sourceBeforeSibling : void 0,
6264
+ sourceAfterSibling: event.kind === "move" ? event.sourceAfterSibling : void 0,
6265
+ diagnostics: event.kind === "move" ? event.diagnostics : void 0,
6266
+ // Insert-specific fields
6267
+ displayName: event.kind === "insert" ? event.displayName : void 0,
6268
+ html: event.kind === "insert" ? event.html : void 0,
6269
+ containerId: event.kind === "insert" ? event.containerId : void 0,
6270
+ containerNodeKey: event.kind === "insert" ? event.containerNodeKey : void 0,
6271
+ insertIndex: event.kind === "insert" ? event.index : void 0,
6272
+ summary: summarizeEditorChangeEvent(event),
6273
+ at: event.at
6274
+ };
6275
+ }
6276
+ function buildArrangeSessionSnapshot(params) {
6277
+ const { enabled, aiOutput } = params;
6278
+ const coalescedChanges = coalesceOrderedChanges(params.orderedChanges);
6279
+ const updatedAt = typeof params.updatedAt === "number" ? params.updatedAt : Date.now();
6280
+ return {
6281
+ mode: enabled ? "arrange" : "off",
6282
+ canUndo: params.orderedChanges.length > 0,
6283
+ changes: coalescedChanges.map(
6284
+ (event, index) => serializeArrangeChange(event, index)
6285
+ ),
6286
+ diagnostics: aiOutput.diagnostics ?? null,
6287
+ output: {
6288
+ json: aiOutput,
6289
+ prompt: aiOutput.aiPromptContext,
6290
+ summary: coalescedChanges.length === 0 ? "No arrange changes recorded yet." : `${coalescedChanges.length} arrange change${coalescedChanges.length === 1 ? "" : "s"} recorded.`
6291
+ },
6292
+ updatedAt
6293
+ };
6294
+ }
6295
+
5732
6296
  // src/client/dnd/design-mode/useDesignModeAutoScroll.ts
5733
6297
  import { useEffect as useEffect7 } from "react";
5734
6298
  function edgeVelocity(distanceToEdge, zone, maxSpeed) {
@@ -5744,11 +6308,16 @@ function findScrollableAtPoint(x, y) {
5744
6308
  return resolveScrollableFromHitStack(stack) ?? (document.scrollingElement ?? document.documentElement);
5745
6309
  }
5746
6310
  function getScrollerKey(el) {
5747
- return el.getAttribute?.(DND_NODE_ID_ATTR2) ?? el.id ?? el.tagName.toLowerCase();
6311
+ return el.getAttribute?.(DND_NODE_ID_ATTR) ?? el.id ?? el.tagName.toLowerCase();
5748
6312
  }
5749
6313
  function pointerInsideRect(x, y, rect, inset = 0) {
5750
6314
  return x >= rect.left - inset && x <= rect.right + inset && y >= rect.top - inset && y <= rect.bottom + inset;
5751
6315
  }
6316
+ var AUTO_SCROLL_ZONE_PX = 120;
6317
+ var AUTO_SCROLL_MAX_SPEED = 24;
6318
+ var SCROLLER_RECT_RECALC_INTERVAL = 8;
6319
+ var SCROLLER_SWITCH_FRAMES = 3;
6320
+ var SCROLLER_EDGE_HYSTERESIS_PX = 18;
5752
6321
  function useDesignModeAutoScroll({
5753
6322
  active,
5754
6323
  mouseRef,
@@ -5757,11 +6326,6 @@ function useDesignModeAutoScroll({
5757
6326
  }) {
5758
6327
  useEffect7(() => {
5759
6328
  if (!active) return;
5760
- const ZONE = 120;
5761
- const MAX = 24;
5762
- const RECALC_INTERVAL = 8;
5763
- const SWITCH_FRAMES = 3;
5764
- const SCROLLER_EDGE_HYSTERESIS = 18;
5765
6329
  let scrollEl = findScrollableAtPoint(mouseRef.current.x, mouseRef.current.y);
5766
6330
  let cachedRect = scrollEl.getBoundingClientRect();
5767
6331
  let rectAge = 0;
@@ -5769,17 +6333,17 @@ function useDesignModeAutoScroll({
5769
6333
  const tick = () => {
5770
6334
  const { x, y } = mouseRef.current;
5771
6335
  const safeZone = safeZoneRef.current;
5772
- if (++rectAge > RECALC_INTERVAL) {
6336
+ if (++rectAge > SCROLLER_RECT_RECALC_INTERVAL) {
5773
6337
  const candidate = findScrollableAtPoint(x, y);
5774
6338
  const candidateKey = getScrollerKey(candidate);
5775
6339
  const currentKey = getScrollerKey(scrollEl);
5776
6340
  const currentRect = scrollEl.getBoundingClientRect();
5777
- if (candidateKey !== currentKey && !pointerInsideRect(x, y, currentRect, SCROLLER_EDGE_HYSTERESIS)) {
6341
+ if (candidateKey !== currentKey && !pointerInsideRect(x, y, currentRect, SCROLLER_EDGE_HYSTERESIS_PX)) {
5778
6342
  const pendingKey = autoScrollStateRef.current.pendingScrollerKey;
5779
6343
  const pendingFrames = pendingKey === candidateKey ? (autoScrollStateRef.current.pendingFrames ?? 0) + 1 : 1;
5780
6344
  autoScrollStateRef.current.pendingScrollerKey = candidateKey;
5781
6345
  autoScrollStateRef.current.pendingFrames = pendingFrames;
5782
- if (pendingFrames >= SWITCH_FRAMES) {
6346
+ if (pendingFrames >= SCROLLER_SWITCH_FRAMES) {
5783
6347
  scrollEl = candidate;
5784
6348
  cachedRect = candidate.getBoundingClientRect();
5785
6349
  autoScrollStateRef.current.pendingScrollerKey = null;
@@ -5800,86 +6364,40 @@ function useDesignModeAutoScroll({
5800
6364
  vx: 0,
5801
6365
  vy: 0
5802
6366
  };
5803
- raf = requestAnimationFrame(tick);
5804
- return;
5805
- }
5806
- const dTop = y - cachedRect.top;
5807
- const dBot = cachedRect.bottom - y;
5808
- let dy = 0;
5809
- if (dTop > 0 && dTop < ZONE) dy = -edgeVelocity(dTop, ZONE, MAX);
5810
- else if (dBot > 0 && dBot < ZONE) dy = edgeVelocity(dBot, ZONE, MAX);
5811
- const dLeft = x - cachedRect.left;
5812
- const dRight = cachedRect.right - x;
5813
- let dx = 0;
5814
- if (dLeft > 0 && dLeft < ZONE) dx = -edgeVelocity(dLeft, ZONE, MAX);
5815
- else if (dRight > 0 && dRight < ZONE) dx = edgeVelocity(dRight, ZONE, MAX);
5816
- if (dy !== 0) scrollEl.scrollTop += dy;
5817
- if (dx !== 0) scrollEl.scrollLeft += dx;
5818
- autoScrollStateRef.current = {
5819
- ...autoScrollStateRef.current,
5820
- scrollerKey: getScrollerKey(scrollEl),
5821
- vx: dx,
5822
- vy: dy
5823
- };
5824
- raf = requestAnimationFrame(tick);
5825
- };
5826
- raf = requestAnimationFrame(tick);
5827
- return () => cancelAnimationFrame(raf);
5828
- }, [active, autoScrollStateRef, mouseRef, safeZoneRef]);
5829
- }
5830
-
5831
- // src/client/dnd/design-mode/useOverlayRefs.ts
5832
- import { useRef as useRef8, useMemo as useMemo5 } from "react";
5833
-
5834
- // src/client/dnd/design-mode/history.ts
5835
- var DEFAULT_LIMIT = 300;
5836
- var DndCommandHistory = class {
5837
- constructor(limit = DEFAULT_LIMIT) {
5838
- this.limit = limit;
5839
- __publicField(this, "undoStack", []);
5840
- __publicField(this, "redoStack", []);
5841
- }
5842
- execute(command, apply) {
5843
- const applied = apply(command, "redo");
5844
- if (!applied) return false;
5845
- this.undoStack.push(command);
5846
- if (this.undoStack.length > this.limit) this.undoStack.shift();
5847
- this.redoStack = [];
5848
- return true;
5849
- }
5850
- undo(apply) {
5851
- const command = this.undoStack.pop();
5852
- if (!command) return false;
5853
- const applied = apply(command, "undo");
5854
- if (!applied) {
5855
- this.undoStack.push(command);
5856
- return false;
5857
- }
5858
- this.redoStack.push(command);
5859
- return true;
5860
- }
5861
- redo(apply) {
5862
- const command = this.redoStack.pop();
5863
- if (!command) return false;
5864
- const applied = apply(command, "redo");
5865
- if (!applied) {
5866
- this.redoStack.push(command);
5867
- return false;
5868
- }
5869
- this.undoStack.push(command);
5870
- return true;
5871
- }
5872
- clear() {
5873
- this.undoStack = [];
5874
- this.redoStack = [];
5875
- }
5876
- canUndo() {
5877
- return this.undoStack.length > 0;
5878
- }
5879
- canRedo() {
5880
- return this.redoStack.length > 0;
5881
- }
5882
- };
6367
+ raf = requestAnimationFrame(tick);
6368
+ return;
6369
+ }
6370
+ const dTop = y - cachedRect.top;
6371
+ const dBot = cachedRect.bottom - y;
6372
+ let dy = 0;
6373
+ if (dTop > 0 && dTop < AUTO_SCROLL_ZONE_PX)
6374
+ dy = -edgeVelocity(dTop, AUTO_SCROLL_ZONE_PX, AUTO_SCROLL_MAX_SPEED);
6375
+ else if (dBot > 0 && dBot < AUTO_SCROLL_ZONE_PX)
6376
+ dy = edgeVelocity(dBot, AUTO_SCROLL_ZONE_PX, AUTO_SCROLL_MAX_SPEED);
6377
+ const dLeft = x - cachedRect.left;
6378
+ const dRight = cachedRect.right - x;
6379
+ let dx = 0;
6380
+ if (dLeft > 0 && dLeft < AUTO_SCROLL_ZONE_PX)
6381
+ dx = -edgeVelocity(dLeft, AUTO_SCROLL_ZONE_PX, AUTO_SCROLL_MAX_SPEED);
6382
+ else if (dRight > 0 && dRight < AUTO_SCROLL_ZONE_PX)
6383
+ dx = edgeVelocity(dRight, AUTO_SCROLL_ZONE_PX, AUTO_SCROLL_MAX_SPEED);
6384
+ if (dy !== 0) scrollEl.scrollTop += dy;
6385
+ if (dx !== 0) scrollEl.scrollLeft += dx;
6386
+ autoScrollStateRef.current = {
6387
+ ...autoScrollStateRef.current,
6388
+ scrollerKey: getScrollerKey(scrollEl),
6389
+ vx: dx,
6390
+ vy: dy
6391
+ };
6392
+ raf = requestAnimationFrame(tick);
6393
+ };
6394
+ raf = requestAnimationFrame(tick);
6395
+ return () => cancelAnimationFrame(raf);
6396
+ }, [active, autoScrollStateRef, mouseRef, safeZoneRef]);
6397
+ }
6398
+
6399
+ // src/client/dnd/design-mode/useOverlayRefs.ts
6400
+ import { useRef as useRef8, useMemo as useMemo5 } from "react";
5883
6401
 
5884
6402
  // src/client/dnd/design-mode/sortable-preview-animator.ts
5885
6403
  function isIdentityTransform(transform) {
@@ -6093,43 +6611,6 @@ function useHoverDetection({
6093
6611
  // src/client/dnd/design-mode/useDragVisualLoop.ts
6094
6612
  import { useEffect as useEffect9 } from "react";
6095
6613
 
6096
- // src/client/dnd/design-mode/drag-lock.ts
6097
- var MIN_UNLOCK_MARGIN = 32;
6098
- var MAX_UNLOCK_MARGIN = 72;
6099
- var MIN_UNLOCK_DWELL_MS = 170;
6100
- var MAX_UNLOCK_DWELL_MS = 300;
6101
- function clamp2(value, min, max) {
6102
- return Math.min(max, Math.max(min, value));
6103
- }
6104
- function resolveCrossUnlockParams(rect) {
6105
- const width = Math.max(0, rect.right - rect.left);
6106
- const height = Math.max(0, rect.bottom - rect.top);
6107
- const shortest = Math.max(1, Math.min(width, height));
6108
- const margin = Math.round(clamp2(shortest * 0.14, MIN_UNLOCK_MARGIN, MAX_UNLOCK_MARGIN));
6109
- const dwellMs = Math.round(clamp2(shortest * 0.7, MIN_UNLOCK_DWELL_MS, MAX_UNLOCK_DWELL_MS));
6110
- return { margin, dwellMs };
6111
- }
6112
- function checkCrossUnlock(lock, adjustedPx, adjustedPy, lockRect, now, margin, dwellMs, graceMs) {
6113
- if (lock.isCrossUnlocked) {
6114
- if (lock.unlockUntilTs !== null && now < lock.unlockUntilTs) return lock;
6115
- return lock;
6116
- }
6117
- const outsideBand = adjustedPx < lockRect.left - margin || adjustedPx > lockRect.right + margin || adjustedPy < lockRect.top - margin || adjustedPy > lockRect.bottom + margin;
6118
- if (!outsideBand) {
6119
- return lock.exitSince !== null ? { ...lock, exitSince: null } : lock;
6120
- }
6121
- const exitSince = lock.exitSince ?? now;
6122
- if (now - exitSince >= dwellMs) {
6123
- return {
6124
- containerId: lock.containerId,
6125
- isCrossUnlocked: true,
6126
- exitSince,
6127
- unlockUntilTs: now + graceMs
6128
- };
6129
- }
6130
- return lock.exitSince !== null ? lock : { ...lock, exitSince: now };
6131
- }
6132
-
6133
6614
  // src/client/dnd/design-mode/quadtree.ts
6134
6615
  function overlaps(a, b) {
6135
6616
  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;
@@ -6139,6 +6620,7 @@ function fitsInside(outer, inner) {
6139
6620
  }
6140
6621
  var CAPACITY = 8;
6141
6622
  var MAX_DEPTH2 = 8;
6623
+ var ROOT_BOUNDS_PAD = 100;
6142
6624
  var QTNode = class _QTNode {
6143
6625
  constructor(bounds, depth = 0) {
6144
6626
  __publicField(this, "bounds");
@@ -6236,12 +6718,11 @@ var Quadtree = class _Quadtree {
6236
6718
  if (r.right > maxX) maxX = r.right;
6237
6719
  if (r.bottom > maxY) maxY = r.bottom;
6238
6720
  }
6239
- const PAD = 100;
6240
6721
  const root = new QTNode({
6241
- x: minX - PAD,
6242
- y: minY - PAD,
6243
- w: maxX - minX + 2 * PAD,
6244
- h: maxY - minY + 2 * PAD
6722
+ x: minX - ROOT_BOUNDS_PAD,
6723
+ y: minY - ROOT_BOUNDS_PAD,
6724
+ w: maxX - minX + 2 * ROOT_BOUNDS_PAD,
6725
+ h: maxY - minY + 2 * ROOT_BOUNDS_PAD
6245
6726
  });
6246
6727
  for (const [id, r] of rects) {
6247
6728
  if (r.width > 0 && r.height > 0) {
@@ -6343,9 +6824,9 @@ function getSlotQueryRadius() {
6343
6824
  const vh = window.innerHeight;
6344
6825
  return Math.min(500, Math.max(100, Math.max(vw, vh) * 0.2));
6345
6826
  }
6346
- var NEST_EDGE_FRACTION = 0.28;
6347
- var ORIGIN_RETURN_BAND_PX = 56;
6348
- var LAST_CONTAINER_REENTRY_SLOP_PX = 40;
6827
+ var NEST_EDGE_FRACTION = 0.2;
6828
+ var ORIGIN_RETURN_BAND_PX = 24;
6829
+ var LAST_CONTAINER_REENTRY_SLOP_PX = 16;
6349
6830
  var ROW_OVERLAP_TOLERANCE_PX = 1;
6350
6831
  function normalizeContainerSet(containers) {
6351
6832
  if (!containers) return null;
@@ -6423,8 +6904,6 @@ var DragIntentEngine = class {
6423
6904
  // Pre-computed set of slot IDs that would produce no movement for the active item.
6424
6905
  __publicField(this, "noOpSlotIds");
6425
6906
  __publicField(this, "originContainerId");
6426
- __publicField(this, "laneRootContainerId");
6427
- __publicField(this, "laneParentId");
6428
6907
  __publicField(this, "laneAllowedContainers");
6429
6908
  __publicField(this, "blockedContainerIds");
6430
6909
  __publicField(this, "targetPlane", "lane");
@@ -6436,8 +6915,6 @@ var DragIntentEngine = class {
6436
6915
  this.queryRadius = getSlotQueryRadius();
6437
6916
  this.slotsByContainer = buildSlotsByContainer(projection.slotIndex);
6438
6917
  this.originContainerId = this.getNodeParentContainerId(activeId);
6439
- this.laneRootContainerId = options?.laneRootContainerId ?? this.originContainerId;
6440
- this.laneParentId = this.laneRootContainerId ? this.containerIndex.get(this.laneRootContainerId)?.parentId ?? null : null;
6441
6918
  this.laneAllowedContainers = normalizeContainerSet(options?.laneAllowedContainers);
6442
6919
  this.blockedContainerIds = normalizeContainerSet(options?.blockedContainerIds);
6443
6920
  const noOpSlotIds = /* @__PURE__ */ new Set();
@@ -6469,10 +6946,6 @@ var DragIntentEngine = class {
6469
6946
  setTargetPlane(plane) {
6470
6947
  this.targetPlane = plane;
6471
6948
  }
6472
- setLaneAllowedContainers(containers) {
6473
- this.laneAllowedContainers = normalizeContainerSet(containers);
6474
- this.reset();
6475
- }
6476
6949
  getTargetPlane() {
6477
6950
  return this.targetPlane;
6478
6951
  }
@@ -6571,7 +7044,7 @@ var DragIntentEngine = class {
6571
7044
  if (this.isInvalidDropContainer(pointerContainerId)) return null;
6572
7045
  for (const childId of container.children) {
6573
7046
  if (childId === this.activeId) continue;
6574
- if (this.containerIndex.has(childId)) continue;
7047
+ if (this.isInvalidDropContainer(childId)) continue;
6575
7048
  const childNode = this.getNodeRect(childId);
6576
7049
  if (!childNode) continue;
6577
7050
  if (!pointInRect(px, py, childNode)) continue;
@@ -6596,9 +7069,16 @@ var DragIntentEngine = class {
6596
7069
  if (laneLock && !this.isLaneContainer(id)) continue;
6597
7070
  if (!pointInRect(px, py, node.rect)) continue;
6598
7071
  if (this.hasUsableSlots(id) === false) continue;
6599
- pointerContainers.push({ id, depth: node.depth, area: node.rect.width * node.rect.height });
7072
+ const r = node.rect;
7073
+ const edgeDistX = Math.min(px - r.left, r.right - px);
7074
+ const edgeDistY = Math.min(py - r.top, r.bottom - py);
7075
+ const minDim = Math.min(r.width, r.height);
7076
+ const edgeFraction = Math.min(edgeDistX, edgeDistY) / Math.max(1, minDim);
7077
+ const edgePenalty = edgeFraction < 0.1 ? 1 : 0;
7078
+ pointerContainers.push({ id, depth: node.depth, area: node.rect.width * node.rect.height, edgePenalty });
6600
7079
  }
6601
7080
  pointerContainers.sort((a, b) => {
7081
+ if (a.edgePenalty !== b.edgePenalty) return a.edgePenalty - b.edgePenalty;
6602
7082
  if (b.depth !== a.depth) return b.depth - a.depth;
6603
7083
  return a.area - b.area;
6604
7084
  });
@@ -6611,7 +7091,9 @@ var DragIntentEngine = class {
6611
7091
  }
6612
7092
  if (this.originContainerId && this.originContainerId !== pointerContainerId && !this.isInvalidDropContainer(this.originContainerId) && !this.isBlockedContainer(this.originContainerId) && (!laneLock || this.isLaneContainer(this.originContainerId)) && this.hasUsableSlots(this.originContainerId)) {
6613
7093
  const originNode = this.containerIndex.get(this.originContainerId);
6614
- if (originNode && pointInExpandedRect(px, py, originNode.rect, ORIGIN_RETURN_BAND_PX)) {
7094
+ const currentNode = pointerContainerId ? this.containerIndex.get(pointerContainerId) : null;
7095
+ const originIsDeeper = originNode && (!currentNode || originNode.depth >= currentNode.depth);
7096
+ if (originIsDeeper && originNode && pointInExpandedRect(px, py, originNode.rect, ORIGIN_RETURN_BAND_PX)) {
6615
7097
  pointerContainerId = this.originContainerId;
6616
7098
  }
6617
7099
  }
@@ -6678,8 +7160,13 @@ var DragIntentEngine = class {
6678
7160
  }
6679
7161
  return n;
6680
7162
  }
7163
+ const SLOT_ZONE_PX = 30;
6681
7164
  for (const child of childRects) {
6682
7165
  const midY = child.rect.top + child.rect.height / 2;
7166
+ const clampedTop = Math.max(child.rect.top, midY - SLOT_ZONE_PX);
7167
+ if (py < clampedTop) return child.index;
7168
+ const clampedBottom = Math.min(child.rect.bottom, midY + SLOT_ZONE_PX);
7169
+ if (py > clampedBottom) continue;
6683
7170
  if (py < midY) return child.index;
6684
7171
  }
6685
7172
  return n;
@@ -6748,15 +7235,14 @@ var DragIntentEngine = class {
6748
7235
  hasUsableSlots(containerId) {
6749
7236
  const filtered = this.getContainerSlots(containerId);
6750
7237
  if (filtered.length > 0) return true;
6751
- return (this.slotsByContainer.get(containerId)?.length ?? 0) > 0;
7238
+ const raw = this.slotsByContainer.get(containerId) ?? [];
7239
+ if (raw.length === 0) return false;
7240
+ const container = this.containerIndex.get(containerId);
7241
+ return !container || container.children.length <= 1;
6752
7242
  }
6753
7243
  isLaneContainer(containerId) {
6754
7244
  if (this.laneAllowedContainers) return this.laneAllowedContainers.has(containerId);
6755
- if (!this.laneRootContainerId) return true;
6756
- if (containerId === this.laneRootContainerId) return true;
6757
- const node = this.containerIndex.get(containerId);
6758
- if (!node) return false;
6759
- return node.parentId === this.laneParentId;
7245
+ return true;
6760
7246
  }
6761
7247
  isBlockedContainer(containerId) {
6762
7248
  return this.blockedContainerIds?.has(containerId) ?? false;
@@ -6767,9 +7253,11 @@ function createDragIntentEngine(projection, activeId, options) {
6767
7253
  }
6768
7254
 
6769
7255
  // src/client/dnd/design-mode/drop-line.ts
7256
+ var DROP_LINE_THICKNESS_PX = 2;
7257
+ var DROP_LINE_MIN_LENGTH_PX = 40;
6770
7258
  function slotDropLine(slot, container, projectionTree, sdx, sdy) {
6771
- const T = 2;
6772
- const MIN = 40;
7259
+ const T = DROP_LINE_THICKNESS_PX;
7260
+ const MIN = DROP_LINE_MIN_LENGTH_PX;
6773
7261
  const bp = computeSlotBoundaryPoint(slot, container, projectionTree);
6774
7262
  if (container.strategy === "horizontal") {
6775
7263
  return {
@@ -6809,9 +7297,6 @@ function slotDropLine(slot, container, projectionTree, sdx, sdy) {
6809
7297
  }
6810
7298
 
6811
7299
  // src/client/dnd/design-mode/preview-layout.ts
6812
- function clampIndex(index, min, max) {
6813
- return Math.max(min, Math.min(index, max));
6814
- }
6815
7300
  function getContainerChildren(projection, containerId) {
6816
7301
  return [...projection.containerIndex.get(containerId)?.children ?? []];
6817
7302
  }
@@ -6931,7 +7416,7 @@ function buildPreviewLayout({
6931
7416
  const originalItemIds = getContainerChildren(projection, containerId);
6932
7417
  const activeIndex = originalItemIds.indexOf(anchorNodeId);
6933
7418
  if (activeIndex < 0) return null;
6934
- const overIndex = clampIndex(targetIndex, 0, Math.max(0, originalItemIds.length - 1));
7419
+ const overIndex = clamp(targetIndex, 0, Math.max(0, originalItemIds.length - 1));
6935
7420
  const previewItemIds = arrayMove(originalItemIds, activeIndex, overIndex);
6936
7421
  const strategy = getContainerStrategy(projection, containerId);
6937
7422
  containers.set(containerId, {
@@ -6971,7 +7456,7 @@ function buildPreviewLayout({
6971
7456
  activePreviewContainers.add(sourceContainerId);
6972
7457
  const targetOriginalItemIds = targetChildren.filter((id) => id !== anchorNodeId);
6973
7458
  const targetPreviewItemIds = [...targetOriginalItemIds];
6974
- const targetOverIndex = clampIndex(targetIndex, 0, targetOriginalItemIds.length);
7459
+ const targetOverIndex = clamp(targetIndex, 0, targetOriginalItemIds.length);
6975
7460
  targetPreviewItemIds.splice(targetOverIndex, 0, anchorNodeId);
6976
7461
  const targetStrategy = getContainerStrategy(projection, targetContainerId);
6977
7462
  const targetStrategyItemIds = [...targetOriginalItemIds, anchorNodeId];
@@ -7003,9 +7488,8 @@ function buildPreviewLayout({
7003
7488
  }
7004
7489
 
7005
7490
  // src/client/dnd/design-mode/useDragVisualLoop.ts
7006
- var CONTAINER_SWITCH_HYSTERESIS = 32;
7007
- var CROSS_UNLOCK_GRACE_MS = 320;
7008
- var SMALL_NODE_NEST_DWELL_MS = 180;
7491
+ var CONTAINER_SWITCH_HYSTERESIS = 12;
7492
+ var SMALL_NODE_NEST_DWELL_MS = 100;
7009
7493
  var LARGE_NODE_NEST_DWELL_MS = 320;
7010
7494
  function useDragVisualLoop({
7011
7495
  activeDrag,
@@ -7054,33 +7538,12 @@ function useDragVisualLoop({
7054
7538
  const scrollDy = window.scrollY - snapshot.scroll.y;
7055
7539
  const adjustedPx = px + scrollDx;
7056
7540
  const adjustedPy = py + scrollDy;
7057
- const lockContainer = refs.lockRef.current.containerId ? refs.projectionRef.current.containerIndex.get(refs.lockRef.current.containerId) ?? null : null;
7058
- if (!refs.lockRef.current.isCrossUnlocked && lockContainer) {
7059
- const { margin, dwellMs } = resolveCrossUnlockParams(lockContainer.rect);
7060
- const nextLock = checkCrossUnlock(
7061
- refs.lockRef.current,
7062
- adjustedPx,
7063
- adjustedPy,
7064
- lockContainer.rect,
7065
- performance.now(),
7066
- margin,
7067
- dwellMs,
7068
- CROSS_UNLOCK_GRACE_MS
7069
- );
7070
- if (nextLock !== refs.lockRef.current) {
7071
- refs.lockRef.current = nextLock;
7072
- if (nextLock.isCrossUnlocked) engine.setLaneAllowedContainers(null);
7073
- }
7074
- }
7075
7541
  const nestPreview = engine.peekNestIntent(px, py, window.scrollX, window.scrollY, snapshot.scroll.x, snapshot.scroll.y);
7076
7542
  const isLargeDraggedNode = shouldBlockNestForLargeNode(activeDrag.w, activeDrag.h);
7077
7543
  let allowNest = false;
7078
7544
  if (nestPreview) {
7079
7545
  const childId = nestPreview.slotId.split("::nest::")[1] ?? "";
7080
- const siblings = engine.getContainerChildren(nestPreview.containerId);
7081
- const isSiblingList = siblings.length >= 2;
7082
- const isSourceContainerNest = nestPreview.containerId === refs.sourceContainerIdRef.current;
7083
- allowNest = !!childId && !isSiblingList && !isLargeDraggedNode && !isSourceContainerNest;
7546
+ allowNest = !!childId && !isLargeDraggedNode;
7084
7547
  }
7085
7548
  if (!allowNest) {
7086
7549
  refs.nestCandidateRef.current = null;
@@ -7090,7 +7553,12 @@ function useDragVisualLoop({
7090
7553
  const now = performance.now();
7091
7554
  if (!refs.nestCandidateRef.current || refs.nestCandidateRef.current.id !== nestPreview.slotId) {
7092
7555
  refs.nestCandidateRef.current = { id: nestPreview.slotId, since: now };
7093
- refs.nestTargetRef.current = null;
7556
+ const hintTargetId = nestPreview.slotId.split("::nest::")[1] ?? "";
7557
+ refs.nestTargetRef.current = refs.nodeMapRef.current.get(hintTargetId) ?? null;
7558
+ const hintRect = snapshot.rects.get(hintTargetId);
7559
+ if (hintRect) {
7560
+ setInsideRect(new DOMRect(hintRect.left - scrollDx, hintRect.top - scrollDy, hintRect.width, hintRect.height));
7561
+ }
7094
7562
  setTargetPlane("lane");
7095
7563
  } else if (now - refs.nestCandidateRef.current.since >= (isLargeDraggedNode ? LARGE_NODE_NEST_DWELL_MS : SMALL_NODE_NEST_DWELL_MS)) {
7096
7564
  setTargetPlane("nest");
@@ -7149,18 +7617,6 @@ function useDragVisualLoop({
7149
7617
  setTargetPlane("lane");
7150
7618
  refs.nestCandidateRef.current = null;
7151
7619
  refs.nestTargetRef.current = null;
7152
- const lockedParentId = refs.lockRef.current.containerId;
7153
- if (!refs.lockRef.current.isCrossUnlocked && (!lockedParentId || intent.containerId !== lockedParentId)) {
7154
- refs.lastIntentRef.current = null;
7155
- refs.visualIntentRef.current = null;
7156
- refs.resolvedReorderRef.current = null;
7157
- refs.finalVisualKeyRef.current = null;
7158
- setInsideRect(null);
7159
- setDropLine(null);
7160
- animator.clear();
7161
- rafId = requestAnimationFrame(tick);
7162
- return;
7163
- }
7164
7620
  refs.lastIntentRef.current = intent;
7165
7621
  refs.visualIntentRef.current = intent;
7166
7622
  setInsideRect(null);
@@ -7192,19 +7648,43 @@ function useDragVisualLoop({
7192
7648
  }
7193
7649
  const sourceCommitLane = refs.sourceCommitLaneRef.current ?? getCommitLaneSnapshot(sourceContainerId);
7194
7650
  const targetCommitLane = getCommitLaneSnapshot(intent.containerId);
7195
- const commitPlacement = resolveCommitPlacementFromVisualTarget(
7651
+ let commitPlacement = resolveCommitPlacementFromVisualTarget(
7196
7652
  targetCommitLane,
7197
7653
  sourceCommitLane,
7198
7654
  activeDrag.anchorNodeId,
7199
7655
  targetInsertIndex
7200
7656
  );
7201
7657
  if (!commitPlacement) {
7202
- refs.resolvedReorderRef.current = null;
7203
- refs.finalVisualKeyRef.current = null;
7204
- setDropLine(null);
7205
- animator.clear();
7206
- rafId = requestAnimationFrame(tick);
7207
- return;
7658
+ const containerEl = refs.nodeMapRef.current.get(intent.containerId)?.element;
7659
+ const sourceCommitNodeId = refs.sourceCommitLaneRef.current?.commitNodeByAnchorId.get(activeDrag.anchorNodeId) ?? activeDrag.anchorNodeId;
7660
+ if (!containerEl) {
7661
+ refs.resolvedReorderRef.current = null;
7662
+ refs.finalVisualKeyRef.current = null;
7663
+ setDropLine(null);
7664
+ animator.clear();
7665
+ rafId = requestAnimationFrame(tick);
7666
+ return;
7667
+ }
7668
+ const targetChildren = Array.from(containerEl.children).filter(
7669
+ (child) => child instanceof HTMLElement
7670
+ );
7671
+ const filteredChildren = targetChildren.filter(
7672
+ (child) => child.getAttribute(DND_NODE_ID_ATTR) !== sourceCommitNodeId
7673
+ );
7674
+ const idx = Math.max(0, Math.min(targetInsertIndex, filteredChildren.length));
7675
+ const beforeChild = filteredChildren[idx] ?? null;
7676
+ const afterChild = idx > 0 ? filteredChildren[idx - 1] ?? null : null;
7677
+ const parentNodeId = ensureParentNodeId(containerEl);
7678
+ const beforeNodeId = beforeChild?.getAttribute(DND_NODE_ID_ATTR) ? ensureParentNodeId(beforeChild) : null;
7679
+ const afterNodeId = afterChild?.getAttribute(DND_NODE_ID_ATTR) ? ensureParentNodeId(afterChild) : null;
7680
+ commitPlacement = {
7681
+ parentNodeId,
7682
+ laneContainerId: intent.containerId,
7683
+ commitNodeId: sourceCommitNodeId,
7684
+ beforeNodeId,
7685
+ afterNodeId,
7686
+ index: idx
7687
+ };
7208
7688
  }
7209
7689
  setDropLine(slotDropLine(slot, container, proj.projectionTree, scrollDx, scrollDy));
7210
7690
  refs.resolvedReorderRef.current = {
@@ -7249,56 +7729,175 @@ function useDragVisualLoop({
7249
7729
 
7250
7730
  // src/client/dnd/design-mode/useDragLifecycle.ts
7251
7731
  import { useState as useState5, useCallback as useCallback6 } from "react";
7252
-
7253
- // src/client/dnd/design-mode/node-key.ts
7254
- var _runtimeKeyMap = /* @__PURE__ */ new WeakMap();
7255
- var _runtimeKeyCounter = 0;
7256
- function getRuntimeKey(el) {
7257
- const existing = _runtimeKeyMap.get(el);
7258
- if (existing) return existing;
7259
- const key2 = `runtime:${++_runtimeKeyCounter}`;
7260
- _runtimeKeyMap.set(el, key2);
7261
- return key2;
7262
- }
7263
- function getNodeKeyFromElement(el) {
7264
- const resolved = resolveDragSurface(el);
7265
- const host = resolved.host ?? el;
7266
- const meta = getEditorMeta(host);
7267
- if (meta) return meta.nodeId;
7268
- const persisted = host.getAttribute("data-dnd-node-id");
7269
- if (persisted) return persisted;
7270
- return getRuntimeKey(host);
7271
- }
7272
- function getInspectorRefFromElement(el) {
7273
- const resolved = resolveDragSurface(el);
7274
- const host = resolved.host ?? el;
7275
- const meta = getEditorMeta(host);
7276
- if (!meta) return null;
7277
- return { relativePath: meta.file, line: meta.line, column: meta.col };
7278
- }
7279
-
7280
- // src/client/dnd/design-mode/useDragLifecycle.ts
7281
- var SOFT_COMMIT_DISTANCE_PX = 14;
7282
- function isLaneCommitValid(intent, resolvedReorder, lockedParentId) {
7283
- if (!intent || intent.mode !== "reorder") return false;
7284
- if (!resolvedReorder) return false;
7285
- if (!lockedParentId) return false;
7286
- if (intent.containerId !== lockedParentId) return false;
7287
- if (resolvedReorder.visual.slotId !== intent.slotId) return false;
7288
- if (resolvedReorder.visual.parentNodeId !== intent.containerId) return false;
7289
- return true;
7290
- }
7291
- function buildRuntimeIdentityLocal(nodeId, resolveNodeElement) {
7732
+ function buildRuntimeIdentityLocal(nodeId, resolveNodeElement, effectiveLaneSnapshot) {
7292
7733
  if (!nodeId) return null;
7293
7734
  const element = resolveNodeElement(nodeId);
7294
7735
  if (!element) return null;
7736
+ const nodeKey = getNodeKeyFromElement(element);
7737
+ const inspectorRef = getInspectorRefFromElement(element);
7738
+ const laneEffectiveNodeId = effectiveLaneSnapshot?.effectiveNodeIdsByCommitNodeId.get(nodeId) ?? null;
7739
+ const effectiveNodeId = typeof laneEffectiveNodeId === "string" && laneEffectiveNodeId.length > 0 ? laneEffectiveNodeId : isSourceBackedRuntimeNodeKey(nodeKey) ? nodeKey : null;
7740
+ const shouldExposeEffectiveIdentity = typeof effectiveNodeId === "string" && effectiveNodeId.length > 0;
7295
7741
  return {
7296
7742
  nodeId,
7297
- nodeKey: getNodeKeyFromElement(element),
7298
- selector: element.getAttribute(DND_NODE_ID_ATTR2) ? `[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(nodeId)}"]` : null,
7299
- inspectorRef: getInspectorRefFromElement(element)
7743
+ nodeKey,
7744
+ selector: element.getAttribute(DND_NODE_ID_ATTR) ? buildNodeSelector(nodeId) : null,
7745
+ inspectorRef,
7746
+ ...shouldExposeEffectiveIdentity ? {
7747
+ effectiveNodeId,
7748
+ effectiveNodeKey: effectiveNodeId,
7749
+ effectiveInspectorRef: inspectorRef
7750
+ } : {}
7751
+ };
7752
+ }
7753
+ function buildMovePayloadWithEffectiveIdentities(params) {
7754
+ const {
7755
+ placement,
7756
+ containerIdentity,
7757
+ beforeSiblingIdentity,
7758
+ afterSiblingIdentity,
7759
+ commitIdentity
7760
+ } = params;
7761
+ return {
7762
+ ...placement,
7763
+ effectiveContainerNodeId: containerIdentity?.effectiveNodeId ?? null,
7764
+ effectiveContainerNodeKey: containerIdentity?.effectiveNodeKey ?? null,
7765
+ effectiveContainerInspectorRef: containerIdentity?.effectiveInspectorRef ?? null,
7766
+ effectiveBeforeNodeId: beforeSiblingIdentity?.effectiveNodeId ?? null,
7767
+ effectiveBeforeNodeKey: beforeSiblingIdentity?.effectiveNodeKey ?? null,
7768
+ effectiveBeforeInspectorRef: beforeSiblingIdentity?.effectiveInspectorRef ?? null,
7769
+ effectiveAfterNodeId: afterSiblingIdentity?.effectiveNodeId ?? null,
7770
+ effectiveAfterNodeKey: afterSiblingIdentity?.effectiveNodeKey ?? null,
7771
+ effectiveAfterInspectorRef: afterSiblingIdentity?.effectiveInspectorRef ?? null,
7772
+ effectiveCommitNodeId: commitIdentity?.effectiveNodeId ?? null,
7773
+ effectiveCommitNodeKey: commitIdentity?.effectiveNodeKey ?? null,
7774
+ effectiveCommitInspectorRef: commitIdentity?.effectiveInspectorRef ?? null
7300
7775
  };
7301
7776
  }
7777
+ function recordCommittedMove(args) {
7778
+ const {
7779
+ command,
7780
+ anchorNodeId,
7781
+ selectedNodeId,
7782
+ anchorEl,
7783
+ selectedEl,
7784
+ nodeKey,
7785
+ inspRef,
7786
+ selectedNodeKey,
7787
+ selectedInspectorRef,
7788
+ sourceLaneSnapshot,
7789
+ targetLaneSnapshot,
7790
+ resolveNodeElement,
7791
+ recordMoveChange
7792
+ } = args;
7793
+ const sourceLaneIdentitySnapshot = captureCommitLaneIdentitySnapshot(
7794
+ sourceLaneSnapshot,
7795
+ resolveNodeElement
7796
+ );
7797
+ const targetLaneIdentitySnapshot = command.to.laneContainerId === command.from.laneContainerId ? sourceLaneIdentitySnapshot : captureCommitLaneIdentitySnapshot(targetLaneSnapshot, resolveNodeElement);
7798
+ const targetLaneAfterIdentitySnapshot = targetLaneIdentitySnapshot ? createCommitLaneIdentitySnapshot({
7799
+ laneContainerId: targetLaneIdentitySnapshot.laneContainerId,
7800
+ commitNodeIds: projectCommitNodeIdsForMove({
7801
+ commitNodeIds: targetLaneIdentitySnapshot.commitNodeIds,
7802
+ movedCommitNodeId: command.commitNodeId,
7803
+ targetIndex: command.to.index
7804
+ }),
7805
+ nodeKeysByCommitNodeId: new Map([
7806
+ ...targetLaneIdentitySnapshot.nodeKeysByCommitNodeId,
7807
+ ...sourceLaneIdentitySnapshot?.nodeKeysByCommitNodeId.has(command.commitNodeId) ? [[command.commitNodeId, sourceLaneIdentitySnapshot.nodeKeysByCommitNodeId.get(command.commitNodeId) ?? null]] : []
7808
+ ])
7809
+ }) : null;
7810
+ const sourceContainerIdentity = buildRuntimeIdentityLocal(
7811
+ command.from.parentNodeId ?? command.from.laneContainerId,
7812
+ resolveNodeElement,
7813
+ sourceLaneIdentitySnapshot
7814
+ );
7815
+ const sourceBeforeSiblingIdentity = buildRuntimeIdentityLocal(command.from.beforeNodeId, resolveNodeElement, sourceLaneIdentitySnapshot);
7816
+ const sourceAfterSiblingIdentity = buildRuntimeIdentityLocal(command.from.afterNodeId, resolveNodeElement, sourceLaneIdentitySnapshot);
7817
+ const sourceCommitIdentity = buildRuntimeIdentityLocal(command.commitNodeId, resolveNodeElement, sourceLaneIdentitySnapshot);
7818
+ const containerIdentity = buildRuntimeIdentityLocal(
7819
+ command.to.parentNodeId ?? command.to.laneContainerId,
7820
+ resolveNodeElement,
7821
+ targetLaneAfterIdentitySnapshot
7822
+ );
7823
+ const beforeSiblingIdentity = buildRuntimeIdentityLocal(command.to.beforeNodeId, resolveNodeElement, targetLaneAfterIdentitySnapshot);
7824
+ const afterSiblingIdentity = buildRuntimeIdentityLocal(command.to.afterNodeId, resolveNodeElement, targetLaneAfterIdentitySnapshot);
7825
+ const targetCommitIdentity = buildRuntimeIdentityLocal(command.commitNodeId, resolveNodeElement, targetLaneAfterIdentitySnapshot);
7826
+ const diagnostics = buildMovePersistenceDiagnostics({
7827
+ sourceLane: sourceLaneIdentitySnapshot,
7828
+ targetLane: targetLaneIdentitySnapshot,
7829
+ movedCommitNodeId: command.commitNodeId,
7830
+ sourceLaneContainerId: command.from.laneContainerId,
7831
+ targetLaneContainerId: command.to.laneContainerId,
7832
+ targetIndex: command.to.index
7833
+ });
7834
+ const componentName = getEditorMeta(anchorEl)?.componentName;
7835
+ const operationId = `${command.at}:${anchorNodeId}`;
7836
+ const beforePayload = buildMovePayloadWithEffectiveIdentities({
7837
+ placement: command.from,
7838
+ containerIdentity: sourceContainerIdentity,
7839
+ beforeSiblingIdentity: sourceBeforeSiblingIdentity,
7840
+ afterSiblingIdentity: sourceAfterSiblingIdentity,
7841
+ commitIdentity: sourceCommitIdentity
7842
+ });
7843
+ const afterPayload = buildMovePayloadWithEffectiveIdentities({
7844
+ placement: command.to,
7845
+ containerIdentity,
7846
+ beforeSiblingIdentity,
7847
+ afterSiblingIdentity,
7848
+ commitIdentity: targetCommitIdentity
7849
+ });
7850
+ recordMoveChange({
7851
+ operationId,
7852
+ commandType: command.type,
7853
+ nodeKey,
7854
+ nodeId: anchorNodeId,
7855
+ inspectorRef: inspRef,
7856
+ selectedNodeKey,
7857
+ selectedNodeId,
7858
+ selectedInspectorRef,
7859
+ anchorNodeKey: nodeKey,
7860
+ anchorNodeId,
7861
+ sourceContainer: sourceContainerIdentity,
7862
+ sourceBeforeSibling: sourceBeforeSiblingIdentity,
7863
+ sourceAfterSibling: sourceAfterSiblingIdentity,
7864
+ diagnostics,
7865
+ container: containerIdentity,
7866
+ beforeSibling: beforeSiblingIdentity,
7867
+ afterSibling: afterSiblingIdentity,
7868
+ componentName,
7869
+ before: beforePayload,
7870
+ after: afterPayload
7871
+ });
7872
+ dispatchRuntimeEvent({
7873
+ type: "element-moved",
7874
+ operationId,
7875
+ commandType: command.type,
7876
+ selected: {
7877
+ nodeId: selectedNodeId,
7878
+ nodeKey: selectedNodeKey,
7879
+ selector: selectedEl.getAttribute(DND_NODE_ID_ATTR) ? buildNodeSelector(selectedNodeId) : null,
7880
+ inspectorRef: selectedInspectorRef,
7881
+ ...targetCommitIdentity?.effectiveNodeId ? { effectiveNodeId: targetCommitIdentity.effectiveNodeId, effectiveNodeKey: targetCommitIdentity.effectiveNodeKey } : {}
7882
+ },
7883
+ anchor: {
7884
+ nodeId: anchorNodeId,
7885
+ nodeKey,
7886
+ selector: anchorEl.getAttribute(DND_NODE_ID_ATTR) ? buildNodeSelector(anchorNodeId) : null,
7887
+ inspectorRef: inspRef,
7888
+ ...targetCommitIdentity?.effectiveNodeId ? { effectiveNodeId: targetCommitIdentity.effectiveNodeId, effectiveNodeKey: targetCommitIdentity.effectiveNodeKey } : {}
7889
+ },
7890
+ sourceContainer: sourceContainerIdentity,
7891
+ sourceBeforeSibling: sourceBeforeSiblingIdentity,
7892
+ sourceAfterSibling: sourceAfterSiblingIdentity,
7893
+ container: containerIdentity,
7894
+ beforeSibling: beforeSiblingIdentity,
7895
+ afterSibling: afterSiblingIdentity,
7896
+ diagnostics,
7897
+ before: beforePayload,
7898
+ after: afterPayload
7899
+ });
7900
+ }
7302
7901
  function useDragLifecycle({
7303
7902
  refs,
7304
7903
  pause,
@@ -7380,12 +7979,10 @@ function useDragLifecycle({
7380
7979
  const srcContainerId = anchor.sourceContainerId;
7381
7980
  const sourceCommitLane = getCommitLaneSnapshot(srcContainerId);
7382
7981
  const startPosition = getCurrentCommitPlacement(anchorNodeId, srcContainerId);
7383
- const laneAllowed = srcContainerId ? /* @__PURE__ */ new Set([srcContainerId]) : null;
7384
7982
  const blockedContainerIds = buildBlockedStructuralContainerIds(proj, refs.nodeMapRef.current, srcContainerId);
7385
7983
  refs.blockedContainerIdsRef.current = blockedContainerIds;
7386
7984
  refs.engineRef.current = createDragIntentEngine(proj, anchorNodeId, {
7387
7985
  laneRootContainerId: srcContainerId,
7388
- laneAllowedContainers: laneAllowed,
7389
7986
  blockedContainerIds
7390
7987
  });
7391
7988
  refs.engineRef.current.setTargetPlane("lane");
@@ -7393,7 +7990,7 @@ function useDragLifecycle({
7393
7990
  refs.sourceContainerIdRef.current = srcContainerId;
7394
7991
  refs.sourceCommitLaneRef.current = sourceCommitLane;
7395
7992
  refs.dragSessionAnchorRef.current = anchor;
7396
- refs.lockRef.current = { containerId: srcContainerId, isCrossUnlocked: false, exitSince: null, unlockUntilTs: null };
7993
+ refs.lockRef.current = { containerId: srcContainerId, isCrossUnlocked: true, exitSince: null, unlockUntilTs: null };
7397
7994
  refs.dragStartPositionRef.current = startPosition;
7398
7995
  refs.safeZoneRef.current = resolveSafeZoneRect();
7399
7996
  pause();
@@ -7419,22 +8016,34 @@ function useDragLifecycle({
7419
8016
  const pointer = refs.mouseRef.current;
7420
8017
  const safeZone = refs.safeZoneRef.current;
7421
8018
  const blockedByGuard = isPointerOutsideSafeZone(pointer.x, pointer.y, safeZone) || isPointerInHardPageEdgeZone(pointer.x, pointer.y);
7422
- const sourcePos = refs.dragStartPositionRef.current ?? getCurrentCommitPlacement(anchorNodeId, refs.sourceContainerIdRef.current);
8019
+ let sourcePos = refs.dragStartPositionRef.current ?? getCurrentCommitPlacement(anchorNodeId, refs.sourceContainerIdRef.current);
8020
+ if (!sourcePos && activeEl && activeEl.parentElement) {
8021
+ const parentEl = activeEl.parentElement;
8022
+ const commitNodeId = activeEl.getAttribute(DND_NODE_ID_ATTR) ?? anchorNodeId;
8023
+ const siblings = Array.from(parentEl.children).filter((c) => c instanceof HTMLElement);
8024
+ const idx = siblings.indexOf(activeEl);
8025
+ if (idx >= 0) {
8026
+ sourcePos = {
8027
+ parentNodeId: ensureParentNodeId(parentEl),
8028
+ laneContainerId: refs.sourceContainerIdRef.current,
8029
+ commitNodeId,
8030
+ beforeNodeId: idx < siblings.length - 1 ? siblings[idx + 1].getAttribute(DND_NODE_ID_ATTR) ?? ensureParentNodeId(siblings[idx + 1]) : null,
8031
+ afterNodeId: idx > 0 ? siblings[idx - 1].getAttribute(DND_NODE_ID_ATTR) ?? ensureParentNodeId(siblings[idx - 1]) : null,
8032
+ index: idx
8033
+ };
8034
+ }
8035
+ }
7423
8036
  const intent = refs.visualIntentRef.current;
7424
- const finalVisual = refs.finalVisualKeyRef.current;
7425
8037
  const nestTarget = refs.nestTargetRef.current;
7426
8038
  const intentHasContainer = intent ? refs.projectionRef.current.containerIndex.has(intent.containerId) : false;
7427
8039
  const intentHasSlot = intent ? intent.mode === "nest" || refs.projectionRef.current.slotIndex.has(intent.slotId) : false;
7428
8040
  const intentValid = !!intent && intentHasContainer && intentHasSlot;
7429
8041
  if (!blockedByGuard && activeEl && sourcePos && (nestTarget || intentValid)) {
7430
8042
  const dropTargetId = nestTarget?.nodeId ?? intent?.containerId;
7431
- if (dropTargetId) {
7432
- const targetSc = refs.nodeMapRef.current.get(dropTargetId);
7433
- if (!targetSc || !isEligibleForDrag(targetSc)) {
7434
- clearDragState();
7435
- syncHistoryAvailability();
7436
- return;
7437
- }
8043
+ if (dropTargetId && !refs.nodeMapRef.current.has(dropTargetId)) {
8044
+ clearDragState();
8045
+ syncHistoryAvailability();
8046
+ return;
7438
8047
  }
7439
8048
  let command = null;
7440
8049
  if (nestTarget) {
@@ -7454,46 +8063,8 @@ function useDragLifecycle({
7454
8063
  };
7455
8064
  }
7456
8065
  } else if (intentValid && intent.mode === "reorder") {
7457
- const lockedParentId = refs.lockRef.current.containerId;
7458
- if (!refs.lockRef.current.isCrossUnlocked && (!lockedParentId || intent.containerId !== lockedParentId)) {
7459
- clearDragState();
7460
- syncHistoryAvailability();
7461
- return;
7462
- }
7463
- const snapshot = refs.snapshotRef.current;
7464
- const targetContainer = refs.projectionRef.current.containerIndex.get(intent.containerId);
7465
- if (targetContainer) {
7466
- const scrollDx = window.scrollX - (snapshot?.scroll.x ?? 0);
7467
- const scrollDy = window.scrollY - (snapshot?.scroll.y ?? 0);
7468
- const adjPx = pointer.x + scrollDx;
7469
- const adjPy = pointer.y + scrollDy;
7470
- const r = targetContainer.rect;
7471
- const inContainer = adjPx >= r.left - 40 && adjPx <= r.right + 40 && adjPy >= r.top - 40 && adjPy <= r.bottom + 40;
7472
- if (!inContainer) {
7473
- const softFallbackAllowed = finalVisual?.mode === "reorder" && finalVisual.containerId === intent.containerId && (() => {
7474
- const slot = refs.projectionRef.current.slotIndex.get(finalVisual.slotId);
7475
- const container = refs.projectionRef.current.containerIndex.get(finalVisual.containerId);
7476
- if (!slot || !container) return false;
7477
- const bp = computeSlotBoundaryPoint(slot, container, refs.projectionRef.current.projectionTree);
7478
- const dist = Math.hypot(adjPx - bp.x, adjPy - bp.y);
7479
- return dist <= SOFT_COMMIT_DISTANCE_PX;
7480
- })();
7481
- if (!softFallbackAllowed) {
7482
- clearDragState();
7483
- syncHistoryAvailability();
7484
- return;
7485
- }
7486
- }
7487
- }
7488
8066
  const resolvedReorder = refs.resolvedReorderRef.current;
7489
- const laneValid = refs.lockRef.current.isCrossUnlocked ? !!resolvedReorder && resolvedReorder.visual.slotId === intent.slotId && resolvedReorder.visual.parentNodeId === intent.containerId : isLaneCommitValid(intent, resolvedReorder, lockedParentId);
7490
- if (!laneValid) {
7491
- clearDragState();
7492
- syncHistoryAvailability();
7493
- return;
7494
- }
7495
- 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;
7496
- if (!visualParity) {
8067
+ if (!resolvedReorder || resolvedReorder.visual.containerId !== intent.containerId) {
7497
8068
  clearDragState();
7498
8069
  syncHistoryAvailability();
7499
8070
  return;
@@ -7509,56 +8080,32 @@ function useDragLifecycle({
7509
8080
  };
7510
8081
  }
7511
8082
  if (command) {
8083
+ const anchorEl = resolveNodeElement(anchorNodeId) ?? activeEl;
8084
+ const selectedEl = resolveNodeElement(selectedNodeId) ?? anchorEl;
8085
+ const nodeKey = getNodeKeyFromElement(anchorEl);
8086
+ const inspRef = getInspectorRefFromElement(anchorEl);
8087
+ const selectedNodeKey = getNodeKeyFromElement(selectedEl);
8088
+ const selectedInspectorRef = getInspectorRefFromElement(selectedEl);
8089
+ const sourceLaneSnapshot = refs.sourceCommitLaneRef.current ?? getCommitLaneSnapshot(command.from.laneContainerId);
8090
+ const targetLaneSnapshot = command.to.laneContainerId === command.from.laneContainerId ? sourceLaneSnapshot : getCommitLaneSnapshot(command.to.laneContainerId);
7512
8091
  const committed = refs.historyRef.current.execute(command, applyCommand);
7513
8092
  if (committed) {
7514
8093
  activeEl.setAttribute("data-dnd-moved", "");
7515
8094
  selectElement(null);
7516
- const anchorEl = resolveNodeElement(anchorNodeId) ?? activeEl;
7517
- const selectedEl = resolveNodeElement(selectedNodeId) ?? anchorEl;
7518
- const nodeKey = getNodeKeyFromElement(anchorEl);
7519
- const inspRef = getInspectorRefFromElement(anchorEl);
7520
- const selectedNodeKey = getNodeKeyFromElement(selectedEl);
7521
- const selectedInspectorRef = getInspectorRefFromElement(selectedEl);
7522
- recordMoveChange({
8095
+ recordCommittedMove({
8096
+ command,
8097
+ anchorNodeId,
8098
+ selectedNodeId,
8099
+ anchorEl,
8100
+ selectedEl,
7523
8101
  nodeKey,
7524
- nodeId: anchorNodeId,
7525
- inspectorRef: inspRef,
8102
+ inspRef,
7526
8103
  selectedNodeKey,
7527
- selectedNodeId,
7528
8104
  selectedInspectorRef,
7529
- anchorNodeKey: nodeKey,
7530
- anchorNodeId,
7531
- componentName: getEditorMeta(anchorEl)?.componentName,
7532
- before: command.from,
7533
- after: command.to
7534
- });
7535
- const containerIdentity = buildRuntimeIdentityLocal(
7536
- command.to.laneContainerId ?? command.to.parentNodeId,
7537
- resolveNodeElement
7538
- );
7539
- const beforeSiblingIdentity = buildRuntimeIdentityLocal(command.to.beforeNodeId, resolveNodeElement);
7540
- const afterSiblingIdentity = buildRuntimeIdentityLocal(command.to.afterNodeId, resolveNodeElement);
7541
- dispatchRuntimeEvent({
7542
- type: "element-moved",
7543
- operationId: `${command.at}:${anchorNodeId}`,
7544
- commandType: command.type,
7545
- selected: {
7546
- nodeId: selectedNodeId,
7547
- nodeKey: selectedNodeKey,
7548
- selector: selectedEl.getAttribute(DND_NODE_ID_ATTR2) ? `[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(selectedNodeId)}"]` : null,
7549
- inspectorRef: selectedInspectorRef
7550
- },
7551
- anchor: {
7552
- nodeId: anchorNodeId,
7553
- nodeKey,
7554
- selector: anchorEl.getAttribute(DND_NODE_ID_ATTR2) ? `[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(anchorNodeId)}"]` : null,
7555
- inspectorRef: inspRef
7556
- },
7557
- container: containerIdentity,
7558
- beforeSibling: beforeSiblingIdentity,
7559
- afterSibling: afterSiblingIdentity,
7560
- before: command.from,
7561
- after: command.to
8105
+ sourceLaneSnapshot,
8106
+ targetLaneSnapshot,
8107
+ resolveNodeElement,
8108
+ recordMoveChange
7562
8109
  });
7563
8110
  }
7564
8111
  }
@@ -7597,7 +8144,7 @@ function useResizeSession({
7597
8144
  const rect = resizeTargetEl.getBoundingClientRect();
7598
8145
  const selectedNodeKey = getNodeKeyFromElement(selectedEl);
7599
8146
  const targetNodeKey = getNodeKeyFromElement(resizeTargetEl);
7600
- const targetNodeId = ensureElementNodeId(resizeTargetEl);
8147
+ const targetNodeId = ensureParentNodeId(resizeTargetEl);
7601
8148
  const startColSpan = parseSpanValue(getComputedStyle(resizeTargetEl).gridColumnEnd || resizeTargetEl.style.gridColumnEnd);
7602
8149
  const startRowSpan = parseSpanValue(getComputedStyle(resizeTargetEl).gridRowEnd || resizeTargetEl.style.gridRowEnd);
7603
8150
  const columnTrackCount = getGridTrackCount(resizeTargetParent ? getComputedStyle(resizeTargetParent).gridTemplateColumns : null);
@@ -7705,8 +8252,10 @@ function useResizeSession({
7705
8252
  after: { width: afterW, height: afterH, colSpan: afterColSpan, rowSpan: afterRowSpan, styles: afterStyles },
7706
8253
  at: Date.now()
7707
8254
  };
8255
+ const operationId = `${resizeCmd.at}:${selectedNodeId}`;
7708
8256
  refs.historyRef.current.execute(resizeCmd, applyCommand);
7709
8257
  recordResizeChange({
8258
+ operationId,
7710
8259
  nodeKey: selectedNodeKey,
7711
8260
  nodeId: selectedNodeId,
7712
8261
  inspectorRef,
@@ -7716,17 +8265,17 @@ function useResizeSession({
7716
8265
  });
7717
8266
  dispatchRuntimeEvent({
7718
8267
  type: "element-resized",
7719
- operationId: `${resizeCmd.at}:${selectedNodeId}`,
8268
+ operationId,
7720
8269
  selected: {
7721
8270
  nodeId: selectedNodeId,
7722
8271
  nodeKey: selectedNodeKey,
7723
- selector: `[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(selectedNodeId)}"]`,
8272
+ selector: buildNodeSelector(selectedNodeId),
7724
8273
  inspectorRef
7725
8274
  },
7726
8275
  anchor: {
7727
8276
  nodeId: targetNodeId,
7728
8277
  nodeKey: targetNodeKey,
7729
- selector: `[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(targetNodeId)}"]`,
8278
+ selector: buildNodeSelector(targetNodeId),
7730
8279
  inspectorRef: getInspectorRefFromElement(element)
7731
8280
  },
7732
8281
  resize: {
@@ -7767,6 +8316,169 @@ function useResizeSession({
7767
8316
  return { handleResizeHandlePointerDown };
7768
8317
  }
7769
8318
 
8319
+ // src/client/dnd/design-mode/useExternalDragLoop.ts
8320
+ import { useEffect as useEffect11, useRef as useRef9 } from "react";
8321
+ var EXTERNAL_DRAG_ACTIVE_ID = "__external-drag-placeholder__";
8322
+ function useExternalDragLoop({
8323
+ externalDrag,
8324
+ refs,
8325
+ elements,
8326
+ resolveNodeElement,
8327
+ setDropLine
8328
+ }) {
8329
+ const engineRef = useRef9(null);
8330
+ const lastIntentRef = useRef9(null);
8331
+ useEffect11(() => {
8332
+ if (!externalDrag) {
8333
+ engineRef.current = null;
8334
+ lastIntentRef.current = null;
8335
+ setDropLine(null);
8336
+ externalDragMouse.commitRequested = false;
8337
+ return;
8338
+ }
8339
+ const projection = buildProjection(elements);
8340
+ if (projection.containerIndex.size === 0) return;
8341
+ const rects = /* @__PURE__ */ new Map();
8342
+ for (const el of elements) {
8343
+ rects.set(el.nodeId, el.rect);
8344
+ }
8345
+ const nodeMap = /* @__PURE__ */ new Map();
8346
+ for (const el of elements) nodeMap.set(el.nodeId, el);
8347
+ const blockedContainerIds = buildBlockedStructuralContainerIds(projection, nodeMap, null);
8348
+ const engine = new DragIntentEngine(projection, EXTERNAL_DRAG_ACTIVE_ID, {
8349
+ blockedContainerIds
8350
+ });
8351
+ engineRef.current = engine;
8352
+ const scrollX = window.scrollX;
8353
+ const scrollY = window.scrollY;
8354
+ let rafId;
8355
+ const tick = () => {
8356
+ if (externalDragMouse.commitRequested) {
8357
+ externalDragMouse.commitRequested = false;
8358
+ commitExternalDrop(
8359
+ externalDragMouse.x,
8360
+ externalDragMouse.y,
8361
+ engine,
8362
+ externalDrag,
8363
+ refs,
8364
+ resolveNodeElement,
8365
+ setDropLine
8366
+ );
8367
+ return;
8368
+ }
8369
+ const intent = engine.resolve(
8370
+ externalDragMouse.x,
8371
+ externalDragMouse.y,
8372
+ window.scrollX,
8373
+ window.scrollY,
8374
+ scrollX,
8375
+ scrollY
8376
+ );
8377
+ if (intent && intent.mode === "reorder") {
8378
+ lastIntentRef.current = intent;
8379
+ const slot = projection.slotIndex.get(intent.slotId);
8380
+ const container = projection.containerIndex.get(intent.containerId);
8381
+ if (slot && container) {
8382
+ const scrollDx = window.scrollX - scrollX;
8383
+ const scrollDy = window.scrollY - scrollY;
8384
+ setDropLine(slotDropLine(slot, container, projection.projectionTree, scrollDx, scrollDy));
8385
+ } else {
8386
+ setDropLine(null);
8387
+ }
8388
+ } else {
8389
+ lastIntentRef.current = null;
8390
+ setDropLine(null);
8391
+ }
8392
+ rafId = requestAnimationFrame(tick);
8393
+ };
8394
+ rafId = requestAnimationFrame(tick);
8395
+ return () => cancelAnimationFrame(rafId);
8396
+ }, [externalDrag, elements, refs, resolveNodeElement, setDropLine]);
8397
+ }
8398
+ function commitExternalDrop(x, y, engine, component, refs, resolveNodeElement, setDropLine) {
8399
+ const scrollX = window.scrollX;
8400
+ const scrollY = window.scrollY;
8401
+ const intent = engine.resolve(x, y, scrollX, scrollY, scrollX, scrollY);
8402
+ if (!intent || intent.mode !== "reorder") {
8403
+ useDesignModeStore.getState().cancelExternalDrag();
8404
+ setDropLine(null);
8405
+ return;
8406
+ }
8407
+ const containerEl = resolveNodeElement(intent.containerId);
8408
+ if (!containerEl) {
8409
+ useDesignModeStore.getState().cancelExternalDrag();
8410
+ setDropLine(null);
8411
+ return;
8412
+ }
8413
+ const template = document.createElement("template");
8414
+ template.innerHTML = component.html.trim();
8415
+ const newElement = template.content.firstElementChild;
8416
+ if (!newElement) {
8417
+ useDesignModeStore.getState().cancelExternalDrag();
8418
+ setDropLine(null);
8419
+ return;
8420
+ }
8421
+ const nodeId = `insert:${Date.now()}`;
8422
+ newElement.setAttribute(DND_NODE_ID_ATTR, nodeId);
8423
+ newElement.setAttribute("data-dnd-force-allow", "");
8424
+ newElement.setAttribute("data-dnd-moved", "");
8425
+ const children = Array.from(containerEl.children).filter(
8426
+ (child) => child instanceof HTMLElement
8427
+ );
8428
+ const beforeChild = children[intent.index] ?? null;
8429
+ containerEl.insertBefore(newElement, beforeChild);
8430
+ const insertCommand = {
8431
+ type: "insert",
8432
+ nodeId,
8433
+ html: component.html,
8434
+ containerId: intent.containerId,
8435
+ index: intent.index,
8436
+ at: Date.now()
8437
+ };
8438
+ const applied = refs.historyRef.current.execute(insertCommand, (command, direction) => {
8439
+ if (command.type !== "insert") return false;
8440
+ return applyInsertCommand(command, direction, resolveNodeElement);
8441
+ });
8442
+ if (applied) {
8443
+ const nodeKey = getNodeKeyFromElement(newElement);
8444
+ const inspectorRef = getInspectorRefFromElement(newElement);
8445
+ const containerNodeKey = getNodeKeyFromElement(containerEl);
8446
+ let containerIdentity = null;
8447
+ if (containerEl) {
8448
+ containerIdentity = {
8449
+ nodeId: intent.containerId,
8450
+ nodeKey: getNodeKeyFromElement(containerEl),
8451
+ selector: containerEl.getAttribute(DND_NODE_ID_ATTR) ? buildNodeSelector(intent.containerId) : null,
8452
+ inspectorRef: getInspectorRefFromElement(containerEl)
8453
+ };
8454
+ }
8455
+ const operationId = `${insertCommand.at}:${nodeId}`;
8456
+ useDesignModeStore.getState().recordInsertChange({
8457
+ operationId,
8458
+ nodeKey,
8459
+ nodeId,
8460
+ inspectorRef,
8461
+ displayName: component.displayName,
8462
+ html: component.html,
8463
+ containerId: intent.containerId,
8464
+ containerNodeKey,
8465
+ index: intent.index
8466
+ });
8467
+ dispatchRuntimeEvent({
8468
+ type: "element-inserted",
8469
+ operationId,
8470
+ nodeId,
8471
+ nodeKey,
8472
+ displayName: component.displayName,
8473
+ html: component.html,
8474
+ container: containerIdentity,
8475
+ index: intent.index
8476
+ });
8477
+ }
8478
+ setDropLine(null);
8479
+ useDesignModeStore.getState().cancelExternalDrag();
8480
+ }
8481
+
7770
8482
  // src/client/dnd/design-mode/DesignModeOverlay.tsx
7771
8483
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
7772
8484
  var DROP_ANIMATION = { duration: DURATION_MS, easing: EASING };
@@ -7812,7 +8524,7 @@ function HitArea({
7812
8524
  onResizeHandlePointerDown
7813
8525
  }) {
7814
8526
  const { setNodeRef, listeners, attributes, isDragging } = useDraggable({ id: scanned.nodeId });
7815
- useEffect11(() => {
8527
+ useEffect12(() => {
7816
8528
  setNodeRef(scanned.element);
7817
8529
  return () => setNodeRef(null);
7818
8530
  }, [setNodeRef, scanned.element]);
@@ -7824,16 +8536,16 @@ function HitArea({
7824
8536
  el.style.removeProperty("opacity");
7825
8537
  }
7826
8538
  }, [isDragging, scanned.element]);
7827
- useEffect11(() => () => {
8539
+ useEffect12(() => () => {
7828
8540
  scanned.element.style.removeProperty("opacity");
7829
8541
  scanned.element.style.removeProperty("transform");
7830
8542
  scanned.element.style.removeProperty("transition");
7831
8543
  scanned.element.style.removeProperty("will-change");
7832
8544
  }, [scanned.element]);
7833
- const divRef = useRef9(null);
8545
+ const divRef = useRef10(null);
7834
8546
  const [hoverResizeHandle, setHoverResizeHandle] = useState6(null);
7835
8547
  const isSelected = scanned.nodeId === selectedNodeId;
7836
- useEffect11(() => {
8548
+ useEffect12(() => {
7837
8549
  const div = divRef.current;
7838
8550
  if (!div) return;
7839
8551
  const onWheel = (e) => {
@@ -7904,6 +8616,7 @@ function DesignModeOverlay() {
7904
8616
  const redoLastChange = useDesignModeStore((s) => s.redoLastChange);
7905
8617
  const orderedChanges = useDesignModeStore((s) => s.orderedChanges);
7906
8618
  const exportChangesForAI = useDesignModeStore((s) => s.exportChangesForAI);
8619
+ const externalDrag = useDesignModeStore((s) => s.externalDrag);
7907
8620
  const { elements, pause, resume } = useElementScanner(enabled);
7908
8621
  const nodeMap = useMemo6(() => {
7909
8622
  const m = /* @__PURE__ */ new Map();
@@ -7918,7 +8631,7 @@ function DesignModeOverlay() {
7918
8631
  const refs = useOverlayRefs(undoRequestId, redoRequestId);
7919
8632
  refs.elementsRef.current = elements;
7920
8633
  refs.nodeMapRef.current = nodeMap;
7921
- useEffect11(() => {
8634
+ useEffect12(() => {
7922
8635
  const m = /* @__PURE__ */ new Map();
7923
8636
  for (const el of draggableElements) m.set(el.nodeId, el);
7924
8637
  refs.draggableNodeMapRef.current = m;
@@ -7926,7 +8639,7 @@ function DesignModeOverlay() {
7926
8639
  const resolveNodeElement = useCallback8((nodeId) => {
7927
8640
  const known = refs.nodeMapRef.current.get(nodeId)?.element;
7928
8641
  if (known) return known;
7929
- return document.querySelector(`[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(nodeId)}"]`);
8642
+ return document.querySelector(buildNodeSelector(nodeId));
7930
8643
  }, []);
7931
8644
  const clearCommitLaneCache = useCallback8(() => {
7932
8645
  refs.commitLaneCacheRef.current.clear();
@@ -7949,7 +8662,7 @@ function DesignModeOverlay() {
7949
8662
  if (command.type === "resize") {
7950
8663
  const resCmd = command;
7951
8664
  const targetId = resCmd.targetNodeId ?? resCmd.nodeId;
7952
- const targetEl = document.querySelector(`[${DND_NODE_ID_ATTR2}="${escapeCssAttrValue(targetId)}"]`);
8665
+ const targetEl = document.querySelector(buildNodeSelector(targetId));
7953
8666
  if (!targetEl) return false;
7954
8667
  const snapshot = direction === "redo" ? resCmd.after : resCmd.before;
7955
8668
  if (snapshot.styles) {
@@ -7960,6 +8673,9 @@ function DesignModeOverlay() {
7960
8673
  }
7961
8674
  return true;
7962
8675
  }
8676
+ if (command.type === "insert") {
8677
+ return applyInsertCommand(command, direction, resolveNodeElement);
8678
+ }
7963
8679
  const applied = applyDndCommandPlacement(command, direction, resolveNodeElement);
7964
8680
  if (!applied) return false;
7965
8681
  clearCommitLaneCache();
@@ -7972,6 +8688,10 @@ function DesignModeOverlay() {
7972
8688
  }
7973
8689
  setHistoryAvailability(refs.historyRef.current.canUndo(), refs.historyRef.current.canRedo());
7974
8690
  }, [historyMode, setHistoryAvailability]);
8691
+ useEffect12(() => {
8692
+ refs.historyRef.current.clear();
8693
+ syncHistoryAvailability();
8694
+ }, [enabled, syncHistoryAvailability]);
7975
8695
  const sensors = useSensors(
7976
8696
  useSensor(PointerSensor, { activationConstraint: { distance: 8 } })
7977
8697
  );
@@ -7982,7 +8702,7 @@ function DesignModeOverlay() {
7982
8702
  );
7983
8703
  const [insideRect, setInsideRect] = useState6(null);
7984
8704
  const [dropLine, setDropLine] = useState6(null);
7985
- useEffect11(() => {
8705
+ useEffect12(() => {
7986
8706
  if (!enabled) return;
7987
8707
  const fn = (e) => {
7988
8708
  refs.mouseRef.current = { x: e.clientX, y: e.clientY };
@@ -7990,7 +8710,7 @@ function DesignModeOverlay() {
7990
8710
  window.addEventListener("pointermove", fn, { capture: true, passive: true });
7991
8711
  return () => window.removeEventListener("pointermove", fn, { capture: true });
7992
8712
  }, [enabled]);
7993
- useEffect11(() => {
8713
+ useEffect12(() => {
7994
8714
  if (!enabled) return;
7995
8715
  if (historyMode !== "local") return;
7996
8716
  const onKeyDown = (e) => {
@@ -8019,7 +8739,7 @@ function DesignModeOverlay() {
8019
8739
  setInsideRect,
8020
8740
  setDropLine
8021
8741
  });
8022
- useEffect11(() => {
8742
+ useEffect12(() => {
8023
8743
  if (!enabled || activeDrag) return;
8024
8744
  if (undoRequestId === refs.processedUndoIdRef.current) return;
8025
8745
  refs.processedUndoIdRef.current = undoRequestId;
@@ -8036,7 +8756,7 @@ function DesignModeOverlay() {
8036
8756
  });
8037
8757
  syncHistoryAvailability();
8038
8758
  }, [undoRequestId, enabled, activeDrag, applyCommand, resume, selectElement, syncHistoryAvailability, undoLastChange]);
8039
- useEffect11(() => {
8759
+ useEffect12(() => {
8040
8760
  if (!enabled || activeDrag || historyMode !== "local") return;
8041
8761
  if (redoRequestId === refs.processedRedoIdRef.current) return;
8042
8762
  refs.processedRedoIdRef.current = redoRequestId;
@@ -8048,42 +8768,29 @@ function DesignModeOverlay() {
8048
8768
  }
8049
8769
  syncHistoryAvailability();
8050
8770
  }, [redoRequestId, enabled, activeDrag, applyCommand, historyMode, resume, selectElement, syncHistoryAvailability, redoLastChange]);
8051
- useEffect11(() => {
8771
+ useEffect12(() => {
8052
8772
  syncHistoryAvailability();
8053
8773
  }, [syncHistoryAvailability]);
8054
- useEffect11(() => {
8774
+ useEffect12(() => {
8055
8775
  dispatchRuntimeEvent({
8056
8776
  type: "design-mode-changed",
8057
8777
  mode: enabled ? "design" : "off"
8058
8778
  });
8059
8779
  }, [enabled]);
8060
- useEffect11(() => {
8780
+ useEffect12(() => {
8061
8781
  const aiOutput = exportChangesForAI();
8062
8782
  const updatedAt = orderedChanges.length > 0 ? orderedChanges[orderedChanges.length - 1]?.at ?? Date.now() : Date.now();
8063
8783
  dispatchRuntimeEvent({
8064
8784
  type: "arrange-session-changed",
8065
- session: {
8066
- mode: enabled ? "arrange" : "off",
8067
- canUndo: orderedChanges.length > 0,
8068
- changes: orderedChanges.map((event, index) => ({
8069
- id: `${event.kind}:${event.nodeKey}:${event.at}:${index}`,
8070
- kind: event.kind,
8071
- nodeKey: event.nodeKey,
8072
- nodeId: event.nodeId,
8073
- inspectorRef: event.inspectorRef,
8074
- summary: summarizeEditorChangeEvent(event),
8075
- at: event.at
8076
- })),
8077
- output: {
8078
- json: aiOutput,
8079
- prompt: aiOutput.aiPromptContext,
8080
- summary: orderedChanges.length === 0 ? "No arrange changes recorded yet." : `${orderedChanges.length} arrange change${orderedChanges.length === 1 ? "" : "s"} recorded.`
8081
- },
8785
+ session: buildArrangeSessionSnapshot({
8786
+ enabled,
8787
+ orderedChanges,
8788
+ aiOutput,
8082
8789
  updatedAt
8083
- }
8790
+ })
8084
8791
  });
8085
8792
  }, [enabled, exportChangesForAI, orderedChanges]);
8086
- useEffect11(() => {
8793
+ useEffect12(() => {
8087
8794
  if (!enabled) return;
8088
8795
  document.body.classList.add("design-mode-active");
8089
8796
  return () => document.body.classList.remove("design-mode-active");
@@ -8096,6 +8803,13 @@ function DesignModeOverlay() {
8096
8803
  autoScrollStateRef: refs.autoScrollStateRef
8097
8804
  });
8098
8805
  useDragVisualLoop({ activeDrag, refs, getCommitLaneSnapshot, setInsideRect, setDropLine });
8806
+ useExternalDragLoop({
8807
+ externalDrag,
8808
+ refs,
8809
+ elements,
8810
+ resolveNodeElement,
8811
+ setDropLine
8812
+ });
8099
8813
  const { handleResizeHandlePointerDown } = useResizeSession({
8100
8814
  enabled,
8101
8815
  refs,
@@ -8151,7 +8865,7 @@ function DesignModeOverlay() {
8151
8865
  }
8152
8866
  }
8153
8867
  ),
8154
- dropLine && activeDrag && /* @__PURE__ */ jsx4(
8868
+ dropLine && (activeDrag ?? externalDrag) && /* @__PURE__ */ jsx4(
8155
8869
  "div",
8156
8870
  {
8157
8871
  "data-design-mode-ui": true,
@@ -8210,35 +8924,40 @@ var combinedCollisionDetection = (args) => {
8210
8924
 
8211
8925
  // src/client/dnd/DndProvider.tsx
8212
8926
  import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
8927
+ var externalDragMouse = { x: 0, y: 0, commitRequested: false };
8213
8928
  function DndProvider({ children }) {
8214
8929
  const setActive = useDndStore((s) => s.setActive);
8215
8930
  const dragEndHandlers = useDndStore((s) => s.dragEndHandlers);
8216
- const toggle = useDesignModeStore((s) => s.toggle);
8217
8931
  const designModeEnabled = useDesignModeStore((s) => s.enabled);
8218
- const setDesignModeEnabled = useDesignModeStore((s) => s.setEnabled);
8219
8932
  const setHistoryMode = useDesignModeStore((s) => s.setHistoryMode);
8220
8933
  const setInspectorTheme = useDesignModeStore((s) => s.setInspectorTheme);
8221
8934
  const requestUndo = useDesignModeStore((s) => s.requestUndo);
8222
- useEffect12(() => {
8935
+ const transitionDesignMode = useCallback9((nextEnabled) => {
8936
+ const designModeState = useDesignModeStore.getState();
8937
+ if (designModeState.enabled === nextEnabled) return;
8938
+ designModeState.resetArrangeSession();
8939
+ designModeState.setEnabled(nextEnabled);
8940
+ }, []);
8941
+ useEffect13(() => {
8223
8942
  const handleKeyDown = (e) => {
8224
8943
  if (e.ctrlKey && e.shiftKey && e.key === "D") {
8225
8944
  e.preventDefault();
8226
- toggle();
8945
+ transitionDesignMode(!useDesignModeStore.getState().enabled);
8227
8946
  }
8228
8947
  };
8229
8948
  window.addEventListener("keydown", handleKeyDown);
8230
8949
  return () => window.removeEventListener("keydown", handleKeyDown);
8231
- }, [toggle]);
8232
- useEffect12(() => {
8950
+ }, [transitionDesignMode]);
8951
+ useEffect13(() => {
8233
8952
  const handleHostCommand = (event) => {
8234
8953
  const detail = event.detail;
8235
8954
  if (!detail || typeof detail !== "object") return;
8236
8955
  switch (detail.type) {
8237
8956
  case "sync-editor-state":
8238
8957
  if (detail.mode === "design") {
8239
- setDesignModeEnabled(true);
8958
+ transitionDesignMode(true);
8240
8959
  } else if (detail.mode === "inspect" || detail.mode === "off") {
8241
- setDesignModeEnabled(false);
8960
+ transitionDesignMode(false);
8242
8961
  }
8243
8962
  if (detail.historyMode) {
8244
8963
  setHistoryMode(detail.historyMode);
@@ -8248,7 +8967,7 @@ function DndProvider({ children }) {
8248
8967
  }
8249
8968
  break;
8250
8969
  case "set-design-mode":
8251
- setDesignModeEnabled(detail.enabled);
8970
+ transitionDesignMode(detail.enabled);
8252
8971
  break;
8253
8972
  case "set-history-mode":
8254
8973
  setHistoryMode(detail.historyMode);
@@ -8259,6 +8978,21 @@ function DndProvider({ children }) {
8259
8978
  case "request-undo":
8260
8979
  requestUndo();
8261
8980
  break;
8981
+ case "external-drag-start":
8982
+ useDesignModeStore.getState().startExternalDrag(detail.component);
8983
+ break;
8984
+ case "external-drag-move":
8985
+ externalDragMouse.x = detail.clientX;
8986
+ externalDragMouse.y = detail.clientY;
8987
+ break;
8988
+ case "external-drag-end":
8989
+ externalDragMouse.x = detail.clientX;
8990
+ externalDragMouse.y = detail.clientY;
8991
+ externalDragMouse.commitRequested = true;
8992
+ break;
8993
+ case "external-drag-cancel":
8994
+ useDesignModeStore.getState().cancelExternalDrag();
8995
+ break;
8262
8996
  default:
8263
8997
  break;
8264
8998
  }
@@ -8268,7 +9002,7 @@ function DndProvider({ children }) {
8268
9002
  window.removeEventListener(HOST_COMMAND_EVENT, handleHostCommand);
8269
9003
  };
8270
9004
  }, [
8271
- setDesignModeEnabled,
9005
+ transitionDesignMode,
8272
9006
  setHistoryMode,
8273
9007
  setInspectorTheme,
8274
9008
  requestUndo
@@ -8303,6 +9037,7 @@ function DndProvider({ children }) {
8303
9037
  const handleDesignModeCrash = useCallback9(() => {
8304
9038
  const designModeState = useDesignModeStore.getState();
8305
9039
  designModeState.resetAll();
9040
+ designModeState.resetArrangeSession();
8306
9041
  designModeState.setEnabled(false);
8307
9042
  }, []);
8308
9043
  if (designModeEnabled) {
@@ -8343,7 +9078,7 @@ function DndProvider({ children }) {
8343
9078
  }
8344
9079
 
8345
9080
  // src/client/useArchieDevToolsRuntime.ts
8346
- import { useEffect as useEffect13 } from "react";
9081
+ import { useEffect as useEffect14, useRef as useRef11 } from "react";
8347
9082
 
8348
9083
  // src/client/inject-inspector/archieOrigins.ts
8349
9084
  var ARCHIE_HOST_ORIGINS = [
@@ -8449,10 +9184,9 @@ function injectInspector(opts = {}) {
8449
9184
  document.head.appendChild(script);
8450
9185
  return null;
8451
9186
  }
8452
- const localInspectorModulePath = "./inspector.js";
8453
9187
  return import(
8454
9188
  /* @vite-ignore */
8455
- localInspectorModulePath
9189
+ "./inspector.js"
8456
9190
  ).then((m) => {
8457
9191
  try {
8458
9192
  m.default();
@@ -8568,18 +9302,26 @@ installDomMetaSanitizer();
8568
9302
  installDomMetaSanitizer();
8569
9303
  function useArchieDevToolsRuntime({
8570
9304
  router,
8571
- inspector = true
9305
+ inspector,
9306
+ inspectorOptions
8572
9307
  }) {
8573
- useEffect13(() => {
9308
+ const stableOptionsKey = JSON.stringify(inspectorOptions ?? null);
9309
+ const inspectorOptionsRef = useRef11(inspectorOptions);
9310
+ inspectorOptionsRef.current = inspectorOptions;
9311
+ useEffect14(() => {
8574
9312
  if (process.env.NODE_ENV !== "development") {
8575
9313
  return;
8576
9314
  }
8577
9315
  const unsubscribe = setupArchieRouteListener(router);
8578
- if (inspector) {
8579
- injectInspector({ scriptSource: "remote" });
9316
+ if (inspector !== false) {
9317
+ const isLocalHost = typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
9318
+ injectInspector({
9319
+ scriptSource: isLocalHost ? "local" : "remote",
9320
+ ...inspectorOptionsRef.current
9321
+ });
8580
9322
  }
8581
9323
  return unsubscribe;
8582
- }, [router, inspector]);
9324
+ }, [router, inspector, stableOptionsKey]);
8583
9325
  }
8584
9326
 
8585
9327
  // src/client/ArchieDevToolProvider.tsx
@@ -8587,9 +9329,10 @@ import { jsx as jsx6 } from "react/jsx-runtime";
8587
9329
  function ArchieDevToolProvider({
8588
9330
  children,
8589
9331
  router,
8590
- inspector = true
9332
+ inspector,
9333
+ inspectorOptions
8591
9334
  }) {
8592
- useArchieDevToolsRuntime({ router, inspector });
9335
+ useArchieDevToolsRuntime({ router, inspector, inspectorOptions });
8593
9336
  return /* @__PURE__ */ jsx6(DndProvider, { children });
8594
9337
  }
8595
9338
  export {