Skip to content

feat(part-page): show a part's ancestors in the breadcrumb - #719

Open
ashleycaselli wants to merge 2 commits into
masterfrom
feat/718-part-breadcrumb-parent
Open

ashleycaselli wants to merge 2 commits into
masterfrom
feat/718-part-breadcrumb-parent

Conversation

@ashleycaselli

@ashleycaselli ashleycaselli commented Sep 17, 2026

Copy link
Copy Markdown
Member

Closes #718.

What changes

A part page's breadcrumb now shows the parts above it, top-down, between the context resource and the part itself.

Page (reached from) Breadcrumb
Approval (from Person) Nanopublication › Docs › Person › Approval
Rounds (from Approval) Nanopublication › Docs › Person › Approval › Rounds
Rounds (opened directly) Nanopublication › Docs › Trust and approval › Rounds
Trust and approval (from Approval) Nanopublication › Docs › Trust and approval

How the ancestors are found

  1. Top level first. A part whose defining nanopublication declares no parent that is itself a part of the context (a documentation topic, say) gets no ancestors, even when reached from another part.
  2. Navigation next. Otherwise the chain starts at the incoming part (set by NavigationContext.withPart only together with its own context, so it is trusted as the back-link already trusts it). Without one, it starts at the declared parent.
  3. Then upwards. From there it follows each part's declared parent: the first schema:about (either spelling) or dct:isPartOf object that ViewDataFetcher.partDefinitionQueryRef resolves as a part of the same context. It stops when a part declares none, when a part would repeat, or after MAX_ANCESTORS (5).

A part without a known definition cannot tell its level, so it keeps the part it was reached from. Each crumb links to the ancestor's part page under the same context; labels come from part-label, else the ancestor's rdfs:label, else its schema:title, else its short name.

The label lookup the page title already did (rdfs:label, else schema:title, #701) moves into getDeclaredLabel, shared with the crumbs; the title behaves as before.

Cost

One cached part-definition lookup per ancestor crumb, synchronous in the constructor like the page's own definition lookup.

Tests

  • New ResourcePartPageParentTest (17 tests): the chain logic takes the part-definition lookup as a function, so it is tested without the network. Covers the Rounds and Trust and approval cases above, the fallback without an incoming part, parents that are not parts of the context, cycles, the depth limit, label fallbacks, and the candidate predicates.
  • Full suite: 1428 tests, 0 failures.
  • Checked by hand on a local instance against the live Nanopublication Docs: Person, and Approval both from Person and opened directly, show the breadcrumbs in the table. The Rounds and Trust and approval rows are covered by unit tests and still need a manual check on a running instance.

🤖 Generated with Claude Code

A part page's breadcrumb now shows the parts above it, top-down, between the
context resource and the part itself: a paragraph reached from a topic reads
Docs > Person > Approval, and one reached from that paragraph reads
Docs > Person > Approval > Rounds.

The chain starts at the part the page was reached from (the incoming part
parameter), or, without one, at the first resource the part's defining
nanopublication declares it to be about or part of (schema:about in either
spelling, or dct:isPartOf) that is itself a part of the context. It then
follows each part's own declared parent upwards, stopping when a part declares
none, a part would repeat, or five ancestors are shown.

A part whose definition declares no parent in the context sits at the top
level, like a documentation topic, and gets no ancestors even when reached
from another part, so Trust and approval opened from a paragraph reads
Docs > Trust and approval. A part without a known definition keeps the part it
was reached from.

The rdfs:label / schema:title lookup for the page title moves into a helper
shared with the ancestor crumbs.

Closes #718

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ashleycaselli
ashleycaselli requested a review from tkuhn September 17, 2026 08:56
@tkuhn

tkuhn commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

I think we should show this always according to the defined hierarchy, so

Nanopublication › Docs › Trust and approval › Rounds

and never like this:

Nanopublication › Docs › Person › Approval › Rounds

And the defined hierarchy should probably use dct:isPartOf, which we currently don't use properly for the Docs. So this would involve changing the Docs definition as well as the behavior of this here.

Ideally parts of parts of parts should show too, and arbitrarily deep into the hierarchy. Not sure to what extent this is possible (might not be a proper tree...).

Review of #718: a part page should read the same wherever it was opened from,
so the breadcrumb now shows only the hierarchy the parts themselves declare.
The incoming part parameter no longer starts the chain; Rounds reads
Docs > Trust and approval > Rounds whether it is opened from the Approval
paragraph or directly. That parameter keeps serving the back-link (#697).

The hierarchy is dct:isPartOf: candidates are ordered with it first and
schema:about only after, so parts that state it properly take precedence while
those published before it keep their breadcrumb until they are republished.

The chain already walked upwards; the cap is now 20 rather than 5, so parts of
parts of parts are shown as deep as they go. The cycle guard stays, since parts
of parts need not form a tree: a part that would repeat ends the chain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ashleycaselli

Copy link
Copy Markdown
Member Author

Done in a0ac17c — the breadcrumb now shows only the declared hierarchy.

  • Never the reader's path. The incoming part parameter no longer starts the chain, so Rounds reads Nanopublication › Docs › Trust and approval › Rounds whether it is opened from the Approval paragraph or directly. That parameter keeps serving the back-link from Breadcrumb from a part page's outgoing links points to the space, not the part #697.
  • dct:isPartOf is the hierarchy predicate. Candidates are ordered with it first and schema:about only after it. I kept schema:about as a fallback rather than dropping it, so the current Docs paragraphs keep their breadcrumb until the definitions are republished — happy to make it strict if you prefer.
  • Arbitrarily deep. The walk already went upwards; the cap is now 20 instead of 5. The cycle guard stays, since as you say this need not be a proper tree: a part that would repeat ends the chain rather than looping.

Tests (15, full suite 1426 passing) cover your example explicitly — opening Rounds from Approval gives the same crumbs as opening it directly — plus a three-level chain (Person › Trust and approval › Approval rounds › Rounds), dct:isPartOf winning over schema:about, cycles and the cap.

On the data side: the Docs paragraphs currently declare schema:about a topic, and the topics declare no parent, so the deepest chain visible today is one level. Stating dct:isPartOf in the Docs definitions (and giving topics a parent where there is one) is what would show the deeper hierarchy. Want me to draft those nanopublications too?

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.

Part pages: show the parent part in the breadcrumb (parts of parts)

2 participants