Skip to content

About pages for spaces, maintained resources, and users (#478)#479

Draft
tkuhn wants to merge 32 commits into
masterfrom
feat/about-pages-478
Draft

About pages for spaces, maintained resources, and users (#478)#479
tkuhn wants to merge 32 commits into
masterfrom
feat/about-pages-478

Conversation

@tkuhn
Copy link
Copy Markdown
Contributor

@tkuhn tkuhn commented Jun 2, 2026

First, low-risk step toward #478. Adds three standalone About pages, reachable by direct URL only — no existing page is changed, and nothing links to them yet.

Page URL
AboutSpacePage /spaceabout?id=
AboutResourcePage /resourceabout?id=
AboutUserPage /userabout?id=

What each page shows

  • Assigned view displays — a table listing the resource's configured view displays (not the rendered data views), built on the get-view-displays query Nanodash uses internally.
  • References — the existing references table.
  • User page also shows a new read-only PublicProfilePanel (introductions, public keys with per-user approval, default license) that works for any user — unlike the session/edit-bound ProfilePage items.

The tables carry their own dct:title, so there are no extra section headers.

Supporting nanopub (published live)

The view-displays listing is backed by a new ResourceView/TabularView nanopub published to the live registry:
https://w3id.org/np/RAQO0kK2FFtdfdGShuLkpFTbrq90btzt38z4w1hGVQfJE/view-displays-view
It deliberately omits gen:appliesToInstancesOf (like the references-view) so it never auto-attaches to the main pages; it's rendered explicitly via View.get(...).

Other changes

  • ReferencesPage.REFERENCES_VIEW promoted to public static for reuse.
  • Three mountPage(...) lines in WicketApplication.

Deliberately deferred (marked // TODO(#478))

  • Tab navigation between each entity's main page and its About page (the eventual goal).
  • Resource-level members/roles (the domain model has none — they live on the parent Space).
  • User space-membership listings (no API to enumerate a user's spaces yet).

Status

mvn compile passes. Not yet verified in a running Nanodash (needs the dev server + real resource IDs to see the tables populate).

🤖 Generated with Claude Code

tkuhn and others added 30 commits June 2, 2026 13:50
Add three standalone, mounted, parameterized About pages, reachable by
direct URL only (not yet linked from the existing pages, and the existing
pages are unchanged):

- AboutSpacePage    -> /spaceabout?id=
- AboutResourcePage -> /resourceabout?id=
- AboutUserPage     -> /userabout?id=

Each page shows a listing of the resource's assigned view displays (built
on the get-view-displays query Nanodash uses internally, rendered via a
newly published ResourceView/TabularView nanopub) plus a references table
-- rather than rendering the assigned data views themselves.

The user About page additionally shows a new read-only PublicProfilePanel
(introductions, public keys with per-user approval, default license) that
works for any user, unlike the session-bound ProfilePage items.

ReferencesPage.REFERENCES_VIEW is promoted to public so the About pages
can reuse it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ripes (#478)

- Render the user's introductions as a proper view on AboutUserPage (live
  ResourceView/TabularView backed by a per-user get-user-introductions query),
  replacing the custom intro DataView.
- Replace PublicProfilePanel with a "Profile" view (default license + profile
  picture, one row each, with source-nanopub column and "update profile
  image/license..." result actions); remove the panel and the public-keys
  section.
- style.css: make the table zebra stripe alpha-based (rgba(0,0,0,0.06)) so it
  darkens whatever is behind it instead of looking brighter on darker
  backgrounds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…display queries (#478)

- AboutSpacePage: show an "Assigned roles" view (role/user/date + source np),
  rendered above the view-displays listing.
- Point the Assigned roles and Assigned view displays views at dedicated,
  less-technical queries (linked view/role/user columns, status, date, source
  np) instead of the app-internal GET_SPACE_ROLES/GET_VIEW_DISPLAYS queries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…stripe (#478)

Renders identically (#F3F6F9) over white sections, but darkens over the
matching #F3F6F9 row-section stripe instead of blending in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ces) (#478)

Extract the duplicated action-rendering loop into a shared addViewActions
helper and call it from the resourceWithProfile == null branch too, so view
"linked actions" (template buttons) appear on the general Spaces page. The
helper only sets the target/context/part params when an id/contextId is
present, so resource-less listings work without bogus params.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bundles of default views and roles, defined once and assigned to
resources. Mirrors the view-display mechanism: authorized agents only
(admins+maintainers, or the user on a user page), latest-wins, with
presets and standalone view displays sharing one override pool.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Presets (issue #302) bundle default views and roles and are assigned to a
resource. The get-view-displays query now also returns preset-supplied views
(unbound ?display), which AbstractResourceWithProfile builds via
ViewDisplay.forPresetView and merges into the existing latest-wins/dedup pool.

Adds Preset and PresetAssignment domain models, the gen: preset vocabulary, and
two About-page listings (authority-filtered to admins/maintainers/affected
user): the existing "View displays" view now includes preset-supplied views
marked with their preset, and a new "Assigned presets" view is shown before it
on the Space, Resource and User About pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#302)

Repoint GET_VIEW_DISPLAYS to get-view-displays v6, which applies the same
authority gate as the About-page listings: only view displays and preset
assignments signed by an admin or maintainer of the owning space, or by the
affected user themselves, are rendered. This also closes the user-page gap
(space-less resources were previously unfiltered).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All QueryResult* views (Table, Rdf, List, ItemList, NanopubSet, PlainParagraph)
now show an italic "(nothing found)" note (class no-records-note) and hide the
data element entirely when empty. Table and Rdf drop Wicket's NoRecordsToolbar
so the header row is no longer shown for empty results; the table's visibility
is driven by onConfigure (with an output-markup placeholder) so the AJAX filter
collapses it when a filter matches nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#302)

Repoint the view-displays-view and preset-assignments-view constants to the
versions that declare an "add view display..." / "add preset..." result action,
and pass the page resource as id/contextId to the table builder so the action
pre-fills param_resource (the target space/resource/user) on the publish form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A preset's gen:hasTopLevelView pins to the resource's own page; gen:hasView
defers to the view's own class/namespace targeting (gen:appliesToInstancesOf /
appliesToNamespace) instead of being forced to the top level. Top-level
rendering now passes the resource's own type (getOwnClasses: Space /
MaintainedResource / IndividualAgent) so a view targeting the resource type
shows there, while a view targeting a part type (e.g. owl:Class) shows on
matching parts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The get-view-displays query now follows each referenced view's
npx:supersedes chain server-side (as list-view-displays does), so ?view
is already the latest version. New artifact:
RAWD7diDNUAR0DLMS5gUMCyk9O6wAu7wRkfTGtS_Hf9Uo/get-view-displays
(supersedes RAd-T...).

Nanodash no longer resolves latest versions one-by-one when building a
page's view displays:
- View.get(id, resolveLatest) overload skips getLatestVersionId; the
  query's already-latest ?view is loaded directly.
- ViewDisplay.get(id, latestViewIri) + 3-arg ctor thread the view IRI
  through; forPresetView loads its view directly.
- The per-display getLatestVersionId in ViewDisplay.get is removed: the
  query's invalidates filter already returns only current display
  nanopubs (all superseded displays are also invalidated under the same
  signing key), so the display np is loaded directly.

Net result: zero one-by-one latest-version round-trips in the
get-view-displays render path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Point GET_VIEW_DISPLAYS at the v8 get-view-displays query
(RAJtBiC...). Both get-view-displays and list-view-displays now
resolve each referenced view to the most recent *current head* of
its version tree -- a nanopub itself neither superseded nor validly
retracted (npx:invalidates) -- with date only as a tiebreak among
surviving heads, instead of picking the max-timestamp node. This is
robust to backdated supersedes and to retracted versions. Verified
identical to the prior queries on all current live data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Point GET_VIEW_DISPLAYS at the v10 query (RAy49uUd...), which wraps
the cross-repo latest-view resolution in a run-once sub-SELECT. The
resolution was previously invoked once per referenced view, costing
~44 federation round-trips (~4.3s of a ~4.5s query); evaluating it
once for the whole view set cuts a 44-display page to ~1.7s, with
identical results (verified against the deployed closure across all
548 views). list-view-displays got the same treatment and
view-displays-view was re-pointed at it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…478)

Replace the standalone About/Raw pages with a tab strip selected via a
?tab= query param on the main /space, /user, /resource and /part pages
(default content). Tab bodies are reusable panels (AboutSpacePanel,
AboutResourcePanel, AboutUserPanel, ExplorePanel) plus DownloadRdfLinks
for Raw; bodies are built conditionally so non-content tabs don't fire
the content queries. The tab strip lives in the breadcrumb stripe
(rounded-top tabs, gray-italic "- About/Explore/Raw" title suffixes);
its bottom border was removed. /explore stays for arbitrary terms but
forwards known resources to their page's Explore tab. References moved
from About to Explore.

Also: deleted AboutSpacePage/AboutUserPage/AboutResourcePage/RawPage and
their mounts; removed the "+ view display" buttons (kept on /part), the
per-id Explore button and the ExploreDisplayMenu/UserPageMenu dropdowns,
"See Your Profile Details", and the raw component on /list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add QueryResult.fitsOnFirstPage() (page size < 1, or total rows <= page
size) and hide the filter input in QueryResultTable/List/ItemList/
NanopubSet/PlainParagraph when it holds, so the Filter box only shows
when there are more rows than fit on the first page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Breadcrumbs: split each label on list/title punctuation (, : ; | and
  spaced -) into separate crumb segments instead of truncating at ": ".
- Action-menu dropdowns: white-background (light) style; nudge the down-
  arrow up so it lines up with the action buttons and filter field.
- Result tables: hide the header of a trailing np/nps column and
  right-align its cells (the ^ source-nanopub links).
- Hide the result filter textfield when all rows fit on the first page.
- Make the per-tab contentContainer a transparent wicket:container so the
  content row-sections stay direct children of #content-pane, fixing the
  alternating background stripes (were appearing only after a refresh).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add ProfileAccountPanel to the current user's /user?tab=about: logout
  (ORCID-login mode only) and an ORCID set/change form (local mode only).
- Dropdown arrows: CSS-drawn outline "v" chevron (no top line), matching
  the action buttons' box and vertical-align so they line up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Embed ProfileSigItem in ProfileAccountPanel so the current user's About
tab also shows their public key and (in local mode) the local key-file
path (migrating profile feature B).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… action

- Revert the introductions-view retract entry action: republish a
  read-only introductions-view (RAElH_0Za…, supersedes the action version)
  and point the constant at it.
- AboutUserPanel: the logged-in user (with a local key) now gets the
  ProfileIntroItem companion *instead of* the read-only view (others keep
  the read-only view) — no more duplicate listing.
- Restyle ProfileIntroItem to match the view tables: a Recommended Actions
  section (paneltitlerow header) holding the recommendation note + actions
  (incl. Create Introduction), then the "👋 Introductions" pseudo-view
  rendered as a table (date/location/keys/np + per-row retract/derive/
  include-keys).
- Style the Account header like Signing Key; fold the "multiple
  introductions" note into the retract bullet so no introductory text sits
  directly in front of the table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Spec for auto-binding view-query placeholders (LOCALPUBKEY, SITEURL) from
the session, plus the empty-into-required entry-action visibility rule, as
the path to replace the custom ProfileIntroItem table with a proper view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Move the four notes from doc/ into docs/ (alongside userlist-views.md).
- Add a **Status:** line (Implemented / In progress / Proposed) to each doc.
- Add docs/README.md index with a status table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
View displays visible only to a given role tier (Maintainer, ...) or
specific role via a single gen:isVisibleTo predicate, riding nanopub-query's
existing role-tier model. Records the AboutUserPanel preset/view-display
action leak as a known gap to fix with the declarative mechanism.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements steps 1-4 of docs/role-specific-views.md:

- KPXL_TERMS: role-tier IRIs (AdminRole/MaintainerRole/MemberRole/
  ObserverRole) and the gen:isVisibleTo predicate.
- SpaceMemberRole: tier field (parsed from the get-space-roles roleType
  column, defaulting to ObserverRole), tier ranking (admin>maintainer>
  member>observer>everyone) and isTier/tierRank helpers; the built-in
  ADMIN_ROLE carries the AdminRole tier.
- Space: consume the roleType column; userTier(iri) and
  viewerHoldsRole(iri, role) lookups.
- View/ViewDisplay: parse gen:isVisibleTo; ViewDisplay.isVisibleTo(...)
  matches a tier threshold or a specific role (no admin override),
  falling back to the view default; empty => everyone.
- AbstractResourceWithProfile.getViewDisplays: drop displays the viewer
  is not entitled to, after the latest-wins/deactivation pass. Session-safe
  via NanodashSession.getCurrentUserIriOrNull() and fails closed off-request.
- Tests for the tier ranking/parsing.

Inert until a view display declares gen:isVisibleTo; maintainer/member
tiers need the get-space-roles roleType republish (P0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The get-space-roles query nanopub was republished (RAKJFw-x..., supersedes
RAERgisN...) to also return ?roleType (npa:hasRoleType), so SpaceMemberRole
can pick up each role's tier for gen:isVisibleTo gating.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nodes

Pivot from whole-view visibility to per-action gating, per the reworked
docs/role-specific-views.md.

- View: parse gen:isVisibleTo on action nodes -> getActionVisibleTo(actionIri).
- SpaceMemberRole.isViewerEntitled(...): tier-threshold / specific-role match
  (no admin override), plus a convenience overload resolving viewer/space/owner
  from the rendered resource. A user page is a degenerate space whose sole admin
  is the owner: tier-gated actions show only to the owner, specific-role gates
  match nobody (observers can be added later).
- Filter actions in all five renderers (additive; undeclared actions unchanged):
  QueryResultTable(Builder), QueryResultList(Builder), QueryResultPlainParagraphBuilder.
- Remove the whole-view filter (AbstractResourceWithProfile) and ViewDisplay/
  View view-level visibility parsing.
- Tests for the tier ranking and isViewerEntitled matching.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The view-creation template can't leave the per-action gen:isVisibleTo
statement optional inside its repeated action group, so it emits an explicit
default. Add gen:EveryoneRole (rank-0 "no restriction" sentinel — a
Nanodash-side visibility tier, never a nanopub-query grant tier):

- KPXL_TERMS.EVERYONE_ROLE; SpaceMemberRole.isTier includes it and
  isViewerEntitled short-circuits to visible (incl. anonymous) when present.
- Tests for the everyone-to-all semantics.
- Doc updated; records the published authoring template
  RA8_hijwsfGCryMYtjtEpec21ZSNY68-qmL0bHRWR0sWM (tier picker + SpaceMemberRole
  API + EveryoneRole default).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s done

Per-action gating is client-side relevance-gating over public data, so there
is no server-side enforcement to do (that framing was a holdover from the
whole-view design). Replace it with an optional performance-only note (have a
query return the viewer's tier directly), and mark the feature Implemented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The presets and view-displays tables in AboutUserPanel / AboutSpacePanel /
AboutResourcePanel were built without resourceWithProfile, so their
"add preset…" / "add view display…" result actions fell into ButtonList's
ungated regular bucket and leaked onto other users' (and visitors') pages.
Pass resourceWithProfile so the actions are owner/admin-gated — on user pages
via the IndividualAgent admin bucket immediately, and on space/resource pages
via the per-action gen:isVisibleTo filter (which needs the governing space
resolved from the resource).

Pairs with the gen:isVisibleTo gen:MaintainerRole republish of the
preset-assignments-view and view-displays-view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tkuhn and others added 2 commits June 8, 2026 13:56
Republished preset-assignments-view (RAdznW...) and view-displays-view
(RAZJUh...) with gen:isVisibleTo gen:MaintainerRole; panels pass
resourceWithProfile. Mark the known gap as fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
View-creation templates can't leave a statement optional inside a repeated
action group, so views now carry every action field with "void" for the
not-applicable ones (its presence lets Nanodash repopulate the action group
when superseding a view). Parse "void" as absent so it never becomes a bogus
param_void in the action link.

Pairs with republishing preset-assignments-view (RA4fgq...) and
view-displays-view (RAl3LO...) with complete "void"-filled action blocks.

Co-Authored-By: Claude Opus 4.8 (1M context) <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.

1 participant