Add schema.org JSON-LD and per-sample descriptions#17
Conversation
Make sample pages legible to AI agents and screen readers without executing JavaScript. Infrastructure: - MainLayout emits a schema.org JSON-LD @graph on every page with WebSite, Organization, SoftwareApplication, WebPage, and (on sample pages) SoftwareSourceCode entities. Site-level entities mirror the GeoBlazor.com marketing site so crawlers can resolve cross-site identity. - HeadContent emits per-page meta description plus Open Graph and Twitter Card tags so sample URLs render rich previews when shared. - SamplePage gains a virtual Description property. Each sample overrides it with a detailed description: what the sample shows, what's on the screen, what the user can do, and which GeoBlazor Razor component it demonstrates. - Description is rendered visibly as a collapsible "About this sample" details element below the article and feeds the JSON-LD description on WebPage and SoftwareSourceCode. Content: - 84 Description overrides — every Core sample (54), every Pro sample (27), plus three pages migrated to SamplePage in this change (GeometryMethods, LocationMethods, GraphicsLegends). Layout: - Article padding-bottom scaled by viewport so the description clears the RenderModeSelector bar at all widths (4rem ≥1400px, 14rem 1075-1399px, 16rem <1075px). - Sidebar adds 3.5rem bottom padding so its scroll content stays above the RenderModeSelector bar. - RenderModeSelector spans full viewport width at all sizes (was offset by sidebar at wide). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds structured, per-page metadata and human-readable descriptions to GeoBlazor sample pages so crawlers/screen readers/AI agents can understand each sample without relying on JavaScript execution, along with a few layout tweaks to accommodate the new “About this sample” section and the render-mode selector bar.
Changes:
- Add per-page
<HeadContent>meta description + Open Graph/Twitter Card tags and emit a schema.org JSON-LD@graph(includingSoftwareSourceCodeon sample pages). - Introduce
SamplePage.Description(virtual) and override it across Core/Pro samples; render it visibly in MainLayout under a collapsible<details>block. - Adjust layout CSS (article padding/scrolling, sidebar padding, render-mode selector positioning).
Reviewed changes
Copilot reviewed 89 out of 89 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/MainLayout.razor | Adds Head meta tags, JSON-LD emission, and renders “About this sample” from CurrentPage.Description. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/MainLayout.razor.css | Updates responsive padding/scroll behavior and styles the <details> description block. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.css | Adds bottom padding so sidebar content clears the render-mode selector bar. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/RenderModeSelector.razor.css | Removes wide-layout left offset so selector spans full viewport width. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/SamplePage.cs | Introduces Description virtual property (default empty) for per-sample text/metadata. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/WebStyleSymbols.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/WFSUtils.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/UpdateFeatureAttributes.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/TimeSlider.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/Swipe.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/StyledGeoJSONLayers.razor.cs | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/SpatialRelationships.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/SketchQuery.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/SearchCustomSource.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/RoutesAndDirections.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/ProWidgets.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/ProBookmarks.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/PrintWidgets.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/PopupEdit.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/OGCFeatureLayers.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/LengthAndArea.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/ImageryGroupBlend.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/HighlightFeaturesByGeometry.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/GroupLayers.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/GraphicsLegends.razor | Migrates to SamplePage, adds PageLinks + Description. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/EditFeatureData.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/DemographicData.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/CustomPopupContents.razor.cs | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/ClusteringPopups.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/Clustering.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/ClusterPieCharts.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/Binning.razor | Adds Description override for the sample. |
| samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Pages/ApplyEdits.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Widgets.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/WebScenes.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/WebMaps.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/WMTSLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/WMSLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/WFSLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/WCSLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/VectorLayer.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/UniqueValueRenderers.razor.cs | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/SqlQuery.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/SqlFilterQuery.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ServiceAreas.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ServerSideQueries.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/SearchMultipleSources.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Scene.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ScaleBarWidgetPage.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ReverseGeolocator.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ReactiveUtils.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/QueryTopFeatures.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/QueryRelatedFeatures.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ProjectionTool.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Popups.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/PopupActions.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/PlaceSelector.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/OpenStreetMapsLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Navigation.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/MeasurementWidgets.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/MarkerRotation.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/MapImageLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ManyGraphics.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/LocationMethods.razor | Migrates to SamplePage, adds PageLinks + Description. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/LocateWidgetPage.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Legends.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/LayerLists.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Labels.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/KMLLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ImageryTileLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ImageryLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/HitTests.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/GraphicTracking.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/GeometryMethods.razor | Migrates to SamplePage, adds PageLinks + Description. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/GeoRSSLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/GeoJSONLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/FeatureLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Events.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Drawing.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/DisplayProjection.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/CompassWidgetPage.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/ClickToAdd.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/CalculateGeometries.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/CSVLayers.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Bookmarks.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/BasemapsLayerLists.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Basemaps.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/BasemapToggleWidgetPage.razor | Adds Description override for the sample. |
| samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/BasemapProjections.razor | Adds Description override for the sample. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| padding-bottom: 16rem; | ||
| height: calc(100vh - 10rem); | ||
| overflow-y: auto; | ||
| } |
There was a problem hiding this comment.
On narrow layouts (<1075px), article.content is given a fixed viewport-based height and overflow-y: auto. Since main also scrolls and contains .main-links above the article, this tends to create nested scrolling/double scrollbars. Consider making only one element scroll on small screens (e.g., remove the article’s fixed height/overflow in the base styles or adjust heights to account for main-links).
…onld # Conflicts: # samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.css
- Canonicalize page URLs used in @id, url, isPartOf, and og:url by stripping query string and fragment from NavigationManager.Uri. Without this, hitting /compass-widget?foo=bar produced an invalid schema.org @id of "/compass-widget?foo=bar#webpage" (two # in one URL) and unstable canonical URLs across query-string variations. - Clear LayoutService.CurrentPage on every navigation by subscribing to NavigationManager.LocationChanged in MainLayout. SamplePage routes re-set CurrentPage in OnInitialized; non-SamplePage routes (Home, NotFound) now leave it null so the layout no longer leaks the previous sample's action buttons, About-this-sample block, meta description, or JSON-LD onto pages that aren't sample pages. - Fix grammar in WFSUtils description: "a label OGC WFS endpoint" to "a labeled OGC WFS endpoint". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 90 out of 90 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private string MetaTitle | ||
| { | ||
| get | ||
| { | ||
| string slug = CurrentSlug; | ||
| return string.IsNullOrEmpty(slug) | ||
| ? "GeoBlazor Samples" | ||
| : $"{SlugToTitle(slug)} — GeoBlazor Sample"; | ||
| } |
There was a problem hiding this comment.
MetaTitle always formats non-empty routes as "… — GeoBlazor Sample" and SlugToTitle only splits on -. This produces incorrect titles for non-sample pages (e.g., /not-found) and broken titles for multi-segment routes like /source-code/{PageUrl} (will include a / in the title). Consider basing the "Sample" suffix on LayoutService.CurrentPage != null and updating the slug-to-title logic to handle / segments (e.g., take the last path segment and replace - with spaces).
| public void Dispose() | ||
| { | ||
| HttpClient.Dispose(); | ||
| LayoutService.OnPageChanged -= StateHasChanged; | ||
| NavigationManager.LocationChanged -= OnLocationChanged; |
There was a problem hiding this comment.
Dispose() calls HttpClient.Dispose(), but this HttpClient is injected from DI (likely scoped/singleton-managed). Disposing it in the layout can break subsequent HTTP calls elsewhere with ObjectDisposedException. Remove the explicit dispose here; let the DI container manage the lifetime (or use IHttpClientFactory/a dedicated disposable client if you truly need per-component ownership).
| .sample-description summary { | ||
| cursor: pointer; | ||
| font-weight: 500; | ||
| color: var(--text-emphasis); | ||
| user-select: none; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 0.6rem; | ||
| list-style: none; | ||
| padding-left: 0.25rem; | ||
| } | ||
|
|
||
| .sample-description summary::-webkit-details-marker { | ||
| display: none; | ||
| } |
There was a problem hiding this comment.
The default disclosure marker is only removed for WebKit (summary::-webkit-details-marker). In Firefox/other browsers the marker will still render, resulting in a duplicated indicator alongside the custom summary::before arrow. Consider also clearing summary::marker (and/or list-style) for cross-browser consistency.
- MetaTitle only adds the "— GeoBlazor Sample" suffix when on an
actual sample page (LayoutService.CurrentPage is not null).
Routes like /not-found previously advertised themselves as
"Not Found — GeoBlazor Sample".
- SlugToTitle takes the last path segment before kebab-splitting,
so multi-segment routes like /source-code/{slug} produce a clean
leaf-only title instead of leaking a / into the rendered title.
- Remove HttpClient.Dispose() from MainLayout.Dispose. Disposing
the DI-injected HttpClient broke any later component using the
same instance with ObjectDisposedException; DI manages lifetime.
- Clear the disclosure marker via summary::marker in addition to
the WebKit pseudo. Without this, Firefox showed both the native
marker and the custom CSS triangle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TimPurdum
left a comment
There was a problem hiding this comment.
Testing issues:
- "About this page" never shows on first navigation from the navbar, only after a page refresh
- The Schema.org LD-JSON is too static for the site, each page should have somewhat different content, espectially the description and url.
The "About this sample" details block, per-page meta tags, and JSON-LD SoftwareSourceCode entry were missing on the first navigation from the navbar - they only appeared after a page refresh. The cause was a render-order race in MainLayout: the LocationChanged handler cleared LayoutService.CurrentPage, but on first navigation the layout could re-render with the new URL before the new SamplePage's OnInitialized had a chance to re-register itself. Replace the clear-on-navigation approach with a URI key on LayoutService - SamplePage records the URI it set CurrentPage for, and MainLayout's new EffectiveCurrentPage getter only treats CurrentPage as live when that URI matches NavigationManager.Uri. Stale references after navigating to a non-SamplePage route are silently ignored with no timing dependency. While there, enrich the per-page JSON-LD with keywords and mainEntity/mainEntityOfPage cross-references so each sample page emits visibly distinct schema.org content, and fix the unresolved double scrollbar on narrow screens by moving article.content's fixed height and overflow-y into the >=1075px media query (where <main> is set to overflow:hidden).
ProWidgets.razor redeclared [Inject] NavigationManager, hiding the one I added to SamplePage in the previous commit and failing the build. Drop the redundant declaration; the base class injection is inherited. While there, remove the description field from the SoftwareSourceCode JSON-LD entity. It duplicated the WebPage description verbatim on every sample page. SoftwareSourceCode now relies on mainEntityOfPage to cross-link to the WebPage where the canonical description lives.
Make sample pages legible to AI agents and screen readers without executing JavaScript.
Infrastructure:
Content:
Layout: