Skip to content

Show a Subject nobody named as a marked stand-in - #1359

Merged
JeroenDeDauw merged 1 commit into
masterfrom
1329-generated-name
Sep 5, 2026
Merged

JeroenDeDauw merged 1 commit into
masterfrom
1329-generated-name

Conversation

@alistair3149

@alistair3149 alistair3149 commented Sep 5, 2026

Copy link
Copy Markdown
Member

For #1329

A Subject with no label falls back to its Schema name (ADR 31), rendered exactly like a name someone typed. "Attendance" is what a curator might have entered, so nothing in the string says nobody did.

Such a Subject now shows as (unnamed Attendance), from a new neowiki-subject-generated-name message.

Why this shape

MediaWiki already brackets a system-supplied stand-in, and records why in the translator note on blanknamespace, "(Main)": "Surrounded by brackets to signal that it's only a symbolic label and not an actual namespace prefix." It is a corpus, not one message — (edit summary removed), (username removed), (no username available) — and NeoWiki already shipped one in neowiki-select-unknown-option, (unknown option).

The marker leads rather than trails: a trailing parenthesis on a name is MediaWiki's disambiguation convention ([[name (context)|]]), so Attendance (unnamed) reads as a deliberately disambiguated real name. It sits in the string rather than in styling, because eight mw.notify calls and an aria-label interpolate a display name into plain text no CSS reaches, and WCAG 1.4.1 rules out colour as the only carrier.

Not "Untitled Attendance": SchemaName permits a Schema called Untitled, and in a museum wiki "Untitled X" reads as a work titled Untitled.

Only the Schema tier is marked. A Main Subject falls back to its page name, which whoever titled the page chose.

Where it is composed

REST gains displayNameIsGenerated. A client cannot derive it: comparing the name with the Schema name gets a Main Subject on a page titled after its Schema wrong, and that name was chosen. GetSubjectResponseItem::fromSubject() takes the nullable chosen name and derives both the display name and the verdict from it, so the two cannot disagree.

The marker itself is composed in the frontend presentation layer, so rdfs:label in both RDF projectors, Lua's subject.label and {{#neowiki_value}} are byte-identical to before. Those return data a template computes with; the marker is chrome for a reader. ADR 31 already gives the reason for RDF specifically: reconciliation keys on labels, so a UI marker in rdfs:label would corrupt what an external service matches against.

The relation picker keeps the bare name at its three sites: its selection becomes the value of an editable text input that feeds the offer to create a Subject under the typed text.

Also fixed, found while building this

  • Promoting a Subject named it before the write, announcing the name it had just stopped having — on both the menu and the drag path. Both now name it after, and only the write is guarded, so a committed write can no longer be reported as failed.
  • The subject tree suppressed its Schema label by comparing it against the name; a marked name no longer matches, so it reads the tier instead.
  • The save error named the dialog's root Subject rather than the pane whose editor it could not read, which in a nested edit is a different Subject.
  • The delete confirmation drops the quotation marks around its name, which the bold already delimits. The other messages keep theirs — a stored label is arbitrary text and needs the delimiter.

Manual Browser Check

Wiki: http://localhost:8484 (or your own dev stack).

  1. Open a page with a Main Subject and add a second Subject without a label — for example a Attendance Subject on a Museum page — via Manage subjects.
  2. On Special:ManageSubjects for that page, confirm the child row reads (unnamed Attendance) while the main row still reads the page title, unmarked.
  3. Confirm the row's Schema link underneath still reads Attendance.
  4. Open the subject editor on the unnamed Subject. The dialog title and the rename field's grey placeholder should both read (unnamed Attendance). Type a label — both should drop the marker.
  5. In Manage subjects, promote the unnamed Subject to main, both from the row's pin button and by dragging. Both toasts should name it after the page, not (unnamed …).
  6. Add a relation on another Subject pointing at the unnamed one. The rendered relation link on the page should read (unnamed Attendance); the picker field while choosing it should read the bare Attendance.
  7. Create a Subject from Add subject on a page that already has a main Subject, leaving the label blank: the placeholder previews (unnamed <Schema>).

Considered, omitted

Three cheaper alternatives, each rejected on outcome rather than cost. All would shrink the diff; none does the job as well.

  • Composing the marker in the backend rather than the frontend. Around 260 lines smaller, but it removes the concept from neither end: the serializer must still be told which tier the name came from, and the frontend still invents Subjects the server has never seen, so the marker's shape ends up written in two places instead of one. A subject response carries no main-subject marker, so a marked displayName cannot be reduced back to a bare one — and the relation picker writes that string into an editable field feeding subject creation, where (unnamed Attendance) would become storable as a real label.

  • {Schema}-{idFragment} as the computed name. Language-independent, so it needs no message and could be composed server-side. Not taken because a machine token appended to a domain noun gets a reader to "a machine made this token", not to "nobody named this thing". A leading fragment would not discriminate at all either: ids open with a nine-character base58 microsecond timestamp, so on the three ids in docs/api/rest-api.md a six-character prefix gives one distinct value across all three.

  • The bare Subject id as the computed name, which is what Wikibase's link formatter does — about eight production lines. Rejected because sibling ids share eight of fifteen leading characters, so it fails at the discrimination it would be chosen for; because the single-slot reader surfaces (a relation link in article prose, {{#neowiki_value}}, Lua) would lose their only type cue; and because a bare id in a name slot already means "this relation target did not resolve" here. Wikibase pairs the id with a phrase wherever it has two slots, and Wikimedia fixed the id-only form as a defect (T423651).

  • Telling two unnamed Subjects apart is the other half of Distinguish computed display names from real labels in the UI #1329 and is not solved here: three rows still read (unnamed Attendance) alike. The evidence points at a second slot rather than a different string — a subordinated id beside the name, as Wikibase does — or a Schema-declared name template (Attendance 2019), which answers the question a curator is actually asking and supersedes this wherever a Schema declares one. Filed separately.

AI-authored — Claude Code, Opus 5 (1M context); form and casing chosen by @alistair3149, who steered the marker into the string after rejecting styling alone; diff not yet human-reviewed, though it has been through /code-review, /security-review, /review-tests, a design pass, two correctness/edge-case passes and a four-angle /simplify, all in the authoring session — so a fresh-context review is still worth having; 133/133 vitest files, all REST integration classes, phpcs, phpstan, eslint and vue-tsc green locally; one reviewer-claimed defect was empirically refuted against master rather than "fixed", and mutation testing drove the added coverage.

@alistair3149

Copy link
Copy Markdown
Member Author
image

@alistair3149
alistair3149 marked this pull request as ready for review September 5, 2026 03:29
For #1329

A Subject with no label falls back to its Schema name (ADR 31), rendered exactly like a name
someone typed. "Attendance" is what a curator might have entered, so nothing in the string says
nobody did.

Such a Subject now shows as "(unnamed Attendance)". MediaWiki brackets a system-supplied stand-in
for this reason and records why on `blanknamespace`, "(Main)": "Surrounded by brackets to signal
that it's only a symbolic label and not an actual namespace prefix." NeoWiki already carried one
of these in `neowiki-select-unknown-option`, "(unknown option)".

The marker leads rather than trails, because a trailing parenthesis on a name is MediaWiki's
disambiguation convention and "Attendance (unnamed)" would read as a name deliberately
disambiguated. It sits in the string rather than in styling, because eight `mw.notify` calls and
an `aria-label` interpolate a display name into plain text no CSS reaches.

Only the Schema tier is marked. A Main Subject falls back to its page name, which whoever titled
the page chose.

The tier travels as a fact: REST gains `displayNameIsGenerated`. A client cannot derive it by
comparing the name with the Schema name, because a Main Subject on a page titled after its Schema
matches that comparison and was named by whoever titled the page.

Composed in the frontend presentation layer, so `rdfs:label` in both projectors, Lua's
`subject.label` and `{{#neowiki_value}}` are unchanged. The subject creator previews the same
marked name the Subject will be shown under once it exists.

The relation picker keeps the bare name: its selection becomes the value of a text input the user
can edit, and feeds the offer to create a Subject under the text typed.

Three things the change made visible, fixed here:

The subject tree suppressed its Schema label by comparing it against the name, which a marked name
no longer equals. It now reads the tier instead.

Promoting a Subject named it before the write rather than after, announcing the name it had just
stopped having - on both the menu and the drag path.

The save error named the dialog's root Subject rather than the pane whose editor it could not
read, which in a nested edit is a different Subject.

The delete confirmation drops the quotation marks around its name, which the bold already
delimits. The other messages keep theirs: a stored label is arbitrary text and needs the
delimiter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DYmoomz9WbTjFWz1fPy57m
@alistair3149
alistair3149 marked this pull request as draft September 5, 2026 03:33
@alistair3149
alistair3149 marked this pull request as ready for review September 5, 2026 04:13
@alistair3149
alistair3149 marked this pull request as draft September 5, 2026 04:18
@alistair3149
alistair3149 marked this pull request as ready for review September 5, 2026 05:05
@JeroenDeDauw
JeroenDeDauw merged commit ff1accc into master Sep 5, 2026
22 checks passed
@JeroenDeDauw
JeroenDeDauw deleted the 1329-generated-name branch September 5, 2026 22:00
JeroenDeDauw added a commit that referenced this pull request Sep 6, 2026
#1357 reached master with a five-argument
Subject constructor call after #1359 had
added the sixth, so master's TypeScript CI and Docker image build fail on
tests/components/common/SubjectPicker.spec.ts.

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.

2 participants