============================================================ SHiRE Brain Node 01 — Laptop UI Inspection ============================================================ UTC timestamp: 2026-08-05T02:51:27+00:00 Host: armoros User: shire3d Repository: /home/shire3d/ARMOR === Current source integrity === d2deff5f4bfe2e2e45bee530e8c3785ea827714f688327362464c453d96c1439 /home/shire3d/ARMOR/main.py 9b815a8ad9937cfe19f4280ce2d2a642c1ab73bd66ab02463d48d296819cb05b /home/shire3d/ARMOR/modules/laptop_dashboard.py cd511fbfefe3b87f1d8c8994a4af492722543bc5eb3c314d5bfff03297502df4 /home/shire3d/ARMOR/modules/shire.py 3351b2972db9ced6f5e978ddce4ed130cdd948c2ae4262a34921fa9b24e64da5 /home/shire3d/ARMOR/modules/dashboard.py 2e9c68e28cfa466e7f2eec36f5443bef60674adb1e32bfb5415ad4df17b84394 /home/shire3d/ARMOR/framework/theme.py 44c99960c00439ee4545eb95df4c980eceb9695a247713b607fce5dfca6c7cfe /home/shire3d/ARMOR/framework/warden_theme.py === Preserving inspected source copies === === Python and Qt runtime === Python 3.12.3 (main, Jun 19 2026, 12:46:00) [GCC 13.3.0] { "pyqt5": true, "required_animation_components": true, "components": [ "QTimer", "QPainter", "QPainterPath", "QRadialGradient", "QPen", "QColor", "QWidget", "QFrame", "QLabel" ] } === UI source structure === { "dashboard": { "classes": [ { "end_line": 141, "line": 19, "methods": [ { "end_line": 61, "line": 22, "name": "__init__" }, { "end_line": 72, "line": 63, "name": "background_for_state" }, { "end_line": 119, "line": 74, "name": "apply_state" }, { "end_line": 123, "line": 121, "name": "set_value" }, { "end_line": 128, "line": 125, "name": "mousePressEvent" }, { "end_line": 141, "line": 130, "name": "mouseReleaseEvent" } ], "name": "MissionStatusTile" }, { "end_line": 903, "line": 144, "methods": [ { "end_line": 348, "line": 145, "name": "__init__" }, { "end_line": 354, "line": 350, "name": "paintEvent" }, { "end_line": 365, "line": 356, "name": "temp_state" }, { "end_line": 372, "line": 367, "name": "update_log" }, { "end_line": 383, "line": 374, "name": "set_smart_detail" }, { "end_line": 390, "line": 385, "name": "clear_quick_actions" }, { "end_line": 399, "line": 392, "name": "add_quick_action" }, { "end_line": 411, "line": 401, "name": "set_quick_actions" }, { "end_line": 418, "line": 413, "name": "open_station" }, { "end_line": 428, "line": 420, "name": "open_view_stls_launcher" }, { "end_line": 443, "line": 430, "name": "show_default_actions" }, { "end_line": 450, "line": 445, "name": "show_heart_oath" }, { "end_line": 466, "line": 452, "name": "refresh_caretaker" }, { "end_line": 499, "line": 468, "name": "show_memory_detail" }, { "end_line": 536, "line": 501, "name": "show_cooling_detail" }, { "end_line": 564, "line": 538, "name": "auto_cool_quick" }, { "end_line": 573, "line": 566, "name": "auto_cooling_on_quick" }, { "end_line": 582, "line": 575, "name": "auto_cooling_off_quick" }, { "end_line": 605, "line": 584, "name": "set_fan_quick" }, { "end_line": 627, "line": 607, "name": "start_night_forge_quick" }, { "end_line": 634, "line": 629, "name": "standby_agents_quick" }, { "end_line": 745, "line": 636, "name": "handle_status_tile" }, { "end_line": 752, "line": 747, "name": "health_colour" }, { "end_line": 857, "line": 754, "name": "update_health_summary" }, { "end_line": 903, "line": 859, "name": "update_status" } ], "name": "Dashboard" } ], "functions": [], "imports": [ "PyQt5.QtWidgets.*", "PyQt5.QtCore.*", "PyQt5.QtGui.*", "services.system_monitor.get_system_snapshot", "services.caretaker_service.caretaker", "core.version.get_display_name", "core.version.get_footer", "core.event_bus.event_bus", "core.service_manager.service_manager", "core.task_manager.task_manager", "core.agent_manager.agent_manager", "core.shire_runtime.shire_runtime", "framework.warden_theme.*", "framework.warden_widgets.*", "framework.warden_scroll.WardenTouchScrollArea", "time", "core.cooling_manager.cooling_manager" ], "lines": 903, "path": "/home/shire3d/ARMOR/modules/dashboard.py" }, "laptop_dashboard": { "classes": [ { "end_line": 146, "line": 54, "methods": [ { "end_line": 91, "line": 57, "name": "__init__" }, { "end_line": 146, "line": 93, "name": "refresh" } ], "name": "VoicePrivacyBanner" }, { "end_line": 206, "line": 149, "methods": [ { "end_line": 202, "line": 152, "name": "__init__" }, { "end_line": 206, "line": 204, "name": "open_target" } ], "name": "LaptopRuneButton" }, { "end_line": 235, "line": 209, "methods": [ { "end_line": 235, "line": 210, "name": "__init__" } ], "name": "PresenceCard" }, { "end_line": 785, "line": 238, "methods": [ { "end_line": 348, "line": 265, "name": "__init__" }, { "end_line": 368, "line": 350, "name": "pulse" }, { "end_line": 373, "line": 370, "name": "_finish_blink" }, { "end_line": 384, "line": 375, "name": "_schedule_next_blink" }, { "end_line": 404, "line": 386, "name": "set_expression_state" }, { "end_line": 415, "line": 406, "name": "current_expression_state" }, { "end_line": 423, "line": 417, "name": "_current_face" }, { "end_line": 433, "line": 425, "name": "showEvent" }, { "end_line": 445, "line": 435, "name": "hideEvent" }, { "end_line": 449, "line": 447, "name": "resizeEvent" }, { "end_line": 501, "line": 451, "name": "_scaled_face_for" }, { "end_line": 785, "line": 503, "name": "paintEvent" } ], "name": "ShireCenterCore" }, { "end_line": 926, "line": 788, "methods": [ { "end_line": 803, "line": 791, "name": "__init__" }, { "end_line": 926, "line": 805, "name": "paintEvent" } ], "name": "VoiceOrb" }, { "end_line": 1355, "line": 929, "methods": [ { "end_line": 937, "line": 932, "name": "__init__" }, { "end_line": 943, "line": 939, "name": "_open_station" }, { "end_line": 967, "line": 945, "name": "_make_nav_button" }, { "end_line": 971, "line": 969, "name": "_toggle_stations" }, { "end_line": 1000, "line": 973, "name": "_build_greeting_card" }, { "end_line": 1036, "line": 1002, "name": "_build_status_card" }, { "end_line": 1076, "line": 1038, "name": "_build_thought_card" }, { "end_line": 1099, "line": 1078, "name": "_build_next_card" }, { "end_line": 1144, "line": 1101, "name": "_build_station_drawer" }, { "end_line": 1294, "line": 1146, "name": "build_ui" }, { "end_line": 1355, "line": 1296, "name": "paintEvent" } ], "name": "LaptopDashboard" } ], "functions": [ { "end_line": 51, "line": 42, "name": "_greeting" } ], "imports": [ "datetime.datetime", "math", "pathlib.Path", "PyQt5.QtCore.Qt", "PyQt5.QtCore.QRectF", "PyQt5.QtCore.QTimer", "PyQt5.QtGui.QBrush", "PyQt5.QtGui.QColor", "PyQt5.QtGui.QFont", "PyQt5.QtGui.QLinearGradient", "PyQt5.QtGui.QPainter", "PyQt5.QtGui.QPainterPath", "PyQt5.QtGui.QPen", "PyQt5.QtGui.QPixmap", "PyQt5.QtGui.QRadialGradient", "PyQt5.QtWidgets.QFrame", "PyQt5.QtWidgets.QGridLayout", "PyQt5.QtWidgets.QHBoxLayout", "PyQt5.QtWidgets.QLabel", "PyQt5.QtWidgets.QPushButton", "PyQt5.QtWidgets.QSizePolicy", "PyQt5.QtWidgets.QVBoxLayout", "PyQt5.QtWidgets.QWidget", "core.voice_privacy.voice_privacy_foundation" ], "lines": 1355, "path": "/home/shire3d/ARMOR/modules/laptop_dashboard.py" }, "main": { "classes": [ { "end_line": 141, "line": 73, "methods": [ { "end_line": 80, "line": 76, "name": "__init__" }, { "end_line": 93, "line": 82, "name": "keyPressEvent" }, { "end_line": 99, "line": 95, "name": "mousePressEvent" }, { "end_line": 125, "line": 101, "name": "mouseReleaseEvent" }, { "end_line": 133, "line": 127, "name": "next_station" }, { "end_line": 141, "line": 135, "name": "previous_station" } ], "name": "ArmorStationStack" }, { "end_line": 217, "line": 144, "methods": [ { "end_line": 217, "line": 147, "name": "__init__" } ], "name": "ArmorOS" } ], "functions": [ { "end_line": 29, "line": 7, "name": "armor_display_profile" }, { "end_line": 233, "line": 220, "name": "main" } ], "imports": [ "os", "socket", "sys", "PyQt5.QtCore.Qt", "PyQt5.QtWidgets.QApplication", "PyQt5.QtWidgets.QStackedWidget", "PyQt5.QtWidgets.QVBoxLayout", "PyQt5.QtWidgets.QWidget", "core.version.get_window_title", "modules.dashboard.Dashboard", "modules.laptop_dashboard.LaptopDashboard", "modules.shire.ShirePage", "modules.forge.ForgePage", "modules.archive.ArchivePage", "modules.atlas.AtlasPage", "modules.media.MediaPage", "modules.system.SystemPage", "modules.sentinel.SentinelPage", "modules.education.EducationPage", "modules.agents.AgentsPage", "modules.tasks.TasksPage", "modules.settings.SettingsPage", "modules.global_prompt_bar.GlobalPromptBar" ], "lines": 237, "path": "/home/shire3d/ARMOR/main.py" }, "shire": { "classes": [ { "end_line": 506, "line": 247, "methods": [ { "end_line": 277, "line": 248, "name": "__init__" }, { "end_line": 292, "line": 279, "name": "mood_faces_enabled" }, { "end_line": 306, "line": 294, "name": "portrait_path_for_mode" }, { "end_line": 321, "line": 308, "name": "reload_portrait" }, { "end_line": 326, "line": 323, "name": "set_mode" }, { "end_line": 330, "line": 328, "name": "animate" }, { "end_line": 339, "line": 332, "name": "mode_color" }, { "end_line": 347, "line": 341, "name": "draw_talking_bars" }, { "end_line": 400, "line": 349, "name": "paint_fallback_visor" }, { "end_line": 494, "line": 402, "name": "paint_portrait_mode" }, { "end_line": 506, "line": 496, "name": "paintEvent" } ], "name": "ShireFaceWidget" }, { "end_line": 560, "line": 510, "methods": [ { "end_line": 516, "line": 514, "name": "__init__" }, { "end_line": 533, "line": 518, "name": "brain_api_url" }, { "end_line": 560, "line": 535, "name": "run" } ], "name": "ShireBrainAskWorker" }, { "end_line": 3869, "line": 563, "methods": [ { "end_line": 846, "line": 565, "name": "__init__" }, { "end_line": 860, "line": 848, "name": "run_shell" }, { "end_line": 863, "line": 862, "name": "yes_no" }, { "end_line": 867, "line": 865, "name": "file_check" }, { "end_line": 893, "line": 869, "name": "task_summary_counts" }, { "end_line": 913, "line": 895, "name": "format_task_line" }, { "end_line": 925, "line": 915, "name": "run_selected_action" }, { "end_line": 940, "line": 927, "name": "action_git_status" }, { "end_line": 950, "line": 942, "name": "action_auto_cool" }, { "end_line": 957, "line": 952, "name": "action_auto_cooling_on" }, { "end_line": 964, "line": 959, "name": "action_auto_cooling_off" }, { "end_line": 969, "line": 966, "name": "action_fan_max" }, { "end_line": 974, "line": 971, "name": "action_fan_off" }, { "end_line": 1001, "line": 976, "name": "task_review" }, { "end_line": 1022, "line": 1003, "name": "complete_night_forge_task" }, { "end_line": 1060, "line": 1024, "name": "collect_setup_context" }, { "end_line": 1134, "line": 1062, "name": "build_next_actions" }, { "end_line": 1158, "line": 1136, "name": "setup_plan" }, { "end_line": 1183, "line": 1160, "name": "create_setup_tasks" }, { "end_line": 1348, "line": 1185, "name": "build_roadmap_items" }, { "end_line": 1397, "line": 1350, "name": "daily_boot_briefing" }, { "end_line": 1432, "line": 1399, "name": "build_guide" }, { "end_line": 1449, "line": 1434, "name": "what_next" }, { "end_line": 1583, "line": 1451, "name": "session_journal" }, { "end_line": 1726, "line": 1585, "name": "setup_wizard_checks" }, { "end_line": 1762, "line": 1728, "name": "setup_wizard" }, { "end_line": 1786, "line": 1764, "name": "wizard_summary" }, { "end_line": 1891, "line": 1788, "name": "smart_setup_check" }, { "end_line": 1903, "line": 1893, "name": "system_status" }, { "end_line": 1913, "line": 1905, "name": "daily_notes" }, { "end_line": 1926, "line": 1918, "name": "shire_privacy_config" }, { "end_line": 1930, "line": 1928, "name": "hide_network_details" }, { "end_line": 1941, "line": 1932, "name": "redact_network_detail" }, { "end_line": 1952, "line": 1943, "name": "is_laptop_command_node" }, { "end_line": 1990, "line": 1955, "name": "shire_appliance_defaults" }, { "end_line": 1994, "line": 1992, "name": "shire_appliance_config_path" }, { "end_line": 2014, "line": 1996, "name": "merge_shire_appliance_config" }, { "end_line": 2026, "line": 2016, "name": "load_shire_appliances" }, { "end_line": 2033, "line": 2028, "name": "get_appliance" }, { "end_line": 2041, "line": 2035, "name": "shire_brain_url" }, { "end_line": 2063, "line": 2044, "name": "remote_power_targets" }, { "end_line": 2094, "line": 2065, "name": "remote_power_warning" }, { "end_line": 2140, "line": 2096, "name": "remote_powerdown_one" }, { "end_line": 2166, "line": 2142, "name": "remote_powerdown_confirmed" }, { "end_line": 2210, "line": 2170, "name": "safe_power_button_all" }, { "end_line": 2396, "line": 2215, "name": "appliance_registry_status" }, { "end_line": 2425, "line": 2398, "name": "set_node_badge_state" }, { "end_line": 2483, "line": 2428, "name": "refresh_node_badge" }, { "end_line": 2603, "line": 2486, "name": "utility_node_status" }, { "end_line": 2632, "line": 2605, "name": "set_brain_badge_state" }, { "end_line": 2648, "line": 2634, "name": "refresh_brain_badge" }, { "end_line": 2675, "line": 2652, "name": "shire_brain_status" }, { "end_line": 2718, "line": 2679, "name": "ask_laptop_brain" }, { "end_line": 2728, "line": 2720, "name": "on_laptop_brain_done" }, { "end_line": 2731, "line": 2730, "name": "on_laptop_brain_finished" }, { "end_line": 2744, "line": 2734, "name": "set_face_mode" }, { "end_line": 2750, "line": 2746, "name": "face_talking" }, { "end_line": 2757, "line": 2752, "name": "toggle_command_mode" }, { "end_line": 2761, "line": 2759, "name": "scroll_output" }, { "end_line": 2765, "line": 2763, "name": "scroll_output_bottom" }, { "end_line": 2784, "line": 2767, "name": "shire_append" }, { "end_line": 2802, "line": 2786, "name": "shire_identity" }, { "end_line": 2809, "line": 2804, "name": "shire_maker" }, { "end_line": 2816, "line": 2811, "name": "shire_origin" }, { "end_line": 2823, "line": 2818, "name": "shire_purpose" }, { "end_line": 2832, "line": 2825, "name": "shire_favourite_comic" }, { "end_line": 2859, "line": 2834, "name": "shire_wake_line" }, { "end_line": 2867, "line": 2861, "name": "shire_lock_message" }, { "end_line": 2918, "line": 2869, "name": "is_shire_locked_command" }, { "end_line": 3126, "line": 2920, "name": "smart_command_alias" }, { "end_line": 3869, "line": 3128, "name": "process_command" } ], "name": "ShirePage" } ], "functions": [ { "end_line": 81, "line": 21, "name": "show_forge_learning_popup" }, { "end_line": 173, "line": 99, "name": "show_forge_research_popup" }, { "end_line": 243, "line": 176, "name": "show_shire_research_celebration" } ], "imports": [ "json", "urllib.request", "urllib.error", "psutil", "PyQt5.QtWidgets.*", "PyQt5.QtCore.Qt", "PyQt5.QtCore.QTimer", "PyQt5.QtCore.QThread", "PyQt5.QtCore.pyqtSignal", "PyQt5.QtGui.QPainter", "PyQt5.QtGui.QColor", "PyQt5.QtGui.QPen", "PyQt5.QtGui.QBrush", "PyQt5.QtGui.QPixmap", "PyQt5.QtGui.QPainterPath", "PyQt5.QtGui.QLinearGradient", "framework.theme.*", "framework.buttons.ActionButton", "services.system_monitor.get_temp", "services.notifications.NotificationService", "services.memory.MemoryService", "services.tasks.TaskService", "services.security_service.security_service", "services.forge_learning_service.forge_learning_service", "services.blender_mastery_commands.blender_mastery_commands", "services.ai_service.AIService", "heart.heart.Heart", "heart.purpose.PurposeEngine", "sentinel.status.check_system", "sentinel.status.forge_score", "chronicle.journal.record_event", "chronicle.journal.session_report", "forge.tools.syntax_check", "forge.tools.backup_armor", "forge.tools.forge_console", "subprocess", "socket", "pathlib.Path", "datetime.datetime", "core.cooling_manager.cooling_manager", "core.task_manager.task_manager", "core.service_manager.service_manager", "framework.warden_scroll.WardenTouchScrollArea", "framework.warden_scroll.enable_touch_scroll", "framework.warden_scroll.enable_text_drag_scroll" ], "lines": 3869, "path": "/home/shire3d/ARMOR/modules/shire.py" } } === Laptop dashboard construction map === /home/shire3d/ARMOR/modules/laptop_dashboard.py:5:from PyQt5.QtCore import Qt, QRectF, QTimer /home/shire3d/ARMOR/modules/laptop_dashboard.py:11: QPainter, /home/shire3d/ARMOR/modules/laptop_dashboard.py:12: QPainterPath, /home/shire3d/ARMOR/modules/laptop_dashboard.py:18: QFrame, /home/shire3d/ARMOR/modules/laptop_dashboard.py:54:class VoicePrivacyBanner(QFrame): /home/shire3d/ARMOR/modules/laptop_dashboard.py:57: def __init__(self): /home/shire3d/ARMOR/modules/laptop_dashboard.py:87: self.timer = QTimer(self) /home/shire3d/ARMOR/modules/laptop_dashboard.py:123: QFrame#voicePrivacyBanner {{ /home/shire3d/ARMOR/modules/laptop_dashboard.py:149:class LaptopRuneButton(QPushButton): /home/shire3d/ARMOR/modules/laptop_dashboard.py:152: def __init__( /home/shire3d/ARMOR/modules/laptop_dashboard.py:209:class PresenceCard(QFrame): /home/shire3d/ARMOR/modules/laptop_dashboard.py:210: def __init__(self, title, accent=PURPLE): /home/shire3d/ARMOR/modules/laptop_dashboard.py:215: QFrame#presenceCard {{ /home/shire3d/ARMOR/modules/laptop_dashboard.py:238:class ShireCenterCore(QWidget): /home/shire3d/ARMOR/modules/laptop_dashboard.py:265: def __init__(self): /home/shire3d/ARMOR/modules/laptop_dashboard.py:332: self.blink_timer = QTimer(self) /home/shire3d/ARMOR/modules/laptop_dashboard.py:345: self.timer = QTimer(self) /home/shire3d/ARMOR/modules/laptop_dashboard.py:503: def paintEvent(self, event): /home/shire3d/ARMOR/modules/laptop_dashboard.py:506: painter = QPainter(self) /home/shire3d/ARMOR/modules/laptop_dashboard.py:508: QPainter.Antialiasing, /home/shire3d/ARMOR/modules/laptop_dashboard.py:512: QPainter.SmoothPixmapTransform, /home/shire3d/ARMOR/modules/laptop_dashboard.py:634: clip_path = QPainterPath() /home/shire3d/ARMOR/modules/laptop_dashboard.py:788:class VoiceOrb(QWidget): /home/shire3d/ARMOR/modules/laptop_dashboard.py:791: def __init__(self): /home/shire3d/ARMOR/modules/laptop_dashboard.py:801: self.timer = QTimer(self) /home/shire3d/ARMOR/modules/laptop_dashboard.py:805: def paintEvent(self, event): /home/shire3d/ARMOR/modules/laptop_dashboard.py:816: painter = QPainter(self) /home/shire3d/ARMOR/modules/laptop_dashboard.py:817: painter.setRenderHint(QPainter.Antialiasing, True) /home/shire3d/ARMOR/modules/laptop_dashboard.py:929:class LaptopDashboard(QWidget): /home/shire3d/ARMOR/modules/laptop_dashboard.py:932: def __init__(self, stack): /home/shire3d/ARMOR/modules/laptop_dashboard.py:973: def _build_greeting_card(self): /home/shire3d/ARMOR/modules/laptop_dashboard.py:1002: def _build_status_card(self): /home/shire3d/ARMOR/modules/laptop_dashboard.py:1038: def _build_thought_card(self): /home/shire3d/ARMOR/modules/laptop_dashboard.py:1078: def _build_next_card(self): /home/shire3d/ARMOR/modules/laptop_dashboard.py:1101: def _build_station_drawer(self): /home/shire3d/ARMOR/modules/laptop_dashboard.py:1102: drawer = QFrame() /home/shire3d/ARMOR/modules/laptop_dashboard.py:1107: QFrame#stationDrawer { /home/shire3d/ARMOR/modules/laptop_dashboard.py:1146: def build_ui(self): /home/shire3d/ARMOR/modules/laptop_dashboard.py:1200: left_column.addWidget(self._build_status_card(), 2) /home/shire3d/ARMOR/modules/laptop_dashboard.py:1296: def paintEvent(self, event): /home/shire3d/ARMOR/modules/laptop_dashboard.py:1299: painter = QPainter(self) /home/shire3d/ARMOR/modules/laptop_dashboard.py:1300: painter.setRenderHint(QPainter.Antialiasing, True) === Laptop dashboard numbered source === 1 from datetime import datetime 2 import math 3 from pathlib import Path 4 5 from PyQt5.QtCore import Qt, QRectF, QTimer 6 from PyQt5.QtGui import ( 7 QBrush, 8 QColor, 9 QFont, 10 QLinearGradient, 11 QPainter, 12 QPainterPath, 13 QPen, 14 QPixmap, 15 QRadialGradient, 16 ) 17 from PyQt5.QtWidgets import ( 18 QFrame, 19 QGridLayout, 20 QHBoxLayout, 21 QLabel, 22 QPushButton, 23 QSizePolicy, 24 QVBoxLayout, 25 QWidget, 26 ) 27 28 from core.voice_privacy import voice_privacy_foundation 29 30 31 GREEN = "#00f58a" 32 PURPLE = "#9b4dff" 33 PURPLE_LIGHT = "#c89cff" 34 CYAN = "#18e6d2" 35 WHITE = "#f4efff" 36 MUTED = "#a89bc4" 37 PANEL = "rgba(7, 7, 22, 225)" 38 PANEL_SOFT = "rgba(10, 7, 28, 190)" 39 BORDER = "rgba(155, 77, 255, 165)" 40 41 42 def _greeting(): 43 hour = datetime.now().hour 44 45 if hour < 12: 46 return "Good morning, Ray." 47 48 if hour < 18: 49 return "Good afternoon, Ray." 50 51 return "Good evening, Ray." 52 53 54 class VoicePrivacyBanner(QFrame): 55 """Live local voice privacy and runtime-state banner.""" 56 57 def __init__(self): 58 super().__init__() 59 60 self.setObjectName("voicePrivacyBanner") 61 self.setFixedHeight(52) 62 self.setMinimumWidth(310) 63 self.setSizePolicy( 64 QSizePolicy.Preferred, 65 QSizePolicy.Fixed, 66 ) 67 68 root = QHBoxLayout(self) 69 root.setContentsMargins(18, 6, 18, 6) 70 root.setSpacing(12) 71 72 self.icon = QLabel("◉") 73 self.icon.setAlignment(Qt.AlignCenter) 74 75 text_column = QVBoxLayout() 76 text_column.setSpacing(0) 77 78 self.top = QLabel() 79 self.bottom = QLabel() 80 81 text_column.addWidget(self.top) 82 text_column.addWidget(self.bottom) 83 84 root.addWidget(self.icon) 85 root.addLayout(text_column) 86 87 self.timer = QTimer(self) 88 self.timer.timeout.connect(self.refresh) 89 self.timer.start(400) 90 91 self.refresh() 92 93 def refresh(self): 94 snapshot = voice_privacy_foundation.snapshot() 95 state = str(snapshot.state).upper() 96 97 if state == "ERROR": 98 accent = "#ff5c70" 99 top_text = "VOICE ERROR" 100 bottom_text = snapshot.error or "VOICE POLICY ERROR" 101 102 elif state == "LISTENING": 103 accent = GREEN 104 top_text = "VOICE LISTENING" 105 bottom_text = "MICROPHONE OPEN • LOCAL ONLY" 106 107 elif state == "PROCESSING": 108 accent = CYAN 109 top_text = "VOICE PROCESSING" 110 bottom_text = "AUDIO DISCARDED • LOCAL ONLY" 111 112 elif state == "READY": 113 accent = PURPLE_LIGHT 114 top_text = "VOICE READY" 115 bottom_text = "MANUAL CAPTURE • LOCAL ONLY" 116 117 else: 118 accent = MUTED 119 top_text = "VOICE MODE OFF" 120 bottom_text = "MICROPHONE CLOSED" 121 122 self.setStyleSheet(f""" 123 QFrame#voicePrivacyBanner {{ 124 background: rgba(4, 7, 18, 220); 125 border: 1px solid {accent}; 126 border-radius: 13px; 127 }} 128 QLabel {{ 129 background: transparent; 130 border: none; 131 }} 132 """) 133 134 self.icon.setStyleSheet( 135 f"color:{accent}; font-size:24px; font-weight:900;" 136 ) 137 138 self.top.setText(top_text) 139 self.top.setStyleSheet( 140 f"color:{accent}; font-size:14px; font-weight:900;" 141 ) 142 143 self.bottom.setText(bottom_text) 144 self.bottom.setStyleSheet( 145 "color:#aa9dc6; font-size:9px; font-weight:800;" 146 ) 147 148 149 class LaptopRuneButton(QPushButton): 150 """Compact Presence Console navigation control.""" 151 152 def __init__( 153 self, 154 icon, 155 title, 156 sub, 157 target_index, 158 stack, 159 compact=False, 160 ): 161 super().__init__() 162 163 self.target_index = target_index 164 self.stack = stack 165 166 if compact: 167 self.setText(f"{icon} {title}") 168 self.setMinimumHeight(48) 169 else: 170 self.setText(f"{icon}\n{title}\n{sub}") 171 self.setMinimumHeight(74) 172 173 self.setCursor(Qt.PointingHandCursor) 174 self.setSizePolicy( 175 QSizePolicy.Expanding, 176 QSizePolicy.Fixed, 177 ) 178 179 self.setStyleSheet(""" 180 QPushButton { 181 background: rgba(8, 5, 25, 225); 182 color: #c69cff; 183 border: 1px solid rgba(155, 77, 255, 185); 184 border-radius: 13px; 185 font-family: "DejaVu Sans Mono", monospace; 186 font-size: 13px; 187 font-weight: 900; 188 padding: 8px 12px; 189 } 190 QPushButton:hover { 191 background: rgba(34, 9, 66, 235); 192 color: #ffffff; 193 border: 1px solid #b76cff; 194 } 195 QPushButton:pressed { 196 background: rgba(0, 245, 138, 65); 197 color: #00f58a; 198 border: 1px solid #00f58a; 199 } 200 """) 201 202 self.clicked.connect(self.open_target) 203 204 def open_target(self): 205 if self.stack is not None: 206 self.stack.setCurrentIndex(self.target_index) 207 208 209 class PresenceCard(QFrame): 210 def __init__(self, title, accent=PURPLE): 211 super().__init__() 212 213 self.setObjectName("presenceCard") 214 self.setStyleSheet(f""" 215 QFrame#presenceCard {{ 216 background: {PANEL}; 217 border: 1px solid {BORDER}; 218 border-radius: 18px; 219 }} 220 QLabel {{ 221 background: transparent; 222 border: none; 223 }} 224 """) 225 226 self.column = QVBoxLayout(self) 227 self.column.setContentsMargins(22, 18, 22, 18) 228 self.column.setSpacing(10) 229 230 heading = QLabel(title) 231 heading.setStyleSheet( 232 f"color:{accent}; font-size:12px; font-weight:900;" 233 ) 234 235 self.column.addWidget(heading) 236 237 238 class ShireCenterCore(QWidget): 239 """Face-first SHIRE presence with truthful expression states.""" 240 241 EXPRESSION_FILES = { 242 "neutral": "shire_neutral.png", 243 "blink_half": "shire_blink_half.png", 244 "blink_closed": "shire_blink_closed.png", 245 "smile": "shire_smile.png", 246 "thinking": "shire_thinking.png", 247 "listening": "shire_listening.png", 248 "focused": "shire_focused.png", 249 "acknowledge": "shire_acknowledge.png", 250 "concerned": "shire_concerned.png", 251 "surprised": "shire_surprised.png", 252 "sentinel_standby": "shire_sentinel_standby.png", 253 "sentinel_active": "shire_sentinel_active.png", 254 "sentinel_lockdown": "shire_sentinel_lockdown.png", 255 } 256 257 NORMAL_BLINK_STATES = { 258 "neutral", 259 "smile", 260 "focused", --- middle section --- 261 "acknowledge", 262 "concerned", 263 } 264 265 def __init__(self): 266 super().__init__() 267 268 self.root = Path(__file__).resolve().parent.parent 269 self.expression_dir = ( 270 self.root 271 / "assets" 272 / "shire" 273 / "expressions" 274 ) 275 276 self.fallback_face_path = ( 277 self.root 278 / "assets" 279 / "shire" 280 / "shire_face_main.png" 281 ) 282 283 self.fallback_face = ( 284 QPixmap(str(self.fallback_face_path)) 285 if self.fallback_face_path.exists() 286 else QPixmap() 287 ) 288 289 self.expression_faces = {} 290 291 for state, filename in self.EXPRESSION_FILES.items(): 292 path = self.expression_dir / filename 293 portrait = ( 294 QPixmap(str(path)) 295 if path.exists() 296 else QPixmap() 297 ) 298 299 if portrait.isNull(): 300 portrait = self.fallback_face 301 302 self.expression_faces[state] = portrait 303 304 self.face_path = ( 305 self.expression_dir 306 / self.EXPRESSION_FILES["neutral"] 307 ) 308 309 self.face = self.expression_faces["neutral"] 310 self.expression_state = "neutral" 311 312 self.tick = 0 313 self.animation_interval_ms = 125 314 self._face_cache = {} 315 316 self._blink_intervals = ( 317 38, 318 52, 319 44, 320 60, 321 34, 322 49, 323 ) 324 325 self._blink_interval_index = 0 326 self._blink_ticks_remaining = ( 327 self._blink_intervals[0] 328 ) 329 self._blink_phase = -1 330 self._blink_duration_ms = 90 331 332 self.blink_timer = QTimer(self) 333 self.blink_timer.setSingleShot(True) 334 self.blink_timer.setTimerType(Qt.PreciseTimer) 335 self.blink_timer.timeout.connect( 336 self._finish_blink 337 ) 338 339 self.setMinimumSize(390, 470) 340 self.setSizePolicy( 341 QSizePolicy.Expanding, 342 QSizePolicy.Expanding, 343 ) 344 345 self.timer = QTimer(self) 346 self.timer.setTimerType(Qt.CoarseTimer) 347 self.timer.timeout.connect(self.pulse) 348 self.timer.start(self.animation_interval_ms) 349 350 def pulse(self): 351 self.tick = (self.tick + 1) % 28800 352 353 if self.expression_state in self.NORMAL_BLINK_STATES: 354 if self._blink_phase < 0: 355 self._blink_ticks_remaining -= 1 356 357 if self._blink_ticks_remaining <= 0: 358 self._blink_phase = 0 359 self.blink_timer.start( 360 self._blink_duration_ms 361 ) 362 else: 363 if self.blink_timer.isActive(): 364 self.blink_timer.stop() 365 366 self._blink_phase = -1 367 368 self.update() 369 370 def _finish_blink(self): 371 self._blink_phase = -1 372 self._schedule_next_blink() 373 self.update() 374 375 def _schedule_next_blink(self): 376 self._blink_interval_index = ( 377 self._blink_interval_index + 1 378 ) % len(self._blink_intervals) 379 380 self._blink_ticks_remaining = ( 381 self._blink_intervals[ 382 self._blink_interval_index 383 ] 384 ) 385 386 def set_expression_state(self, state): 387 """Select one approved expression without inventing state.""" 388 389 state = str(state).strip().lower() 390 391 if state not in self.expression_faces: 392 return False 393 394 self.expression_state = state 395 self.face = self.expression_faces[state] 396 397 if self.blink_timer.isActive(): 398 self.blink_timer.stop() 399 400 self._blink_phase = -1 401 self._schedule_next_blink() 402 self.update() 403 404 return True 405 406 def current_expression_state(self): 407 """Return the portrait state currently being rendered.""" 408 409 if ( 410 self.expression_state in self.NORMAL_BLINK_STATES 411 and self._blink_phase == 0 412 ): 413 return "blink_closed" 414 415 return self.expression_state 416 417 def _current_face(self): 418 state = self.current_expression_state() 419 420 return self.expression_faces.get( 421 state, 422 self.fallback_face, 423 ) 424 425 def showEvent(self, event): 426 super().showEvent(event) 427 428 if not self.timer.isActive(): 429 self.timer.start( 430 self.animation_interval_ms 431 ) 432 433 self.update() 434 435 def hideEvent(self, event): 436 if self.timer.isActive(): 437 self.timer.stop() 438 439 if self.blink_timer.isActive(): 440 self.blink_timer.stop() 441 442 self._blink_phase = -1 443 self._schedule_next_blink() 444 445 super().hideEvent(event) 446 447 def resizeEvent(self, event): 448 self._face_cache.clear() 449 super().resizeEvent(event) 450 451 def _scaled_face_for( 452 self, 453 state, 454 portrait_size, 455 ): 456 face = self.expression_faces.get( 457 state, 458 self.fallback_face, 459 ) 460 461 if face.isNull(): 462 return QPixmap() 463 464 target_size = max( 465 1, 466 int( 467 math.ceil( 468 float(portrait_size) * 1.025 469 ) 470 ), 471 ) 472 473 cache_key = ( 474 state, 475 int(face.cacheKey()), 476 target_size, 477 ) 478 479 cached = self._face_cache.get( 480 cache_key 481 ) 482 483 if cached is not None and not cached.isNull(): 484 return cached 485 486 scaled = face.scaled( 487 target_size, 488 target_size, 489 Qt.KeepAspectRatioByExpanding, 490 Qt.SmoothTransformation, 491 ) 492 493 if scaled.isNull(): 494 return face 495 496 if len(self._face_cache) >= 18: 497 self._face_cache.clear() 498 499 self._face_cache[cache_key] = scaled 500 501 return scaled 502 503 def paintEvent(self, event): 504 del event 505 506 painter = QPainter(self) 507 painter.setRenderHint( 508 QPainter.Antialiasing, 509 True, 510 ) 511 painter.setRenderHint( 512 QPainter.SmoothPixmapTransform, 513 True, 514 ) 515 516 w = self.width() 517 h = self.height() 518 cx = w / 2 519 cy = h * 0.47 520 521 seconds = ( 522 self.tick 523 * self.animation_interval_ms 524 / 1000.0 525 ) 526 527 aura = QRadialGradient( 528 cx, 529 cy, 530 min(w, h) * 0.48, 531 ) 532 aura.setColorAt( 533 0.0, 534 QColor(80, 25, 135, 115), 535 ) 536 aura.setColorAt( 537 0.55, 538 QColor(37, 7, 82, 60), 539 ) 540 aura.setColorAt( 541 1.0, 542 QColor(0, 0, 0, 0), 543 ) 544 545 painter.setPen(Qt.NoPen) 546 painter.setBrush(QBrush(aura)) 547 painter.drawEllipse( 548 QRectF( 549 cx - min(w, h) * 0.48, 550 cy - min(w, h) * 0.48, 551 min(w, h) * 0.96, 552 min(w, h) * 0.96, 553 ) 554 ) 555 556 pulse_offset = ( 557 math.sin(seconds * 0.75) + 1.0 558 ) / 2.0 559 560 for index in range(5): 561 radius = ( 562 min(w, h) 563 * (0.34 + index * 0.065) 564 + pulse_offset * 4.0 565 ) 566 567 alpha = max( 568 35, 569 145 - index * 22, 570 ) 571 572 painter.setPen( 573 QPen( 574 QColor( 575 155, 576 77, 577 255, 578 alpha, 579 ), 580 1.4 if index else 2.2, 581 ) 582 ) 583 584 painter.setBrush(Qt.NoBrush) 585 painter.drawEllipse( 586 QRectF( 587 cx - radius, 588 cy - radius, 589 radius * 2, 590 radius * 2, 591 ) 592 ) 593 594 base_portrait_size = min( 595 w * 0.72, 596 h * 0.72, 597 ) 598 599 breath = math.sin( 600 (2.0 * math.pi * seconds) / 6.6 601 ) 602 603 drift_x = ( 604 2.5 605 * math.sin( 606 ((2.0 * math.pi * seconds) / 11.2) 607 + 0.35 608 ) 609 ) 610 611 drift_y = ( 612 2.8 613 * math.sin( 614 (2.0 * math.pi * seconds) / 8.3 615 ) 616 ) 617 618 breathing_scale = 1.0 + ( 619 breath * 0.008 620 ) 621 622 portrait_size = ( 623 base_portrait_size 624 * breathing_scale 625 ) 626 627 portrait_rect = QRectF( 628 cx - portrait_size / 2 + drift_x, 629 cy - portrait_size / 2 + drift_y, 630 portrait_size, 631 portrait_size, 632 ) 633 634 clip_path = QPainterPath() 635 clip_path.addEllipse(portrait_rect) 636 637 painter.save() 638 painter.setClipPath(clip_path) 639 640 rendered_state = ( 641 self.current_expression_state() 642 ) 643 644 scaled = self._scaled_face_for( 645 rendered_state, 646 base_portrait_size, 647 ) 648 649 if not scaled.isNull(): 650 source_size = min( 651 float(scaled.width()), 652 float(scaled.height()), 653 ) 654 655 source_x = max( 656 0.0, 657 ( 658 scaled.width() 659 - source_size 660 ) / 2.0, 661 ) 662 663 source_y = max( 664 0.0, 665 ( 666 scaled.height() 667 - source_size 668 ) / 2.0, 669 ) 670 671 painter.drawPixmap( 672 portrait_rect, 673 scaled, 674 QRectF( 675 source_x, 676 source_y, 677 source_size, 678 source_size, 679 ), 680 ) 681 else: 682 painter.fillRect( 683 portrait_rect, 684 QColor(7, 5, 20), 685 ) 686 687 painter.setPen( 688 QPen( 689 QColor(155, 77, 255), 690 2, 691 ) 692 ) 693 694 painter.setFont( 695 QFont( 696 "DejaVu Sans Mono", 697 25, 698 QFont.Bold, 699 ) 700 ) 701 702 painter.drawText( 703 portrait_rect, 704 Qt.AlignCenter, 705 "SHIRE\nPRESENCE", 706 ) 707 708 painter.restore() 709 710 painter.setPen( 711 QPen( 712 QColor(190, 118, 255, 230), 713 3, 714 ) 715 ) 716 painter.setBrush(Qt.NoBrush) 717 painter.drawEllipse(portrait_rect) 718 719 lower_gradient = QLinearGradient( 720 0, 721 h * 0.62, 722 0, 723 h, 724 ) 725 lower_gradient.setColorAt( 726 0.0, 727 QColor(0, 0, 0, 0), 728 ) 729 lower_gradient.setColorAt( 730 1.0, 731 QColor(1, 1, 8, 220), 732 ) 733 734 painter.setPen(Qt.NoPen) 735 painter.setBrush( 736 QBrush(lower_gradient) 737 ) 738 painter.drawRect( 739 QRectF( 740 0, 741 h * 0.60, 742 w, 743 h * 0.40, 744 ) 745 ) 746 747 painter.setPen( 748 QColor(PURPLE_LIGHT) 749 ) 750 painter.setFont( 751 QFont( 752 "DejaVu Sans Mono", 753 22, 754 QFont.Bold, 755 ) 756 ) 757 painter.drawText( 758 QRectF( 759 0, 760 h - 72, --- final section --- 761 w, 762 30, 763 ), 764 Qt.AlignCenter, 765 "SHIRE", 766 ) 767 768 painter.setPen(QColor(GREEN)) 769 painter.setFont( 770 QFont( 771 "DejaVu Sans Mono", 772 10, 773 QFont.Bold, 774 ) 775 ) 776 painter.drawText( 777 QRectF( 778 0, 779 h - 40, 780 w, 781 22, 782 ), 783 Qt.AlignCenter, 784 "PRESENCE ONLINE • APPROVAL GATE ACTIVE", 785 ) 786 787 788 class VoiceOrb(QWidget): 789 """Visual mirror of the approved manual voice state.""" 790 791 def __init__(self): 792 super().__init__() 793 794 self.setMinimumHeight(104) 795 self.setMaximumHeight(116) 796 self.setSizePolicy( 797 QSizePolicy.Expanding, 798 QSizePolicy.Fixed, 799 ) 800 801 self.timer = QTimer(self) 802 self.timer.timeout.connect(self.update) 803 self.timer.start(300) 804 805 def paintEvent(self, event): 806 del event 807 808 snapshot = voice_privacy_foundation.snapshot() 809 state = str(snapshot.state).upper() 810 811 listening = state == "LISTENING" 812 processing = state == "PROCESSING" 813 ready = state == "READY" 814 error = state == "ERROR" 815 816 painter = QPainter(self) 817 painter.setRenderHint(QPainter.Antialiasing, True) 818 819 w = self.width() 820 h = self.height() 821 cx = w / 2 822 cy = 42 823 824 if error: 825 orb_color = QColor("#ff5c70") 826 elif listening: 827 orb_color = QColor(GREEN) 828 elif processing: 829 orb_color = QColor(CYAN) 830 elif ready: 831 orb_color = QColor(PURPLE_LIGHT) 832 else: 833 orb_color = QColor(PURPLE) 834 835 for index, width_ratio in enumerate( 836 (0.42, 0.32, 0.23) 837 ): 838 rect_width = w * width_ratio 839 840 painter.setPen( 841 QPen( 842 QColor( 843 orb_color.red(), 844 orb_color.green(), 845 orb_color.blue(), 846 105 - index * 20, 847 ), 848 1.4, 849 ) 850 ) 851 852 painter.setBrush(Qt.NoBrush) 853 painter.drawEllipse( 854 QRectF( 855 cx - rect_width / 2, 856 cy - 25 - index * 3, 857 rect_width, 858 50 + index * 6, 859 ) 860 ) 861 862 painter.setPen(QPen(orb_color, 2)) 863 painter.setBrush(QColor(18, 7, 42, 240)) 864 painter.drawEllipse( 865 QRectF(cx - 31, cy - 31, 62, 62) 866 ) 867 868 painter.setPen(QPen(orb_color, 4)) 869 painter.setBrush(Qt.NoBrush) 870 painter.drawRoundedRect( 871 QRectF(cx - 8, cy - 16, 16, 29), 872 8, 873 8, 874 ) 875 painter.drawLine( 876 int(cx), 877 int(cy + 14), 878 int(cx), 879 int(cy + 24), 880 ) 881 painter.drawLine( 882 int(cx - 9), 883 int(cy + 24), 884 int(cx + 9), 885 int(cy + 24), 886 ) 887 888 if listening: 889 state_text = "LISTENING" 890 detail_text = "Speech is filling the top prompt" 891 892 elif processing: 893 state_text = "PROCESSING" 894 detail_text = "Finalising local transcript" 895 896 elif ready: 897 state_text = "VOICE READY" 898 detail_text = "Use MIC in the top prompt bar" 899 900 elif error: 901 state_text = "VOICE ERROR" 902 detail_text = snapshot.error or "Check voice configuration" 903 904 else: 905 state_text = "MICROPHONE OFF" 906 detail_text = "Voice capture is unavailable" 907 908 painter.setPen(orb_color) 909 painter.setFont( 910 QFont("DejaVu Sans Mono", 11, QFont.Bold) 911 ) 912 painter.drawText( 913 QRectF(0, 76, w, 18), 914 Qt.AlignCenter, 915 state_text, 916 ) 917 918 painter.setPen(QColor(MUTED)) 919 painter.setFont( 920 QFont("DejaVu Sans Mono", 8, QFont.Bold) 921 ) 922 painter.drawText( 923 QRectF(0, 94, w, 16), 924 Qt.AlignCenter, 925 detail_text, 926 ) 927 928 929 class LaptopDashboard(QWidget): 930 """SHIRE AIOS face-first Presence Console.""" 931 932 def __init__(self, stack): 933 super().__init__() 934 935 self.stack = stack 936 self.station_drawer = None 937 self.build_ui() 938 939 def _open_station(self, target_index): 940 self.stack.setCurrentIndex(target_index) 941 942 if self.station_drawer is not None: 943 self.station_drawer.setVisible(False) 944 945 def _make_nav_button( 946 self, 947 icon, 948 label, 949 target_index, 950 compact=True, 951 ): 952 button = LaptopRuneButton( 953 icon, 954 label, 955 "", 956 target_index, 957 self.stack, 958 compact=compact, 959 ) 960 961 button.clicked.disconnect() 962 button.clicked.connect( 963 lambda checked=False, index=target_index: 964 self._open_station(index) 965 ) 966 967 return button 968 969 def _toggle_stations(self): 970 visible = not self.station_drawer.isVisible() 971 self.station_drawer.setVisible(visible) 972 973 def _build_greeting_card(self): 974 card = PresenceCard("SHIRE PRESENCE", PURPLE_LIGHT) 975 976 greeting = QLabel(_greeting()) 977 greeting.setStyleSheet( 978 f"color:{PURPLE_LIGHT}; " 979 "font-size:23px; font-weight:900;" 980 ) 981 982 message = QLabel( 983 "The Workshop is ready.\n" 984 "No proposal is active.\n\n" 985 "How can I help you today?" 986 ) 987 988 message.setWordWrap(True) 989 message.setStyleSheet( 990 "color:#d7c8f4; " 991 "font-size:14px; " 992 "font-weight:700; " 993 "line-height:1.35;" 994 ) 995 996 card.column.addWidget(greeting) 997 card.column.addWidget(message) 998 card.column.addStretch(1) 999 1000 return card 1001 1002 def _build_status_card(self): 1003 card = PresenceCard("SYSTEM STATUS", CYAN) 1004 1005 rows = ( 1006 ("CORE", "ONLINE", GREEN), 1007 ("SESSION", "LOCAL X11", CYAN), 1008 ("MICROPHONE", "CLOSED", PURPLE_LIGHT), 1009 ("AUTHORITY", "RAY APPROVAL", GREEN), 1010 ) 1011 1012 for title, value, accent in rows: 1013 row = QHBoxLayout() 1014 1015 left = QLabel(title) 1016 left.setStyleSheet( 1017 "color:#8e82aa; " 1018 "font-size:9px; " 1019 "font-weight:900;" 1020 ) 1021 1022 right = QLabel(value) 1023 right.setAlignment(Qt.AlignRight) 1024 right.setStyleSheet( 1025 f"color:{accent}; " 1026 "font-size:11px; " 1027 "font-weight:900;" 1028 ) 1029 1030 row.addWidget(left) 1031 row.addStretch(1) 1032 row.addWidget(right) 1033 1034 card.column.addLayout(row) 1035 1036 return card 1037 1038 def _build_thought_card(self): 1039 card = PresenceCard("CURRENT THOUGHT", PURPLE_LIGHT) 1040 1041 concept = QLabel("◌") 1042 concept.setAlignment(Qt.AlignCenter) 1043 concept.setMinimumHeight(88) 1044 concept.setStyleSheet( 1045 "color:#b66cff; " 1046 "font-size:64px; " 1047 "font-weight:500;" 1048 ) 1049 1050 title = QLabel("NO ACTIVE DESIGN PREVIEW") 1051 title.setAlignment(Qt.AlignCenter) 1052 title.setStyleSheet( 1053 f"color:{PURPLE_LIGHT}; " 1054 "font-size:12px; " 1055 "font-weight:900;" 1056 ) 1057 1058 note = QLabel( 1059 "Approved STL concepts and Workshop ideas " 1060 "will appear here." 1061 ) 1062 1063 note.setWordWrap(True) 1064 note.setAlignment(Qt.AlignCenter) 1065 note.setStyleSheet( 1066 "color:#9e91ba; " 1067 "font-size:10px; " 1068 "font-weight:700;" 1069 ) 1070 1071 card.column.addWidget(concept) 1072 card.column.addWidget(title) 1073 card.column.addWidget(note) 1074 card.column.addStretch(1) 1075 1076 return card 1077 1078 def _build_next_card(self): 1079 card = PresenceCard("NEXT UP", CYAN) 1080 1081 items = ( 1082 "Open Workshop", 1083 "Review Sentinel", 1084 "Continue Education", 1085 ) 1086 1087 for item in items: 1088 label = QLabel(f"› {item}") 1089 label.setStyleSheet( 1090 "color:#c8afff; " 1091 "font-size:12px; " 1092 "font-weight:800; " 1093 "padding:5px 0;" 1094 ) 1095 card.column.addWidget(label) 1096 1097 card.column.addStretch(1) 1098 1099 return card 1100 1101 def _build_station_drawer(self): 1102 drawer = QFrame() 1103 drawer.setObjectName("stationDrawer") 1104 drawer.setVisible(False) 1105 1106 drawer.setStyleSheet(""" 1107 QFrame#stationDrawer { 1108 background: rgba(5, 4, 18, 240); 1109 border: 1px solid rgba(155, 77, 255, 180); 1110 border-radius: 16px; 1111 } 1112 """) 1113 1114 grid = QGridLayout(drawer) 1115 grid.setContentsMargins(12, 12, 12, 12) 1116 grid.setHorizontalSpacing(10) 1117 grid.setVerticalSpacing(10) 1118 1119 stations = ( 1120 ("▤", "MEMORY", 3), 1121 ("◇", "ATLAS", 4), 1122 ("✦", "EDUCATION", 11), 1123 ("♟", "AGENTS", 7), 1124 ("☷", "TASKS", 8), 1125 ("⚙", "SYSTEM", 6), 1126 ("⌁", "SETTINGS", 9), 1127 ("⌨", "COMMAND", 1), 1128 ) 1129 1130 for index, (icon, label, target) in enumerate(stations): 1131 button = self._make_nav_button( 1132 icon, 1133 label, 1134 target, 1135 compact=True, 1136 ) 1137 1138 grid.addWidget( 1139 button, 1140 index // 4, 1141 index % 4, 1142 ) 1143 1144 return drawer 1145 1146 def build_ui(self): 1147 self.setObjectName("presenceConsole") 1148 1149 self.setStyleSheet(""" 1150 QWidget#presenceConsole { 1151 background: #020208; 1152 color: #f4efff; 1153 font-family: "DejaVu Sans Mono", monospace; 1154 } 1155 QLabel { 1156 background: transparent; 1157 } 1158 """) 1159 1160 root = QVBoxLayout(self) 1161 root.setContentsMargins(26, 20, 26, 16) 1162 root.setSpacing(12) 1163 1164 header = QHBoxLayout() 1165 header.setSpacing(18) 1166 1167 brand_column = QVBoxLayout() 1168 brand_column.setSpacing(0) 1169 1170 brand = QLabel("SHIRE AIOS") 1171 brand.setStyleSheet( 1172 f"color:{PURPLE}; " 1173 "font-size:27px; " 1174 "font-weight:900; " 1175 "letter-spacing:2px;" 1176 ) 1177 1178 core_state = QLabel("CORE ONLINE") 1179 core_state.setStyleSheet( 1180 f"color:{GREEN}; " 1181 "font-size:10px; " 1182 "font-weight:900;" 1183 ) 1184 1185 brand_column.addWidget(brand) 1186 brand_column.addWidget(core_state) 1187 1188 header.addLayout(brand_column) 1189 header.addStretch(1) 1190 header.addWidget(VoicePrivacyBanner()) 1191 1192 root.addLayout(header) 1193 1194 stage = QHBoxLayout() 1195 stage.setSpacing(14) 1196 1197 left_column = QVBoxLayout() 1198 left_column.setSpacing(12) 1199 left_column.addWidget(self._build_greeting_card(), 3) 1200 left_column.addWidget(self._build_status_card(), 2) 1201 1202 center_column = QVBoxLayout() 1203 center_column.setSpacing(2) 1204 1205 center_title = QLabel("SHIRE PRESENCE") 1206 center_title.setAlignment(Qt.AlignCenter) 1207 center_title.setStyleSheet( 1208 f"color:{PURPLE_LIGHT}; " 1209 "font-size:12px; " 1210 "font-weight:900; " 1211 "letter-spacing:2px;" 1212 ) 1213 1214 center_column.addWidget(center_title) 1215 center_column.addWidget(ShireCenterCore(), 1) 1216 center_column.addWidget(VoiceOrb()) 1217 1218 right_column = QVBoxLayout() 1219 right_column.setSpacing(12) 1220 right_column.addWidget(self._build_thought_card(), 3) 1221 right_column.addWidget(self._build_next_card(), 2) 1222 1223 stage.addLayout(left_column, 5) 1224 stage.addLayout(center_column, 8) 1225 stage.addLayout(right_column, 5) 1226 1227 root.addLayout(stage, 1) 1228 1229 dock = QHBoxLayout() 1230 dock.setSpacing(10) 1231 1232 dock.addWidget( 1233 self._make_nav_button("●", "SHIRE", 1) 1234 ) 1235 1236 dock.addWidget( 1237 self._make_nav_button("⚒", "FORGE", 2) 1238 ) 1239 1240 dock.addWidget( 1241 self._make_nav_button("♜", "SENTINEL", 10) 1242 ) 1243 1244 stations_button = QPushButton("☰ STATIONS") 1245 stations_button.setCursor(Qt.PointingHandCursor) 1246 stations_button.setMinimumHeight(48) 1247 1248 stations_button.setStyleSheet(""" 1249 QPushButton { 1250 background: rgba(8, 5, 25, 225); 1251 color: #00f58a; 1252 border: 1px solid rgba(0, 245, 138, 185); 1253 border-radius: 13px; 1254 font-family: "DejaVu Sans Mono", monospace; 1255 font-size: 13px; 1256 font-weight: 900; 1257 padding: 8px 12px; 1258 } 1259 QPushButton:hover { 1260 background: rgba(0, 55, 39, 230); 1261 color: #ffffff; 1262 border: 1px solid #00f58a; 1263 } 1264 QPushButton:pressed { 1265 background: rgba(155, 77, 255, 75); 1266 } 1267 """) 1268 1269 stations_button.clicked.connect( 1270 self._toggle_stations 1271 ) 1272 1273 dock.addWidget(stations_button) 1274 1275 root.addLayout(dock) 1276 1277 self.station_drawer = self._build_station_drawer() 1278 root.addWidget(self.station_drawer) 1279 1280 hint = QLabel( 1281 "VOICE PREVIEW READY • " 1282 "Use MIC in the top prompt bar • " 1283 "Audio is processed locally and discarded" 1284 ) 1285 1286 hint.setAlignment(Qt.AlignCenter) 1287 hint.setStyleSheet( 1288 "color:#8f82a8; " 1289 "font-size:9px; " 1290 "font-weight:800; " 1291 "padding:1px;" 1292 ) 1293 1294 root.addWidget(hint) 1295 1296 def paintEvent(self, event): 1297 del event 1298 1299 painter = QPainter(self) 1300 painter.setRenderHint(QPainter.Antialiasing, True) 1301 1302 gradient = QLinearGradient( 1303 0, 1304 0, 1305 self.width(), 1306 self.height(), 1307 ) 1308 1309 gradient.setColorAt(0.0, QColor(2, 2, 9)) 1310 gradient.setColorAt(0.48, QColor(7, 3, 18)) 1311 gradient.setColorAt(1.0, QColor(2, 2, 8)) 1312 1313 painter.fillRect( 1314 self.rect(), 1315 QBrush(gradient), 1316 ) 1317 1318 painter.setPen( 1319 QPen(QColor(113, 50, 190, 22), 1) 1320 ) 1321 1322 spacing = 54 1323 1324 for x in range(0, self.width(), spacing): 1325 painter.drawLine(x, 0, x, self.height()) 1326 1327 for y in range(0, self.height(), spacing): 1328 painter.drawLine(0, y, self.width(), y) 1329 1330 center_glow = QRadialGradient( 1331 self.width() / 2, 1332 self.height() / 2, 1333 self.width() * 0.42, 1334 ) 1335 1336 center_glow.setColorAt( 1337 0.0, 1338 QColor(106, 25, 180, 50), 1339 ) 1340 1341 center_glow.setColorAt( 1342 1.0, 1343 QColor(0, 0, 0, 0), 1344 ) 1345 1346 painter.setPen(Qt.NoPen) 1347 painter.setBrush(QBrush(center_glow)) 1348 painter.drawEllipse( 1349 QRectF( 1350 self.width() * 0.14, 1351 -self.height() * 0.10, 1352 self.width() * 0.72, 1353 self.height() * 1.20, 1354 ) 1355 ) === Main launcher and laptop-dashboard references === /home/shire3d/ARMOR/main.py:16: host = socket.gethostname() /home/shire3d/ARMOR/main.py:49: QApplication, /home/shire3d/ARMOR/main.py:50: QStackedWidget, /home/shire3d/ARMOR/main.py:57:from modules.dashboard import Dashboard /home/shire3d/ARMOR/main.py:58:from modules.laptop_dashboard import LaptopDashboard /home/shire3d/ARMOR/main.py:73:class ArmorStationStack(QStackedWidget): /home/shire3d/ARMOR/main.py:156: "ARMOR OS - Laptop Command Station" /home/shire3d/ARMOR/main.py:167: home_page = LaptopDashboard( /home/shire3d/ARMOR/main.py:171: home_page = Dashboard( /home/shire3d/ARMOR/main.py:221: app = QApplication(sys.argv) /home/shire3d/ARMOR/main.py:224: QMainWindow, QWidget, QStackedWidget { /home/shire3d/ARMOR/main.py:231: window.showFullScreen() /home/shire3d/ARMOR/modules/shire.py:235: dialog.show() /home/shire3d/ARMOR/modules/shire.py:560: self.done.emit(False, "Laptop brain request failed.\nError: " + str(exc)) /home/shire3d/ARMOR/modules/shire.py:709: self.input.show() /home/shire3d/ARMOR/modules/shire.py:742: self.send_button.show() /home/shire3d/ARMOR/modules/shire.py:1944: """ENG-LAPTOP-0006A / ENG-UTIL-0004A: Laptop-only command node check.""" /home/shire3d/ARMOR/modules/shire.py:1946: expected = self.get_appliance("laptop").get("host", "armoros") /home/shire3d/ARMOR/modules/shire.py:1947: return socket.gethostname().strip().lower() == str(expected).strip().lower() /home/shire3d/ARMOR/modules/shire.py:1950: return socket.gethostname().strip().lower() == "armoros" /home/shire3d/ARMOR/modules/shire.py:1960: "name": "ARMOR Laptop Brain", /home/shire3d/ARMOR/modules/shire.py:1961: "host": "armoros", /home/shire3d/ARMOR/modules/shire.py:2073: self.shire_append(f"Use ARMOR Laptop / {self.get_appliance('laptop').get('host', 'armoros')} over Tailscale.") /home/shire3d/ARMOR/modules/shire.py:2171: """ENG-UTIL-0003B: Laptop-only safe GUI power button flow.""" /home/shire3d/ARMOR/modules/shire.py:2175: self.shire_append(f"This button only works from ARMOR Laptop / {self.get_appliance('laptop').get('host', 'armoros')}.") /home/shire3d/ARMOR/modules/shire.py:2181: self.shire_append("Laptop will stay on.") /home/shire3d/ARMOR/modules/shire.py:2187: warn.setInformativeText("Node powers down first. Pi powers down second. Laptop stays on.") /home/shire3d/ARMOR/modules/shire.py:2228: laptop.get("name", "ARMOR Laptop Brain"), /home/shire3d/ARMOR/modules/shire.py:2229: laptop.get("host", "armoros"), /home/shire3d/ARMOR/modules/shire.py:2263: self.shire_append(f"✅ {laptop.get('name', 'ARMOR Laptop Brain')}: ONLINE") /home/shire3d/ARMOR/modules/shire.py:2265: self.shire_append(f"⚠ {laptop.get('name', 'ARMOR Laptop Brain')}: WARNING") /home/shire3d/ARMOR/modules/shire.py:2267: self.shire_append(f"❌ {laptop.get('name', 'ARMOR Laptop Brain')}: ERROR - {exc}") /home/shire3d/ARMOR/modules/shire.py:2272: current_host = socket.gethostname().strip().lower() /home/shire3d/ARMOR/modules/shire.py:2295: f"hostname; cd {pi_repo} && git rev-parse --short HEAD", /home/shire3d/ARMOR/modules/shire.py:2311: current_host = socket.gethostname().strip().lower() /home/shire3d/ARMOR/modules/shire.py:2312: laptop_host = str(laptop.get("host", "armoros")).strip().lower() /home/shire3d/ARMOR/modules/shire.py:2358: "echo NODE=$(hostname); " /home/shire3d/ARMOR/modules/shire.py:2487: """ENG-UTIL-0001A / ENG-UTIL-0004A: Laptop-side SHIRE Utility Node / Laser status.""" /home/shire3d/ARMOR/modules/shire.py:2550: "echo NODE_HOST=$(hostname); " /home/shire3d/ARMOR/modules/shire.py:2666: self.shire_append("Link: Pi → Laptop Brain over Tailscale.") /home/shire3d/ARMOR/modules/shire.py:2754: self.input.show() /home/shire3d/ARMOR/modules/shire.py:2755: self.send_button.show() /home/shire3d/ARMOR/modules/shire.py:3192: self.shire_append("🧠 Redirecting ASK to Laptop SHIRE Brain...") /home/shire3d/ARMOR/modules/shire.py:3659: f"LAPTOP: {self.get_appliance('laptop').get('host', 'armoros')} / {self.redact_network_detail(self.get_appliance('laptop').get('ip', '100.74.51.32'))}\n" /home/shire3d/ARMOR/modules/shire.py:3780: self.shire_append("🧠 Redirecting ASK to Laptop SHIRE Brain...") /home/shire3d/ARMOR/modules/shire.py:3866: QApplication.processEvents() /home/shire3d/ARMOR/modules/dashboard.py:144:class Dashboard(WardenBackground): === Theme colour and font references === /home/shire3d/ARMOR/framework/theme.py:1:GREEN = "#00ff66" /home/shire3d/ARMOR/framework/theme.py:2:PURPLE = "#b13cff" /home/shire3d/ARMOR/framework/theme.py:3:BLACK = "#02040a" /home/shire3d/ARMOR/framework/theme.py:4:DARK = "#07111f" /home/shire3d/ARMOR/framework/theme.py:5:DARK2 = "#0b1b2e" /home/shire3d/ARMOR/framework/theme.py:6:WHITE = "#f4f4f4" /home/shire3d/ARMOR/framework/theme.py:7:AMBER = "#ffbf00" /home/shire3d/ARMOR/framework/theme.py:8:RED = "#ff3333" /home/shire3d/ARMOR/framework/warden_theme.py:1:BLACK_BG = "#000000" /home/shire3d/ARMOR/framework/warden_theme.py:5:GREEN = "#00ff66" /home/shire3d/ARMOR/framework/warden_theme.py:6:PURPLE = "#b02cff" /home/shire3d/ARMOR/framework/warden_theme.py:7:AMBER = "#ffcc00" /home/shire3d/ARMOR/framework/warden_theme.py:8:RED = "#ff3333" /home/shire3d/ARMOR/framework/warden_theme.py:9:WHITE = "#e8fff2" /home/shire3d/ARMOR/framework/warden_theme.py:10:MUTED = "#91a0aa" /home/shire3d/ARMOR/framework/warden_theme.py:11:OFFLINE = "#777777" /home/shire3d/ARMOR/framework/warden_theme.py:15: "online": GREEN, /home/shire3d/ARMOR/framework/warden_theme.py:16: "ready": GREEN, /home/shire3d/ARMOR/framework/warden_theme.py:17: "partial": AMBER, /home/shire3d/ARMOR/framework/warden_theme.py:19: "warning": AMBER, /home/shire3d/ARMOR/framework/warden_theme.py:20: "critical": RED, /home/shire3d/ARMOR/framework/warden_theme.py:22: }.get(state, GREEN) /home/shire3d/ARMOR/modules/laptop_dashboard.py:8: QColor, /home/shire3d/ARMOR/modules/laptop_dashboard.py:31:GREEN = "#00f58a" /home/shire3d/ARMOR/modules/laptop_dashboard.py:32:PURPLE = "#9b4dff" /home/shire3d/ARMOR/modules/laptop_dashboard.py:33:PURPLE_LIGHT = "#c89cff" /home/shire3d/ARMOR/modules/laptop_dashboard.py:34:CYAN = "#18e6d2" /home/shire3d/ARMOR/modules/laptop_dashboard.py:35:WHITE = "#f4efff" /home/shire3d/ARMOR/modules/laptop_dashboard.py:36:MUTED = "#a89bc4" /home/shire3d/ARMOR/modules/laptop_dashboard.py:98: accent = "#ff5c70" /home/shire3d/ARMOR/modules/laptop_dashboard.py:103: accent = GREEN /home/shire3d/ARMOR/modules/laptop_dashboard.py:108: accent = CYAN /home/shire3d/ARMOR/modules/laptop_dashboard.py:124: background: rgba(4, 7, 18, 220); /home/shire3d/ARMOR/modules/laptop_dashboard.py:125: border: 1px solid {accent}; /home/shire3d/ARMOR/modules/laptop_dashboard.py:126: border-radius: 13px; /home/shire3d/ARMOR/modules/laptop_dashboard.py:129: background: transparent; /home/shire3d/ARMOR/modules/laptop_dashboard.py:130: border: none; /home/shire3d/ARMOR/modules/laptop_dashboard.py:145: "color:#aa9dc6; font-size:9px; font-weight:800;" /home/shire3d/ARMOR/modules/laptop_dashboard.py:181: background: rgba(8, 5, 25, 225); /home/shire3d/ARMOR/modules/laptop_dashboard.py:182: color: #c69cff; /home/shire3d/ARMOR/modules/laptop_dashboard.py:183: border: 1px solid rgba(155, 77, 255, 185); /home/shire3d/ARMOR/modules/laptop_dashboard.py:184: border-radius: 13px; /home/shire3d/ARMOR/modules/laptop_dashboard.py:185: font-family: "DejaVu Sans Mono", monospace; /home/shire3d/ARMOR/modules/laptop_dashboard.py:191: background: rgba(34, 9, 66, 235); /home/shire3d/ARMOR/modules/laptop_dashboard.py:192: color: #ffffff; /home/shire3d/ARMOR/modules/laptop_dashboard.py:193: border: 1px solid #b76cff; /home/shire3d/ARMOR/modules/laptop_dashboard.py:196: background: rgba(0, 245, 138, 65); /home/shire3d/ARMOR/modules/laptop_dashboard.py:197: color: #00f58a; /home/shire3d/ARMOR/modules/laptop_dashboard.py:198: border: 1px solid #00f58a; /home/shire3d/ARMOR/modules/laptop_dashboard.py:216: background: {PANEL}; /home/shire3d/ARMOR/modules/laptop_dashboard.py:217: border: 1px solid {BORDER}; /home/shire3d/ARMOR/modules/laptop_dashboard.py:218: border-radius: 18px; /home/shire3d/ARMOR/modules/laptop_dashboard.py:221: background: transparent; /home/shire3d/ARMOR/modules/laptop_dashboard.py:222: border: none; /home/shire3d/ARMOR/modules/laptop_dashboard.py:534: QColor(80, 25, 135, 115), /home/shire3d/ARMOR/modules/laptop_dashboard.py:538: QColor(37, 7, 82, 60), /home/shire3d/ARMOR/modules/laptop_dashboard.py:542: QColor(0, 0, 0, 0), /home/shire3d/ARMOR/modules/laptop_dashboard.py:574: QColor( /home/shire3d/ARMOR/modules/laptop_dashboard.py:684: QColor(7, 5, 20), /home/shire3d/ARMOR/modules/laptop_dashboard.py:689: QColor(155, 77, 255), /home/shire3d/ARMOR/modules/laptop_dashboard.py:712: QColor(190, 118, 255, 230), /home/shire3d/ARMOR/modules/laptop_dashboard.py:727: QColor(0, 0, 0, 0), /home/shire3d/ARMOR/modules/laptop_dashboard.py:731: QColor(1, 1, 8, 220), /home/shire3d/ARMOR/modules/laptop_dashboard.py:748: QColor(PURPLE_LIGHT) /home/shire3d/ARMOR/modules/laptop_dashboard.py:768: painter.setPen(QColor(GREEN)) /home/shire3d/ARMOR/modules/laptop_dashboard.py:825: orb_color = QColor("#ff5c70") /home/shire3d/ARMOR/modules/laptop_dashboard.py:827: orb_color = QColor(GREEN) /home/shire3d/ARMOR/modules/laptop_dashboard.py:829: orb_color = QColor(CYAN) /home/shire3d/ARMOR/modules/laptop_dashboard.py:831: orb_color = QColor(PURPLE_LIGHT) /home/shire3d/ARMOR/modules/laptop_dashboard.py:833: orb_color = QColor(PURPLE) /home/shire3d/ARMOR/modules/laptop_dashboard.py:842: QColor( /home/shire3d/ARMOR/modules/laptop_dashboard.py:863: painter.setBrush(QColor(18, 7, 42, 240)) /home/shire3d/ARMOR/modules/laptop_dashboard.py:918: painter.setPen(QColor(MUTED)) /home/shire3d/ARMOR/modules/laptop_dashboard.py:990: "color:#d7c8f4; " /home/shire3d/ARMOR/modules/laptop_dashboard.py:1003: card = PresenceCard("SYSTEM STATUS", CYAN) /home/shire3d/ARMOR/modules/laptop_dashboard.py:1006: ("CORE", "ONLINE", GREEN), /home/shire3d/ARMOR/modules/laptop_dashboard.py:1007: ("SESSION", "LOCAL X11", CYAN), /home/shire3d/ARMOR/modules/laptop_dashboard.py:1009: ("AUTHORITY", "RAY APPROVAL", GREEN), /home/shire3d/ARMOR/modules/laptop_dashboard.py:1017: "color:#8e82aa; " /home/shire3d/ARMOR/modules/laptop_dashboard.py:1045: "color:#b66cff; " /home/shire3d/ARMOR/modules/laptop_dashboard.py:1066: "color:#9e91ba; " /home/shire3d/ARMOR/modules/laptop_dashboard.py:1079: card = PresenceCard("NEXT UP", CYAN) /home/shire3d/ARMOR/modules/laptop_dashboard.py:1090: "color:#c8afff; " /home/shire3d/ARMOR/modules/laptop_dashboard.py:1108: background: rgba(5, 4, 18, 240); /home/shire3d/ARMOR/modules/laptop_dashboard.py:1109: border: 1px solid rgba(155, 77, 255, 180); /home/shire3d/ARMOR/modules/laptop_dashboard.py:1110: border-radius: 16px; /home/shire3d/ARMOR/modules/laptop_dashboard.py:1151: background: #020208; /home/shire3d/ARMOR/modules/laptop_dashboard.py:1152: color: #f4efff; /home/shire3d/ARMOR/modules/laptop_dashboard.py:1153: font-family: "DejaVu Sans Mono", monospace; /home/shire3d/ARMOR/modules/laptop_dashboard.py:1156: background: transparent; /home/shire3d/ARMOR/modules/laptop_dashboard.py:1180: f"color:{GREEN}; " /home/shire3d/ARMOR/modules/laptop_dashboard.py:1250: background: rgba(8, 5, 25, 225); /home/shire3d/ARMOR/modules/laptop_dashboard.py:1251: color: #00f58a; /home/shire3d/ARMOR/modules/laptop_dashboard.py:1252: border: 1px solid rgba(0, 245, 138, 185); /home/shire3d/ARMOR/modules/laptop_dashboard.py:1253: border-radius: 13px; /home/shire3d/ARMOR/modules/laptop_dashboard.py:1254: font-family: "DejaVu Sans Mono", monospace; /home/shire3d/ARMOR/modules/laptop_dashboard.py:1260: background: rgba(0, 55, 39, 230); /home/shire3d/ARMOR/modules/laptop_dashboard.py:1261: color: #ffffff; /home/shire3d/ARMOR/modules/laptop_dashboard.py:1262: border: 1px solid #00f58a; /home/shire3d/ARMOR/modules/laptop_dashboard.py:1265: background: rgba(155, 77, 255, 75); /home/shire3d/ARMOR/modules/laptop_dashboard.py:1288: "color:#8f82a8; " /home/shire3d/ARMOR/modules/laptop_dashboard.py:1309: gradient.setColorAt(0.0, QColor(2, 2, 9)) /home/shire3d/ARMOR/modules/laptop_dashboard.py:1310: gradient.setColorAt(0.48, QColor(7, 3, 18)) /home/shire3d/ARMOR/modules/laptop_dashboard.py:1311: gradient.setColorAt(1.0, QColor(2, 2, 8)) /home/shire3d/ARMOR/modules/laptop_dashboard.py:1319: QPen(QColor(113, 50, 190, 22), 1) /home/shire3d/ARMOR/modules/laptop_dashboard.py:1338: QColor(106, 25, 180, 50), /home/shire3d/ARMOR/modules/laptop_dashboard.py:1343: QColor(0, 0, 0, 0), === Current runtime process references === 7463 shire3d python /home/shire3d/ARMOR/venv/bin/python /home/shire3d/ARMOR/ai/brain_server.py === Repository state before UI work === M ai/brain_server.py M scripts/start_armor_laptop.sh ?? ai/worker_manager.py RESULT: LAPTOP SHIRE UI CAPTURED FOR BRAIN NODE PATCH