Skip to content

Develop - #373

Draft
namedgraph wants to merge 184 commits into
masterfrom
develop
Draft

Develop#373
namedgraph wants to merge 184 commits into
masterfrom
develop

Conversation

@namedgraph

Copy link
Copy Markdown
Member

No description provided.

namedgraph and others added 30 commits August 26, 2026 18:17
Blocks now carry the classes ui_kits/app/app.css targets, replacing the
Bootstrap grid ones:

  row-fluid block  ->  block ldh-block
  main span7       ->  main ldh-block-body
  well             ->  ldh-block-head

'block' and 'main' stay alongside the new names: 62 and 11 CSR handler
patterns anchor on those tokens, so dropping them would unhook drag-drop,
inline editing and the block toolbars.

The header gains the two flex children app.css expects - a titles group
(type list as badge, h2.ttl, description as span.sub instead of a <p>)
and an .actions group holding the timestamp and actions.

bs2:Left and bs2:Right emitted reserved grid columns for backlinks,
related resources and parallax navigation. The design system has no
columns; that content belongs to a block-scoped drawer, so they now
render inside div.ldh-drawer. They stay unconditional because CSR fills
them later.

Two CSR call sites had to follow the markup:

- view.xsl took the nav placeholders with a child step, which the drawer
  wrapper broke - faceted search would have lost its facets and parallax
  its controls, silently, since an empty for-each is not an error.
- chart.xsl and query.xsl lift the header out before replacing block
  content via ./div/div[@Class = 'well'], declared as="element()", so the
  rename would have been a runtime type error rather than a silent miss.

Still to port: bs2:Left/bs2:Right internals to the drawer's own
structure, the drawer's open/close behaviour, bs2:Actions buttons to .tb,
the YouTube header's inner markup, and dropping app.css itself in -
without which this renders unstyled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
colors_and_type.css (tokens), app.css (components), retro.css (m3 skin)
copied from the design system export; fonts.css @font-faces the vendored
woff2s - Geist, Geist Mono, Instrument Serif (latin subsets) and the
Material Symbols Rounded variable icon font - so no external font
requests. The root element takes data-retro="m3" data-theme="light",
which the skin keys on.

Linked after the Bootstrap stylesheets so the design system wins where
both target the same elements; the Bootstrap links come out at the end
of the port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bs2:NavBar becomes the ldh-header grid (wordmark | address | actions),
replacing the Bootstrap fixed-top collapse structure; NavBarLeft/Main/
Right and their span columns go. The brand takes the wordmark shape,
the address bar the ldh-address skin (same form, 'navbar-form' token and
input#uri kept for the CSR submit handler), and the right side becomes
ldh-icon-btn buttons with Material Symbols glyphs - apps grid, settings
gear, notification bell (admin) - plus an initials ldh-avatar for the
agent. btn-group/dropdown-toggle tokens stay: the CSR handler toggles
.open on them.

ldh:LeftSidebar takes the ldh-sidebar skin: sb-search field, sb-section/
sb-heading groups, sb-tree rows with Material carets. The flyout
mechanics are untouched - hidden by default, edge-hover shows, mouseout
hides - and document-tree/class-list/btn-expand-tree/btn-geo/btn-latest
anchors stay for the CSR handlers.

ldh-bridge.css is the deliberate shim between CSR state tokens and the
design system: dropdown .open menus, ul.nav strips, the tab bar, tree
rows and the expand/collapse carets, styled with the system's tokens.
Rules leave this file as components finish porting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generic bs2:Row got the drawer in the shell port, but two more block
templates - the ldh:XHTML content block and the edit-form row - still
applied bs2:Left/bs2:Right in-flow, so their backlinks headings rendered
inside the block body. The drawer markup is extracted into the
ldh:BlockLinksDrawer named template (dh title + close, db holding
bs2:Left/bs2:Right) and all three block shapes call it; bs2:Left/Right
defaults become drawer groups (left-nav/right-nav dgroup) and the
backlinks well becomes a dgroup with its nav-header kept for the CSR
load-on-click handler.

The header actions gain the tb-links trigger for @Rdf:about resources;
CSR handlers in block.xsl open the drawer (display flex) and close it
from its dh button. bs2:Actions loses the Bootstrap button faces:
btn-copy-uri and btn-edit become .tb toolbar icons (content_copy, edit)
with their handler tokens kept.

Verified in the browser: 14 drawers on the dev root document, all
hidden; clicking tb-links opens the block's drawer with hydrated
facet/backlinks content, its close button hides it again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bs2:ActionBar becomes the zoned ldh-actionbar grid (add | breadcrumb+
timestamp | actions), dropping its container/row/span wrappers. The
Create/Add cluster sits in ldh-add-wrap, breadcrumbs take the classic
ldh-bc skin on the same CSR-built ul, and doc-controls becomes the
quiet mono ldh-ab-ts stamp (History link unchanged).

The right-side document actions lose their sprite-logo faces: delete,
save-as, access and edit are .tb icon buttons with Material glyphs and
their identity tokens (btn-delete, btn-save-as, btn-acl btn-access-form,
btn-edit) plus disabled state written explicitly. Export and the layout
mode switcher stay dropdowns but with .tb trigger faces; mode glyphs
come from the new $ldh:mode-icons map shared by the switcher button and
the bs2:ModeListItem entries.

bs2:TypeList becomes the badge strip (ldh-typelist), facet headers move
from nav-header btn to the drawer's dh2 look, and the bridge styles the
read-mode dl/dt/dd as design-system property rows and ul.pager - whose
exact-class markup the CSR paging handlers match on - as prev/next
buttons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Modals: the CSR appends div.modal.fade.in and removes it on close, so
the bridge styles those tokens as the ldh-modal card - centered fixed
panel, radius/shadow, mhead-style header with close, scrolling body,
footer bar shared with .form-actions. No modal.xsl template changes.

Forms: control-group/control-label/controls stay (they are CSR anchors
for constraint violations and constructor rows) and become a two-column
property grid with explicit cell placement - RDF/POST hidden inputs and
<hr> separators are also direct children of .control-group and were
silently taking the first grid cell, pushing every label off its row.
Inputs, textareas and selects get the field skin with the focus ring;
type hints (.help-inline) read quiet and turn danger only inside .error
groups; submit buttons take the accent skin.

Verified in the browser: the document edit modal renders the two-column
statement layout with working focus states and footer actions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
static/css/bootstrap.css and bootstrap-responsive.css are no longer
linked. ldh-bridge.css replaces the primitives LDH still emits - .btn
faces, carets, badges, alerts, progress bars, tables, typeahead menus -
at :where() zero specificity so design-system component classes always
win. The two remaining bootstrap.css links are app-specific rule files
(Web-Client's and LDH's own) that keep the historical name only.

Bootstrap-only vocabulary dies per the census of what the CSR actually
anchors on:

- purged outright (no anchors): span2-span10, offset*, well/well-small,
  sidebar-nav, nav-list, input-*, form-search, input-append, btn-small,
  btn-large, inline, table-striped/bordered, progress-striped,
  pull-* trimmed where flex replaced floats
- renamed on both sides, emission and contains-token anchors together:
  span12 -> row-main, row-fluid -> block-row,
  form-horizontal -> ldh-prop-form, alert alert-error -> ldhc-alert
  va-danger, container-fluid folded into content-body
- kept, still meaningful as CSR anchors: block, main, control-group/
  control-label/controls, progress/bar/active, dropdown/btn-group/open,
  modal family, navbar/navbar-form, breadcrumb, pager/previous/next,
  tab-pane/tab-content, nav-header, search-form/search-query
- btn-danger remove buttons become ldhc-btn in-negative, keeping their
  remove-action/link-remove handler tokens

The footer takes the design system's cols/ftitle shape, and the content
body gets its stacking rhythm and max-width from the bridge now that
Bootstrap's row margins are gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A dt followed by several dds dropped the extra dds into the term column
under grid auto-placement; dt/dd now take explicit columns. .btn-primary
picks up a link-colored text rule somewhere upstream, which vanishes on
the accent fill - one real-specificity color rule pins it to
--fg-on-accent. Content column takes a 1280px measure and the block
cards get their stacking margins back (Bootstrap's row margins are
gone).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, WYMEditor

static/css/bootstrap.css and bootstrap.min.css (the 2.3.2 framework
files) are deleted - nothing links them since the framework drop. The
script chain loses static/js/bootstrap.js and jquery.min.js: dropdowns,
modals and tabs have been IXSL-driven for years, and the only jQuery
left in LDH code was the RDF/POST subject-type/value glue.

js/jquery.js becomes js/functions.js with the IXSL contract intact -
fetchDispatchXML, onSubjectTypeChange, onSubjectValueChange and
ixslTemplateListener keep their names and behaviour (the two subject
handlers rewritten from jQuery to plain DOM). The Bootstrap-layout
pieces of its ready block die with the markup they served (btn-navbar
collapse, $().dropdown fallback); outside-click dropdown close and the
typeahead autocomplete-off live on as vanilla listeners.

The WYMEditor bundle (2.2MB, jQuery-based) goes too - replaced by the
RDFa editor in 5.7.0, nothing references it.

Verified in the browser: no jQuery-undefined errors, Export dropdown
opens via IXSL and closes on outside click, the edit modal renders. The
one remaining console 404 is the dev dataset's own broken test object
(?uri=https://example.org/aaa), not a regression.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
com/atomgraph/linkeddatahub/css/bootstrap.css never was the framework -
it holds LDH's own application rules that accumulated under the
historical name. It becomes ldh.css, minus the ten rules whose selector
tokens no longer occur in any LDH or Web-Client markup (old breadcrumb
logos, action-bar span layout, btn-ban/btn-context, dead sidebar list
skins).

Web-Client's bootstrap.css stays linked: it styles Web-Client-emitted
markup and lives in that repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two things made the page read as a wall of anonymous white cards rather
than the design:

- The SaxonJS bs2:Row wrapper carried the ldh-block card skin AND passed
  it to its next-match inner block, so every CSR-wrapped block rendered
  card-in-card - visible as sliver borders around alerts and empty-
  looking bars. The wrapper keeps only the 'block' anchor token now; the
  inner block is the card.
- ldh:XHTML content blocks are prose, and the design renders prose as
  quiet blocks (no surface, part of the page flow). Their class default
  becomes 'block ldh-block is-quiet', which app.css already styles.

The [Language: en] chip in the reported screenshot does not reproduce in
a fresh Chromium or Firefox - it is the stale-SEF browser cache
(/static/ is served immutable); a hard reload clears it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The legacy .action-bar rule pins it at --action-bar-top with a 51px
fallback - the old Bootstrap navbar height - while the new ldh-header is
56px tall and was not sticky at all, so on scroll the header left the
viewport and content showed through a 51px gap above the stuck action
bar.

The header is now sticky at top 0 and --action-bar-top is defined as the
header height, so the two form one pinned chrome stack; #tab-bar's
legacy 51px offset follows the same variable. The action bar trades the
legacy gray fill for the surface color with a bottom hairline.

Verified scrolled 1200px down: header spans 0-56, action bar starts at
exactly 56, content scrolls beneath both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The breadcrumb items were still li > sprite-classed anchor + divider.
bs2:BreadCrumbListItem now emits the design's crumb anatomy - a.crumb
(is-current on the leaf) holding a Material glyph chosen by document
type (folder for Root/Container, description for Item, link otherwise)
plus the label, with span.sep between crumbs - inside the same ul both
render paths share; the CSR-created ul takes the ldh-breadcrumb classes.
The crumb builds its own <a> with the imports/default.xsl href recipe
rather than overriding the generic xhtml:Anchor mode, which would have
re-defaulted every anchor's href away from ldh:href().

The action-bar port had also silently reversed the document buttons:
the old markup floated each one right, so DOM order delete->edit read
on screen as edit->delete, and the flex container preserved DOM order
instead. The DOM now matches the screen order the floats produced:
edit, access, save-as, delete, then export and the mode switcher.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…alert blocks

The container block's double bottom border had three contributors, found
by DOM forensics rather than guesswork:

- CSR hydration re-renders a row as a full ldh-block card inside the SSR
  wrapper card, stacking two borders 13px apart. One card layer only
  now: a nested .ldh-block flattens (no border/shadow/radius/surface).
- The token rename swept the XSLT but not ldh.css, orphaning its
  row-fluid/span12/form-horizontal/input-append selectors - drag-handle
  hiding, block clipping, drag-over and diff borders, the progress-bar
  sizing that kept the loading skeleton invisible after errors, and the
  search-field flex. All follow the rename (block/block-row/row-main/
  ldh-prop-form); the dead action-bar span rule is deleted.
- A block whose content collapsed to a single error alert kept its card
  chrome, framing the alert with white lips. Such blocks go quiet via
  :has() - the alert brings its own surface - and alerts lose their
  stray margins inside blocks. The m3 skin's resting M3-elevation
  shadow, which reads as a hard line under every card, rests flat per
  the design's card preview (border instead; hover unchanged), and the
  bridge table rule no longer draws a separator under the last row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bar's controls were still Bootstrap skeletons (btn-group +
ul.dropdown-menu) under bridge CSS. They now emit the design system's
own structures, as divs per house style:

- Create: ldh-add-wrap > ldh-btn (add glyph + label + caret) +
  ldh-add-menu with .it entries; bs2:ConstructorListItem renders the
  design item shape (ico/body/lbl, folder for Container, description
  for Item) while keeping the add-constructor/data-for-class/
  data-create-graph contract. Both end-user and admin variants.
- Add: ldh-of-wrap > ghost ldh-btn + ldh-of-menu, generate-containers /
  import-ontology tokens kept.
- Document actions collapse into the design's single Actions menu
  (bolt + label + caret): Edit / Access / Save as, a divider, the three
  RDF export links (target=_blank keeps them outside CSR interception),
  a divider, and a danger-styled Delete - handler tokens and disabled
  states preserved. bs2:MediaTypeList dies with its dead export-rdf
  interception exemption; its links live in the menu now.
- Mode switcher: ldh-mode > label-row trigger (glyph + active-mode label
  + caret) + modes-pop with .mi entries as anchors, mode-class and
  active tokens kept alongside is-active.
- Breadcrumbs switch to the pills variant: bc-pill chips with chevron
  separators in a div container on both render paths.
- The timestamp gains the schedule glyph; the address bar shows the
  document URI on SSR and always mirrors it on navigation (previously
  blanked for local documents - the design's Header shows the URL).

All design menus open through the existing CSR btn-group 'open' toggle;
the bridge maps that state onto ldh-add-menu/ldh-of-menu/modes-pop
visibility and exempts the address field from the form-field skin.

Verified in the browser before the stack was stopped externally: Create
pill, Actions menu with dividers and danger Delete, pill breadcrumb,
labeled Content switcher all render and open; the address-bar change
compiles but its browser check is pending the stack's return.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The XHTML/Object create-block buttons were the last old-school controls:
Bootstrap primary pills with sprite faces. They are ghost ldh-btns now,
with Material glyphs (notes / data_object) and their create-action /
add-constructor / data-for-class contract intact.

The sprite system itself goes: all 73 background-image rules pointing at
the icons/ directory are deleted from ldh.css and the directory itself
is removed - every ported control renders Material Symbols instead. The
ldh:logo templates stay purely as class-token carriers for the CSR
handlers that anchor on them; with the sprite CSS gone they paint
nothing. The one non-CSS consumer, the signup-complete <img>, becomes a
check_circle glyph.

Verified in the browser: the create-block buttons render as quiet design
pills, and the address bar now shows the document URI (the CSR change
from the previous commit, unverifiable then while the stack was down).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bridge's Bootstrap-replacement .caret was a 0x0 border triangle,
which collided with the ported markup's <span class="msi caret">
expand_more</span>: the triangle painted inside the button while the
glyph text overflowed the zero-size box as a stray chevron below it.

.caret is now a Material glyph in both generations - ported spans render
their own expand_more, and legacy empty <span class="caret"/> spans
(login dropdown, sort toggle, the drawer's backlinks heading) get the
same glyph via ::before, with caret-reversed flipping to expand_less.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…les only

Of the empty <span class="caret"/> leftovers, five were static dropdown
carets - they now ship the expand_more glyph directly, like the rest of
the ported markup. The two caret-reversed collapse indicators stay empty
by design: the CSR handlers flip the caret-reversed class on them, and
only ::before content can follow a class change on a ligature-font
glyph. The bridge comment records that distinction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sticky action bar (z-index 999 from the legacy rules) painted over
modal dialogs, hiding their header - the bridge had given modals z-index
110. Modals sit at 1050 now, above the whole chrome stack, with a
non-interactive dim behind them via body:has(); typeahead suggestion
menus move above the modal plane so in-form typeaheads keep working.

Design menus (Create/Actions/modes) also stayed open after picking an
item. The outside-click closer now also closes on menu-item picks,
deferred a tick so it settles after the IXSL btn-group toggle that
handles the same click - otherwise the two could race and re-open the
menu.

Verified in the browser: Create > Container opens the modal above the
chrome with the page dimmed behind it, and the Create menu is closed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The m3 skin paints every .ldh-header descendant with the inverse near-
white text color for the dark bar - correct for the bar itself, but the
light dropdown panels inside it (Applications, Settings, agent) rendered
white-on-white and read as empty. Panel content takes the regular ink
back at higher specificity, section headers keep the hint color, and the
avatar's initials stay dark in its hover/open state where the fill goes
light.

Settings had no pull-right token, so its menu left-aligned and clipped
off the viewport; header menus now hug the right edge regardless.

Menu picks also close these header menus via the same deferred closer as
the action-bar menus.

Verified in the browser: all three menus open readable and right-
aligned - System apps list, Application/Administration/Namespace
ontology, and the agent entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Census: zero CSR anchors on pull-*, and floats are inert inside flex
containers and on absolutely positioned elements - so every pull token
whose parent already became flex chrome (header action strips, block
head actions, alerts, dropdown toggles) or that rode an absolute menu
was doing nothing. Those 14 are gone; the Actions wrapper layout keys on
its position in .actions instead of the token.

What stays, deliberately: pull-right on ul.dropdown-menu as the
alignment signal for the not-yet-ported menus, and the still-active
floats in interior form markup (btn-remove-property/btn-add rows,
typeahead metadata, the hover-overlay edit buttons) - those go when
their components get ported, and the bridge documents exactly that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The legacy modal markup puts button.close FIRST and titles the dialog
with a <legend>; the bridge's flex header laid children out in DOM order
- close at the left, the title squeezed into a wrapping column, the
description eating the rest. The header is a two-column grid now: the
title column (legend/h2/h3 styled as the design's mhead title, any <p>
as a muted subtitle under it) and the close button pinned top-right,
regardless of source order.

Verified on the Request access modal: title left, description under it,
close at the right - matching the design's mhead anatomy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both bs2:Chart forms (the RDF and SPARQL-results variants, which the CSR
re-render also compiles in) swap their fieldset block-row for the design
system's chart-controls three-column grid with .field cells - mono
uppercase overline labels, full-width selects, the series multi-select
as a listbox. Select classes/ids (chart-type / chart-category /
chart-series, name=ou|ol) are untouched, so the onchange handlers and
the RDF/POST encoding are unaffected.

Fieldsets and legends lose their Bootstrap-era box look globally:
borderless sections with overline legends, which also fixes the
old-school group boxes in the access modal. Multi-selects inside the
controls grid keep their natural height.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full-DOM audit (Playwright crawl of every menu, modal, form and mode
against the design-system contracts) surfaced the components that lost
their faces when the sprite CSS died, plus one real regression risk:

- The constructor modal had an empty <legend> - it is titled with the
  class label now (ac:object-label in xsl:try; the bare call throws and
  killed the whole modal render, which the first fix attempt proved the
  hard way. The catch falls back to the URI local name.)
- Remove-statement and remove-resource buttons were empty invisible
  boxes in every editing form: .tb buttons with the close glyph now, in
  all three emitters (constructor.xsl - with the xsl:attribute reordered
  before the glyph span, imports/default.xsl, resource.xsl), handler
  tokens kept
- The search modal's submit button was invisible: search glyph
- Add-statement button swaps the HTML entity cross for the add glyph
- Graph HUD buttons gain fit_screen / fullscreen glyphs per OtherModes
- Dead tokens purged: wymupdate, dl-horizontal, the sprite-era class
  injection on block-title anchors

Verified in the browser: Create > Container opens titled with glyphed
remove buttons (4/4); the per-block edit button opens the inline editing
form with glyphed controls and footer actions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A faceless-button DOM sweep (every button with no text, glyph or image,
across edit mode, the create modal and the inline block edit form)
caught the emitters whose only face was a sprite class injected via the
ldh:logo mode: the subject-edit toggle in form legends (edit glyph), the
add-value button on property controls (add glyph) and the rdf:type
control group's remove button in imports/rdf.xsl, which now matches the
canonical tb btn-remove-property + close-glyph shape. Dead sprite
background-position rules and the border-triangle caret-reversed rule
(the double-arrow bug's last holdout) leave ldh.css. Re-swept after
deploying: no faceless buttons remain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…glish go with it

The facets answer to 'where did the filters go': out of the links
drawer, into a ldh-view-toolbar at the top of every view block, per the
ViewBlocks contract. Left zone: filter_alt lead + one dropdown pill per
facetable property - the pill is the old nav-header (button-shaped, same
hidden subject/predicate/object inputs), so the lazy value-load onclick
and the checkbox filter onchange run unchanged; the hydrated ul.nav now
doubles as the facet-pop popover card. Right zone: the inline-create
button, Total results, the order-by select + direction button, and the
mode dropdown (with a visible active-mode glyph from ldh:mode-icons).
The drawer group the facets vacated hides when empty; parallax stays in
the drawer. Verified in the browser: pill opens, values load with
counts, checking a value filters the results.

The last pull-left/pull-right tokens are purged along the way - header
menus right-align via a header-scoped rule, the form legend is
restructured (label first, subject controls in a right-hugging .actions
cluster, remove button last), remove/add groups trail their form rows
via flex order, the language badge follows its value, and the typeahead
metadata span is .meta. The bridge float shims are deleted.

Branch-introduced English strings move to translations.rdf (add-stmt,
remove-resource, remove-link, get-started, learn-more, footer titles
and links, export format names); the facet lead reuses filter-title.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The three serialization links (RDF/XML, Turtle, JSON-LD) move out of the
Actions menu into a new bs2:ExportList mode that bs2:ActionBarRight
renders after the mode switcher: an icon-only ghost button (download
glyph, titled from the existing export translation entry) opening the
same target=_blank links. Same signed-in guard and HTTP-error
suppression as before, same ldh-of-wrap anatomy, so the open-toggle and
close-on-pick wiring covers it unchanged. The Actions menu keeps
edit / access / save-as / delete, minus the now-stray second divider.

Verified in the browser: the button renders right of Content, its menu
lists the three formats with the correct accept hrefs, and the Actions
menu no longer carries them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The edge-hover flyout stopped appearing: the port stamped the sidebar
with the design's ldh-sidebar class, whose resting state is
translateX(-100%) waiting for an is-open toggle - but the CSR handlers
carry show/hide as inline display (mouse at x=0 sets block, mouseout
sets none). The handler fired, display flipped, and the panel stayed
288px off-screen. The bridge now neutralizes the slide transform so
display alone controls visibility, and starts the panel below the
sticky header instead of the design's full-height top:0.

Verified in the browser: mouse at the left edge slides out the panel
(search, document tree, class pills, other views) at x:0 under the
header; mousing out hides it again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ith it

Every form footer (modal and inline) now follows the design's footer
anatomy: secondaries (Cancel/Reset/Close) are ghost ldh-btns, the
primary (Save/Compare/Allow/Annotate) is a filled ldh-btn with a glyph,
and it sits last so the ghost buttons lead into it. Handler tokens
(btn-save, btn-cancel, btn-reset, btn-close, btn-save-chart, spo-action,
cancel-action) ride along unchanged, as do the two $button-class param
defaults that feed the ldh:logo class injection.

The Bootstrap-era form-actions class itself is gone: the design system
has no such band (its ldh-form-actions-* names mean the per-form actions
dropdown), so the footers take the design's ldh-block-foot token - all
emitters, the one CSR matcher that anchored on it, and the bridge rules
renamed together. modal-footer stays as the sticky-scroll token, its
hardcoded white swapped for the surface token. Also fixed en route: a
stray quote in ldh:Form's save button class, and the access form's
hardcoded Allow label now comes from a new translations entry.

Verified in the browser: the create modal footer renders ghost Reset +
filled glyphed Save on the ldh-block-foot band.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…column

The rdf:RDF form shell gains a show-close-button param (default false, so
the SSR EditMode page keeps its plain footer) emitting a ghost Close
button ahead of Reset; the generic modal.xsl close handler already
matches any button.btn-close inside a modal, so no new wiring. All six
modal flows through the shell opt in: document create, document edit,
add-instance, app settings, and both constraint-violation re-render
paths. The three modal footers that already had a Close (import
ontology, generate containers, request access) join the new convention:
ghost Close and Reset leading into the ldh-btn primary. Also swept up:
form-actions-class param values that still said 'form-actions' (the
class-literal rename missed them), and modal.xsl's button-class defaults
move off btn btn-primary.

Modals now span the content column - width matches .content-body's inner
width (max 1280px minus side padding) instead of the 880px cap.
Verified in the browser: create modal footer reads Close / Reset / Save,
inline form reads Cancel / Reset / Save, and the modal box is
pixel-identical in x and width to the content blocks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
namedgraph and others added 30 commits September 2, 2026 14:19
…t describes.

Two versioning tests failed in CI run 33626306733 while the platform did exactly
what they were asserting: the commits landed in ~1.3s, and 664d147d carried the
agent's WebID as its author. What failed was how the tests read GitHub back.

Each polled the Contents API until the file appeared, then threw that result away
and re-derived it from a fresh read. PUT-creates-commit read commits?path= once,
and got an empty listing: GitHub indexes a path's commit history asynchronously,
so a file that is already readable can still have no history. DELETE-removes-file
re-issued the identical contents request 150ms after it had succeeded and got a
404 back. Both tests finished in under 5s, so neither loop ran to its timeout.

The poll is now the assertion. PUT-creates-commit waits for the author it wants
to compare, GET-timemap - which passed this run, on the same pattern and the same
luck - waits for the listing its TimeMap is built from, and DELETE-removes-file
keeps what its loop found rather than asking again. This is what GET-version and
GET-timegate already do, and why they pass: they spent 10-12s waiting out the
same lag the other three refused to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WK1ssk2A67MYseeH3hWnhf
…s already are.

Web-Client now folds every value of a property into the one cell its header
allots, each value a block of its own inside a container the cell holds. Give
that container the treatment the property list's dd's already have: a divider
between consecutive values, and a cap on the stack.

The cap belongs on the container rather than the cell because a td cannot bound
its own height. Past it the surplus scrolls, so an order with fifty line items
stays whole and reachable instead of setting the row height for the whole table.

The language pill moves behind the ac:lang-tag mode Web-Client applies from
wherever values are laid out. It was written inline in the dd and would have had
to be written again for the cell; now the property list and the table cell read
the same pill from one place, and the mode is what a skin answers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
Versioning has served all three RFC 7089 roles from the document URI since
5.9.0, but the CLI could reach only two of them, and only by concatenating the
query string itself. ?timegate it could not reach at all: negotiation is carried
by an Accept-Datetime request header, and no command could send one. So the
suite that covers the contract asserted two thirds of it through ldh and dropped
to curl for the rest, converting datetimes with a `date -u -j -f ... || date -u
-d ...` pair to paper over BSD and GNU disagreeing about how to parse a flag.

get now takes --version, --timemap and --timegate as an exclusive group, each
still one request, plus a --datetime that reads RFC 1123 or ISO 8601 and writes
the zero-padded RFC 1123 the grammar asks for. The ISO half is what removes the
date(1) dance: a prov:generatedAtTime lifted out of a TimeMap, or a commit
datetime out of the GitHub API, goes straight back in.

--timegate prints the Location as the only line on stdout, which is the same
contract create-item has and what lets the negotiated version pipe into another
get. It is also the one request that needs no --accept, a 302 having no
representation to negotiate, so the option moves from picocli's required to a
check in call(). Through --proxy the printed URI is rewritten back to the
logical origin, as everywhere else the CLI prints one.

The enablers were already in place and unused: Core's get() has taken a header
map since 4.0.14, and the client has never followed redirects, on the grounds
that the scripts called curl without -L. That decision was about matching the
scripts; it is what makes reading Location possible.

Nothing here has run against a live instance. The suite is gated on a GitHub
token and skips without one, so CI is where these paths first execute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
Seventy-seven echoes across eighteen scripts printed DEBUG lines, most of them
on every passing run. They accumulated the way CLAUDE.md prescribes - when a
test fails, add output showing expected against actual, then diagnose - but the
step after that, taking the scaffolding back out once the cause is found, was
never taken. What was left restated in a string what the line under it already
asserts in code, and buried the runs that mattered under the ones that didn't.

Three kinds went beyond the echo itself. A label ending in a colon introduced a
bare dump on the next line, so removing only the label would have left the body
printing. Failure branches carried `gh api ... || true` calls, and one carried a
five-line curl piped into sed, that no assertion reads and no exit code depends
on. PUT-creates-commit had an if whose entire body was diagnostic and which
never exited; the equality test after it already fails on an empty author.

import_triple_count() had no callers left once its two call sites went.

Every assertion stands, and the comments were already saying what the strings
were repeating. What is lost is expected-against-actual at the moment of
failure: a failing run now names the script and not the values. That is the
first thing to add back when one fails, which is where CLAUDE.md starts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
The pill was written for the property list and scoped to it: .ldh-block dl >
div > dd .chip-inline. But ac:lang-tag emits the badge from wherever values are
laid out, and since a table cell started holding every value a property has, it
emits it there too - where no rule matched it and it fell back to bare inline
text glued to the value it annotates, so a Lithuanian label read "Seimaslt".
The graph3d node details had the same gap.

So the pill belongs to .chip-inline itself rather than to a position inside one
container, the way .btn-type already belongs to the type and not to the header
list it first appeared in. A property renders every language it carries,
ordered by the reader's preference, so the tag is what tells the values apart:
it has to survive being laid out somewhere new. Its tag stays on one line -
sr-Latn-RS must not break the way the prose beside it does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
…ent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
These blocks are never stored. ldh:RenderRow queries /ns for the views attached
to a resource's types, fetches each one and appends it to the host wrapper, so
the block is rebuilt on every load. ac:uuid() gave each rebuild a fresh subject,
which left the block's Copy URI value true for exactly as long as the page
stayed open.

What identifies such a block is the resource it hangs off and the view it
renders, and both are already in hand at the insertion point. Deriving the
fragment from that pair reproduces the same URI the next time the block is
derived. Both halves are needed: ldh:RenderRow visits every wrapper, so a
container listing injects the same view under each of its children, and the
view URI alone would hand them all one @about - enough to make element-by-about
ambiguous and to give ldh:ScrollToFragment more than one element to land on.

Combining them means hashing. No digest is reachable from XSLT in either
product, and crypto.subtle.digest returns a promise, so it cannot stand where
ac:uuid() stands. djb2 over the codepoints needs only multiplication and
addition, which XPath 3.1 has, so one definition serves Saxon-HE and Saxon-JS
alike. Random UUIDs stay right wherever a block's subject is written down, so
ldh:CreateBlock and the form flows keep theirs. The line is whether the URI is
stored or re-derived.

A stable fragment is still not one you can follow, because the scroll ran too
early. ldh:RenderTab defers a fragment scroll until the page's block factories
settle, but the view chain fired its per-view promises as detached instructions
and returned, so it settled as the /ns query came back and the countdown
reached zero while the document held none of the injected blocks. The chains
are returned and joined with ixsl:all now, down to the injected block's own
hydration: the block's results are what give it its height, and a scroll that
lands before they arrive lands on a block that then grows out from under it.

The per-view context merge kept the first 'request' it saw, which was the /ns
query rather than the view fetch. Nothing read it while the fetch took the
request directly, but the fan-out reads it from the map, and ldh:retry-request
re-fires whatever it holds when a 429 comes back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
A <pre> in an ldh:XHTML block had no skin left once Bootstrap 2 went, so a
Turtle listing ran on the bare page background. It now takes the recessed
well the inline .ldh-code chip and the error-detail listing already share,
which tints with the theme rather than fixing a white that inverts wrong in
dark mode. Colour and size come from the inline chip instead of the error
listing, whose --fg-muted at --fs-xs demotes text that here is the content;
and a long IRI scrolls the block instead of wrapping, so a listing keeps the
line structure it was written with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
… dls to their own layout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1UnYpbW5aYLj7LbwtyUo1
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
Installing a package is one ldh:import triple in the application settings, and
the CLI's answer to that had been "use ldh patch". But add-ontology-import is
the same shape -- one owl:imports triple, PATCHed -- and it has a command,
because sparing the caller the RDF is what the CLI is for. Here the caller was
being asked for rather more: the /settings path, the unabbreviated ldh:import
IRI, SPARQL update syntax, and the subject to hang the triple off. That last one
is not guessable at all. The application is identified by an opaque
urn:linkeddatahub:apps/end-user, not by its origin, so the updates match it by
type instead of naming it and the caller never has to learn the URN exists.

list-packages is the affordance ldh patch cannot substitute for. The registry is
not the application's own URI and is not live yet, resolving from a bundled copy
through the Linked Data proxy, so the listing is read the same way the settings
modal reads it -- through ?uri= -- and cross-referenced against the ldh:import
triples already in the settings. One tab-separated line per package keeps it
greppable.

The names stay on the declaration rather than reviving install/uninstall, which
were deleted along with their endpoints when packages became declarative: those
verbs mutated webapp files, and nothing here does. Removing an import that is
not there succeeds and changes nothing, as a DELETE should.

StubServer grew per-path responses because list-packages is the first command to
read two documents with different media types in one run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
… landed.

Bootstrap 2 is gone, and an override emitting its class vocabulary still
compiles and still runs -- it just renders unstyled, with no diagnostic. A
failure that gives no signal of its own is what the major version is for, so
5.11.0 becomes 6.0.0 and the section leads with a migration list naming each
replacement.

The Fixed entries are for work already committed but never written down, and the
Web-Client 5.0.4 bump landed with the Content-Language change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
… key for.

SaxonJS backs distinct-values() and xsl:for-each-group with a hash trie whose
insert recurses once per character of the key. A key longer than the JS stack
allows -- around 6-7K frames -- takes the whole transform down with "too much
recursion", and the keys that get there are real content: an XHTML content
block's serialized literal is the object of a triple, so it lands in
merge-rdfxml's group-by and in ldh:triple-key verbatim.

ldh:bounded-key() passes anything under 1000 characters through unchanged and
folds the rest into a prefix, a length and a rolling hash. Distinctness is what
these keys are for, and prefix plus length plus djb2-style hash keeps it for
content that differs anywhere -- while the bounded result stays inside the
recursion the trie can afford.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
The canonicalization treated class, id, aria-*, data-*, role and hidden as
editor bookkeeping and dropped them all, and stripped button outright as an
active element. That is right for what the editor injects and wrong for what an
author wrote: a tab strip is buttons carrying role, aria-selected and
aria-controls over panels carrying id and hidden, and none of it came back from
storage.

C2 now strips only what the editor itself mints -- contenteditable, draggable,
tabindex="-1" -- plus @Style, which no editing gesture can author but browsers
mint freely during editing and paste. @Class is subtracted token by token, the
editor's own rdfa-editor-* and transient gesture markers going and authored
tokens staying, and an attribute left empty drops. button is exempt from S1
because S2 has already stripped its handlers, leaving inert markup. A host that
wants stricter storage layers its own stripping at higher import precedence.

C6 and C7 asked whether an element carried RDFa before unwrapping it as browser
junk; they now ask whether it carries any attribute that survives
canonicalization, so an authored class or id is meaning too. An empty a[@id] is
an anchor target rather than the empty inline C8 discards.

The tab strip then needs to work in both directions. In read mode the click
handler moves is-on and aria-selected to the clicked tab and unhides the panel
it aria-controls, guarded on panels existing so the query block's paneless mode
tabs keep their own handlers. While a region is edited every panel is shown
stacked -- an editor that hides content cannot edit it -- and hidden reasserts
itself on restore.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
…ing.

The XMLLiteral autosave fired on every focusout that left the region, so opening
a block and clicking away rewrote the stored literal with a value identical to
the one already there -- and minted a version recording it. A no-op save is
still a save.

The region now stashes its canonical form when it is activated, and the
focusout compares against that baseline. Unchanged content takes the path Cancel
takes: the pre-editing snapshot is restored and editing ends, so leaving the
editor always leaves it. Changed content saves and moves the baseline forward,
so continued editing only ever saves what came after; a rejected save leaves the
form re-rendered with its violations, which re-dirties as soon as the author
fixes them.

Comparing needs one canonical form, not two implementations of it, so the
serialization the ol input already did becomes ldh:canonical-content() and the
baseline and the dirty check call it. Restoring needs one implementation too:
Cancel's body becomes ldh:CancelEditing on the block. It also had to start
replacing the snapshot element rather than its children -- the edit-mode render
replaces the block wrapper itself, and an XHTML block is quiet prose in read
mode but carded while editing, so restoring only the children left the card
around them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
… DOM workarounds they emulate.

ixsl:replace-element replaces the attribute-copy emulations in ldh:replace-block-element and the inline row-form success handler (the follow-up listener re-inits now re-resolve the inserted node by the rendered element's id, since the replacement detaches the host), and the add-typeahead button swaps itself for the lookup markup instead of rebuilding all of its parent's children. ixsl:insert-before retires the remove/re-append dances: the constructor fieldset lands above the still-live Add constructor button, and new property control groups land above the property picker, which also deletes the positional [last()] / control-group-count re-lookups. In the rdfa-editor, the rendering island and the invalid-region fix write nodes directly instead of a serialize + innerHTML round-trip (closing the self-closing-tag fragment-parser hazard), and the block-paste path threads pasted blocks in with a single ixsl:insert-after instead of a staging div and an xsl:iterate. Live-node moves (drag-drop, toolbar adoption, caret machinery) stay imperative - result-document copies content, it cannot relocate DOM nodes. The unused ldh:replace-content helper is removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1UnYpbW5aYLj7LbwtyUo1
…s one shared pipeline.

signup.xsl moves from client/admin/ down to admin/ and is included by layout.xsl alongside client.xsl: since the SSR shell became the default for local pages, its client-only bs2:ContentBody/bs2:Row overrides never fired on direct loads and the form only appeared after in-app navigation. The rendering overrides are product-neutral; the browser-only submit handling is use-when-guarded to SaxonJS, restoring the guard idiom the file had before it went client-side in #294. The placeholder div, the constructed-doc promise chain and ldh:render-signup-form are deleted - one synchronous bs2:Row serves both the server render and client-side re-renders.

That works because ldh:construct-instance, ldh:instance-term, ldh:marker-types and ldh:triples-to-descriptions move to the shared imports/default.xsl, with parsing behind the new dual ldh:parse-query: the browser keeps the SPARQL.js 2.x Parser, the server gets the new ParseQuery extension function (Jena QueryFactory) which emits the same parse-tree JSON subset in the same term serialization - the lockstep contract between the two parsers and the consuming stylesheets is documented on all three sides. The ldh:construct-forClass wrapper is deleted; its two call sites (signup and the $constructor tunnel default in resource.xsl, which now also instantiates constructors server-side instead of hitting the removed SSR stub) inline the constructor-query/fetch/construct-instance primitives. The unused ldh:parse-html and the commented-out ldh:new are removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1UnYpbW5aYLj7LbwtyUo1
… the editor.

Only leaf text hosts are contenteditable, so the surface between them - sibling
margins, the drag-handle gutter, a structural container's own box, chrome on a
structural block - had no focusable ancestor. A press there dropped focus off the
canvas, and the autosave's focusout guard reads a null relatedTarget as leaving
the editor, so an incidental click saved and exited.

The region now carries tabindex, absorbing that focus, and a press outside every
editable host places the caret at the point itself: the nearest host takes focus
and the caret lands where the pointer is. Its sweep anchor is host-less, since the
preventDefault leaves no native drag session for the mousemove takeover to ride.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016YxhcqMPK1kZR1pxxJqoge
…g generic alerts, with the type set read from the block's @typeof.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WK1ssk2A67MYseeH3hWnhf
…tton mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1UnYpbW5aYLj7LbwtyUo1
…lls with required markers and description toggletips, capped controls with inline annotations in read-height rows, compact textarea defaults, and the vendored controls/overlays stylesheets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WK1ssk2A67MYseeH3hWnhf
…icky bars drop below the overlay scale so dialog heads no longer hide under the navbar.

The RDFa annotation overlay sheds the bare modal token (Esc and ldh:CloseModal no longer destroy it) and carries its own card chrome; .modal-header/.modal-body survive only rescoped to it and the map info window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfCWF1dExp6zyLrdfrAsZ4
…description is an association list, so the grid projection rides the elements specified for it, with the RDFa attributes on the dd rows and the dl's UA margin bridged until the design system carries the reset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WK1ssk2A67MYseeH3hWnhf
…gle request they name, import carries the composite workflows, and the packages family and the admin scope replace the bin/ directory mirror.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1UnYpbW5aYLj7LbwtyUo1
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