From f54882a0705938815a9d0a6748a52069fdb10a16 Mon Sep 17 00:00:00 2001 From: Volker Christian Date: Wed, 14 Oct 2026 16:26:39 +0200 Subject: [PATCH] fix: resolve organization profile images in GitHub Mobile --- profile/README.md | 16 ++++++++-------- profile/template.md | 18 +++++++++--------- tests/test_navigation.py | 16 ++++++++++++++++ tests/test_sync.py | 8 +++++--- tools/snodec_sync/navigation.py | 7 +++---- 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/profile/README.md b/profile/README.md index c9be240..53e3ce4 100644 --- a/profile/README.md +++ b/profile/README.md @@ -1,16 +1,16 @@

- - SNode.C — one event loop, a whole ecosystem: SNode.C, MQTTSuite, AISuite, CodexUI, Packages, the Book Companion and community projects. + + SNode.C — one event loop, a whole ecosystem: SNode.C, MQTTSuite, AISuite, CodexUI, Packages, the Book Companion and community projects.

# SNode.C - - Organization · Ecosystem + + Organization · Ecosystem @@ -23,10 +23,10 @@ SNode.C builds event-driven networking in C++. At its centre is [SNode.C](https:

-Products -Install -Learn -Contribute +Products +Install +Learn +Contribute

diff --git a/profile/template.md b/profile/template.md index 5760f1a..c9fca2f 100644 --- a/profile/template.md +++ b/profile/template.md @@ -1,16 +1,16 @@ - +

- - SNode.C — one event loop, a whole ecosystem: SNode.C, MQTTSuite, AISuite, CodexUI, Packages, the Book Companion and community projects. + + SNode.C — one event loop, a whole ecosystem: SNode.C, MQTTSuite, AISuite, CodexUI, Packages, the Book Companion and community projects.

# SNode.C - - Organization · Ecosystem + + Organization · Ecosystem @@ -23,10 +23,10 @@ SNode.C builds event-driven networking in C++. At its centre is [SNode.C](https:

-Products -Install -Learn -Contribute +Products +Install +Learn +Contribute

diff --git a/tests/test_navigation.py b/tests/test_navigation.py index f94152b..879f0ed 100644 --- a/tests/test_navigation.py +++ b/tests/test_navigation.py @@ -74,6 +74,22 @@ def test_control_repository_uses_declared_navigation(self): self.assertIn('SNode.C', (repo / 'docs/media/nav/up-snodec-light.svg').read_text()) self.assertEqual(page_headers(repo, check=True), []) + def test_absolute_profile_menu_keeps_local_generation_and_url(self): + repo = self.fixture('.github') + asset = repo / 'profile/media/menu/products.svg' + asset.parent.mkdir(parents=True) + asset.write_text(_menu_svg('Products', 274)) + url = 'https://raw.githubusercontent.com/SNodeC/.github/refs/heads/main/profile/media/menu/products.svg' + foreign = 'https://example.com/menu.svg' + profile = repo / 'profile/README.md' + profile.write_text(f'# Organization\n\n\n' + f'\n') + page_headers(repo) + self.assertIn('width="160" height="28"', asset.read_text()) + self.assertIn(f'src="{url}" width="160" height="28"', profile.read_text()) + self.assertIn(f'src="{foreign}" width="274" height="28"', profile.read_text()) + self.assertEqual(page_headers(repo, check=True), []) + def test_plan_chains_and_label_limit(self): validate_plan(self.plan) invalid = copy.deepcopy(self.plan) diff --git a/tests/test_sync.py b/tests/test_sync.py index 15ae615..19d388c 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -240,11 +240,13 @@ def test_template_markers_are_exactly_once_and_standalone(self): with self.assertRaisesRegex(ValueError, 'exactly one standalone'): render_profile(self.catalog, self.metadata, template) - def test_relative_media_paths_exist(self): - paths = re.findall(r'(?:src|srcset)="(media/[^"\s]+)"', self.profile) + def test_profile_images_use_absolute_public_urls_and_owned_assets(self): + prefix = 'https://raw.githubusercontent.com/SNodeC/.github/refs/heads/main/profile/' + paths = re.findall(r'(?:src|srcset)="([^"\s]+)"', self.profile) self.assertTrue(paths) for path in paths: - self.assertTrue((ROOT / 'profile' / path).is_file(), path) + self.assertTrue(path.startswith(prefix), path) + self.assertTrue((ROOT / 'profile' / path.removeprefix(prefix)).is_file(), path) def test_metadata_is_separate_and_complete(self): self.assertEqual(set(self.metadata), {e['name'] for e in self.catalog}) diff --git a/tools/snodec_sync/navigation.py b/tools/snodec_sync/navigation.py index 1831184..959f1a9 100644 --- a/tools/snodec_sync/navigation.py +++ b/tools/snodec_sync/navigation.py @@ -266,10 +266,9 @@ def anchor(match): text = re.sub(r'\[!\[([^\]]*)\]\(([^)]+)\)\]\(([^)]+)\)', lambda m: '' if _up_link(m[2], m[3], path, ancestors) else m[0], text) for src in re.findall(r'(?:src="|!\[[^\]]*\]\()([^"\s)]+\.svg)', text): - if src.startswith(('https:', 'http:', '/')): - continue - asset = posixpath.normpath(posixpath.join(posixpath.dirname(path), src)) - if '..' in PurePosixPath(asset).parts or not (checkout / asset).is_file(): + asset = posixpath.normpath(posixpath.join(posixpath.dirname(path), + src.removeprefix('https://raw.githubusercontent.com/SNodeC/.github/refs/heads/main/profile/') if path == 'profile/README.md' else src)) + if asset.startswith('/') or ':/' in asset or '..' in PurePosixPath(asset).parts or not (checkout / asset).is_file(): continue svg = ET.parse(checkout / asset).getroot() if svg.attrib.get('height') != '28' or 'nav' in PurePosixPath(asset).parts: