Skip to content

Move a Subject to another page - #1356

Merged
JeroenDeDauw merged 1 commit into
masterfrom
feature/1338-move-subject-to-page
Sep 7, 2026
Merged

Move a Subject to another page#1356
JeroenDeDauw merged 1 commit into
masterfrom
feature/1338-move-subject-to-page

Conversation

@alistair3149

Copy link
Copy Markdown
Member

Fixes #1338

A Subject created in flow from a relation field is stored on the page holding the Subject whose
relation was being filled in, which is often not where it belongs. Relocating one meant deleting it
and recreating it elsewhere, which mints a new id and breaks every relation pointing at the old one.

What it does

Every Subject row on the Data tab gains a Move action, before Delete in both the button strip and
the overflow menu. It opens a dialog holding a page lookup, a checkbox for making the Subject the
target page's Main Subject, and the notices each choice earns. The Subject keeps its id, so the
relations targeting it keep resolving and the subject-page index follows it to its new page.

A page that does not exist yet can be named in the lookup; it is created when the move is confirmed,
so an abandoned dialog leaves nothing behind.

POST /neowiki/v0/subject/{subjectId}/move is the write surface.

Decisions worth challenging

The source page is written first. A move is two page edits and there is no cross-page write
primitive. The graph projection is page-scoped: every page write rewrites the nodes of the Subjects
that page holds and tears down the ones it no longer does, so the page written last is the one that
decides what the moved Subject's node looks like. Writing the source last strips the node the target
write has just built and leaves a bare :Subject stub belonging to no page until the next rebuild,
while the wiki database and the index both look correct. The cost of source-first is a window in
which the Subject is on no page; a target write that fails is answered by writing the source page
back exactly as it was read.

Move is offered on the main-subject row as well, with a warning that the page will be left
without a main subject. Withholding it would only have hidden the action behind a demote-then-move
dance.

The promotion checkbox is never checked for you, and once a target is chosen it names the
Subject that promoting would demote, so an effect on a page the user is not looking at is stated
rather than sprung.

Page search is MediaWiki's own /v1/search/title, which already carries page ids and drops
results the viewer may not read. It is scoped to the wiki's default search namespaces, so a page
outside them has to be typed in full. NeoWiki adds no page-search endpoint of its own.

The target page is not restricted by namespace, because creating a Subject is not either.
Restricting only move would be inconsistent.

Manual Browser Check

On a wiki with the demo data, as a user who can edit:

  1. Open Rijksmuseum, then the Data tab. Every row has a Move button between copy-link and
    delete; narrow the window past the mobile breakpoint and it is in the ⋯ menu instead, above
    Delete.
  2. Move one of the Rijksmuseum attendance subjects. The dialog opens with Move disabled. Type
    ACME — matching pages appear, with Use "ACME" as a new page last, under a separator.
  3. Pick ACME Inc. Tick the checkbox: it names ACME Inc's current main subject as the one that
    would be demoted. Untick it and move. A green toast names the subject and its new page, and the
    row leaves the listing.
  4. Open ACME Inc's Data tab: the attendance subject is there as a child. Its ID is unchanged.
  5. Move it back to Rijksmuseum. On the source row for the main subject, open Move: an inline
    warning says the page will be left without a main subject.
  6. In the dialog, type a title no page has and pick Use "…" as a new page, then Move. The page
    is created and the subject lands on it; visit the page to confirm.
  7. Type a title, pick the create option, then correct the text without re-picking. Move is offered
    only once a target is chosen again — it must not create the page you first typed.

Testing

1903 vitest and 1979 PHPUnit tests pass, plus the @group Database endpoint tests. The new tests
were each mutation-verified: every guard inverted and seen to fail by name. One of them mounts the
real graph and asserts the moved Subject has exactly one hosting page node and keeps its schema
label, which is what the source-first ordering buys.

Exercised end to end against a dev wiki: a Subject moved between pages and back, its relations still
resolving, both pages carrying the edit summary, and every refusal path (400, 403, 404, 409, missing
CSRF) answered as documented.

AI-authored — Claude Code, Opus 5 (1M context) (ultracode); detailed spec in one message from @alistair3149, who took both open UI decisions against my recommendation, cut a NeoWiki page-search endpoint I had proposed, and corrected three details since; diff not yet human-reviewed, though it has been through a multi-pass AI review whose findings are folded in; 1903 vitest and 1979 PHPUnit tests plus the Database-group endpoint tests green locally, new tests mutation-verified, and the move exercised end to end against a dev wiki including the graph projection.

Production notes

The write order is the one thing worth a reviewer's attention. An earlier arrangement passed every test — wiki database and subject-page index both correct — while leaving the moved Subject a bare :Subject stub in Neo4j. Only querying the graph after a live move caught it, which is why MoveSubjectApiTest now asserts on the projection and not just on the slots.

@alistair3149

alistair3149 commented Sep 3, 2026

Copy link
Copy Markdown
Member Author
Recording.2026-09-03.154423.mp4

@alistair3149
alistair3149 marked this pull request as ready for review September 3, 2026 20:32
@alistair3149
alistair3149 requested review from JeroenDeDauw and malberts and a lite review from Copilot and removed request for Copilot September 3, 2026 20:54
@alistair3149
alistair3149 force-pushed the feature/1338-move-subject-to-page branch from 5563f10 to 9a05ba4 Compare September 3, 2026 21:14
@alistair3149
alistair3149 marked this pull request as draft September 3, 2026 21:14
@alistair3149
alistair3149 force-pushed the feature/1338-move-subject-to-page branch from 9a05ba4 to 5c41fec Compare September 4, 2026 01:31
@alistair3149
alistair3149 marked this pull request as ready for review September 4, 2026 02:03
@JeroenDeDauw

JeroenDeDauw commented Sep 6, 2026

Copy link
Copy Markdown
Member

Bikeshed, but I feel strongly about this: the icon makes me think "drag", esp since we have drag capabilities there. First icon that comes to mind as replacment is "=>"

Edit: done via

@JeroenDeDauw

Copy link
Copy Markdown
Member

Decisions worth challenging

Section looks good

Fixes #1338

A Subject created in flow from a relation field is stored on the page holding the Subject whose
relation was being filled in, which is often not where it belongs. Relocating one meant deleting it
and recreating it elsewhere, which mints a new id and breaks every relation pointing at the old one.

## What it does

Every Subject row on the Data tab gains a Move action, before Delete in both the button strip and
the overflow menu. It opens a dialog holding a page lookup, a checkbox for making the Subject the
target page's Main Subject, and the notices each choice earns. The Subject keeps its id, so the
relations targeting it keep resolving and the subject-page index follows it to its new page.

A page that does not exist yet can be named in the lookup; it is created when the move is confirmed,
so an abandoned dialog leaves nothing behind.

`POST /neowiki/v0/subject/{subjectId}/move` is the write surface.

## Decisions worth challenging

**The source page is written first.** A move is two page edits and there is no cross-page write
primitive. The graph projection is page-scoped: every page write rewrites the nodes of the Subjects
that page holds and tears down the ones it no longer does, so the page written last is the one that
decides what the moved Subject's node looks like. Writing the source last strips the node the target
write has just built and leaves a bare `:Subject` stub belonging to no page until the next rebuild,
while the wiki database and the index both look correct. The cost of source-first is a window in
which the Subject is on no page; a target write that fails is answered by writing the source page
back exactly as it was read.

**Move is offered on the main-subject row as well**, with a warning that the page will be left
without a main subject. Withholding it would only have hidden the action behind a demote-then-move
dance.

**The promotion checkbox is never checked for you**, and once a target is chosen it names the
Subject that promoting would demote, so an effect on a page the user is not looking at is stated
rather than sprung.

**Page search is MediaWiki's own** `/v1/search/title`, which already carries page ids and drops
results the viewer may not read. It is scoped to the wiki's default search namespaces, so a page
outside them has to be typed in full. NeoWiki adds no page-search endpoint of its own.

**The target page is not restricted by namespace**, because creating a Subject is not either.
Restricting only move would be inconsistent.

## Manual Browser Check

On a wiki with the demo data, as a user who can edit:

1. Open `Rijksmuseum`, then the **Data** tab. Every row has a Move button between copy-link and
   delete; narrow the window past the mobile breakpoint and it is in the ⋯ menu instead, above
   Delete.
2. Move one of the *Rijksmuseum attendance* subjects. The dialog opens with Move disabled. Type
   `ACME` — matching pages appear, with **Use "ACME" as a new page** last, under a separator.
3. Pick `ACME Inc`. Tick the checkbox: it names *ACME Inc*'s current main subject as the one that
   would be demoted. Untick it and move. A green toast names the subject and its new page, and the
   row leaves the listing.
4. Open `ACME Inc`'s Data tab: the attendance subject is there as a child. Its ID is unchanged.
5. Move it back to `Rijksmuseum`. On the source row for the **main** subject, open Move: an inline
   warning says the page will be left without a main subject.
6. In the dialog, type a title no page has and pick **Use "…" as a new page**, then Move. The page
   is created and the subject lands on it; visit the page to confirm.
7. Type a title, pick the create option, then correct the text without re-picking. Move is offered
   only once a target is chosen again — it must not create the page you first typed.

## Testing

1903 vitest and 1979 PHPUnit tests pass, plus the `@group Database` endpoint tests. The new tests
were each mutation-verified: every guard inverted and seen to fail by name. One of them mounts the
real graph and asserts the moved Subject has exactly one hosting page node and keeps its schema
label, which is what the source-first ordering buys.

Exercised end to end against a dev wiki: a Subject moved between pages and back, its relations still
resolving, both pages carrying the edit summary, and every refusal path (400, 403, 404, 409, missing
CSRF) answered as documented.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012KQMvby9ED7qd7E2XC6csE
@JeroenDeDauw

Copy link
Copy Markdown
Member

I rebased this PR

@JeroenDeDauw
JeroenDeDauw merged commit 0b9622b into master Sep 7, 2026
21 checks passed
@JeroenDeDauw
JeroenDeDauw deleted the feature/1338-move-subject-to-page branch September 7, 2026 14:54
JeroenDeDauw added a commit that referenced this pull request Sep 7, 2026
Follows-up to #1356

The Move action on the Data tab used `cdxIconMove`, the four-way arrow that is also the
browser's drag cursor, one button away from the row's drag handle in a list that is dragged to
reorder. A glyph that says "drag me" sat on the button that relocates a Subject to another page.

It now uses `cdxIconArticleRedirect`, a page with an arrow leading away from it: the
container-plus-arrow shape mail and file tools use for "move to", with a page as the container.
The dialog's confirm button and the overflow-menu entry carry the same icon, and the
ResourceLoader icon list follows.

Considered, omitted: a custom page-with-arrow glyph without the redirect association, and moving
the action into a labelled overflow menu on desktop.
JeroenDeDauw added a commit that referenced this pull request Sep 7, 2026
* Order the row actions by what they do to the row

Follows-up to #1356

The Data tab's row actions ran Promote, Edit, Copy link, Move, Delete, then the drag handle.
Copy link, which changes nothing, sat between actions that do; the rarely used Promote led the
strip where the most used action, Edit, belongs; and the handle, which is grabbed rather than
clicked, followed Delete with nothing between them.

The strip now runs Copy link, Edit, Promote, Move, Delete: the one action that leaves the row
as it is first, then the two that change it in place, then the two that take it out of the
listing, with Delete last. The main row keeps its pin as the indicator beside the title. The
overflow menu follows the same order, and the drag handle stands a little apart from the buttons.

Considered, omitted: moving the drag handle to the row's left edge, the wider list convention,
which would put it beside the expand chevron and the main row's pin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Keep the order tests in the block that already sets them up

The two order tests arrived in a describe of their own with a copy of the move-action block's
setup, and left the two move-before-delete tests they subsume in place. They now sit in that
block, and the subsumed tests are gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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.

Move a Subject to another page

2 participants