Skip to content

[BUGFIX] Fix drop-at-top sorting inversion (#738)#755

Draft
dbraendle wants to merge 2 commits into
b13:masterfrom
dbraendle:fix/738-drop-at-top-sorting-anchor
Draft

[BUGFIX] Fix drop-at-top sorting inversion (#738)#755
dbraendle wants to merge 2 commits into
b13:masterfrom
dbraendle:fix/738-drop-at-top-sorting-anchor

Conversation

@dbraendle

@dbraendle dbraendle commented Jul 3, 2026

Copy link
Copy Markdown

Draft — sketch of a possible direction for #738, not production-ready yet.

I'm sharing this early so the direction can be discussed against real code rather than pseudocode. Comments and pushback are very welcome.

Idea

Instead of returning a different anchor from ContainerService::getNewContentElementAtTopTargetInColumn() — which would require a DataHandler concept of "insert before X" that does not really exist — this tries to restore the invariant before the existing -container_uid anchor is used:

container.sorting < min(child.sorting)

If that invariant holds, -container_uid becomes a valid top-of-children anchor again: DataHandler computes a sorting value between the container and the next record in that global ordering, which puts the moved record before the existing children.

Rough shape

CommandMapBeforeStartHook::rewriteCommandMapTargetForTopAtContainer() now covers two paths:

  • Path 1: target > 0, drop-at-top from a positive page uid. If getNewContentElementAtTopTargetInColumn() returns the -container_uid fallback, the hook delegates the renumbering to Integrity\Sorting::normalizeChildrenSortingForContainer(), then uses the anchor as before.
  • Path 2: target = -container_uid already at hook time. This was previously skipped by the target > 0 gate. A companion branch now applies the same renumbering. The anchor itself remains unchanged.

The renumbering lives in Integrity\Sorting. It reads children through the Container model and uses the configured container column order from tcaRegistry. It writes only sorting and tstamp directly via Connection::update() — intentionally not through DataHandler, to avoid re-entering the hook stack from inside a hook.

Sorting::SORT_INTERVAL mirrors DataHandler's default sort interval instead of keeping the number inline.

Scope: live workspace only

The renumbering method short-circuits in any non-live workspace.

Rationale: in a non-live workspace, ContainerFactory can return live records for children that have no workspace overlay yet. A direct SQL update would then risk mutating live rows from a workspace DataHandler run. Workspace handling therefore needs an overlay-aware follow-up and is intentionally not part of this draft.

Verified only for the live workspace / default-language path.

Test coverage included

Two functional tests are included as a starting point:

  • command already arrives with target = -container_uid
  • command arrives with positive page target and is rewritten to the -container_uid fallback

Both use a container with sorting=10000, existing children at sorting=100/200, and a moved record that should land at the top of the children after the operation.

Not covered yet

  • Workspace path: deliberately no-op outside workspace 0.
  • Dedicated translated-container tests.
  • Dedicated multi-column / nested-container tests.
  • Workspace no-op regression test proving live rows are not mutated from a workspace run.
  • Final decision on whether direct SQL updates of sorting/tstamp are acceptable here.

If the general direction — restoring the invariant before using the existing anchor — is wrong, I'd rather hear that early before polishing the implementation further.

Dennis Brändle added 2 commits July 3, 2026 13:22
When the drop-at-top target for a container child column falls back
to -container_uid (either through the Path 1 rewrite from a positive
page target, or through Path 2 commands that already carry
target = -container_uid), DataHandler used the container record's own
page-level sorting as the anchor to compute the moved record's new
sorting.

Whenever the invariant container.sorting < min(child.sorting) does
not hold — which arises naturally on populated pages, in workspaces
with MOVE_POINTER children keeping low sortings, or after editorial
moves — the computed sorting sat above the existing children, so the
moved element appeared at the *bottom* of the children list even
though the editor visually dropped it at the top.

Before returning the -container_uid anchor the hook now renumbers the
container's children so that all their sortings sit strictly above the
container's own sorting. This restores the invariant and lets
DataHandler place the moved record inside the children's sort space.

Path 2 (target = -container_uid) was previously skipped entirely by
`rewriteCommandMapTargetForTopAtContainer` because of the `target > 0`
gate; it is now covered by a companion branch.

The renumber uses the current BE user's active workspace to scope both
read and update.

Fixes b13#738
Adjustments after review:

- Move the drop-at-top renumber out of CommandMapBeforeStartHook into
  Integrity\Sorting::normalizeChildrenSortingForContainer(). The hook now
  delegates via the injected Sorting service.
- Read children through the Container model (via ContainerFactory), which
  keeps the read side language- and configured column-order-scoped.
  Renumbering iterates tcaRegistry->getAvailableColumns() in configured
  order and preserves within-column sorting order.
- Extract the sort interval into a Sorting::SORT_INTERVAL constant that
  mirrors DataHandler::\$sortIntervals.
- Explicitly skip in non-live workspaces. In a non-live workspace the
  ContainerFactory can return live records for children that have no
  workspace overlay yet; a direct SQL update would then silently mutate
  live rows from a workspace DataHandler run. Workspace handling is
  intentionally left for a follow-up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant