Skip to content

CON-15: move pages with the keyboard and on a touch screen - #85

Open
molgerz wants to merge 3 commits into
mainfrom
feat/con-15-move-page-keyboard-touch
Open

molgerz wants to merge 3 commits into
mainfrom
feat/con-15-move-page-keyboard-touch

Conversation

@molgerz

@molgerz molgerz commented Sep 14, 2026

Copy link
Copy Markdown
Owner

Closes CON-15.

Moving a page no longer needs a mouse. HTML5 drag & drop has no keyboard path and does not fire on a touch screen at all, so on a phone moving a page was impossible rather than awkward.

What is in it

  • A real button on every tree row (in the tab order, reachable by finger) opens a menu with the four steps every outline editor has: up, down, in under the sibling above, out to behind the parent.
  • A second panel, "Move to…", lists every page the move may land under plus the top level, in tree order and filterable by name — the destination four steps cannot reach. Excluded are the page itself, its own subtree and its current parent, the same three a drop refuses.
  • Where each step lands is decided in src/domain/move-tree.ts, away from the menu. A step that picks a parent writes no order key and lets the level sort by title, exactly like dropping onto that row — the tree must not look different depending on what moved the page.
  • The trigger follows the "+" on the Pages heading: revealed on hover or focus, always visible on a coarse pointer.
  • A step that would land the page on the placement it already has is now disabled instead of silently doing nothing. Siblings share an order key when their titles normalise identically, and keyBetween answers "behind the pair" for equal bounds — right for a drop, wrong for a step. A tie is stepped over as a whole run.
  • Dragging is untouched and stays the primary gesture.

How to test

  1. Tab through the sidebar: every row's move button is reachable, and Enter opens the menu.
  2. Walk a page up, down, in and out with the keyboard only. The sidebar order matches what the menu promised each time.
  3. On a phone or with the device toolbar in touch mode: the button is visible without hovering, and all four steps work.
  4. Entries that cannot go anywhere (top row "up", root page "out") are greyed out rather than failing after the click.
  5. The regression this PR adds: give two sibling pages titles that sort identically (e.g. "Report" and "report") and neither a hand-placed position. "Up"/"Down" across that pair either moves the page past the whole run or is greyed out — no enabled entry that does nothing.
  6. "Move to…": filter by name, pick a page on the other side of the tree, confirm it lands as a child sorted by title. The page itself, its subtree and its current parent are not offered.
  7. Drag & drop still behaves as before.

Automated: typecheck, lint, tests and build are green.

M and others added 3 commits September 14, 2026 14:22
Moving a page was drag & drop only. HTML5 drag & drop has no keyboard path at
all, and on a touch screen it does not fire even once — so on a phone moving a
page was not awkward, it was impossible. docs/06 carried that as an open
point ever since the page's own move picker was dropped.

One menu answers both. Its trigger is a real button on every tree row, so it
is in the tab order and under a finger; its entries are the four steps every
outline editor has — up, down, in under the sibling above, out to behind the
parent. Reimplementing the drag on pointer events would have given the touch
screen a gesture and the keyboard still nothing.

Dragging stays the primary gesture and is untouched: it says where a page
lands better than any list can.

Where each step lands is `src/domain/move-tree.ts`, away from the menu,
because it is a question about the tree and because the order keys are only
checkable in isolation. A step that picks a *parent* writes no key and lets
the new level sort by title, which is exactly what dropping onto that row
already does — the tree must not look different depending on whether a mouse
or the keyboard moved the page. A key is written only where a position within
a level is genuinely chosen: up, down and out.

The trigger follows the "+" on the Pages heading: hidden until the row is
hovered or focused, because the tree is read far more often than it is
rearranged. A coarse pointer has no hover to reveal it with, so there it is
always visible — without that, the touch half of this ticket would have
shipped invisible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The four steps reach a neighbour. Filing a page under one at the other end of
the wiki is one drag with a mouse and, without one, a dozen repeats of "move
down" — so the menu gets a second panel: every page the move may land under
plus the top level, in tree order, filterable by name.

This is the one thing dragging has no equivalent of, and deliberately so: a
drag can only end where the pointer can reach, a list can name a row that is
scrolled away or folded shut. Left out are the page itself, its own subtree
and the parent it already has — the same three a drop refuses, refused here
for the same reason rather than reported as an error after the click.

Picking a destination writes no order key, the same as "in" and the same as
dropping onto that row. docs/02 said this of the drop; it now says it of
choosing a parent, however the parent was chosen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two siblings share an order key whenever their titles normalise identically and
neither has been placed by hand. `keyBetween` answers "behind the pair" for
equal bounds — right for a drop, wrong for a step, which asks to overtake one
row and would pass both. The step back then computed the very same key, so the
menu entry looked enabled and did nothing when clicked, because `useMovePage`
drops a no-op placement without a word.

A tie above or below is now stepped over as a whole run, and any plan that
lands the page on the placement it already has is reported as "nowhere to go" —
the same answer the first and last row of a level already get, and the menu
greys it out before it is tried.
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