Skip to content

Show a Subject's Schema as one badge wherever it is named - #1345

Merged
JeroenDeDauw merged 5 commits into
masterfrom
1328-schema-badge
Sep 7, 2026
Merged

Show a Subject's Schema as one badge wherever it is named#1345
JeroenDeDauw merged 5 commits into
masterfrom
1328-schema-badge

Conversation

@alistair3149

@alistair3149 alistair3149 commented Sep 2, 2026

Copy link
Copy Markdown
Member

For #1328

A Subject's Schema was drawn four ways: a permission-gated link in the editor header that opened the
Schema editor rather than navigating, subtle text beside a navigator row, a subtitle link in the
infobox, and another beside a statement count in the Subjects manager. Nothing about any of them said
"this string is a Schema" — the register they share is also the register of statement counts, subject
ids and the tree's group captions.

SchemaNameDisplay renders the bare name as a badge and owns the rules each surface used to decide
for itself: where the link goes, whether it opens a tab of its own, and when the name is withheld
because it would only repeat the name beside it.

Decisions worth challenging

A badge, deliberately not a CdxInfoChip. A chip is a bordered pill at @font-size-small, which
is the size of a navigator row's own text; thirteen of them down the demo wiki's tree read as a wall.
The badge is a step smaller, filled rather than outlined, and on @background-color-neutral rather
than -subtle, which would all but vanish on a hovered row and clash on a selected one. Chip and
badge stay distinct shapes for distinct ideas — the chip means Property Type on the Schema page, and
this PR leaves it alone.

The tree gets a slot rather than a stylesheet. NeoTree gains a secondary slot, rendered
inside the element that held the plain text, so the tree learns nothing about Schemas and one
component still owns the badge. The row's DOM is unchanged, which matters:
SubjectTree.spec.ts:809 asserts a row's children and text exactly, to catch a re-introduced glyph,
and it still does.

Always a link, and the withholding rule moves in. Reading a Schema is not an edit right, so the
badge no longer hides behind neowiki-schema-edit. schemaNameToShow takes the Subject and answers for every surface, so a Subject whose own name is its
Schema name (ADR 31) shows it once — the infobox and the Subjects manager printed it twice before. It reads
the Subject rather than the name a surface shows, since (unnamed Company) names the Schema too.

Not here: the editor's own header. It keeps Schema: Company for now. The follow-up removes that
subtitle wholesale, and changing its click target here would quietly retire the only in-dialog route
to the Schema editor — a call that belongs with the PR redesigning that header. So this PR leaves the
editor header inconsistent with the tree below it, on purpose and briefly.

Also here: neowiki-infobox-type is deleted, being declared and defined but referenced by no code;
DataExportButton's copy of the visually-hidden rules moves to a shared mixin.

Testing

All tests pass. The new ones were mutation-verified: disabling the withholding rule, making new-tab stop
opening one, linking the tree's badge, removing the badge from the tree, dropping the guard at a manager row,
handing the tree the bare Schema name, and ignoring the label typed in a pane each turn a test red by name.
Several of those found real gaps — the first cut of this PR had no test that would have noticed a surface
forgetting to withhold the badge.

Manual Browser Check

On a wiki with the demo data, logged in:

  1. Open ACME Inc. Under the infobox title, Company is a small filled badge, not blue link text.
    It links to Schema:Company in the same tab. Hovering underlines it.
  2. Open a page whose Main Subject has no label of its own — its title already is its Schema name.
    The badge is absent rather than repeating the title. (Special:Subjects on such a page shows the
    same: the row named after its Schema carries no badge, one named after its page does.)
  3. Click the infobox pencil. Every navigator row carries the same badge. Rows stay 32px; the badge
    does not push them taller. The badge is not a link here — the row is the click target.
  4. Select a row. The badge still separates from the selected row's blue-tinted background.
  5. Switch the skin to dark. The badge follows: dark fill, light text, same contrast relationship.
  6. With a screen reader, the badge announces "Schema: Company" — the noun is present once, not twice.
  7. The dialog header still reads Schema: Company as a link that opens the Schema editor. That is
    deliberate for this PR; the follow-up removes it.

AI-authored — Claude Code, Opus 5 (1M context); design chosen by @alistair3149 from options put to them in a design artifact, including the badge-not-chip call and the decision to unify behaviour as well as appearance; diff not yet human-reviewed; verified: 1689 vitest tests, lint and build green locally, six mutations confirmed red, and the badge checked in a browser against a dev wiki in light and dark, in the infobox, the Subjects manager and the editor's navigator.

@alistair3149

Copy link
Copy Markdown
Member Author
image image

@JeroenDeDauw

JeroenDeDauw commented Sep 6, 2026

Copy link
Copy Markdown
Member

Merged master into this branch in 6c9c9af. The conflicts were the overlap with #1359: it shows a Subject nobody named as (unnamed Company) and decides the tree's schema label by its new nameIsGenerated flag, while this PR decides by comparing the displayed name. A plain textual merge would have compared the marked name in the pane and the tree and shown the badge beside (unnamed Company), and the pane had no test that would have noticed.

What the merge commit does instead:

  • schemaNameToShow takes the Subject and withholds the badge where the Subject's own name (getDisplayName(), unmarked) is its Schema name. That covers the generated name and one typed after the Schema, and nobody can hand it the marked string.
  • SchemaNameDisplay loses its displayName prop and always renders; each surface guards its own badge with v-if. The walk carries the outcome as WalkNode.schemaLabel, replacing master's nameIsGenerated, whose only consumer was that decision.
  • Tests: the helper and badge specs follow; the manager's wiring test became an outcome test (main row badged, the (unnamed Person) row not); three pane tests added. Four mutations (never withholding, the pane ignoring the typed label, a manager row losing its guard, the walk handing over the bare name) each turn exactly the expected tests red.

Two commits on top:

Checked in a browser on a worktree wiki: the infobox, and ?action=subjects on Rijksmuseum with an unnamed Attendance child added. Labelled rows carry the badge; (unnamed Attendance) carries neither badge nor bullet. The PR body's withholding-rule paragraph and the test-count sentence are edited to match.

AI-authored — Claude Code, Fable 5.1 (max); one-line ask from @JeroenDeDauw to resolve the conflicts, no revisions; diff not yet human-reviewed; 1889 vitest tests, vue-tsc build and lint green locally in a scratch container, four mutations checked, two surfaces checked in a browser.

@JeroenDeDauw JeroenDeDauw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, checked stuff locally. Fable saying you should have another look due to semantic merge conflict resolution

alistair3149 and others added 5 commits September 7, 2026 20:07
`DataExportButton` carried its own copy of the nine declarations that take an element out of
the visual layout while leaving it in the accessibility tree. A second caller is arriving, so
they move to `assets/mixins.less` beside `keyboard-focus.less`, imported by reference so
nothing is emitted until a rule calls it.

Neither Codex nor this build offers one to reuse: Codex's public mixins are `css-icon`, `link`
and `button-layout-flush`, and MediaWiki core's `.mixin-screen-reader-text()` is out of reach
because the Vite build sets no LESS include path to core.

The mixin body is the block it replaces, unchanged, so the emitted CSS for
`.ext-neowiki-data-export__sr-only` is byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R7zBSDFPUVStRFFTzjMLL8
A `secondary` slot renders in place of a node's plain `secondaryLabel` text, inside the same
element, so a consumer can give that label a treatment of its own without the tree learning
what it means. It forwards through `NeoTreeNode` recursively, like `trailing` already does,
and keeps the plain text as its default content.

The slot decorates a label rather than replacing the decision to show one: a node with no
`secondaryLabel` renders no secondary element, and therefore no slot. That is a real limit of
the shape rather than an oversight, so a test pins it.

Separately, `__node-secondary` may now shrink. Under `flex: none` a long secondary label took
its full max-content width and squeezed the node's own name toward nothing, then pushed the
navigator into horizontal scroll; it now gives way to the name and truncates itself.

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

A Subject's Schema was drawn four ways: a permission-gated link in the editor header that
opened the Schema editor rather than navigating, subtle text beside a navigator row, a
subtitle link in the infobox, and another beside a statement count in the Subjects manager.
Nothing about any of them said "this string is a Schema" — the register they shared is also
the register of statement counts, subject ids and the tree's group captions.

`SchemaNameDisplay` renders the bare name as a badge and owns the rules the surfaces used to
each decide for themselves: where the link goes, and when the name is withheld because it
would only repeat the name beside it.

Deliberately not a `CdxInfoChip`. A chip is a bordered pill at the size of a navigator row's
own text, and thirteen down the demo wiki's tree read as a wall; the badge is a step smaller
and squared off. The two stay distinct shapes for distinct ideas — the chip means Property
Type on the Schema page, and is left alone.

Where it links it takes Codex's normal-weight progressive-action button. Those colours are
polyfilled onto the badge itself: the Codex MediaWiki serves ships most of them at older
values and the two subtle hover and active fills not at all, and it defines the custom
properties it does ship, so a `var()` fallback would never reach ours. `light-dark()` supplies
both arms, since MediaWiki's dark mode sets `color-scheme` on the root, and `@supports` guards
the block so a browser without it keeps MediaWiki's own values rather than one that is invalid
at substitution. Remove that block once MediaWiki 1.47 carries the values.

`:visited`, `:hover` and `:focus` are spelled out throughout, which is load-bearing: core
styles `a:visited` and underlines `a:hover, a:focus`, each at specificity (0,1,1) — a
pseudo-class counts as a class — so they tie with a bare `a.<class>` and win on load order.

The withholding rule lives in `schemaNameToShow`, shared by the badge and by the tree's own
`secondaryLabel`, so a Subject displayed under its Schema name (ADR 31) shows it once; the
infobox and the Subjects manager printed it twice before. The infobox gates its
`role="heading"` wrapper on the same rule, or a withheld badge would leave an unnamed heading
in the accessibility tree.

Two layout fixes the badge's own `max-width` could not make: `min-width: 0` on the infobox
header's text column, because a flex item's automatic minimum size is its content's
max-content width and a 50-character Schema name pushed the badge through the infobox border;
and the tree reaching the badge through NeoTree's slot rather than a stylesheet, so one
component owns it and the row's DOM is unchanged — `SubjectTree.spec` asserts a row's children
and text exactly, to catch a re-introduced glyph, and still does.

Also here: `neowiki-infobox-type` is deleted, being declared and defined but referenced by no
code, in the repo or in wiki content.

Not here: the editor's own header, which keeps `Schema: X` until the follow-up removes that
subtitle wholesale.

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

The dialog's header named the root Subject and carried the field that renamed it, so a nested
pane could not be renamed at all and the header described a Subject the user might not be
looking at. It named the root's Schema too, with the same problem. Neither could follow the
active pane: `title` is a CdxDialog prop, so binding it re-renders the dialog, and under the
spec suite's teleport stub that remounts every pane.

So the header stops naming anything, and with nothing left to slot it goes back to Codex's own.
`use-close-button` renders the same header the `#header` slot was reproducing line for line —
title group, `<h2>`, quiet close button — and Codex then names the dialog with
`aria-labelledby` pointing at that heading rather than an `aria-label` parallel to it. Out with
the slot go the CdxButton and icon imports, a second lookup of the same message, an `:inert`
that guarded a field no longer there, the replicated header rules and the link mixin.

Two rules survive as deliberate overrides rather than replications: `.cdx-dialog__header`'s
padding is unconditional in Codex, not a `--default` rule, so the header runs 49px against
every other dialog's 65px, and `align-items` is centred rather than baseline. Both because the
row is one line of static text beside a 32px close button, which sets the height.

The Subject's name, its rename control and its Schema move into the pane that edits them. The
Schema is the badge from the first commit, opened in a new tab for the reason the storage line
beside it already gives: this dialog holds unsaved edits for every open pane and nothing guards
a navigation away from it. A plain left click by someone who may edit the Schema opens the
editor instead; every gesture a browser uses to open a link elsewhere is left alone. Only the
root pane offers the editor, since that is the Schema `SchemaEditorDialog` is bound to, and
that restriction is asserted in the dialog as well as the pane — the pane knows the intent, the
dialog holds the consequence.

The badge takes `paneName`, so a Subject already displayed under its Schema name is not named
twice: without it the heading and the badge both read "Person" for a label-less child, which is
the duplication `schemaNameToShow` exists to remove. Its row may shrink, so the badge truncates
itself rather than pushing the header wider than the pane and giving the whole form a
horizontal scrollbar.

`neowiki-subject-editor-title` takes no parameter now, so the dialog's accessible name and its
visible heading say the same thing.

Claude-Session: https://claude.ai/code/session_01R7zBSDFPUVStRFFTzjMLL8

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bullet before a row's statement count dates from when the Schema link was always there. With the badge withheld from a row named after its Schema, the count opened with a dangling bullet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@JeroenDeDauw
JeroenDeDauw merged commit fe6c2f9 into master Sep 7, 2026
20 checks passed
@JeroenDeDauw
JeroenDeDauw deleted the 1328-schema-badge branch September 7, 2026 18:23
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