diff --git a/builder/mac-mini-dashboard/index.html b/builder/mac-mini-dashboard/index.html index 72ec3ad..9afa1b5 100644 --- a/builder/mac-mini-dashboard/index.html +++ b/builder/mac-mini-dashboard/index.html @@ -52,6 +52,7 @@ .nav-pills::-webkit-scrollbar { display: none; } .nav-pill { flex: 0 0 auto; padding: 10px 24px; border-radius: 10px; font-size: 14px; font-weight: 500; color: var(--text2); cursor: pointer; border: none; background: none; transition: all 0.2s; display: flex; align-items: center; gap: 8px; white-space: nowrap; } .nav-pill:hover { color: var(--text); } +.nav-pill:focus-visible { outline: 3px solid rgba(234,179,8,0.38); outline-offset: 2px; } .nav-pill.active { background: var(--card); color: var(--text); box-shadow: 0 1px 4px rgba(0,0,0,0.06); } .nav-pill .pill-icon { font-size: 16px; } .nav-pill .pill-badge { font-size: 11px; background: var(--accent-bg); color: var(--accent); padding: 2px 8px; border-radius: 6px; font-weight: 600; } @@ -182,6 +183,165 @@ .section { display: none; } .section.active { display: block; } +/* Platforms */ +.platforms-header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 20px; + align-items: end; + margin-bottom: 22px; + padding-bottom: 18px; + border-bottom: 1px solid var(--border2); +} +.platforms-eyebrow { + color: var(--accent); + font-size: 11px; + font-weight: 800; + letter-spacing: 0.9px; + text-transform: uppercase; +} +.platforms-title { + margin-top: 5px; + font-size: 26px; + font-weight: 800; + letter-spacing: -0.4px; +} +.platforms-copy { + max-width: 620px; + margin-top: 6px; + color: var(--text2); + font-size: 13px; + line-height: 1.55; +} +.platforms-legend { + display: flex; + gap: 8px; + flex-wrap: wrap; + justify-content: flex-end; +} +.platforms-legend span, +.platform-access { + display: inline-flex; + align-items: center; + width: fit-content; + border: 1px solid var(--border2); + border-radius: 6px; + background: var(--card); + color: var(--text2); + padding: 4px 7px; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.25px; + text-transform: uppercase; +} +.platforms-legend .private, +.platform-access.private { background: var(--accent-bg); color: #9A7100; border-color: rgba(234,179,8,0.28); } +.platforms-legend .public, +.platform-access.public { background: var(--green-bg); color: #16803A; border-color: rgba(34,197,94,0.24); } +.platform-directory { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + column-gap: 32px; + row-gap: 0; + border-top: 1px solid var(--border2); +} +.platform-entry { + min-width: 0; + display: grid; + grid-template-columns: 38px minmax(0, 1fr); + gap: 14px; + padding: 19px 4px; + border-bottom: 1px solid var(--border2); +} +.platform-entry.priority { + position: relative; + padding-left: 14px; + background: linear-gradient(90deg, var(--accent-bg), transparent 62%); +} +.platform-entry.priority::before { + content: ''; + position: absolute; + top: 14px; + bottom: 14px; + left: 0; + width: 3px; + border-radius: 2px; + background: var(--accent); +} +.platform-index { + color: var(--muted); + font-family: 'SF Mono', 'JetBrains Mono', monospace; + font-size: 11px; + font-weight: 700; + line-height: 22px; +} +.platform-entry.priority .platform-index { color: #9A7100; } +.platform-content { min-width: 0; } +.platform-name { + font-size: 15px; + font-weight: 750; + line-height: 1.4; +} +.platform-description { + margin-top: 3px; + color: var(--text2); + font-size: 12px; + line-height: 1.45; +} +.platform-links { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; +} +.platform-link { + display: inline-flex; + align-items: center; + gap: 6px; + min-height: 34px; + border-bottom: 1px solid currentColor; + color: var(--text); + font-size: 12px; + font-weight: 700; + line-height: 1.25; + text-decoration: none; + transition: color 0.15s ease, transform 0.15s ease; +} +.platform-link::after { content: '\2197'; color: var(--muted); } +.platform-link:hover { color: #9A7100; transform: translateY(-1px); } +.platform-link:focus-visible { + border-radius: 4px; + outline: 3px solid rgba(234,179,8,0.34); + outline-offset: 4px; +} +.platform-note { + grid-column: 1 / -1; + margin-top: 18px; + color: var(--muted); + font-size: 11px; + line-height: 1.5; +} + +@media (max-width: 760px) { + .platforms-header { grid-template-columns: 1fr; align-items: start; gap: 12px; } + .platforms-title { font-size: 22px; } + .platforms-legend { justify-content: flex-start; } + .platform-directory { grid-template-columns: 1fr; } + .platform-entry { padding-left: 2px; padding-right: 2px; } + .platform-entry.priority { padding-left: 14px; } + .platform-links { align-items: flex-start; flex-direction: column; } + .platform-link { max-width: 100%; overflow-wrap: anywhere; } +} + +@media (max-width: 360px) { + .platform-entry { grid-template-columns: 28px minmax(0, 1fr); gap: 8px; } +} + +@media (prefers-reduced-motion: reduce) { + .platform-link { transition: none; } + .platform-link:hover { transform: none; } +} + /* Footer */ .footer { margin-top: 32px; text-align: center; font-size: 12px; color: var(--muted); display: flex; align-items: center; justify-content: center; gap: 8px; } .live-dot { width: 6px; height: 6px; background: var(--green); border-radius: 50%; animation: pulse-green 2s infinite; } @@ -2586,24 +2746,127 @@

Hello, Maxim

- + + + + +
+
+
+
Common Center directory
+

Platforms

+

One quiet index for the products, private admin surfaces and Telegram bots that remain active while the Mac Mini is offline.

+
+
+ Private ยท SSH + Public link +
+
+ +
+ + + + +
+ 03 +
+

Health OS

+

Health workspace. Availability is checked after opening the public route.

+ +
+
+ + + + + + + +
+ 07 +
+

JARVIS

+

Personal assistant interface in Telegram.

+ +
+
+ +

Private links use the local loopback address and become available only through your authenticated SSH tunnel. This directory does not claim live health.

+
+
@@ -3179,18 +3442,26 @@

Hello, Maxim

// Navigation function showSection(name) { document.querySelectorAll('.section').forEach(s => s.classList.remove('active')); - document.querySelectorAll('.nav-pill').forEach(p => p.classList.remove('active')); - document.getElementById('section-' + name).classList.add('active'); + document.querySelectorAll('.nav-pill').forEach(p => { + p.classList.remove('active'); + p.setAttribute('aria-pressed', 'false'); + }); + const section = document.getElementById('section-' + name); + if (!section) return; + section.classList.add('active'); const trigger = (typeof event !== 'undefined' && event.currentTarget) ? event.currentTarget : Array.from(document.querySelectorAll('.nav-pill')) .find(p => p.getAttribute('onclick') === `showSection('${name}')`); - if (trigger) trigger.classList.add('active'); + if (trigger) { + trigger.classList.add('active'); + trigger.setAttribute('aria-pressed', 'true'); + } if (name === 'logs' && !currentLogService) loadLogTabs(); } const INITIAL_SECTION = new URLSearchParams(window.location.search).get('tab'); -if (['mac-mini', 'air', 'pro', 'agents'].includes(INITIAL_SECTION)) { +if (['mac-mini', 'air', 'pro', 'agents', 'platforms'].includes(INITIAL_SECTION)) { showSection(INITIAL_SECTION); } diff --git a/builder/tests/test_command_center_platforms.py b/builder/tests/test_command_center_platforms.py new file mode 100644 index 0000000..e70cb28 --- /dev/null +++ b/builder/tests/test_command_center_platforms.py @@ -0,0 +1,207 @@ +from __future__ import annotations + +from html.parser import HTMLParser +from pathlib import Path +import re +import unittest +from urllib.parse import urlsplit + + +DASHBOARD_PATH = ( + Path(__file__).resolve().parents[1] / "mac-mini-dashboard" / "index.html" +) + +EXPECTED_PLATFORMS = { + "financial": ( + "JobRadar / Financial OS", + ( + "http://127.0.0.1:8792", + "https://t.me/JobsRadarS_bot", + ), + ), + "mydictionary": ( + "MyDictionary", + ( + "http://127.0.0.1:8787/admin", + "https://t.me/max_context_bot", + ), + ), + "health": ("Health OS", ("https://health.meshly.fr/",)), + "ai-singularity": ( + "AI Singularity", + ("http://127.0.0.1:8001",), + ), + "context-news": ( + "Context News France", + ( + "http://127.0.0.1:8002", + "https://t.me/croissantfr_bot", + ), + ), + "accountable": ( + "Accountable OS", + ("http://127.0.0.1:4174",), + ), + "jarvis": ("JARVIS", ("https://t.me/max_jarvis_hoian_bot",)), +} + + +class _CommandCenterParser(HTMLParser): + def __init__(self) -> None: + super().__init__() + self.nav_targets: dict[str, str] = {} + self.platform_section_count = 0 + self.platforms: dict[str, dict[str, object]] = {} + self._nav_target: str | None = None + self._nav_text: list[str] = [] + self._platform_id: str | None = None + self._platform_depth = 0 + + def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: + attributes = dict(attrs) + classes = (attributes.get("class") or "").split() + + onclick = attributes.get("onclick") or "" + nav_target = re.fullmatch(r"showSection\(['\"]([^'\"]+)['\"]\)", onclick) + if tag == "button" and "nav-pill" in classes and nav_target: + self._nav_target = nav_target.group(1) + self._nav_text = [] + + if attributes.get("id") == "section-platforms": + self.platform_section_count += 1 + + platform_id = attributes.get("data-platform-id") + if platform_id is not None: + self._platform_id = platform_id + self._platform_depth = 1 + self.platforms[platform_id] = { + "name": attributes.get("data-platform-name"), + "links": [], + } + elif self._platform_id is not None: + self._platform_depth += 1 + + if tag == "a" and self._platform_id is not None: + links = self.platforms[self._platform_id]["links"] + assert isinstance(links, list) + links.append(attributes) + + def handle_data(self, data: str) -> None: + if self._nav_target is not None: + self._nav_text.append(data) + + def handle_endtag(self, tag: str) -> None: + if tag == "button" and self._nav_target is not None: + self.nav_targets[self._nav_target] = " ".join( + "".join(self._nav_text).split() + ) + self._nav_target = None + self._nav_text = [] + + if self._platform_id is not None: + self._platform_depth -= 1 + if self._platform_depth == 0: + self._platform_id = None + + +class CommandCenterPlatformsTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.html = DASHBOARD_PATH.read_text(encoding="utf-8") + cls.parser = _CommandCenterParser() + cls.parser.feed(cls.html) + + def test_historical_command_center_identity_is_preserved(self): + self.assertIn("Hello, Maxim", self.html) + for target, label in ( + ("mac-mini", "Mac Mini"), + ("air", "Air"), + ("pro", "Pro"), + ("agents", "Agents"), + ): + self.assertIn(target, self.parser.nav_targets) + self.assertIn(label, self.parser.nav_targets[target]) + + def test_platforms_is_a_reachable_top_level_surface(self): + self.assertEqual(self.parser.platform_section_count, 1) + self.assertIn("platforms", self.parser.nav_targets) + self.assertIn("Platforms", self.parser.nav_targets["platforms"]) + + initial_guard = re.search( + r"\[([^\]]+)\]\.includes\(INITIAL_SECTION\)", + self.html, + ) + self.assertIsNotNone(initial_guard, "INITIAL_SECTION needs a safe allowlist") + allowed = re.findall(r"['\"]([^'\"]+)['\"]", initial_guard.group(1)) + self.assertIn("platforms", allowed) + + def test_platform_directory_has_exact_registry_and_safe_destinations(self): + self.assertEqual(set(self.parser.platforms), set(EXPECTED_PLATFORMS)) + + for platform_id, (expected_name, expected_hrefs) in EXPECTED_PLATFORMS.items(): + with self.subTest(platform=platform_id): + platform = self.parser.platforms[platform_id] + self.assertEqual(platform["name"], expected_name) + links = platform["links"] + assert isinstance(links, list) + self.assertEqual( + tuple(link.get("href") for link in links), + expected_hrefs, + ) + for link in links: + self.assertEqual(link.get("target"), "_blank") + self.assertEqual( + set((link.get("rel") or "").split()), + {"noopener", "noreferrer"}, + ) + + href = link.get("href") or "" + parsed = urlsplit(href) + self.assertIsNone(parsed.username) + self.assertIsNone(parsed.password) + self.assertEqual(parsed.query, "") + self.assertEqual(parsed.fragment, "") + self.assertNotIn("51.255.36.141", href) + self.assertNotRegex( + href, + r"(?i)(?:file:|/Users/|/home/|/srv/)", + ) + if parsed.hostname == "127.0.0.1": + self.assertEqual(parsed.scheme, "http") + else: + self.assertEqual(parsed.scheme, "https") + self.assertIn( + parsed.hostname, + {"t.me", "health.meshly.fr"}, + ) + + def test_platform_directory_is_responsive_keyboard_safe_and_motion_safe(self): + self.assertTrue( + 'class="platform-directory"' in self.html, + "Platforms needs the platform-directory layout hook", + ) + self.assertTrue( + re.search( + r"@media\s*\([^)]*max-width[^)]*\)[\s\S]*?\.platform-directory", + self.html, + ) + is not None, + "platform-directory needs a responsive breakpoint", + ) + self.assertTrue( + re.search(r"\.platform-link:focus-visible\s*\{", self.html) is not None, + "platform links need a visible keyboard focus treatment", + ) + self.assertTrue( + re.search( + r"@media\s*\(prefers-reduced-motion:\s*reduce\)\s*\{" + r"[\s\S]*?\.platform-link", + self.html, + ) + is not None, + "platform link motion needs a reduced-motion override", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/builder/tests/test_department_campus_visual_integration.py b/builder/tests/test_department_campus_visual_integration.py index 4088ea1..3ff8a12 100644 --- a/builder/tests/test_department_campus_visual_integration.py +++ b/builder/tests/test_department_campus_visual_integration.py @@ -180,7 +180,7 @@ def _css_declarations(self, selector_fragment: str) -> str: matches.append(declarations) return "\n".join(matches) - def test_ac_1_top_navigation_contains_only_the_four_public_surfaces(self): + def test_ac_1_top_navigation_contains_only_the_five_public_surfaces(self): parser = self._dashboard() self.assertEqual( @@ -190,6 +190,7 @@ def test_ac_1_top_navigation_contains_only_the_four_public_surfaces(self): ("air", "Air"), ("pro", "Pro"), ("agents", "Agents"), + ("platforms", "Platforms"), ], ) @@ -210,7 +211,10 @@ def test_ac_2_agents_is_default_and_legacy_tabs_cannot_be_selected(self): "query-string tab restoration must retain an explicit safe allowlist", ) allowed = re.findall(r"['\"]([^'\"]+)['\"]", initial_guard.group(1)) - self.assertEqual(allowed, ["mac-mini", "air", "pro", "agents"]) + self.assertEqual( + allowed, + ["mac-mini", "air", "pro", "agents", "platforms"], + ) def test_ac_3_hq_keeps_its_id_but_uses_the_new_visible_russian_copy(self): structure = self._campus_structure()