linux observer
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Align tray app text with solstone brand guidelines

Lowercase all user-facing labels, tooltips, and submenu headers.
Rename active state from "recording" to "observing". Reorder menu
to match macOS (journal/captures before pause). Expand About submenu
with version, source code link, and PBC tagline. Update SVG icon
title and aria-label metadata across both icon directories.

+63 -52
+2 -2
contrib/icons/hicolor/scalable/status/solstone-error.svg
··· 1 1 <svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" 2 - role="img" aria-label="Solstone icon error state"> 3 - <title>Solstone icon -- error</title> 2 + role="img" aria-label="solstone icon error state"> 3 + <title>solstone — error</title> 4 4 <defs> 5 5 <mask id="ixko" maskUnits="userSpaceOnUse"> 6 6 <rect x="2.5" y="2.5" width="27.0" height="27.0" fill="white"/>
+2 -2
contrib/icons/hicolor/scalable/status/solstone-paused.svg
··· 1 1 <svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" 2 - role="img" aria-label="Solstone icon paused state"> 3 - <title>Solstone icon -- paused</title> 2 + role="img" aria-label="solstone icon paused state"> 3 + <title>solstone — paused</title> 4 4 <defs> 5 5 <mask id="icko" maskUnits="userSpaceOnUse"> 6 6 <rect x="2.5" y="2.5" width="27.0" height="27.0" fill="white"/>
+2 -2
contrib/icons/hicolor/scalable/status/solstone-recording.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" role="img" aria-label="Solstone sun"> 2 - <title>Solstone sun</title> 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" role="img" aria-label="solstone icon observing state"> 2 + <title>solstone — observing</title> 3 3 <!-- Sun rays: 10 floating wedges with curved inner arc matching the ring --> 4 4 <path fill="#F5C740" d="M16.0 2.5 L18.6 7.3 A9.1 9.1 0 0 0 13.4 7.3 Z M23.9 5.1 L23.2 10.5 A9.1 9.1 0 0 0 19.0 7.4 Z M28.8 11.8 L25.1 15.8 A9.1 9.1 0 0 0 23.5 10.9 Z M28.8 20.2 L23.5 21.1 A9.1 9.1 0 0 0 25.1 16.2 Z M23.9 26.9 L19.0 24.6 A9.1 9.1 0 0 0 23.2 21.5 Z M16.0 29.5 L13.4 24.7 A9.1 9.1 0 0 0 18.6 24.7 Z M8.1 26.9 L8.8 21.5 A9.1 9.1 0 0 0 13.0 24.6 Z M3.2 20.2 L6.9 16.2 A9.1 9.1 0 0 0 8.5 21.1 Z M3.2 11.8 L8.5 10.9 A9.1 9.1 0 0 0 6.9 15.8 Z M8.1 5.1 L13.0 7.4 A9.1 9.1 0 0 0 8.8 10.5 Z"/> 5 5 <!-- Sun ring: open annulus -->
+2 -2
contrib/icons/hicolor/scalable/status/solstone-syncing.svg
··· 1 1 <svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" 2 - role="img" aria-label="Solstone icon partial state"> 3 - <title>Solstone icon -- partial</title> 2 + role="img" aria-label="solstone icon syncing state"> 3 + <title>solstone — syncing</title> 4 4 <!-- Top 5 rays only (no clip path needed) --> 5 5 <path fill="#F5C740" d="M16.0 2.5 L18.6 7.3 A9.1 9.1 0 0 0 13.4 7.3 Z"/> 6 6 <path fill="#F5C740" d="M23.9 5.1 L23.2 10.5 A9.1 9.1 0 0 0 19.0 7.4 Z"/>
+2 -2
src/solstone_linux/icons/hicolor/scalable/status/solstone-error.svg
··· 1 1 <svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" 2 - role="img" aria-label="Solstone icon error state"> 3 - <title>Solstone icon -- error</title> 2 + role="img" aria-label="solstone icon error state"> 3 + <title>solstone — error</title> 4 4 <defs> 5 5 <mask id="ixko" maskUnits="userSpaceOnUse"> 6 6 <rect x="2.5" y="2.5" width="27.0" height="27.0" fill="white"/>
+2 -2
src/solstone_linux/icons/hicolor/scalable/status/solstone-paused.svg
··· 1 1 <svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" 2 - role="img" aria-label="Solstone icon paused state"> 3 - <title>Solstone icon -- paused</title> 2 + role="img" aria-label="solstone icon paused state"> 3 + <title>solstone — paused</title> 4 4 <defs> 5 5 <mask id="icko" maskUnits="userSpaceOnUse"> 6 6 <rect x="2.5" y="2.5" width="27.0" height="27.0" fill="white"/>
+2 -2
src/solstone_linux/icons/hicolor/scalable/status/solstone-recording.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" role="img" aria-label="Solstone sun"> 2 - <title>Solstone sun</title> 1 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" role="img" aria-label="solstone icon observing state"> 2 + <title>solstone — observing</title> 3 3 <!-- Sun rays: 10 floating wedges with curved inner arc matching the ring --> 4 4 <path fill="#F5C740" d="M16.0 2.5 L18.6 7.3 A9.1 9.1 0 0 0 13.4 7.3 Z M23.9 5.1 L23.2 10.5 A9.1 9.1 0 0 0 19.0 7.4 Z M28.8 11.8 L25.1 15.8 A9.1 9.1 0 0 0 23.5 10.9 Z M28.8 20.2 L23.5 21.1 A9.1 9.1 0 0 0 25.1 16.2 Z M23.9 26.9 L19.0 24.6 A9.1 9.1 0 0 0 23.2 21.5 Z M16.0 29.5 L13.4 24.7 A9.1 9.1 0 0 0 18.6 24.7 Z M8.1 26.9 L8.8 21.5 A9.1 9.1 0 0 0 13.0 24.6 Z M3.2 20.2 L6.9 16.2 A9.1 9.1 0 0 0 8.5 21.1 Z M3.2 11.8 L8.5 10.9 A9.1 9.1 0 0 0 6.9 15.8 Z M8.1 5.1 L13.0 7.4 A9.1 9.1 0 0 0 8.8 10.5 Z"/> 5 5 <!-- Sun ring: open annulus -->
+2 -2
src/solstone_linux/icons/hicolor/scalable/status/solstone-syncing.svg
··· 1 1 <svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" 2 - role="img" aria-label="Solstone icon partial state"> 3 - <title>Solstone icon -- partial</title> 2 + role="img" aria-label="solstone icon syncing state"> 3 + <title>solstone — syncing</title> 4 4 <!-- Top 5 rays only (no clip path needed) --> 5 5 <path fill="#F5C740" d="M16.0 2.5 L18.6 7.3 A9.1 9.1 0 0 0 13.4 7.3 Z"/> 6 6 <path fill="#F5C740" d="M23.9 5.1 L23.2 10.5 A9.1 9.1 0 0 0 19.0 7.4 Z"/>
+42 -31
src/solstone_linux/tray.py
··· 15 15 16 16 from dbus_next.aio import MessageBus 17 17 18 + from . import __version__ 18 19 from .dbusmenu import DBusMenu, MenuItem, separator 19 20 from .sni import StatusNotifierItem, register_with_watcher 20 21 ··· 167 168 """Build the full tray menu structure.""" 168 169 169 170 # ── Status submenu (live data) ── 170 - self._status_item = MenuItem(label="recording", enabled=False) 171 + self._status_item = MenuItem(label="observing", enabled=False) 171 172 self._sync_item = MenuItem(label="sync: up to date", enabled=False) 172 173 self._segment_item = MenuItem(label="segment: --:--", enabled=False) 173 174 self._cache_item = MenuItem(label="cache: --", enabled=False) ··· 175 176 self._uptime_item = MenuItem(label="uptime: --", enabled=False) 176 177 177 178 status_submenu = MenuItem( 178 - label="Status", 179 + label="status", 179 180 children_display="submenu", 180 181 ) 181 182 status_submenu.children = [ ··· 192 193 pause_15m = MenuItem(label="15 minutes", callback=lambda: self._pause(900)) 193 194 pause_30m = MenuItem(label="30 minutes", callback=lambda: self._pause(1800)) 194 195 pause_1h = MenuItem(label="1 hour", callback=lambda: self._pause(3600)) 195 - pause_indef = MenuItem(label="Until I resume", callback=lambda: self._pause(0)) 196 + pause_indef = MenuItem(label="until I resume", callback=lambda: self._pause(0)) 196 197 197 198 self._pause_submenu = MenuItem( 198 - label="Pause", 199 + label="pause", 199 200 children_display="submenu", 200 201 ) 201 202 self._pause_submenu.children = [pause_15m, pause_30m, pause_1h, pause_indef] 202 203 203 204 self._resume_item = MenuItem( 204 - label="Resume", 205 + label="resume", 205 206 visible=False, 206 207 callback=self._resume, 207 208 ) 208 209 209 210 # ── Open journal / Show captures ── 210 211 open_journal = MenuItem( 211 - label="Open journal", 212 + label="open journal", 212 213 callback=self._open_journal, 213 214 ) 214 215 215 216 open_captures = MenuItem( 216 - label="Show captures", 217 + label="show captures", 217 218 callback=self._open_captures, 218 219 ) 219 220 220 221 # ── Settings submenu ── 221 222 settings_open_config = MenuItem( 222 - label="Open config.json", 223 + label="open config.json", 223 224 callback=self._open_config, 224 225 ) 225 226 settings_copy_agent = MenuItem( 226 - label="Copy coding agent instructions", 227 + label="copy coding agent instructions", 227 228 callback=self._copy_agent_instructions, 228 229 ) 229 230 230 231 settings_submenu = MenuItem( 231 - label="Settings", 232 + label="settings", 232 233 children_display="submenu", 233 234 ) 234 235 settings_submenu.children = [ ··· 237 238 ] 238 239 239 240 # ── About submenu ── 240 - about_observers = MenuItem( 241 - label="solstone.app/observers", 241 + about_version = MenuItem( 242 + label=f"solstone observer v{__version__}", 243 + enabled=False, 244 + ) 245 + about_website = MenuItem( 246 + label="solstone.app", 242 247 callback=lambda: self._open_url("https://solstone.app/observers"), 248 + ) 249 + about_source = MenuItem( 250 + label="source code", 251 + callback=lambda: self._open_url("https://github.com/solpbc/solstone-linux"), 243 252 ) 244 253 about_privacy = MenuItem( 245 - label="Privacy policy", 254 + label="privacy policy", 246 255 callback=lambda: self._open_url("https://solpbc.org/privacy"), 247 256 ) 248 257 about_copyright = MenuItem( 249 - label="\u00a9 sol pbc", 258 + label="\u00a9 2026 sol pbc \u2014 a public benefit corporation", 250 259 enabled=False, 251 260 ) 252 261 253 262 about_submenu = MenuItem( 254 - label="About", 263 + label="about", 255 264 children_display="submenu", 256 265 ) 257 266 about_submenu.children = [ 258 - about_observers, 267 + about_version, 268 + about_website, 269 + about_source, 259 270 about_privacy, 260 271 separator(), 261 272 about_copyright, ··· 263 274 264 275 # ── Quit ── 265 276 quit_item = MenuItem( 266 - label="Quit solstone observer", 277 + label="quit solstone observer", 267 278 callback=self._quit, 268 279 ) 269 280 ··· 272 283 [ 273 284 status_submenu, 274 285 separator(), 275 - self._pause_submenu, 276 - self._resume_item, 277 - separator(), 278 286 open_journal, 279 287 open_captures, 288 + separator(), 289 + self._pause_submenu, 290 + self._resume_item, 280 291 separator(), 281 292 settings_submenu, 282 293 about_submenu, ··· 305 316 306 317 # Update status submenu item 307 318 labels = { 308 - "recording": "recording", 319 + "recording": "observing", 309 320 "paused": "paused", 310 321 "idle": "idle (screen inactive)", 311 322 "stopped": "not running", ··· 319 330 self._resume_item.visible = is_paused 320 331 if is_paused and self.paused_remaining > 0: 321 332 mins = self.paused_remaining // 60 322 - self._resume_item.label = f"Resume ({mins}m remaining)" 333 + self._resume_item.label = f"resume ({mins}m remaining)" 323 334 else: 324 - self._resume_item.label = "Resume" 335 + self._resume_item.label = "resume" 325 336 self.menu.update_item(self._pause_submenu) 326 337 self.menu.update_item(self._resume_item) 327 338 ··· 388 399 # Update pause remaining in resume button 389 400 if self.status == "paused" and pause_remaining > 0: 390 401 pr_mins = pause_remaining // 60 391 - self._resume_item.label = f"Resume ({pr_mins}m remaining)" 402 + self._resume_item.label = f"resume ({pr_mins}m remaining)" 392 403 self.menu.update_item(self._resume_item) 393 404 394 405 def _build_tooltip(self) -> str: ··· 396 407 parts = [] 397 408 398 409 status_html = { 399 - "recording": "<b>Recording</b>", 400 - "paused": "<b>Paused</b>", 401 - "idle": "Idle (screen inactive)", 402 - "stopped": "<font color='#cc3333'>Not running</font>", 410 + "recording": "<b>observing</b>", 411 + "paused": "<b>paused</b>", 412 + "idle": "idle (screen inactive)", 413 + "stopped": "<font color='#cc3333'>not running</font>", 403 414 } 404 415 parts.append(status_html.get(self.status, self.status)) 405 416 406 417 if self.sync_status == "synced": 407 - parts.append("All segments synced") 418 + parts.append("all segments synced") 408 419 elif self.sync_progress: 409 - parts.append(f"Sync: {self.sync_progress}") 420 + parts.append(f"sync: {self.sync_progress}") 410 421 else: 411 - parts.append(f"Sync: {self.sync_status}") 422 + parts.append(f"sync: {self.sync_status}") 412 423 413 424 if self.error: 414 425 parts.append(f"<font color='#cc3333'>{self.error}</font>")
+5 -5
tests/test_tray.py
··· 47 47 app._build_menu() 48 48 49 49 assert isinstance(app._status_item, MenuItem) 50 - assert app._status_item.label == "recording" 50 + assert app._status_item.label == "observing" 51 51 assert app._status_item.enabled is False 52 52 assert app._sync_item.label == "sync: up to date" 53 53 assert app._pause_submenu.children_display == "submenu" ··· 150 150 151 151 tooltip = app._build_tooltip() 152 152 153 - assert "<b>Recording</b>" in tooltip 154 - assert "All segments synced" in tooltip 153 + assert "<b>observing</b>" in tooltip 154 + assert "all segments synced" in tooltip 155 155 156 156 def test_build_tooltip_stopped(self): 157 157 app = _make_app() ··· 159 159 160 160 tooltip = app._build_tooltip() 161 161 162 - assert "Not running" in tooltip 162 + assert "not running" in tooltip 163 163 164 164 def test_build_tooltip_error(self): 165 165 app = _make_app() ··· 176 176 177 177 tooltip = app._build_tooltip() 178 178 179 - assert "Sync: 2/5" in tooltip 179 + assert "sync: 2/5" in tooltip 180 180 181 181 182 182 class TestUpdate: