Drag a layer row in the LayerPanel; a 2 px primary-tint drop
indicator paints between rows; release commits the move. Supports
both same-parent reordering and cross-parent reparenting.
Core (`document/`):
- `Document::reorder_before(source, anchor)` / `reorder_after`
with editability + cycle + existence pre-checks. The mutation
phase calls `extract_node` + `insert_before/after_in_children`
via shared walkers — guarded by the pre-check pass so the
Err(node) arm of the insert helpers stays unreachable.
- New walkers: `extract_node`, `insert_before/after_in_children`
(`Result<(), Node>` so the node bounces back on miss),
`children_contain_descendant`. All `pub(in crate::document)`
so sibling submodules (`mutators`) can call them via
`super::walkers::*`.
Widget (`widgets/layer_panel.rs`):
- `DropPosition::{Before, After}` + `DropTarget { anchor,
position, indicator_y }` re-exported from `widgets::`.
- `LayerPanel::drop_target_at(rect, point) -> Option<DropTarget>`
walks the layer rows; upper half → Before, lower half → After.
- `LayerPanel.drop_target` field + `from_document_with_drop`
ctor so the host can thread the active drop target through
to paint without touching the existing API. Drop-indicator
paints last so it sits above row chrome.
- Layout constants promoted to `pub(crate)` and tests moved to
a sibling `widgets/layer_panel_tests.rs` to honor the 800-line
cap (mirrors the `document/` split pattern).
Host wiring (native + web parity):
- `LayerDragState { source, start_y, current_x/y, active }` on
both `WidgetHostNative` and web `WidgetHost`.
- `apply_press` peeks for a Layer row hit and seeds the drag
candidate (selection still happens immediately on press).
- `apply_cursor_move` promotes candidate → active once the
cursor moves past a 4 px screen-space threshold (mirrors the
marquee activation heuristic).
- `apply_release_with_viewport` calls `commit_layer_drag`,
which runs `drop_target_at` against the live cursor pos and
dispatches to `reorder_before` / `reorder_after`. Viewport-
less `apply_release` drops the candidate silently (no rect
to compute against).
- Paint paths build the panel via `from_document_with_drop`
when `layer_drag.active` is true.
Tests (8 new, 137 total in shell-core):
- 6 mutator tests in `document/tests_mutators.rs`:
same-parent before/after move, cross-parent reparenting,
cycle rejection, locked/hidden source rejection,
same-id/missing-id rejection.
- 1 drop-target hit-test in `widgets/layer_panel_tests.rs`
covering both halves of a row (Before / After) and the
`indicator_y` contract.
All checks clean: cargo fmt + cargo test + cargo check
--workspace + tools/check-widget-boundary.sh. Every file
under the 800-line ceiling.