Make the Subject label optional - #1309
Draft
alistair3149 wants to merge 7 commits into
Draft
Conversation
A Subject is about to be allowed no label, so every surface that shows one needs a value to fall back on, and they all need the same one. Mirrors the frontend's defaultSubjectLabel, which applies the same rule to a Subject that does not exist yet. Also exports defaultSubjectLabel from the frontend public API, so extensions displaying Subjects can reach it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fixes #1283 A stored default cannot be told apart from a chosen label, which is what keeps a renamed page from renaming the Subjects that took their name from it, and what would make an importer's synthesized names look curated. The label becomes optional and every surface computes what to show instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
For #1283 An absent label means nobody named this Subject. The slot omits the key, and a blank label is read as absence so the two cannot drift apart. Display falls back at the point of use: a Main Subject to its page name, every other Subject to its Schema name. The editor and the creator show that name as a placeholder rather than storing it. Read APIs return the stored label, now nullable, alongside a non-null displayName. Lua diverges deliberately, keeping subject.label as the name to show and adding subject.storedLabel, because an absent key is nil there and concatenating it takes the whole page down. The graph materializes the fallback for Main Subjects only, where the page name is a true name and a page move already keeps it current. A label-less Child Subject gets no name property: the Schema name there would make every unnamed Subject of a Schema indistinguishable in query results, and the Schema is already on the node as its other label. That makes the absence of a name no longer the mark of a stub, so the orphan sweep now identifies one by its node labels; the old test would have let it delete a real Subject. RDF emits rdfs:label for every Subject from the effective name, since consumers key on it and the Schema appears there as rdf:type rather than as a label. A Mapping's labelPredicate is emitted only from a stored label: it states what the thing is called in the target ontology's own vocabulary, and a Schema name under foaf:name would assert a type as a name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
For #1283 The stale-label fix only reaches Subjects that have no stored label, so without this the Subjects that already exist keep the bug. The heuristic is that the stored label repeats what would be shown anyway; it can only misfire on someone who deliberately typed that, and loses nothing when it does. --dry-run reports the page and Subject of every label it would clear, which is the only mitigation available, since nothing in storage records whether a label was chosen. Both eras of the default are cleared. Before child Subjects defaulted to their Schema name, every Subject on a page defaulted to the page name, so matching only the Schema name would pass over almost every real default. The page name is matched prefixed and unprefixed, because the editor filled the field from the unprefixed title while the page node carries the prefixed one. Every cleared label costs a revision: this codebase has no null-edit path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
For #1283 Code review of the optional-label change. Three defects, the rest documentation. SubjectResolver named every relation target as though it were unplaced, so a target that is its page's Main Subject read as its Schema name in wikitext and Lua while the same Subject read as the page name in every View. Its docblock blamed the resolver for being unable to place the target; it can, and at no extra cost, since the Subject lookup was already resolving the page and loading all its Subjects before dropping the placement. The subject creator built its placeholder from the unprefixed title, so on a wiki with an extra content namespace it previewed "Onboarding" for a Subject that would go on to display "Handbook:Onboarding". The page-name read was duplicated verbatim between the display-name rule and the graph projector; it now lives on PageProperties, next to the key and the caveat about provider chains. Documentation: the migration renames a Child Subject that carried the page name and drops it from label search, which neither the ADR nor the operations guide admitted; clearing costs a revision per page, not per label; the ADR asked for a rebuild the script makes unnecessary; the payload shape doc never gained displayName; and the graph model offered a coalesce that cannot be written, since the Schema is a node label rather than a property. Tests for the gaps the review found: the subject-page lookup had none, so its cursor and its latest-revision join were both free to break; the migration's batch loop never ran past one iteration and neither failure path was covered; and nothing pinned a page move renaming a label-less Main Subject, which is the whole reason the graph derives that name rather than storing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
For #1283 A blind text review against the reader map in docs/AGENTS.md. The relation docs were wrong. They said a target with no label is named after its Schema, which stopped being true when SubjectResolver started resolving the target's page: a target that is its page's Main Subject reads as the page name. The Lua note about nw.getSubject, which really does fall through to the Schema name, stays. The Subject shape doc claimed every endpoint returning a Subject serves it; subject-labels returns id and label pairs and does not. The ADR blamed the revision-per-page cost on the absence of a null-edit path, which is not the cause: clearing a label changes slot content, so it needs a revision either way. The rest is trimming. The fallback rule was stated in three RDF files; it now lives in rdf-export.md and the others point there. The graph model restated the Schema-label line above its own table and explained that Cypher returns null for an absent property. The upgrade guide carried the one-off script twice, as fenced blocks, under a trigger no reader can identify in a project with no release numbers; both are now one sentence pointing at the task's home. One term, display name, replaces the six the diff had grown. Also swaps the retired label-required code out of SubjectViolationBanners' fixtures, which sampled it for banner partitioning and would have asserted on a violation that can no longer occur. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
For #1283 RedHerb is a plain-JS extension linted by its own eslint, whose parser rejects nullish coalescing. `make ts-lint` covers resources/ext.neowiki only, so the parse error surfaced first in CI. `||` is equivalent here: the only falsy label the getter can return is the empty string, which is what the fallback produces anyway. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1283
A Subject may now have no label. The slot omits the key, and a blank label is read as absence, so the two cannot drift
apart. Display falls back at the point of use — a Main Subject to its page name, every other Subject to its Schema
name — held in one rule,
SubjectDisplayName, mirrored for the creator byplaceholderSubjectLabel.ts.The requirement was enforced in three independent layers, all dropped together: the
label-requiredviolation, hardguards in the create and replace Actions, and
PARAM_REQUIREDon four endpoints. Relaxing fewer would have beeninvisible from the API.
Decisions worth a look:
Subject of a Schema indistinguishable in query results, and the Schema is already the node's second label. That
makes an absent
nameno longer the mark of a stub, so the orphan sweep now identifies one by its node labels —the old oracle would have deleted real Subjects.
rdfs:labelalways falls back; a Mapping'slabelPredicatedoes not. That term states what the thing iscalled in the target ontology, and a Schema name under
foaf:namewould assert a type as a name, whichreconciliation keys on.
subject.labelis the display name andsubject.storedLabelthe nullablestored one, because an absent key is
nilin Lua and concatenating it replaces the rendered page with a scripterror. REST splits them the other way, with a nullable
labeland a non-nulldisplayName.labelclears a stored one.NeoWiki:ClearDefaultSubjectLabelsclears pre-existing stored defaults once. It is load-bearing rather than polish:the stale-label fix only reaches Subjects that have no stored label, so without it existing Subjects keep the bug.
Considered, omitted:
titled "configurable / extendable", but its body asks for an outcome — stop showing the page name, show the Schema
name — which this delivers. The consumer with the strongest claim renders its own subject column from the REST API
and now receives
label,displayNameandschema, so it needs no server-side hook. If configurability doesarrive it is most likely Schema-authored data, for the identical-siblings case, which wants more arguments to a
pure function rather than dependency injection.
can gain discriminators later without touching stored data, which stored defaults could not.
Known gaps, not fixed here:
?oldid=views resolve main-ness from the live page, so a historical label-less Subjectcan be named from today's state; and
GET /subject-labelsreturns a computed page name in itslabelfield for alabel-less Main Subject without saying so.
Manual Browser Check
a placeholder, and the field itself is empty. On a page that already has a Main Subject, it shows the schema
name instead.
action=raw&slot=neoshows its entrywith no
labelkey.a statement and save — a labelled Subject must keep its label (the field round-trips the stored value, not the
placeholder).
GET /rest.php/neowiki/v0/subject/<id>returns"label": nullbeside a non-null"displayName".