From 095ee9c77ac40f18848d9f01a16ceb746c6199ca Mon Sep 17 00:00:00 2001 From: Volker Christian Date: Thu, 15 Oct 2026 04:29:16 +0200 Subject: [PATCH 1/2] Unify banner navigation across repository, organization and account pages --- CONTRIBUTING.md | 4 +- README.md | 10 ++-- brand/navigation/octicons/NOTICE.md | 2 +- brand/navigation/octicons/person.svg | 1 + docs/media/control-banner.svg | 17 ++++++ docs/media/nav/account-dark.svg | 22 ++++++++ docs/media/nav/account-icon-dark.svg | 22 ++++++++ docs/media/nav/account-icon-light.svg | 22 ++++++++ docs/media/nav/account-light.svg | 22 ++++++++ docs/media/nav/banner-workflows-compact.svg | 18 +++++- docs/media/nav/banner-workflows.svg | 18 +++++- docs/media/nav/organization-dark.svg | 2 +- docs/media/nav/organization-light.svg | 2 +- docs/media/nav/repository-dark.svg | 2 +- docs/media/nav/repository-light.svg | 2 +- docs/workflow-graph.md | 6 +- navigation/README.md | 10 ++-- profile/README.md | 39 +++++++------ profile/github-metadata.json | 11 ++-- profile/media/nav/account-dark.svg | 22 ++++++++ profile/media/nav/account-icon-dark.svg | 22 ++++++++ profile/media/nav/account-icon-light.svg | 22 ++++++++ profile/media/nav/account-light.svg | 22 ++++++++ profile/media/nav/organization-dark.svg | 22 ++++++++ profile/media/nav/organization-icon-dark.svg | 22 ++++++++ profile/media/nav/organization-icon-light.svg | 22 ++++++++ profile/media/nav/organization-light.svg | 22 ++++++++ profile/media/nav/repository-dark.svg | 22 ++++++++ profile/media/nav/repository-icon-dark.svg | 22 ++++++++ profile/media/nav/repository-icon-light.svg | 22 ++++++++ profile/media/nav/repository-light.svg | 22 ++++++++ profile/template.md | 27 +++++---- tests/test_navigation.py | 42 +++++++++++--- tests/test_sync.py | 6 +- tools/snodec_sync/__main__.py | 4 +- tools/snodec_sync/navigation.py | 56 +++++++++---------- 36 files changed, 536 insertions(+), 95 deletions(-) create mode 100644 brand/navigation/octicons/person.svg create mode 100644 docs/media/control-banner.svg create mode 100644 docs/media/nav/account-dark.svg create mode 100644 docs/media/nav/account-icon-dark.svg create mode 100644 docs/media/nav/account-icon-light.svg create mode 100644 docs/media/nav/account-light.svg create mode 100644 profile/media/nav/account-dark.svg create mode 100644 profile/media/nav/account-icon-dark.svg create mode 100644 profile/media/nav/account-icon-light.svg create mode 100644 profile/media/nav/account-light.svg create mode 100644 profile/media/nav/organization-dark.svg create mode 100644 profile/media/nav/organization-icon-dark.svg create mode 100644 profile/media/nav/organization-icon-light.svg create mode 100644 profile/media/nav/organization-light.svg create mode 100644 profile/media/nav/repository-dark.svg create mode 100644 profile/media/nav/repository-icon-dark.svg create mode 100644 profile/media/nav/repository-icon-light.svg create mode 100644 profile/media/nav/repository-light.svg diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07d0b0a..63e8619 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to SNode.C projects -Choose the owning repository from the [organization profile](https://github.com/SNodeC). Use that project's **Contribute** menu for its issue tracker, build instructions and engineering guidelines. Report the revision, platform, steps to reproduce and expected/actual behavior; remove credentials, private configuration and private conversation data from reports. +Choose the owning repository from the [organization profile](https://github.com/SNodeC#banner). Use that project's **Contribute** menu for its issue tracker, build instructions and engineering guidelines. Report the revision, platform, steps to reproduce and expected/actual behavior; remove credentials, private configuration and private conversation data from reports. Keep changes scoped to their owning project. Do not create, change or require licence files, move release tags, or change package/security infrastructure without explicit maintainer approval. For a private vulnerability report, follow [SECURITY.md](SECURITY.md), not a public issue. @@ -8,4 +8,4 @@ Keep changes scoped to their owning project. Do not create, change or require li Open control-repository pull requests in [SNodeC/.github](https://github.com/SNodeC/.github/pulls). Repository identity belongs in `catalog.yml`; build settings belong in the owning repository's manifest. The profile is generated from `catalog.yml` (rows) and `profile/template.md` (prose). Edit those two files, never `profile/README.md`. Optional `tier` selects product, learn, install or secondary; `title` and `purpose` are single-line product display text; boolean `student` adds an entry to Student projects. Product rows require title/purpose and product/app kind; learn titles are optional, and secondary products require `student: true`. -Run the generator and checks described in [README.md](README.md). Cite sources for identity changes; use `confirm: true` for summaries needing maintainer confirmation. The [scoped README frame](templates/readme-frame.md) applies only to the four repositories explicitly enrolled in that frame. +Run the generator and checks described in [README.md](README.md#banner). Cite sources for identity changes; use `confirm: true` for summaries needing maintainer confirmation. The [scoped README frame](templates/readme-frame.md) applies only to the four repositories explicitly enrolled in that frame. diff --git a/README.md b/README.md index eeddab8..2368afc 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,18 @@ + +

SNode.C — Organization control repository

-

SNode.C organization

+

Volker Christian accountSNode.C organizationSNode.C repository

# SNode.C control repository -This repository owns the organization catalog and the [profile](profile/README.md) generated from catalog rows and authored prose. Brand tokens define the shared accents, contrast pairs and glyphs. The shared Verify workflow provides reusable, manifest-driven verification. The navigation plan, page-header generator and optional `docs-audit` command keep documentation paths, generated navigation and tracked-page reachability reviewable from one control point. +This repository owns the organization catalog and the [profile](profile/README.md#banner) generated from catalog rows and authored prose. Brand tokens define the shared accents, contrast pairs and glyphs. The shared Verify workflow provides reusable, manifest-driven verification. The navigation plan, page-header generator and optional `docs-audit` command keep documentation paths, generated navigation and tracked-page reachability reviewable from one control point. ## Documentation -This repository owns organization-profile generation and reusable project verification. The [workflow graph](docs/workflow-graph.md#page-overview) documents all entry points, repository handoffs and publication guards. +This repository owns organization-profile generation and reusable project verification. The [workflow graph](docs/workflow-graph.md#banner) documents all entry points, repository handoffs and publication guards. - [Navigation generator](navigation/README.md) - [Navigation plan review](navigation/PLAN.md) @@ -44,7 +46,7 @@ repository, not the working directory. - `catalog.yml`: identity, classification, ecosystem edges and lifecycle only. Entry order controls row order. Optional `tier` selects `product`, `learn`, `install` or the default `secondary`; product rows require single-line `title` and `purpose`. Learn titles are optional. Boolean `student` places entries in Student projects; a product with `student: true` also retains its product row. Product tiers require product/app kind; secondary products require `student: true`. -- [profile/template.md](profile/template.md): authored profile prose and exactly one standalone marker each for Products, Learn and More rows. `snodec_sync profile` combines it with `catalog.yml`; `profile/README.md` remains generated. +- [profile/template.md](profile/template.md#banner): authored profile prose and exactly one standalone marker each for Products, Learn and More rows. `snodec_sync profile` combines it with `catalog.yml`; `profile/README.md` remains generated. - `profile/github-metadata.json`: checked-in GitHub API evidence for default branches, workflow paths and release tags. No timestamps or network requests enter rendering. Update this snapshot deliberately when those facts change; diff --git a/brand/navigation/octicons/NOTICE.md b/brand/navigation/octicons/NOTICE.md index 01c2599..b860953 100644 --- a/brand/navigation/octicons/NOTICE.md +++ b/brand/navigation/octicons/NOTICE.md @@ -1,6 +1,6 @@ # Octicons attribution -Icons are the 16px `arrow-up`, `repo` and `organization` SVGs from `@primer/octicons` 19.40.0 (https://github.com/primer/octicons), copied without path changes. The following notice is also included in every generated header button. No repository licence files are added or changed. +Icons are the 16px `arrow-up`, `repo`, `organization` and `person` SVGs from `@primer/octicons` 19.40.0 (https://github.com/primer/octicons), copied without path changes. The person icon was retrieved from the upstream main branch on 2026-10-15; the other icons are from the version above. The following notice is also included in every generated header button. No repository licence files are added or changed. ```text MIT License diff --git a/brand/navigation/octicons/person.svg b/brand/navigation/octicons/person.svg new file mode 100644 index 0000000..a58e186 --- /dev/null +++ b/brand/navigation/octicons/person.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/media/control-banner.svg b/docs/media/control-banner.svg new file mode 100644 index 0000000..697db48 --- /dev/null +++ b/docs/media/control-banner.svg @@ -0,0 +1,17 @@ +SNode.C — Organization control + + + + + + + + + + + + + + + + SNode.COrganization control diff --git a/docs/media/nav/account-dark.svg b/docs/media/nav/account-dark.svg new file mode 100644 index 0000000..3ae0841 --- /dev/null +++ b/docs/media/nav/account-dark.svg @@ -0,0 +1,22 @@ +Volker Christian accountVolker Christian diff --git a/docs/media/nav/account-icon-dark.svg b/docs/media/nav/account-icon-dark.svg new file mode 100644 index 0000000..a86e189 --- /dev/null +++ b/docs/media/nav/account-icon-dark.svg @@ -0,0 +1,22 @@ +Volker Christian account diff --git a/docs/media/nav/account-icon-light.svg b/docs/media/nav/account-icon-light.svg new file mode 100644 index 0000000..87f6230 --- /dev/null +++ b/docs/media/nav/account-icon-light.svg @@ -0,0 +1,22 @@ +Volker Christian account diff --git a/docs/media/nav/account-light.svg b/docs/media/nav/account-light.svg new file mode 100644 index 0000000..e2e6ee1 --- /dev/null +++ b/docs/media/nav/account-light.svg @@ -0,0 +1,22 @@ +Volker Christian accountVolker Christian diff --git a/docs/media/nav/banner-workflows-compact.svg b/docs/media/nav/banner-workflows-compact.svg index b7347e9..b3b6451 100644 --- a/docs/media/nav/banner-workflows-compact.svg +++ b/docs/media/nav/banner-workflows-compact.svg @@ -1 +1,17 @@ -SNode.C — WorkflowsSNode.CWorkflows +SNode.C — Workflows + + + + + + + + + + + + + + + + SNode.CWorkflows diff --git a/docs/media/nav/banner-workflows.svg b/docs/media/nav/banner-workflows.svg index 25459fd..15fd9ec 100644 --- a/docs/media/nav/banner-workflows.svg +++ b/docs/media/nav/banner-workflows.svg @@ -1 +1,17 @@ -SNode.C — WorkflowsSNode.CWorkflows +SNode.C — Workflows + + + + + + + + + + + + + + + + SNode.CWorkflows diff --git a/docs/media/nav/organization-dark.svg b/docs/media/nav/organization-dark.svg index b7f440f..c0d96ad 100644 --- a/docs/media/nav/organization-dark.svg +++ b/docs/media/nav/organization-dark.svg @@ -19,4 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -->Organization + -->SNode.C diff --git a/docs/media/nav/organization-light.svg b/docs/media/nav/organization-light.svg index b1f89a0..3462c04 100644 --- a/docs/media/nav/organization-light.svg +++ b/docs/media/nav/organization-light.svg @@ -19,4 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -->Organization + -->SNode.C diff --git a/docs/media/nav/repository-dark.svg b/docs/media/nav/repository-dark.svg index 7daeddd..b6a0d0d 100644 --- a/docs/media/nav/repository-dark.svg +++ b/docs/media/nav/repository-dark.svg @@ -19,4 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -->Repository + -->SNode.C diff --git a/docs/media/nav/repository-light.svg b/docs/media/nav/repository-light.svg index 8bf0be0..f7d0586 100644 --- a/docs/media/nav/repository-light.svg +++ b/docs/media/nav/repository-light.svg @@ -19,4 +19,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -->Repository + -->SNode.C diff --git a/docs/workflow-graph.md b/docs/workflow-graph.md index 7969d76..14c4d8c 100644 --- a/docs/workflow-graph.md +++ b/docs/workflow-graph.md @@ -1,7 +1,7 @@ - +

SNode.C — Workflows

-

Up to SNode.CSNode.C organizationSNode.C repository

+

Up to SNode.CVolker Christian accountSNode.C organizationSNode.C repository

# SNode.C workflow graph @@ -102,7 +102,7 @@ A fresh manual start is separate from **Re-run jobs**. Retries require the origi Both DistributionPackages publication workflows share `package-publication` concurrency with cancellation disabled. This serializes publication, not all product build jobs. APT publication uses GPG repository signing; RPM publication signs packages and repository metadata; OpenWrt 24.10 uses usign and 25.12 uses APK signing. -`DistributionPackages/site/` owns the public README, guides and keys guide. `ci/publish/publication.py` renders status from Packages records. “Available” describes the published feed; “In build” describes the recorded build attempt. A manual render copies current authored pages and renders those records; it neither compiles packages nor discovers missing publications nor repairs incorrect `status.json` state. See [DistributionPackages maintainer operations](https://github.com/SNodeC/DistributionPackages/blob/main/docs/maintainers.md#page-overview) and the [public publication model](https://github.com/SNodeC/Packages/blob/main/docs/publication.md#page-overview). +`DistributionPackages/site/` owns the public README, guides and keys guide. `ci/publish/publication.py` renders status from Packages records. “Available” describes the published feed; “In build” describes the recorded build attempt. A manual render copies current authored pages and renders those records; it neither compiles packages nor discovers missing publications nor repairs incorrect `status.json` state. See [DistributionPackages maintainer operations](https://github.com/SNodeC/DistributionPackages/blob/main/docs/maintainers.md#banner) and the [public publication model](https://github.com/SNodeC/Packages/blob/main/docs/publication.md#banner). ## Book companion publication diff --git a/navigation/README.md b/navigation/README.md index 3aee92e..e0a80be 100644 --- a/navigation/README.md +++ b/navigation/README.md @@ -34,9 +34,11 @@ header = render_header('Packages', 'status/snodec.md') The read-only library accepts a catalog name or checkout path. Future PROJECTS-driven status pages inherit their declared section/sibling Up policy; they need no additional project registry. Other undeclared presentation pages fail. Runtime uses Python's standard library and PyYAML only. The checked-in marks and Octicons are inputs, not runtime downloads. -The generator replaces marked headers, preserves historic anchors outside the block and generates section lists from actual H1s in plan order. Root READMEs and the profile get no page headers. Banner plus button row stays unchanged: ↑ parent on the left; Organization first in HTML, then Repository, both right-aligned. The Organization button is labelled Organization. Buttons stay 160 px wide. Native wrapping remains unchanged. Structural title menus are arrow-free; onward and route links are ordinary text. +The generator replaces marked headers, preserves historic anchors and generates section lists from actual H1s in plan order. Each page has a stable `banner` anchor before its banner; root READMEs place it before the hero. Up and page-level links target `#banner`; intentional section, source-file, download and external-documentation links retain their targets. Pages without a banner retain their existing destinations. -Desktop banners remain 1280×56. At viewport widths up to 640 px, `` selects the existing 480×72 compact SVG, with the same mark, palette and typography. Below 640 px, child-page Repository and Organization use 36×28 icon-only SVGs; the parent stays 160×28 and the header stays on one row. Desktop and root-only Organization buttons remain full-size. No CSS workaround or alternate renderer is introduced. DistributionPackages reuses the authoritative Packages gear and infrastructure accent instead of duplicating its graphic. +The banner row shows Up on the left and Repository, Organization and Account on the right. Identity buttons display names with Octicons: the repository's display name, SNode.C, and Volker Christian. Titles and accessible image labels identify the destination type, including the two SNode.C buttons. Right floats use reverse HTML order so the visible order is repository, organization, account. The organization links to `https://github.com/SNodeC#banner`; the account links to `https://github.com/VolkerChristian#banner`. Root identity rows use the same renderer. + +Desktop banners remain 1280×56; below 640 px the existing 480×72 compact SVG is selected. Identity buttons select 36×28 icons on mobile, including root rows; Up stays 160×28. Full button widths grow for long names. Native GitHub wrapping is preserved. DistributionPackages reuses the Packages gear and infrastructure accent; organization control uses the SNode.C mark. ## Tests and releases @@ -44,6 +46,6 @@ Run `python -m unittest discover -s tests -v` for generator and workflow behavio ## Shared visual spec -Navigation and title-menu labels use 15 px / 600 with `-apple-system, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif`; banners use the same stack at their existing sizes. Heroes retain DejaVu Sans. Outline navigation and filled `#203440` title menus remain distinct. Mobile icon selection uses the approved three `` elements plus a fallback ``, with no image width/height on the two child-page right buttons; SVG intrinsic sizes and native GitHub padding are preserved. The snode.c GitHub gate passed at 390 and 1440 px in light/dark modes before rollout. +Navigation and title-menu labels use 15 px / 600 with `-apple-system, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif`; banners use the same stack at their existing sizes. Heroes retain DejaVu Sans. Outline navigation and filled `#203440` title menus remain distinct. Mobile icon selection uses the approved three `` elements plus a fallback ``, with no image width/height on the three identity buttons; SVG intrinsic sizes and native GitHub padding are preserved. The snode.c GitHub gate passed at 390 and 1440 px in light/dark modes before rollout. -Root Organization blocks are generated from the same markup and SVG renderer. Book Companion can be generated in its exported public checkout without enrolling its learning pages; copy the generated root README and two Organization assets back to the private source through the existing export allow-list. No private source is published by this tool. +Root Organization blocks are generated from the same markup and SVG renderer. Book Companion can be generated in its exported public checkout without enrolling its learning pages; copy the generated root README and identity assets back to the private source through the existing export allow-list. No private source is published by this tool. diff --git a/profile/README.md b/profile/README.md index 53e3ce4..50a4832 100644 --- a/profile/README.md +++ b/profile/README.md @@ -1,4 +1,5 @@ +

@@ -6,6 +7,10 @@

+ +

Volker Christian accountSNode.C organizationSNode.C repository

+ + # SNode.C @@ -14,12 +19,12 @@ -[![SNode.C release](https://img.shields.io/github/v/release/SNodeC/snode.c?label=SNode.C&style=flat)](https://github.com/SNodeC/snode.c/releases) [![MQTTSuite release](https://img.shields.io/github/v/release/SNodeC/mqttsuite?label=MQTTSuite&style=flat)](https://github.com/SNodeC/mqttsuite/releases) [![Packages](https://img.shields.io/badge/Packages-signed-334155?style=flat)](https://github.com/SNodeC/Packages#readme) +[![SNode.C release](https://img.shields.io/github/v/release/SNodeC/snode.c?label=SNode.C&style=flat)](https://github.com/SNodeC/snode.c/releases) [![MQTTSuite release](https://img.shields.io/github/v/release/SNodeC/mqttsuite?label=MQTTSuite&style=flat)](https://github.com/SNodeC/mqttsuite/releases) [![Packages](https://img.shields.io/badge/Packages-signed-334155?style=flat)](https://github.com/SNodeC/Packages#banner) **Write the protocol. Run the messages. Ship it signed.** -SNode.C builds event-driven networking in C++. At its centre is [SNode.C](https://github.com/SNodeC/snode.c#project-overview), a C++20 framework for Linux; around it are MQTT and AI suites, a coding-agent workspace and signed packages for six Linux distributions. +SNode.C builds event-driven networking in C++. At its centre is [SNode.C](https://github.com/SNodeC/snode.c#banner), a C++20 framework for Linux; around it are MQTT and AI suites, a coding-agent workspace and signed packages for six Linux distributions.

@@ -34,26 +39,26 @@ SNode.C builds event-driven networking in C++. At its centre is [SNode.C](https: | Project | What it is for | Status | | --- | --- | --- | -| **[SNode.C](https://github.com/SNodeC/snode.c#project-overview)** | Write networked programs and your own protocols in C++: event-driven, with HTTP, WebSocket, SSE and MQTT
[Quick start](https://github.com/SNodeC/snode.c#quick-start) · [Install](https://github.com/SNodeC/snode.c#install) · [Releases](https://github.com/SNodeC/snode.c/releases) | [![CI](https://img.shields.io/github/actions/workflow/status/SNodeC/snode.c/build-and-test.yml?branch=master&label=CI&style=flat)](https://github.com/SNodeC/snode.c/actions/workflows/build-and-test.yml) | -| **[MQTTSuite](https://github.com/SNodeC/mqttsuite#project-overview)** | Run an MQTT broker, translate topics, bridge brokers and store messages
[Quick start](https://github.com/SNodeC/mqttsuite#quick-start) · [Install](https://github.com/SNodeC/mqttsuite#install) · [Releases](https://github.com/SNodeC/mqttsuite/releases) | [![CI](https://img.shields.io/github/actions/workflow/status/SNodeC/mqttsuite/build-and-test.yml?branch=master&label=CI&style=flat)](https://github.com/SNodeC/mqttsuite/actions/workflows/build-and-test.yml) | -| **[AISuite](https://github.com/SNodeC/AISuite#project-overview)** | Connect applications to coding agents: asynchronous C++ and TypeScript
[Quick start](https://github.com/SNodeC/AISuite#quick-start) · [Install](https://github.com/SNodeC/AISuite#install) | [![CI](https://img.shields.io/github/actions/workflow/status/SNodeC/AISuite/build-and-test.yml?branch=master&label=CI&style=flat)](https://github.com/SNodeC/AISuite/actions/workflows/build-and-test.yml) | -| **[Codex(W)UI](https://github.com/SNodeC/CodexUI#project-overview)** | Follow a coding agent's work in a native Qt or browser workspace
[Quick start](https://github.com/SNodeC/CodexUI#quick-start) · [Install](https://github.com/SNodeC/CodexUI#install) | [![CI](https://img.shields.io/github/actions/workflow/status/SNodeC/CodexUI/build-and-qualify.yml?branch=master&label=CI&style=flat)](https://github.com/SNodeC/CodexUI/actions/workflows/build-and-qualify.yml) | +| **[SNode.C](https://github.com/SNodeC/snode.c#banner)** | Write networked programs and your own protocols in C++: event-driven, with HTTP, WebSocket, SSE and MQTT
[Quick start](https://github.com/SNodeC/snode.c#quick-start) · [Install](https://github.com/SNodeC/snode.c#install) · [Releases](https://github.com/SNodeC/snode.c/releases) | [![CI](https://img.shields.io/github/actions/workflow/status/SNodeC/snode.c/build-and-test.yml?branch=master&label=CI&style=flat)](https://github.com/SNodeC/snode.c/actions/workflows/build-and-test.yml) | +| **[MQTTSuite](https://github.com/SNodeC/mqttsuite#banner)** | Run an MQTT broker, translate topics, bridge brokers and store messages
[Quick start](https://github.com/SNodeC/mqttsuite#quick-start) · [Install](https://github.com/SNodeC/mqttsuite#install) · [Releases](https://github.com/SNodeC/mqttsuite/releases) | [![CI](https://img.shields.io/github/actions/workflow/status/SNodeC/mqttsuite/build-and-test.yml?branch=master&label=CI&style=flat)](https://github.com/SNodeC/mqttsuite/actions/workflows/build-and-test.yml) | +| **[AISuite](https://github.com/SNodeC/AISuite#banner)** | Connect applications to coding agents: asynchronous C++ and TypeScript
[Quick start](https://github.com/SNodeC/AISuite#quick-start) · [Install](https://github.com/SNodeC/AISuite#install) | [![CI](https://img.shields.io/github/actions/workflow/status/SNodeC/AISuite/build-and-test.yml?branch=master&label=CI&style=flat)](https://github.com/SNodeC/AISuite/actions/workflows/build-and-test.yml) | +| **[Codex(W)UI](https://github.com/SNodeC/CodexUI#banner)** | Follow a coding agent's work in a native Qt or browser workspace
[Quick start](https://github.com/SNodeC/CodexUI#quick-start) · [Install](https://github.com/SNodeC/CodexUI#install) | [![CI](https://img.shields.io/github/actions/workflow/status/SNodeC/CodexUI/build-and-qualify.yml?branch=master&label=CI&style=flat)](https://github.com/SNodeC/CodexUI/actions/workflows/build-and-qualify.yml) | | **[SNode.C-idp](https://github.com/SNodeC/snode.c-idp)** | SNode.C Identity Provider: OAuth2 and OpenID Connect with TOTP multi-factor authentication | | ## Install -SNode.C and MQTTSuite are available as signed packages from [Packages](https://github.com/SNodeC/Packages#readme). Pick the guide for your installed distribution; each covers quick and custom installation, manual repository setup and updates. +SNode.C and MQTTSuite are available as signed packages from [Packages](https://github.com/SNodeC/Packages#banner). Pick the guide for your installed distribution; each covers quick and custom installation, manual repository setup and updates. | Distribution | Install guide | | --- | --- | -| OpenWrt | [OpenWrt guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/openwrt.md#page-overview) | -| Raspberry Pi OS | [Raspberry Pi OS guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/raspberry-pi-os.md#page-overview) | -| Debian | [Debian guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/debian.md#page-overview) | -| Ubuntu | [Ubuntu guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/ubuntu.md#page-overview) | -| Rocky Linux | [Rocky Linux guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/rocky-linux.md#page-overview) | -| Fedora | [Fedora guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/fedora.md#page-overview) | +| OpenWrt | [OpenWrt guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/openwrt.md#banner) | +| Raspberry Pi OS | [Raspberry Pi OS guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/raspberry-pi-os.md#banner) | +| Debian | [Debian guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/debian.md#banner) | +| Ubuntu | [Ubuntu guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/ubuntu.md#banner) | +| Rocky Linux | [Rocky Linux guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/rocky-linux.md#banner) | +| Fedora | [Fedora guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/fedora.md#banner) | -Supported releases, architectures and current builds: [package status](https://github.com/SNodeC/Packages/blob/main/status/README.md#page-overview). +Supported releases, architectures and current builds: [package status](https://github.com/SNodeC/Packages/blob/main/status/README.md#banner). ## Learn @@ -112,8 +117,8 @@ Further example programs are listed under [More from SNode.C](#more-from-snodec) | Project | Purpose | | --- | --- | -| [DistributionPackages](https://github.com/SNodeC/DistributionPackages) | Build recipes, release CI and signing for distribution packages | -| [.github](https://github.com/SNodeC/.github) | Organization catalog, profile, brand tokens and reusable CI workflows | +| [DistributionPackages](https://github.com/SNodeC/DistributionPackages#banner) | Build recipes, release CI and signing for distribution packages | +| [.github](https://github.com/SNodeC/.github#banner) | Organization catalog, profile, brand tokens and reusable CI workflows | @@ -150,5 +155,5 @@ Issues and pull requests are welcome in each project's repository. Read the [con Copyright © Volker Christian and contributors. Each project states its own license. -Maintainer: [Volker Christian](https://github.com/VolkerChristian). [Contributing](https://github.com/SNodeC/.github/blob/main/CONTRIBUTING.md) · [Security](https://github.com/SNodeC/.github/blob/main/SECURITY.md) +Maintainer: [Volker Christian](https://github.com/VolkerChristian#banner). [Contributing](https://github.com/SNodeC/.github/blob/main/CONTRIBUTING.md) · [Security](https://github.com/SNodeC/.github/blob/main/SECURITY.md) diff --git a/profile/github-metadata.json b/profile/github-metadata.json index 1796a3a..afc19fb 100644 --- a/profile/github-metadata.json +++ b/profile/github-metadata.json @@ -1,11 +1,12 @@ { ".github": { "default_branch": "main", + "readme_anchor": "banner", "workflow": ".github/workflows/validate-organization.yml" }, "AISuite": { "default_branch": "master", - "readme_anchor": "project-overview", + "readme_anchor": "banner", "source_commit": "45bbc477546bca8055575c17cebbb16db7bb24be", "workflow": ".github/workflows/build-and-test.yml" }, @@ -21,12 +22,13 @@ }, "CodexUI": { "default_branch": "master", - "readme_anchor": "project-overview", + "readme_anchor": "banner", "source_commit": "33a5357bee5978ecaa31a2b7dab55b858843e9f7", "workflow": ".github/workflows/build-and-qualify.yml" }, "DistributionPackages": { "default_branch": "main", + "readme_anchor": "banner", "source_commit": "f941021a88953cf962ac2b926e456cb25d2fda18", "workflow": ".github/workflows/sign-and-publish-packages.yml" }, @@ -61,6 +63,7 @@ }, "Packages": { "default_branch": "main", + "readme_anchor": "banner", "source_commit": "5db17156d81577f85705c114ccb4c73e635dfe75" }, "SNode.C-Book-Companion": { @@ -111,7 +114,7 @@ }, "mqttsuite": { "default_branch": "master", - "readme_anchor": "project-overview", + "readme_anchor": "banner", "release": { "prerelease": false, "tag": "v1.0.2" @@ -134,7 +137,7 @@ }, "snode.c": { "default_branch": "master", - "readme_anchor": "project-overview", + "readme_anchor": "banner", "release": { "prerelease": false, "tag": "v2.0.0" diff --git a/profile/media/nav/account-dark.svg b/profile/media/nav/account-dark.svg new file mode 100644 index 0000000..3ae0841 --- /dev/null +++ b/profile/media/nav/account-dark.svg @@ -0,0 +1,22 @@ +Volker Christian accountVolker Christian diff --git a/profile/media/nav/account-icon-dark.svg b/profile/media/nav/account-icon-dark.svg new file mode 100644 index 0000000..a86e189 --- /dev/null +++ b/profile/media/nav/account-icon-dark.svg @@ -0,0 +1,22 @@ +Volker Christian account diff --git a/profile/media/nav/account-icon-light.svg b/profile/media/nav/account-icon-light.svg new file mode 100644 index 0000000..87f6230 --- /dev/null +++ b/profile/media/nav/account-icon-light.svg @@ -0,0 +1,22 @@ +Volker Christian account diff --git a/profile/media/nav/account-light.svg b/profile/media/nav/account-light.svg new file mode 100644 index 0000000..e2e6ee1 --- /dev/null +++ b/profile/media/nav/account-light.svg @@ -0,0 +1,22 @@ +Volker Christian accountVolker Christian diff --git a/profile/media/nav/organization-dark.svg b/profile/media/nav/organization-dark.svg new file mode 100644 index 0000000..c0d96ad --- /dev/null +++ b/profile/media/nav/organization-dark.svg @@ -0,0 +1,22 @@ +SNode.C organizationSNode.C diff --git a/profile/media/nav/organization-icon-dark.svg b/profile/media/nav/organization-icon-dark.svg new file mode 100644 index 0000000..83b391c --- /dev/null +++ b/profile/media/nav/organization-icon-dark.svg @@ -0,0 +1,22 @@ +SNode.C organization diff --git a/profile/media/nav/organization-icon-light.svg b/profile/media/nav/organization-icon-light.svg new file mode 100644 index 0000000..ba05659 --- /dev/null +++ b/profile/media/nav/organization-icon-light.svg @@ -0,0 +1,22 @@ +SNode.C organization diff --git a/profile/media/nav/organization-light.svg b/profile/media/nav/organization-light.svg new file mode 100644 index 0000000..3462c04 --- /dev/null +++ b/profile/media/nav/organization-light.svg @@ -0,0 +1,22 @@ +SNode.C organizationSNode.C diff --git a/profile/media/nav/repository-dark.svg b/profile/media/nav/repository-dark.svg new file mode 100644 index 0000000..b6a0d0d --- /dev/null +++ b/profile/media/nav/repository-dark.svg @@ -0,0 +1,22 @@ +SNode.C repositorySNode.C diff --git a/profile/media/nav/repository-icon-dark.svg b/profile/media/nav/repository-icon-dark.svg new file mode 100644 index 0000000..a47adf8 --- /dev/null +++ b/profile/media/nav/repository-icon-dark.svg @@ -0,0 +1,22 @@ +SNode.C repository diff --git a/profile/media/nav/repository-icon-light.svg b/profile/media/nav/repository-icon-light.svg new file mode 100644 index 0000000..ba93b86 --- /dev/null +++ b/profile/media/nav/repository-icon-light.svg @@ -0,0 +1,22 @@ +SNode.C repository diff --git a/profile/media/nav/repository-light.svg b/profile/media/nav/repository-light.svg new file mode 100644 index 0000000..f7d0586 --- /dev/null +++ b/profile/media/nav/repository-light.svg @@ -0,0 +1,22 @@ +SNode.C repositorySNode.C diff --git a/profile/template.md b/profile/template.md index c9fca2f..0897c96 100644 --- a/profile/template.md +++ b/profile/template.md @@ -1,4 +1,5 @@ +

@@ -6,6 +7,10 @@

+ +

Volker Christian accountSNode.C organizationSNode.C repository

+ + # SNode.C @@ -14,12 +19,12 @@ -[![SNode.C release](https://img.shields.io/github/v/release/SNodeC/snode.c?label=SNode.C&style=flat)](https://github.com/SNodeC/snode.c/releases) [![MQTTSuite release](https://img.shields.io/github/v/release/SNodeC/mqttsuite?label=MQTTSuite&style=flat)](https://github.com/SNodeC/mqttsuite/releases) [![Packages](https://img.shields.io/badge/Packages-signed-334155?style=flat)](https://github.com/SNodeC/Packages#readme) +[![SNode.C release](https://img.shields.io/github/v/release/SNodeC/snode.c?label=SNode.C&style=flat)](https://github.com/SNodeC/snode.c/releases) [![MQTTSuite release](https://img.shields.io/github/v/release/SNodeC/mqttsuite?label=MQTTSuite&style=flat)](https://github.com/SNodeC/mqttsuite/releases) [![Packages](https://img.shields.io/badge/Packages-signed-334155?style=flat)](https://github.com/SNodeC/Packages#banner) **Write the protocol. Run the messages. Ship it signed.** -SNode.C builds event-driven networking in C++. At its centre is [SNode.C](https://github.com/SNodeC/snode.c#project-overview), a C++20 framework for Linux; around it are MQTT and AI suites, a coding-agent workspace and signed packages for six Linux distributions. +SNode.C builds event-driven networking in C++. At its centre is [SNode.C](https://github.com/SNodeC/snode.c#banner), a C++20 framework for Linux; around it are MQTT and AI suites, a coding-agent workspace and signed packages for six Linux distributions.

@@ -36,18 +41,18 @@ SNode.C builds event-driven networking in C++. At its centre is [SNode.C](https: ## Install -SNode.C and MQTTSuite are available as signed packages from [Packages](https://github.com/SNodeC/Packages#readme). Pick the guide for your installed distribution; each covers quick and custom installation, manual repository setup and updates. +SNode.C and MQTTSuite are available as signed packages from [Packages](https://github.com/SNodeC/Packages#banner). Pick the guide for your installed distribution; each covers quick and custom installation, manual repository setup and updates. | Distribution | Install guide | | --- | --- | -| OpenWrt | [OpenWrt guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/openwrt.md#page-overview) | -| Raspberry Pi OS | [Raspberry Pi OS guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/raspberry-pi-os.md#page-overview) | -| Debian | [Debian guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/debian.md#page-overview) | -| Ubuntu | [Ubuntu guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/ubuntu.md#page-overview) | -| Rocky Linux | [Rocky Linux guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/rocky-linux.md#page-overview) | -| Fedora | [Fedora guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/fedora.md#page-overview) | +| OpenWrt | [OpenWrt guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/openwrt.md#banner) | +| Raspberry Pi OS | [Raspberry Pi OS guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/raspberry-pi-os.md#banner) | +| Debian | [Debian guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/debian.md#banner) | +| Ubuntu | [Ubuntu guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/ubuntu.md#banner) | +| Rocky Linux | [Rocky Linux guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/rocky-linux.md#banner) | +| Fedora | [Fedora guide](https://github.com/SNodeC/Packages/blob/main/docs/installation/fedora.md#banner) | -Supported releases, architectures and current builds: [package status](https://github.com/SNodeC/Packages/blob/main/status/README.md#page-overview). +Supported releases, architectures and current builds: [package status](https://github.com/SNodeC/Packages/blob/main/status/README.md#banner). ## Learn @@ -66,5 +71,5 @@ Issues and pull requests are welcome in each project's repository. Read the [con Copyright © Volker Christian and contributors. Each project states its own license. -Maintainer: [Volker Christian](https://github.com/VolkerChristian). [Contributing](https://github.com/SNodeC/.github/blob/main/CONTRIBUTING.md) · [Security](https://github.com/SNodeC/.github/blob/main/SECURITY.md) +Maintainer: [Volker Christian](https://github.com/VolkerChristian#banner). [Contributing](https://github.com/SNodeC/.github/blob/main/CONTRIBUTING.md) · [Security](https://github.com/SNodeC/.github/blob/main/SECURITY.md) diff --git a/tests/test_navigation.py b/tests/test_navigation.py index 879f0ed..692e872 100644 --- a/tests/test_navigation.py +++ b/tests/test_navigation.py @@ -123,8 +123,9 @@ def test_all_generated_links_and_assets_resolve(self): before_root = (repo / 'README.md').read_text() page_headers(repo) self.assertNotIn('page-header', (repo / 'README.md').read_text()) - self.assertEqual(re.findall(r'href="[^"]+"', before_root), - re.findall(r'href="[^"]+"', (repo / 'README.md').read_text())) + strip_identity = lambda value: re.sub(r'.*?', '', value, flags=re.S) + self.assertEqual(re.findall(r'href="[^"]+"', strip_identity(before_root)), + re.findall(r'href="[^"]+"', strip_identity((repo / 'README.md').read_text()))) for page in config['pages']: if page.get('deleted'): continue @@ -137,7 +138,7 @@ def test_all_generated_links_and_assets_resolve(self): self.assertNotIn('')[1].split('

')[0]) for target in re.findall(r'(?:href|src|srcset)="([^"]+)"', header): if target.startswith('https:'): - self.assertEqual(target, 'https://github.com/SNodeC') + self.assertIn(target, ('https://github.com/SNodeC#banner', 'https://github.com/VolkerChristian#banner')) continue file, _, fragment = target.partition('#') destination = (repo / page['new']).parent / file @@ -153,7 +154,7 @@ def test_all_generated_links_and_assets_resolve(self): def test_project_registry_extension_inherits_generated_policy(self): text = render_header('Packages', 'status/third-project.md') - self.assertIn('href="README.md#page-overview"', text) + self.assertIn('href="README.md#banner"', text) self.assertIn('banner-package-status.svg', text) with self.assertRaises(ValueError): render_header('Packages', 'docs/unplanned.md') @@ -178,7 +179,7 @@ def test_menu_changes_and_protected_body(self): self.assertIn('```sh\necho unchanged\n```', p.read_text()) self.assertIn('id="historic-anchor"', p.read_text()) section = (repo / 'docs/installation/README.md').read_text() - self.assertIn('[Debian](debian.md#page-overview)', section) + self.assertIn('[Debian](debian.md#banner)', section) self.assertNotIn('install-snodec.md', section) def test_read_only_check_and_cli(self): @@ -224,7 +225,7 @@ def test_mobile_sources_intrinsic_sizes_and_shared_font_spec(self): from tools.snodec_sync.navigation import _header_button, FONT header = render_header('snode.c', 'docs/guides/README.md') self.assertLess(header.index('SNode.C organization'), header.index('SNode.C repository')) - self.assertEqual(header.count('(max-width: 640px) and (prefers-color-scheme: dark)'), 2) + self.assertEqual(header.count('(max-width: 640px) and (prefers-color-scheme: dark)'), 3) self.assertEqual(header.count('width="160" height="28"'), 1) for width in (36, 160): svg = ET.fromstring(_header_button('Repository', 'Repository', 'repo', '#075985', 'light', width)) @@ -247,5 +248,32 @@ def test_companion_root_organization_is_generated_without_page_enrollment(self): self.assertIn('Preserved reader text.', text) self.assertLess(text.index('snodec:begin organization'), text.index('# Companion')) self.assertTrue((repo / 'docs/media/nav/organization-light.svg').is_file()) - self.assertFalse((repo / 'docs/media/nav/repository-light.svg').exists()) + self.assertTrue((repo / 'docs/media/nav/repository-light.svg').exists()) + self.assertTrue((repo / 'docs/media/nav/account-light.svg').exists()) self.assertEqual(page_headers(repo, check=True), []) + + def test_banner_precedes_image_and_identity_destinations(self): + repo = self.fixture('snode.c') + page_headers(repo) + for path in ['README.md', 'docs/README.md', 'docs/guides/build-from-source.md']: + body = (repo / path).read_text() + self.assertEqual(body.count('id="banner"'), 1) + self.assertLess(body.index('id="banner"'), body.index('# ')) + if path != 'README.md': + self.assertLess(body.index('id="banner"'), body.index('')) + self.assertIn('https://github.com/SNodeC#banner', body) + self.assertIn('https://github.com/VolkerChristian#banner', body) + self.assertIn('id="page-overview"', body) + for stem, title, label in [('repository', 'SNode.C repository', 'SNode.C'), + ('organization', 'SNode.C organization', 'SNode.C'), + ('account', 'Volker Christian account', 'Volker Christian')]: + asset = (repo / f'docs/media/nav/{stem}-light.svg').read_text() + self.assertIn(f'{title}', asset) + self.assertIn(f'>{label}', asset) + self.assertEqual(page_headers(repo, check=True), []) + + def test_long_repository_name_is_not_clipped(self): + import xml.etree.ElementTree as ET + from tools.snodec_sync.navigation import _header_button + svg = ET.fromstring(_header_button('DistributionPackages', 'DistributionPackages repository', 'repo', '#075985', 'light')) + self.assertGreater(int(svg.attrib['width']), 160) diff --git a/tests/test_sync.py b/tests/test_sync.py index 19d388c..2bc97b4 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -147,9 +147,9 @@ def test_only_verified_product_workflows_and_releases(self): def test_repository_navigation_uses_metadata_anchors_only(self): for name in ['snode.c', 'mqttsuite', 'AISuite', 'CodexUI']: - self.assertIn(f'](https://github.com/SNodeC/{name}#project-overview)**', self.profile) - self.assertNotIn(f'{name}#project-overview/actions/', self.profile) - self.assertNotIn(f'{name}#project-overview/releases/', self.profile) + self.assertIn(f'](https://github.com/SNodeC/{name}#banner)**', self.profile) + self.assertNotIn(f'{name}#banner/actions/', self.profile) + self.assertNotIn(f'{name}#banner/releases/', self.profile) self.metadata['AISuite'].pop('readme_anchor') rendered = render_profile(self.catalog, self.metadata) row = next(line for line in rendered.splitlines() if line.startswith('| **[AISuite](')) diff --git a/tools/snodec_sync/__main__.py b/tools/snodec_sync/__main__.py index ed21c75..274370e 100644 --- a/tools/snodec_sync/__main__.py +++ b/tools/snodec_sync/__main__.py @@ -124,7 +124,7 @@ def render_profile(catalog, metadata, template=None): badge = f"[![CI](https://img.shields.io/github/actions/workflow/status/SNodeC/{quote(name, safe='')}/{quote(Path(info['workflow']).name, safe='')}?branch={branch}&label=CI&style=flat)]({workflow})" products.append(f"| **[{text(entry['title'])}]({repo_url(name, anchor)})** | {purpose} | {badge} |") elif tier == "learn": - learn.append(f"| **[{text(entry.get('title', name))}]({url})** | {text(entry['summary'])} |") + learn.append(f"| **[{text(entry.get('title', name))}]({repo_url(name, info.get("readme_anchor", ""))})** | {text(entry['summary'])} |") if tier != "secondary" and entry.get("student") is not True: continue group = ("Archive" if entry["status"] != "active" else "Student projects" @@ -136,7 +136,7 @@ def render_profile(catalog, metadata, template=None): if group == "Upstream forks" else text(entry["summary"])) if group == "Archive": purpose += f" · {entry['status']}" - groups[group].append(f"| [{text(name)}]({url}) | {purpose} |") + groups[group].append(f"| [{text(name)}]({repo_url(name, info.get("readme_anchor", ""))}) | {purpose} |") summaries = ["small programs built on SNode.C", "built in courses and projects", "not built on SNode.C", "package pipeline and organization control", "kept for future work", "retiring and retired"] more = [] diff --git a/tools/snodec_sync/navigation.py b/tools/snodec_sync/navigation.py index 959f1a9..44bc544 100644 --- a/tools/snodec_sync/navigation.py +++ b/tools/snodec_sync/navigation.py @@ -124,11 +124,11 @@ def _button(nav, stem, title, href, right=False, mobile=False): f'alt="{escape(title)}" title="{escape(title)}"{size}>') -def _organization(): - return ('\n

' + - _button('docs/media/nav', 'organization', 'SNode.C organization', - 'https://github.com/SNodeC', True) + - '

\n') +def _identity(nav, display, repository): + return ''.join(_button(nav, stem, title, href, True, True) for stem, title, href in [ + ('account', 'Volker Christian account', 'https://github.com/VolkerChristian#banner'), + ('organization', 'SNode.C organization', 'https://github.com/SNodeC#banner'), + ('repository', display + ' repository', repository)]) def _header(config, page_path): @@ -136,18 +136,16 @@ def _header(config, page_path): display = config['display'] nav = _relative('docs/media/nav', page_path) escape = lambda text: html.escape(text, quote=True) - up_fragment = 'project-overview' if page['up'] == 'README.md' else 'page-overview' - buttons = [_button(nav, 'up-' + slug(page['up_label']), 'Up to ' + config['menu_labels'].get(page['up_label'], page['up_label']), - _relative(page['up'], page_path) + '#' + up_fragment), - _button(nav, 'organization', 'SNode.C organization', 'https://github.com/SNodeC', True, True), - _button(nav, 'repository', display + ' repository', - _relative('README.md', page_path) + '#project-overview', True, True)] - return ('\n\n' + buttons = _button(nav, 'up-' + slug(page['up_label']), + 'Up to ' + config['menu_labels'].get(page['up_label'], page['up_label']), + _relative(page['up'], page_path) + '#banner') + buttons += _identity(nav, display, _relative('README.md', page_path) + '#banner') + return ('\n\n' f'

' f'

\n' - '

' + ''.join(buttons) + '

\n') + '

' + buttons + '

\n') def render_header(repo, page_path): @@ -166,6 +164,7 @@ def _svg(width, height, label, body): def _header_button(label, title, icon, color, theme, width=160): + width = max(width, 44 + len(label) * 8) if width != 36 else width fill, stroke, text = (('#f6f8fa', '#d0d7de', '#1f2328') if theme == 'light' else ('#161b22', '#3d444d', '#e6edf3')) original = ET.parse(ROOT / f'brand/navigation/octicons/{icon}.svg').getroot() @@ -180,7 +179,7 @@ def _header_button(label, title, icon, color, theme, width=160): def _banner(name, display, label, color, compact=False): - mark = ET.parse(ROOT / f'brand/navigation/marks/{"Packages" if name == "DistributionPackages" else name}.svg').getroot() + mark = ET.parse(ROOT / f'brand/navigation/marks/{ {"DistributionPackages": "Packages", ".github": "snode.c"}.get(name, name)}.svg').getroot() x, y, width, height = map(float, mark.attrib['viewBox'].split()) scale = 44 / height graphic = ''.join(ET.tostring(child, encoding='unicode') for child in mark) @@ -232,7 +231,7 @@ def _section_list(config, path, texts): if page.get('deleted') or page.get('excluded') or page['up'] != path or page['new'] not in texts: continue title = re.search(r'^# (.+)$', texts[page['new']], re.M)[1] - entries.append(f'- [{title}]({_relative(page["new"], path)}#page-overview)') + entries.append(f'- [{title}]({_relative(page["new"], path)}#banner)') return '\n' + '\n'.join(entries) + '\n' @@ -240,7 +239,7 @@ def _up_link(src, href, path, ancestors): target, _, fragment = href.partition('#') target = posixpath.normpath(posixpath.join(posixpath.dirname(path), target)) return PurePosixPath(src).name.startswith('back-') or ( - target in ancestors and fragment in ('', 'page-overview', 'project-overview', 'readme')) + target in ancestors and fragment in ('', 'banner', 'page-overview', 'project-overview', 'readme')) def _menus(checkout, config, texts, outputs): @@ -252,16 +251,8 @@ def _menus(checkout, config, texts, outputs): ancestors.add(parent) if parent == 'README.md': break - def anchor(match): - tag = match[0] - image = re.search(r']*src="([^"]+)"[^>]*>', tag) - if not image: - return tag - href = re.search(r'href="([^"]+)"', tag)[1] - if _up_link(image[1], href, path, ancestors): - return '' - return tag - text = re.sub(r']*href="[^"]+"[^>]*>\s*]*>\s*', anchor, original) + text = re.sub(r']*href="([^"]+)"[^>]*>\s*]*src="([^"]+)"[^>]*>\s*', + lambda m: '' if _up_link(m[2], m[1], path, ancestors) else m[0], original) # Markdown image uplinks are likewise buttons, not ordinary prose references. text = re.sub(r'\[!\[([^\]]*)\]\(([^)]+)\)\]\(([^)]+)\)', lambda m: '' if _up_link(m[2], m[3], path, ancestors) else m[0], text) @@ -358,7 +349,9 @@ def page_headers(repo, check=False): texts[path] = (prefixes[path] + '\n\n' if prefixes[path] else '') + body + '\n' root_organization = name in ('.github', 'SNode.C-Book-Companion') or 'snodec:begin organization' in texts['README.md'] if root_organization: - block = _organization() + block = ('\n

' + + _identity('docs/media/nav', config['display'], '#banner') + + '

\n') text = texts['README.md'] pattern = r'.*?' if re.search(pattern, text, re.S): @@ -366,21 +359,22 @@ def page_headers(repo, check=False): else: text = re.sub(r'^# ', lambda _: block + '\n\n# ', text, count=1, flags=re.M) texts['README.md'] = text + texts['README.md'] = '\n' + re.sub(r'\s*', '', texts['README.md']) outputs.update(texts) active = [p for p in config['pages'] if not p.get('deleted') and not p.get('excluded')] for label in sorted({p['label'] for p in active}): for compact in (False, True): outputs[f'docs/media/nav/banner-{slug(label)}{"-compact" if compact else ""}.svg'] = _banner(name, config['display'], label, accent['dark']['text'], compact) - buttons = [('repository', 'Repository', config['display'] + ' repository', 'repo', accent), - ('organization', 'Organization', 'SNode.C organization', 'organization', org_accent)] + buttons = [('repository', config['display'], config['display'] + ' repository', 'repo', accent), + ('organization', 'SNode.C', 'SNode.C organization', 'organization', org_accent), + ('account', 'Volker Christian', 'Volker Christian account', 'person', org_accent)] buttons += [('up-' + slug(label), config['menu_labels'].get(label, label), 'Up to ' + config['menu_labels'].get(label, label), 'arrow-up', accent) for label in sorted({p['up_label'] for p in active})] - buttons = [button for button in buttons if active or button[0] == 'organization'] if active or root_organization: for stem, label, title, icon, colors in buttons: for theme in ('light', 'dark'): outputs[f'docs/media/nav/{stem}-{theme}.svg'] = _header_button(label, title, icon, colors[theme]['text'], theme) - if active and stem in ('repository', 'organization'): + if stem in ('repository', 'organization', 'account'): outputs[f'docs/media/nav/{stem}-icon-{theme}.svg'] = _header_button(label, title, icon, colors[theme]['text'], theme, 36) drift = [path for path, text in sorted(outputs.items()) if not (checkout / path).is_file() or (checkout / path).read_text() != text] From 318c63c750abce047cfaaeed1076b0aff93875c1 Mon Sep 17 00:00:00 2001 From: Volker Christian Date: Thu, 15 Oct 2026 04:33:09 +0200 Subject: [PATCH 2/2] Document the approved banner destinations and named identity buttons --- templates/readme-frame.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/readme-frame.md b/templates/readme-frame.md index dbfb3da..fa52ddb 100644 --- a/templates/readme-frame.md +++ b/templates/readme-frame.md @@ -63,11 +63,11 @@ Install with multiple routes begins with **Choose your route.**; omit that promp Configuration is an H2 overview in all four READMEs: endpoints, persisted settings and trust boundaries, then the authoritative runtime guide. Build guides own installation and verification; first-success procedures are never duplicated in those guides. Footer contribution links point to the repository's own Contributing section; shared organization defaults must not describe control-repository operations as product contribution instructions. -Supporting pages enrolled in `navigation/plan.yml` use the generated `page-header` block and `#page-overview` anchor. Section folders use `README.md`. The banner identifies the repository and section; its button row has ↑ parent on the left and Repository/Organization on the right. Organization comes first in the HTML because both right-side buttons use `align="right"`. All navigation buttons are 160 px wide; native phone wrapping is accepted. The Organization label stays Organization. Existing root banners stay unchanged, with a right-aligned Organization button underneath and the stable `#project-overview` anchor. Header and section-list blocks are owned exclusively by `python tools/snodec_sync page-headers`; do not hand-edit them. Asset notes and coding-agent instructions are linked documentation, not presentation pages. +Supporting pages enrolled in `navigation/plan.yml` use the generated `page-header` block and a stable `#banner` anchor immediately before the slim banner. Root and profile pages put the same anchor before their tall banner. Section folders use `README.md`. The banner row places ↑ parent on the left, then named repository, SNode.C organization and Volker Christian account buttons on the right, each with its corresponding Octicon. Right floats use reverse HTML order to retain that visible order. Hover titles and accessible image labels distinguish the SNode.C repository and organization. Identity buttons use 36×28 icons below 640 px; desktop widths accommodate the names. All page-level and Up links target the destination banner when it exists; intentional section, code, release, download and external-documentation links keep their targets. Existing overview anchors remain valid. Header and section-list blocks are owned exclusively by `python tools/snodec_sync page-headers`; do not hand-edit them. Asset notes and coding-agent instructions are linked documentation, not presentation pages. Docs indexes retain the common menu as route maps. Individual L2 guides use the generated page header for return navigation and only relevant local navigation, not another complete README menu or a self-linking Build button. Standard menu targets are available on preview branches; preserve meaningful existing anchors when they still name substantive sections. Do not fabricate compatibility or platform support from version numbers alone. -The generated Repository, Organization and ↑ parent buttons provide return navigation; do not add duplicate Back blocks. User guides and implementation/qualification records occupy separately labelled groups in docs indexes; historical records do not define current product behavior. +The generated repository, organization, account and ↑ parent buttons provide return navigation; do not add duplicate Back blocks. User guides and implementation/qualification records occupy separately labelled groups in docs indexes; historical records do not define current product behavior. ## Navigation and tables