aline-ai 0.5.7__py3-none-any.whl → 0.5.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aline-ai
3
- Version: 0.5.7
3
+ Version: 0.5.9
4
4
  Summary: Shared AI memory; everyone knows everything in teams
5
5
  Author: Sharemind
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
- aline_ai-0.5.7.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
- realign/__init__.py,sha256=yVsHlHyZpDRPyaFzYXTltDHKp6XXpiIGe-TlrznKALo,1623
1
+ aline_ai-0.5.9.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
+ realign/__init__.py,sha256=OLwYTOwCSddSWbQNVsKd5oap0zIeASs5UyR2ZpBXeP0,1623
3
3
  realign/claude_detector.py,sha256=ZLSJacMo6zzQclXByABKA70UNpstxqIv3fPGqdpA934,2792
4
- realign/cli.py,sha256=yeq_a3Peoqx8N13Jo2etjJtbTCZYpuqwoMMyAPdrANs,30569
4
+ realign/cli.py,sha256=9VS3WbysZ78NRK5EvkJVg8s6Uh2TQjsGX1E9Pl81pHc,31234
5
5
  realign/codex_detector.py,sha256=N9ulgMgvTzDfXE4s4vLd6OoS0hT7R6h2bDFFXWa-2hE,4183
6
6
  realign/config.py,sha256=lIKZqeOwYc_gHo760lYYX6PnapuKrCWGqT5SA8-PbeA,12044
7
7
  realign/context.py,sha256=S1YEUn5HWSDTerDDMsSsRV871IZxoaxDjPTPI2z6-Xs,9976
@@ -39,13 +39,13 @@ realign/commands/export_shares.py,sha256=Djy1aO7MoU1_ewzn6CZ43oNhSEEonV3sTkSQbHg
39
39
  realign/commands/import_shares.py,sha256=ukX8huvLvEM5g0qEIoqrV1-imz1g-r0Jj2FqD-ojrIA,25297
40
40
  realign/commands/init.py,sha256=ef-q3Qz5D_0Eqld8qjtX26X2QrovBSYcva3uAjiJuwk,33015
41
41
  realign/commands/restore.py,sha256=s2BxQZHxQw9r12NzRVsK20KlGafy5AIoSjWMo5PcnHY,11173
42
- realign/commands/search.py,sha256=RUdseQsjy-SNfKFkGLWrE4IhxkzgkW9IIxAX33XnCHk,24589
42
+ realign/commands/search.py,sha256=QJrC0hln9sCDFxXbpo0nPGMHXrud18qA5QfRyD0z6fQ,25926
43
43
  realign/commands/upgrade.py,sha256=L3PLOUIN5qAQTbkfoVtSsIbbzEezA_xjjk9F1GMVfjw,12781
44
44
  realign/commands/watcher.py,sha256=fWL3kaRkqE03-NtFLaXlx93hJAQrAuNPSoYhOyQZfq8,136273
45
45
  realign/commands/worker.py,sha256=K1DG1uZ--ebKwklHCyIFdN_axoLjL9Onx8Naq-DOZBs,23078
46
46
  realign/dashboard/__init__.py,sha256=QZkHTsGityH8UkF8rmvA3xW7dMXNe0swEWr443qfgCM,128
47
- realign/dashboard/app.py,sha256=LPgDXCqXHtJXzgFiwJvWolLJZ34EU4gejLrBIrgEk4Y,12233
48
- realign/dashboard/tmux_manager.py,sha256=DdCiumQ7YQZnje5VfOQ60585C0X6Va_AhBQi_zmhE0Y,24035
47
+ realign/dashboard/app.py,sha256=jyW6mqmItTy253CPSqInxctkWzkrGEikdy-ikuShQ14,13299
48
+ realign/dashboard/tmux_manager.py,sha256=EfhmB5FF9MH1rtM-ICM0BFJ25kBg6mlMbmA4t4tcVRA,24409
49
49
  realign/dashboard/screens/__init__.py,sha256=US6sAmQs5VVkH2tFkH_z0WDT4H8cVhLL-JckfSR1yQY,446
50
50
  realign/dashboard/screens/create_agent.py,sha256=ugEs3IHrT7FsbuMEwyrqY3eoylp_pbftw42_Fu07tF4,7419
51
51
  realign/dashboard/screens/create_event.py,sha256=oiQY1zKpUYnQU-5fQLeuZH9BV5NClE5B5XZIVBYG5A8,5506
@@ -53,15 +53,15 @@ realign/dashboard/screens/event_detail.py,sha256=OLaL3-FgAohDdzVlfuUw5yh2SR49IHI
53
53
  realign/dashboard/screens/help_screen.py,sha256=Icrcvbgyz49R2tBiu8vBZ4CLm6iYclv_-FTa2pCFRRQ,3398
54
54
  realign/dashboard/screens/session_detail.py,sha256=gfpUIhMO00ecMlMyzpkxDdvGb9zhESEvxwrJvqLuHOI,9603
55
55
  realign/dashboard/screens/share_import.py,sha256=hl2x0yGVycsoUI76AmdZTAV-br3Q6191g5xHHrZ8hOA,6318
56
- realign/dashboard/styles/dashboard.tcss,sha256=9sSIs3r4V8eeTwCK56s7fnYxjMEuASP8EcmK1fhpUmA,3454
56
+ realign/dashboard/styles/dashboard.tcss,sha256=ewonevBGLN-dfSsgxUk4VBCPchtxY4rx_vj1u6Ox2Fw,3454
57
57
  realign/dashboard/widgets/__init__.py,sha256=3Pf2_K9obrertgv_psfxradgkI9RXlmjoXYQH7oBKm0,583
58
58
  realign/dashboard/widgets/config_panel.py,sha256=Afezfd6nvHo0Q44IS2UZTPJsYmHbqzjx7bi5jWrCDPA,11182
59
- realign/dashboard/widgets/events_table.py,sha256=OG9RjwU4c50-RUMmdhXzmIMnYrt6_mCP1GNQWDAX95s,30368
59
+ realign/dashboard/widgets/events_table.py,sha256=dXN_aD94YJ1fDSV2B_5m7YMvMU3bNUhGFCMIRWvvLMg,31141
60
60
  realign/dashboard/widgets/header.py,sha256=0HHCFXX7F3C6HII-WDwOJwWkJrajmKPWmdoMWyOkn9E,1587
61
61
  realign/dashboard/widgets/openable_table.py,sha256=GeJPDEYp0kRHShqvmPMzAePpYXRZHUNqcWNnxqsqxjA,1963
62
62
  realign/dashboard/widgets/search_panel.py,sha256=ZNJDfwDSxUFnCeltYQYsQsPJ6t4HDeNWpENoTOoBdVM,8951
63
- realign/dashboard/widgets/sessions_table.py,sha256=toHE96RLwddqXE9Ykocy6loqoGld_6gFawLwdiiJ2cA,32877
64
- realign/dashboard/widgets/terminal_panel.py,sha256=uXgPcgjWaQ2tTD6Mx6ikCXzq6wYqh-ft0Bait83_DKE,28290
63
+ realign/dashboard/widgets/sessions_table.py,sha256=PohOkg-ESLBa-Sq0PdLPhV-YzVXOGpUo5ETs0MYO4u8,33415
64
+ realign/dashboard/widgets/terminal_panel.py,sha256=YreJ9EiU3OA1qAfcWZ9XuptvCJe8-REKcr03sm7dY-Q,28751
65
65
  realign/dashboard/widgets/watcher_panel.py,sha256=O_mdDacgc87xA-5KEfta53Ik_Xsk_B2OfwenMOTtGw8,19722
66
66
  realign/dashboard/widgets/worker_panel.py,sha256=F_jKWABuCNmjQgeeuCr4KnFRKdY4CLTNcEXMYwsNaSk,18691
67
67
  realign/db/__init__.py,sha256=-1d-Zc4IOUVokbdTXi3R-bIwlkFEPAz_qTHAdcsdp6g,1870
@@ -88,8 +88,8 @@ realign/triggers/next_turn_trigger.py,sha256=BpP0PWn4mU1MZd6mv89jWcjs8Jtv0zEWapW
88
88
  realign/triggers/registry.py,sha256=cb-AVLbYB2pqwfWL3q1DQxLv4kOw7g7m-GshTdfFESc,3827
89
89
  realign/triggers/turn_status.py,sha256=wAZEhXDAmDoX5F-ohWfSnZZ0eA6DAJ9svSPiSv_f6sg,6041
90
90
  realign/triggers/turn_summary.py,sha256=f3hEUshgv9skJ9AbfWpoYs417lsv_HK2A_vpPjgryO4,4467
91
- aline_ai-0.5.7.dist-info/METADATA,sha256=SG0PLhxEgWQIrjE3rCiSk9DFURyfEkxXteRKqdFhijU,1597
92
- aline_ai-0.5.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
93
- aline_ai-0.5.7.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
94
- aline_ai-0.5.7.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
95
- aline_ai-0.5.7.dist-info/RECORD,,
91
+ aline_ai-0.5.9.dist-info/METADATA,sha256=sh6dCwAskyh891skkjvCNomqfJ2RoYTLWPzccHRpUt8,1597
92
+ aline_ai-0.5.9.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
93
+ aline_ai-0.5.9.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
94
+ aline_ai-0.5.9.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
95
+ aline_ai-0.5.9.dist-info/RECORD,,
realign/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  import hashlib
4
4
  from pathlib import Path
5
5
 
6
- __version__ = "0.5.7"
6
+ __version__ = "0.5.9"
7
7
 
8
8
 
9
9
  def get_realign_dir(project_root: Path) -> Path:
realign/cli.py CHANGED
@@ -819,18 +819,36 @@ def version():
819
819
  def dashboard(
820
820
  ctx: typer.Context,
821
821
  dev: bool = typer.Option(False, "--dev", help="Enable developer mode (shows Watcher and Worker tabs)"),
822
+ debug: bool = typer.Option(False, "--debug", help="Enable debug logging to ~/.aline/.logs/dashboard.log"),
822
823
  ):
823
824
  """Open the interactive TUI dashboard."""
825
+ import os
826
+ import traceback
827
+
828
+ # Set debug log level if requested
829
+ if debug:
830
+ os.environ["REALIGN_LOG_LEVEL"] = "DEBUG"
831
+
824
832
  from .dashboard.tmux_manager import bootstrap_dashboard_into_tmux
833
+ from .logging_config import setup_logger
834
+
835
+ # Initialize logger before dashboard
836
+ logger = setup_logger("realign.dashboard", "dashboard.log")
837
+ logger.info(f"Dashboard command invoked (dev={dev}, debug={debug})")
825
838
 
826
- bootstrap_dashboard_into_tmux()
839
+ try:
840
+ bootstrap_dashboard_into_tmux()
827
841
 
828
- from .dashboard.app import AlineDashboard
842
+ from .dashboard.app import AlineDashboard
829
843
 
830
- # Use dev flag from this command or inherit from parent context
831
- dev_mode = dev or (ctx.obj.get("dev", False) if ctx.obj else False)
832
- dash = AlineDashboard(dev_mode=dev_mode)
833
- dash.run()
844
+ # Use dev flag from this command or inherit from parent context
845
+ dev_mode = dev or (ctx.obj.get("dev", False) if ctx.obj else False)
846
+ dash = AlineDashboard(dev_mode=dev_mode)
847
+ dash.run()
848
+ except Exception as e:
849
+ logger.error(f"Dashboard crashed: {e}\n{traceback.format_exc()}")
850
+ # Re-raise so user sees the error
851
+ raise
834
852
 
835
853
 
836
854
  # Restore command group
@@ -528,6 +528,19 @@ def search_command(
528
528
 
529
529
  console.print(f"[dim]Found {', '.join(summary_parts)}.[/dim]")
530
530
 
531
+ # Check if any result count hits the limit - suggest increasing limit
532
+ hit_limit = (
533
+ event_count == limit
534
+ or turn_count == limit
535
+ or session_count == limit
536
+ or (type == "content" and len(results.get("content", [])) == limit)
537
+ )
538
+ if hit_limit:
539
+ console.print(
540
+ f"[yellow]Results may be truncated (limit={limit}). "
541
+ f"Use --limit N to see more results.[/yellow]"
542
+ )
543
+
531
544
  # === Original structured output for non-regex mode ===
532
545
  else:
533
546
  if type == "all":
@@ -624,6 +637,23 @@ def search_command(
624
637
  "\n[dim]Tip: Use --verbose to see Markdown previews, or --no-regex for exact keyword match.[/dim]"
625
638
  )
626
639
 
640
+ # Check if any result count hits the limit - suggest increasing limit
641
+ event_count = len(results.get("events", []))
642
+ turn_count = len(results.get("turns", []))
643
+ session_count = len(results.get("sessions", []))
644
+ content_count = len(results.get("content", []))
645
+ hit_limit = (
646
+ event_count == limit
647
+ or turn_count == limit
648
+ or session_count == limit
649
+ or content_count == limit
650
+ )
651
+ if hit_limit:
652
+ console.print(
653
+ f"[yellow]Results may be truncated (limit={limit}). "
654
+ f"Use --limit N to see more results.[/yellow]"
655
+ )
656
+
627
657
  return 0
628
658
 
629
659
  except Exception as e:
realign/dashboard/app.py CHANGED
@@ -3,12 +3,14 @@
3
3
  import subprocess
4
4
  import sys
5
5
  import time
6
+ import traceback
6
7
  from pathlib import Path
7
8
 
8
9
  from textual.app import App, ComposeResult
9
10
  from textual.binding import Binding
10
11
  from textual.widgets import Footer, TabbedContent, TabPane
11
12
 
13
+ from ..logging_config import setup_logger
12
14
  from .widgets import (
13
15
  AlineHeader,
14
16
  WatcherPanel,
@@ -20,6 +22,9 @@ from .widgets import (
20
22
  TerminalPanel,
21
23
  )
22
24
 
25
+ # Set up dashboard logger - logs to ~/.aline/.logs/dashboard.log
26
+ logger = setup_logger("realign.dashboard", "dashboard.log")
27
+
23
28
 
24
29
  def _detect_system_dark_mode() -> bool:
25
30
  """Detect if the system is in dark mode.
@@ -77,28 +82,35 @@ class AlineDashboard(App):
77
82
  """
78
83
  super().__init__()
79
84
  self.dev_mode = dev_mode
85
+ logger.info(f"AlineDashboard initialized (dev_mode={dev_mode})")
80
86
 
81
87
  def compose(self) -> ComposeResult:
82
88
  """Compose the dashboard layout."""
83
- yield AlineHeader()
84
- tab_ids = self._tab_ids()
85
- with TabbedContent(initial=tab_ids[0] if tab_ids else "terminal"):
86
- with TabPane("Agents", id="terminal"):
87
- yield TerminalPanel()
88
- if self.dev_mode:
89
- with TabPane("Watcher", id="watcher"):
90
- yield WatcherPanel()
91
- with TabPane("Worker", id="worker"):
92
- yield WorkerPanel()
93
- with TabPane("Contexts", id="sessions"):
94
- yield SessionsTable()
95
- with TabPane("Share", id="events"):
96
- yield EventsTable()
97
- with TabPane("Config", id="config"):
98
- yield ConfigPanel()
99
- with TabPane("Search", id="search"):
100
- yield SearchPanel()
101
- yield Footer()
89
+ logger.debug("compose() started")
90
+ try:
91
+ yield AlineHeader()
92
+ tab_ids = self._tab_ids()
93
+ with TabbedContent(initial=tab_ids[0] if tab_ids else "terminal"):
94
+ with TabPane("Agents", id="terminal"):
95
+ yield TerminalPanel()
96
+ if self.dev_mode:
97
+ with TabPane("Watcher", id="watcher"):
98
+ yield WatcherPanel()
99
+ with TabPane("Worker", id="worker"):
100
+ yield WorkerPanel()
101
+ with TabPane("Contexts", id="sessions"):
102
+ yield SessionsTable()
103
+ with TabPane("Share", id="events"):
104
+ yield EventsTable()
105
+ with TabPane("Config", id="config"):
106
+ yield ConfigPanel()
107
+ with TabPane("Search", id="search"):
108
+ yield SearchPanel()
109
+ yield Footer()
110
+ logger.debug("compose() completed successfully")
111
+ except Exception as e:
112
+ logger.error(f"compose() failed: {e}\n{traceback.format_exc()}")
113
+ raise
102
114
 
103
115
  def _tab_ids(self) -> list[str]:
104
116
  if self.dev_mode:
@@ -107,10 +119,16 @@ class AlineDashboard(App):
107
119
 
108
120
  def on_mount(self) -> None:
109
121
  """Apply theme based on system settings and watch for changes."""
110
- self._sync_theme()
111
- # Check for system theme changes every 2 seconds
112
- self.set_interval(2, self._sync_theme)
113
- self._quit_confirm_deadline: float | None = None
122
+ logger.info("on_mount() started")
123
+ try:
124
+ self._sync_theme()
125
+ # Check for system theme changes every 2 seconds
126
+ self.set_interval(2, self._sync_theme)
127
+ self._quit_confirm_deadline: float | None = None
128
+ logger.info("on_mount() completed successfully")
129
+ except Exception as e:
130
+ logger.error(f"on_mount() failed: {e}\n{traceback.format_exc()}")
131
+ raise
114
132
 
115
133
  def _sync_theme(self) -> None:
116
134
  """Sync app theme with system theme."""
@@ -341,8 +359,14 @@ class AlineDashboard(App):
341
359
 
342
360
  def run_dashboard() -> None:
343
361
  """Run the Aline Dashboard."""
344
- app = AlineDashboard()
345
- app.run()
362
+ logger.info("Starting Aline Dashboard")
363
+ try:
364
+ app = AlineDashboard()
365
+ app.run()
366
+ logger.info("Aline Dashboard exited normally")
367
+ except Exception as e:
368
+ logger.error(f"Dashboard crashed: {e}\n{traceback.format_exc()}")
369
+ raise
346
370
 
347
371
 
348
372
  if __name__ == "__main__":
@@ -202,14 +202,14 @@ TerminalPanel .terminal-row Button.terminal-switch {
202
202
 
203
203
  TerminalPanel .terminal-row Button.terminal-close {
204
204
  padding: 0;
205
- width: 2;
206
- min-width: 2;
205
+ width: 3;
206
+ min-width: 3;
207
207
  }
208
208
 
209
209
  TerminalPanel .terminal-row Button.terminal-toggle {
210
210
  padding: 0;
211
- width: 2;
212
- min-width: 2;
211
+ width: 3;
212
+ min-width: 3;
213
213
  }
214
214
 
215
215
  TerminalPanel .context-sessions {
@@ -15,11 +15,15 @@ import stat
15
15
  import subprocess
16
16
  import sys
17
17
  import time
18
+ import traceback
18
19
  import uuid
19
20
  from dataclasses import dataclass
20
21
  from pathlib import Path
21
22
  from typing import Iterable, Sequence
22
23
 
24
+ from ..logging_config import setup_logger
25
+
26
+ logger = setup_logger("realign.dashboard.tmux", "dashboard.log")
23
27
 
24
28
  OUTER_SESSION = "aline"
25
29
  OUTER_WINDOW = "dashboard"
@@ -364,11 +368,17 @@ def bootstrap_dashboard_into_tmux() -> None:
364
368
 
365
369
  This is intended to be called when *not* already inside tmux.
366
370
  """
367
- if in_tmux() or not tmux_available():
371
+ logger.debug("bootstrap_dashboard_into_tmux() started")
372
+ if in_tmux():
373
+ logger.debug("Already in tmux, skipping bootstrap")
374
+ return
375
+ if not tmux_available():
376
+ logger.debug("tmux not available, skipping bootstrap")
368
377
  return
369
378
 
370
379
  # Maximize terminal window before attaching to tmux
371
380
  _maximize_terminal_window()
381
+ logger.debug("Terminal window maximized")
372
382
 
373
383
  # Ensure session exists.
374
384
  has = _run_outer_tmux(["has-session", "-t", OUTER_SESSION], capture=True)
@@ -6,6 +6,7 @@ import json
6
6
  import os
7
7
  import shutil
8
8
  import subprocess
9
+ import traceback
9
10
  from datetime import datetime
10
11
  from typing import Optional, Set
11
12
  from urllib.parse import urlparse
@@ -18,8 +19,11 @@ from textual.reactive import reactive
18
19
  from textual.worker import Worker, WorkerState
19
20
  from textual.widgets import Button, DataTable, Static
20
21
 
22
+ from ...logging_config import setup_logger
21
23
  from .openable_table import OpenableDataTable
22
24
 
25
+ logger = setup_logger("realign.dashboard.events", "dashboard.log")
26
+
23
27
 
24
28
  class EventsListTable(OpenableDataTable):
25
29
  """Events list table with multi-select behavior."""
@@ -123,38 +127,50 @@ class EventsTable(Container):
123
127
 
124
128
  def compose(self) -> ComposeResult:
125
129
  """Compose the events table layout."""
126
- with Vertical(classes="action-section"):
127
- yield Button(
128
- "Load selected context to current agent",
129
- id="load-context-btn",
130
- variant="primary",
131
- disabled=True,
132
- )
133
- yield Button(
134
- "Share selected events to others",
135
- id="share-event-btn",
136
- variant="primary",
137
- disabled=True,
138
- )
139
- yield Button(
140
- "Import context from others",
141
- id="share-import-btn",
142
- variant="primary",
143
- )
144
- yield Static(id="section-header", classes="section-header")
145
- with Container(classes="table-container"):
146
- yield EventsListTable(id="events-table")
147
- yield Static(id="pagination-info", classes="pagination-info")
148
- yield Static(id="stats-info", classes="stats-info")
130
+ logger.debug("EventsTable.compose() started")
131
+ try:
132
+ with Vertical(classes="action-section"):
133
+ yield Button(
134
+ "Load selected context to current agent",
135
+ id="load-context-btn",
136
+ variant="primary",
137
+ disabled=True,
138
+ )
139
+ yield Button(
140
+ "Share selected events to others",
141
+ id="share-event-btn",
142
+ variant="primary",
143
+ disabled=True,
144
+ )
145
+ yield Button(
146
+ "Import context from others",
147
+ id="share-import-btn",
148
+ variant="primary",
149
+ )
150
+ yield Static(id="section-header", classes="section-header")
151
+ with Container(classes="table-container"):
152
+ yield EventsListTable(id="events-table")
153
+ yield Static(id="pagination-info", classes="pagination-info")
154
+ yield Static(id="stats-info", classes="stats-info")
155
+ logger.debug("EventsTable.compose() completed")
156
+ except Exception as e:
157
+ logger.error(f"EventsTable.compose() failed: {e}\n{traceback.format_exc()}")
158
+ raise
149
159
 
150
160
  def on_mount(self) -> None:
151
161
  """Set up the table on mount."""
152
- table = self.query_one("#events-table", EventsListTable)
153
- table.owner = self
154
- self._setup_table_columns(table)
155
-
156
- # Calculate initial rows per page
157
- self._calculate_rows_per_page()
162
+ logger.debug("EventsTable.on_mount() started")
163
+ try:
164
+ table = self.query_one("#events-table", EventsListTable)
165
+ table.owner = self
166
+ self._setup_table_columns(table)
167
+
168
+ # Calculate initial rows per page
169
+ self._calculate_rows_per_page()
170
+ logger.debug("EventsTable.on_mount() completed")
171
+ except Exception as e:
172
+ logger.error(f"EventsTable.on_mount() failed: {e}\n{traceback.format_exc()}")
173
+ raise
158
174
 
159
175
  def on_resize(self) -> None:
160
176
  """Handle window resize to adjust rows per page."""
@@ -6,6 +6,7 @@ import json
6
6
  import os
7
7
  import shutil
8
8
  import subprocess
9
+ import traceback
9
10
  from datetime import datetime
10
11
  from pathlib import Path
11
12
  from typing import List, Optional, Set
@@ -18,8 +19,11 @@ from textual.reactive import reactive
18
19
  from textual.worker import Worker, WorkerState
19
20
  from textual.widgets import Button, DataTable, Static
20
21
 
22
+ from ...logging_config import setup_logger
21
23
  from .openable_table import OpenableDataTable
22
24
 
25
+ logger = setup_logger("realign.dashboard.sessions", "dashboard.log")
26
+
23
27
 
24
28
  class SessionsListTable(OpenableDataTable):
25
29
  """Sessions list table with multi-select behavior."""
@@ -145,13 +149,19 @@ class SessionsTable(Container):
145
149
 
146
150
  def on_mount(self) -> None:
147
151
  """Set up the table on mount."""
148
- table = self.query_one("#sessions-table", SessionsListTable)
149
- table.owner = self
152
+ logger.debug("SessionsTable.on_mount() started")
153
+ try:
154
+ table = self.query_one("#sessions-table", SessionsListTable)
155
+ table.owner = self
150
156
 
151
- self._setup_table_columns(table)
157
+ self._setup_table_columns(table)
152
158
 
153
- # Calculate initial rows per page
154
- self._calculate_rows_per_page()
159
+ # Calculate initial rows per page
160
+ self._calculate_rows_per_page()
161
+ logger.debug("SessionsTable.on_mount() completed")
162
+ except Exception as e:
163
+ logger.error(f"SessionsTable.on_mount() failed: {e}\n{traceback.format_exc()}")
164
+ raise
155
165
 
156
166
  def on_resize(self) -> None:
157
167
  """Handle window resize to adjust rows per page."""
@@ -793,6 +803,7 @@ class SessionsTable(Container):
793
803
  }
794
804
 
795
805
  # Get paginated sessions
806
+ # Use cached total_turns instead of subquery for performance
796
807
  offset = (int(page) - 1) * int(rows_per_page)
797
808
  rows = conn.execute(
798
809
  """
@@ -802,7 +813,7 @@ class SessionsTable(Container):
802
813
  s.workspace_path,
803
814
  s.session_title,
804
815
  s.last_activity_at,
805
- (SELECT COUNT(*) FROM turns WHERE session_id = s.id) AS turn_count
816
+ s.total_turns
806
817
  FROM sessions s
807
818
  ORDER BY s.last_activity_at DESC
808
819
  LIMIT ? OFFSET ?
@@ -847,7 +858,8 @@ class SessionsTable(Container):
847
858
  "last_activity": activity_str,
848
859
  }
849
860
  )
850
- except Exception:
861
+ except Exception as e:
862
+ logger.error(f"_collect_snapshot failed: {e}\n{traceback.format_exc()}")
851
863
  total_sessions = 0
852
864
  stats = {}
853
865
  sessions = []
@@ -11,6 +11,7 @@ import asyncio
11
11
  import os
12
12
  import re
13
13
  import shlex
14
+ import traceback
14
15
  from pathlib import Path
15
16
  from typing import Callable
16
17
 
@@ -21,6 +22,9 @@ from textual.widgets import Button, Static
21
22
  from rich.text import Text
22
23
 
23
24
  from .. import tmux_manager
25
+ from ...logging_config import setup_logger
26
+
27
+ logger = setup_logger("realign.dashboard.terminal", "dashboard.log")
24
28
 
25
29
 
26
30
  # Signal directory for permission request notifications
@@ -199,8 +203,8 @@ class TerminalPanel(Container, can_focus=True):
199
203
  }
200
204
 
201
205
  TerminalPanel .terminal-row Button.terminal-close {
202
- width: 2;
203
- min-width: 2;
206
+ width: 3;
207
+ min-width: 3;
204
208
  height: 2;
205
209
  margin-left: 1;
206
210
  padding: 0;
@@ -217,8 +221,8 @@ class TerminalPanel(Container, can_focus=True):
217
221
  }
218
222
 
219
223
  TerminalPanel .terminal-row Button.terminal-toggle {
220
- width: 2;
221
- min-width: 2;
224
+ width: 3;
225
+ min-width: 3;
222
226
  height: 2;
223
227
  margin-left: 1;
224
228
  padding: 0;
@@ -291,21 +295,27 @@ class TerminalPanel(Container, can_focus=True):
291
295
  self._signal_watcher: _SignalFileWatcher | None = None
292
296
 
293
297
  def compose(self) -> ComposeResult:
294
- controls_enabled = self.supported()
295
- with Horizontal(classes="summary"):
296
- yield Button(
297
- "+ Create",
298
- id="new-agent",
299
- variant="primary",
300
- disabled=not controls_enabled,
301
- )
302
- with Vertical(id="terminals", classes="list"):
303
- if controls_enabled:
304
- yield Static(
305
- "No terminals yet. Click 'Create' to open a new agent terminal."
298
+ logger.debug("TerminalPanel.compose() started")
299
+ try:
300
+ controls_enabled = self.supported()
301
+ with Horizontal(classes="summary"):
302
+ yield Button(
303
+ "+ Create",
304
+ id="new-agent",
305
+ variant="primary",
306
+ disabled=not controls_enabled,
306
307
  )
307
- else:
308
- yield Static(self._support_message())
308
+ with Vertical(id="terminals", classes="list"):
309
+ if controls_enabled:
310
+ yield Static(
311
+ "No terminals yet. Click 'Create' to open a new agent terminal."
312
+ )
313
+ else:
314
+ yield Static(self._support_message())
315
+ logger.debug("TerminalPanel.compose() completed")
316
+ except Exception as e:
317
+ logger.error(f"TerminalPanel.compose() failed: {e}\n{traceback.format_exc()}")
318
+ raise
309
319
 
310
320
  def on_show(self) -> None:
311
321
  # Don't `await refresh_data()` directly here: Textual may do an initial layout pass with