From 3a7eca9862a3e4e789417e0fb0d651bf9927bf16 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Tue, 8 Sep 2026 18:51:37 -0700 Subject: [PATCH 1/3] fix(pages): render GitHub repository sources as one URL - keep repository-backed payload pages free of a duplicate Working File row\n- derive one safe blob/HEAD browse link from GitHub floating locators\n- preserve escaped non-link fallback for unsafe repository URLs\n- document the distinction between floating working source and exact state --- documentation/notes/wu.resource-pages.md | 2 + src/runtime/weave/pages.ts | 53 ++++++++++++++++++++++++ src/runtime/weave/pages_test.ts | 2 +- tests/integration/weave_test.ts | 2 +- 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/documentation/notes/wu.resource-pages.md b/documentation/notes/wu.resource-pages.md index 8be95107..e1eb9f03 100644 --- a/documentation/notes/wu.resource-pages.md +++ b/documentation/notes/wu.resource-pages.md @@ -34,6 +34,8 @@ A ResourcePage is assembled from three layers: Runtime code owns graph discovery and source resolution. Templates and stylesheets should arrange already-resolved document and panel data; they should not read RDF graphs, local files, remote URLs, mesh inventories, or config sources themselves. +Repository-backed payload pages show Repository Source instead of a duplicate Working File row. For a safe GitHub floating repository locator, Weave renders the repository URL and repository-relative path as one `blob/HEAD/` browse link. `HEAD` reflects the floating working source; exact release identity remains on the relevant HistoricalState. + ## Built-In Panels The default Semantic Site presentation currently supports these generated panels: diff --git a/src/runtime/weave/pages.ts b/src/runtime/weave/pages.ts index df5da2c4..94999ff2 100644 --- a/src/runtime/weave/pages.ts +++ b/src/runtime/weave/pages.ts @@ -1237,6 +1237,21 @@ function toRenderMetadataRow( if (row.kind === "repositorySource") { const repositoryUrl = row.repositorySource.repositoryUrl; const repositoryPathFromRoot = row.repositorySource.repositoryPathFromRoot; + const repositoryFileUrl = toGitHubRepositoryFileUrl( + repositoryUrl, + repositoryPathFromRoot, + ); + if (repositoryFileUrl) { + return { + label: row.label, + value: repositoryFileUrl, + html: `${ + escapeHtml(repositoryFileUrl) + }`, + }; + } const repositoryUrlHtml = isSafeHttpUrl(repositoryUrl) ? ` + !segment || segment === "." || segment === ".." || segment.includes("\\") + ) + ) { + return undefined; + } + const encodedPath = fileSegments.map(encodeURIComponent).join("/"); + return `https://github.com/${encodeURIComponent(owner)}/${ + encodeURIComponent(repository) + }/blob/HEAD/${encodedPath}`; +} + function toExtractionSourceMetadataRows( meshRootHref: string, meshLabel: string, diff --git a/src/runtime/weave/pages_test.ts b/src/runtime/weave/pages_test.ts index 72edaaeb..83183eec 100644 --- a/src/runtime/weave/pages_test.ts +++ b/src/runtime/weave/pages_test.ts @@ -327,7 +327,7 @@ Deno.test("renderResourcePage renders URL and floating repository working locato ); assertStringIncludes( html, - 'Repository Sourcehttps://github.com/semantic-flow/sflo.gitsemantic-flow-core-ontology.ttl', + 'Repository Sourcehttps://github.com/semantic-flow/sflo/blob/HEAD/semantic-flow-core-ontology.ttl', ); assertFalse( html.includes( diff --git a/tests/integration/weave_test.ts b/tests/integration/weave_test.ts index 1ca0f83f..67c618b1 100644 --- a/tests/integration/weave_test.ts +++ b/tests/integration/weave_test.ts @@ -3107,7 +3107,7 @@ Deno.test("executeGenerate renders working URL and floating repository source lo ); assertStringIncludes( page, - 'Repository Sourcehttps://github.com/semantic-flow/mesh-sidecar-fantasy-rules.gitontology/fantasy-rules-ontology.ttl', + 'Repository Sourcehttps://github.com/semantic-flow/mesh-sidecar-fantasy-rules/blob/HEAD/ontology/fantasy-rules-ontology.ttl', ); assertFalse( page.includes( From c1265f6bf5c8a029f6a2761a007a9f33a28e9438 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Tue, 8 Sep 2026 18:57:23 -0700 Subject: [PATCH 2/3] fix(pages): punctuate named history descriptions - render possessive apostrophes for named ArtifactHistory labels\n- cover the releases history summary in integration output --- src/runtime/weave/page_model_assembly.ts | 8 +++++--- tests/integration/weave_test.ts | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/runtime/weave/page_model_assembly.ts b/src/runtime/weave/page_model_assembly.ts index 16e226ac..0ac250d7 100644 --- a/src/runtime/weave/page_model_assembly.ts +++ b/src/runtime/weave/page_model_assembly.ts @@ -456,9 +456,11 @@ function describeSemanticFlowResource( } const stateHistory = findHistoryForState(resourcePath, historyGroups); if (stateHistory) { - return `Historical state for the ${ - toLastPathSegment(stateHistory.path) - } artifact history`; + const historyLabel = toLastPathSegment(stateHistory.path); + const possessiveHistoryLabel = historyLabel.endsWith("s") + ? `${historyLabel}'` + : `${historyLabel}'s`; + return `Historical state for the ${possessiveHistoryLabel} artifact history`; } if (historyGroups.some((group) => group.path === resourcePath)) { const ownerResourcePath = dirname(resourcePath); diff --git a/tests/integration/weave_test.ts b/tests/integration/weave_test.ts index 67c618b1..9ec232eb 100644 --- a/tests/integration/weave_test.ts +++ b/tests/integration/weave_test.ts @@ -2880,6 +2880,10 @@ Deno.test("executeGenerate lists every sidecar payload history with working hist releaseStatePage, '

a sflo:HistoricalState

', ); + assertStringIncludes( + releaseStatePage, + '

Historical state for the releases' artifact history

', + ); assertStringIncludes( releaseStatePage, "Manifestations", From a6b1fec55239b70071b9424e2b3a20e0aa7e7269 Mon Sep 17 00:00:00 2001 From: Dave Richardson Date: Wed, 9 Sep 2026 10:10:42 -0700 Subject: [PATCH 3/3] fix(pages): link historical states to their history Render the named ArtifactHistory label in generated HistoricalState summaries as a safe internal link while keeping all surrounding summary text escaped. Use the history name attributively without a possessive apostrophe and cover the release-history output in integration tests. --- src/core/weave/resource_page_models.ts | 6 ++++ src/runtime/weave/page_model_assembly.ts | 5 +--- src/runtime/weave/pages.ts | 38 +++++++++++++++++++++++- tests/integration/weave_test.ts | 2 +- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/core/weave/resource_page_models.ts b/src/core/weave/resource_page_models.ts index 369baddb..0958608b 100644 --- a/src/core/weave/resource_page_models.ts +++ b/src/core/weave/resource_page_models.ts @@ -144,12 +144,18 @@ export interface ResourcePageDocumentModel { stylesheetHrefs?: readonly string[]; title: string; summary?: string; + summaryLink?: ResourcePageSummaryLinkModel; rdfClasses: readonly ResourcePageRdfClassModel[]; breadcrumbs: readonly ResourcePageBreadcrumbModel[]; metadata: readonly ResourcePageMetadataModel[]; panels: readonly ResourcePagePanelModel[]; } +export interface ResourcePageSummaryLinkModel { + label: string; + href: string; +} + export interface ResourcePageBreadcrumbModel { label: string; href?: string; diff --git a/src/runtime/weave/page_model_assembly.ts b/src/runtime/weave/page_model_assembly.ts index 0ac250d7..98e25d6d 100644 --- a/src/runtime/weave/page_model_assembly.ts +++ b/src/runtime/weave/page_model_assembly.ts @@ -457,10 +457,7 @@ function describeSemanticFlowResource( const stateHistory = findHistoryForState(resourcePath, historyGroups); if (stateHistory) { const historyLabel = toLastPathSegment(stateHistory.path); - const possessiveHistoryLabel = historyLabel.endsWith("s") - ? `${historyLabel}'` - : `${historyLabel}'s`; - return `Historical state for the ${possessiveHistoryLabel} artifact history`; + return `Historical state for the ${historyLabel} artifact history`; } if (historyGroups.some((group) => group.path === resourcePath)) { const ownerResourcePath = dirname(resourcePath); diff --git a/src/runtime/weave/pages.ts b/src/runtime/weave/pages.ts index 94999ff2..90c0f655 100644 --- a/src/runtime/weave/pages.ts +++ b/src/runtime/weave/pages.ts @@ -18,6 +18,7 @@ import type { ResourcePageReferenceLinkModel, ResourcePageReferenceTargetLinkModel, ResourcePageSectionModel, + ResourcePageSummaryLinkModel, } from "../../core/weave/resource_page_models.ts"; import { Parser, type Quad, type Term } from "n3"; import { codeToHtml } from "shiki"; @@ -46,6 +47,7 @@ import { SFLO_NAMESPACE, SFLO_PREFIX, } from "../../core/rdf/namespaces.ts"; +import { findHistoryForState } from "../../core/weave/resource_page_history_groups.ts"; interface ResourcePageRenderInput { meshLabel: string; @@ -62,6 +64,7 @@ interface ResourcePageRenderInput { title: string; breadcrumbs: readonly ResourcePageBreadcrumb[]; summary?: string; + summaryLink?: ResourcePageSummaryLinkModel; rdfClasses: readonly ResourcePageRdfClass[]; metadataRows: readonly ResourcePageMetadataRow[]; childrenRows: readonly ResourcePageMetadataRow[]; @@ -832,6 +835,10 @@ function toDefaultResourcePageDocumentModel( ? rdfFacts.classes : [classifyResourcePage(resourcePath, page.historyGroups ?? [])]; const resourcePathArtifactRole = artifactRoleForResourcePath(resourcePath); + const stateHistory = findHistoryForState( + resourcePath, + page.historyGroups ?? [], + ); return { kind: "simple", @@ -853,6 +860,14 @@ function toDefaultResourcePageDocumentModel( resourcePath, ), summary: page.description, + ...(stateHistory + ? { + summaryLink: { + label: toLastPathSegment(stateHistory.path), + href: toMeshResourceHref(meshRootHref, stateHistory.path), + }, + } + : {}), rdfClasses, metadata: [ { label: "Canonical IRI", value: canonical }, @@ -1131,6 +1146,7 @@ function toResourcePageRenderInput( title: document.title, breadcrumbs: document.breadcrumbs, summary: document.summary, + summaryLink: document.summaryLink, rdfClasses: document.rdfClasses, metadataRows: toRenderMetadataRows( document.meshRootHref, @@ -1392,6 +1408,24 @@ ${ `; } +function renderSummary( + summary: string, + link?: ResourcePageSummaryLinkModel, +): string { + if (!link) { + return escapeHtml(summary); + } + const labelIndex = summary.indexOf(link.label); + if (labelIndex < 0) { + return escapeHtml(summary); + } + const before = summary.slice(0, labelIndex); + const after = summary.slice(labelIndex + link.label.length); + return `${escapeHtml(before)}${ + escapeHtml(link.label) + }${escapeHtml(after)}`; +} + async function renderDefaultResourcePage( input: ResourcePageRenderInput, ): Promise { @@ -1402,7 +1436,9 @@ async function renderDefaultResourcePage( ? ` \n` : ""; const summary = input.summary - ? `

${escapeHtml(input.summary)}

\n` + ? `

${ + renderSummary(input.summary, input.summaryLink) + }

\n` : ""; const classes = input.rdfClasses.length > 0 ? `

a ${ diff --git a/tests/integration/weave_test.ts b/tests/integration/weave_test.ts index 9ec232eb..a5cd61ab 100644 --- a/tests/integration/weave_test.ts +++ b/tests/integration/weave_test.ts @@ -2882,7 +2882,7 @@ Deno.test("executeGenerate lists every sidecar payload history with working hist ); assertStringIncludes( releaseStatePage, - '

Historical state for the releases' artifact history

', + '

Historical state for the releases artifact history

', ); assertStringIncludes( releaseStatePage,