aline-ai 0.5.5__py3-none-any.whl → 0.5.7__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.
@@ -0,0 +1,114 @@
1
+ """Help screen modal for the dashboard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from textual.app import ComposeResult
6
+ from textual.binding import Binding
7
+ from textual.containers import Container, Vertical
8
+ from textual.screen import ModalScreen
9
+ from textual.widgets import Button, Static
10
+
11
+
12
+ class HelpScreen(ModalScreen):
13
+ """Modal showing keyboard shortcuts and help information."""
14
+
15
+ BINDINGS = [
16
+ Binding("escape", "close", "Close", show=False),
17
+ Binding("?", "close", "Close", show=False),
18
+ ]
19
+
20
+ DEFAULT_CSS = """
21
+ HelpScreen {
22
+ align: center middle;
23
+ }
24
+
25
+ HelpScreen #help-root {
26
+ width: 50;
27
+ height: auto;
28
+ max-height: 80%;
29
+ padding: 1 2;
30
+ background: $background;
31
+ border: solid $accent;
32
+ }
33
+
34
+ HelpScreen #help-title {
35
+ height: auto;
36
+ margin-bottom: 1;
37
+ text-style: bold;
38
+ text-align: center;
39
+ }
40
+
41
+ HelpScreen .shortcut-section {
42
+ height: auto;
43
+ margin-bottom: 1;
44
+ }
45
+
46
+ HelpScreen .section-title {
47
+ height: auto;
48
+ color: $text-muted;
49
+ margin-bottom: 0;
50
+ }
51
+
52
+ HelpScreen .shortcut-row {
53
+ height: auto;
54
+ padding: 0 1;
55
+ }
56
+
57
+ HelpScreen .shortcut-key {
58
+ width: 14;
59
+ height: auto;
60
+ color: $accent;
61
+ }
62
+
63
+ HelpScreen .shortcut-desc {
64
+ width: 1fr;
65
+ height: auto;
66
+ }
67
+
68
+ HelpScreen #close-btn {
69
+ margin-top: 1;
70
+ width: 100%;
71
+ }
72
+ """
73
+
74
+ def compose(self) -> ComposeResult:
75
+ with Container(id="help-root"):
76
+ yield Static("Keyboard Shortcuts", id="help-title")
77
+
78
+ with Vertical(classes="shortcut-section"):
79
+ yield Static("Navigation", classes="section-title")
80
+ yield self._shortcut_row("Tab", "Next tab")
81
+ yield self._shortcut_row("Shift+Tab", "Previous tab")
82
+ yield self._shortcut_row("n", "Next page")
83
+ yield self._shortcut_row("p", "Previous page")
84
+
85
+ with Vertical(classes="shortcut-section"):
86
+ yield Static("Actions", classes="section-title")
87
+ yield self._shortcut_row("Enter", "Open selected item")
88
+ yield self._shortcut_row("Space", "Toggle selection")
89
+ yield self._shortcut_row("c", "Create event")
90
+ yield self._shortcut_row("l", "Load context")
91
+ yield self._shortcut_row("y", "Import share")
92
+ yield self._shortcut_row("s", "Switch view")
93
+ yield self._shortcut_row("r", "Refresh")
94
+
95
+ with Vertical(classes="shortcut-section"):
96
+ yield Static("General", classes="section-title")
97
+ yield self._shortcut_row("?", "Show this help")
98
+ yield self._shortcut_row("Ctrl+C x2", "Quit")
99
+
100
+ yield Button("Close", id="close-btn", variant="primary")
101
+
102
+ def _shortcut_row(self, key: str, description: str) -> Static:
103
+ """Create a shortcut row with key and description."""
104
+ return Static(f"[bold $accent]{key:<12}[/] {description}", classes="shortcut-row")
105
+
106
+ def on_mount(self) -> None:
107
+ self.query_one("#close-btn", Button).focus()
108
+
109
+ def action_close(self) -> None:
110
+ self.app.pop_screen()
111
+
112
+ def on_button_pressed(self, event: Button.Pressed) -> None:
113
+ if event.button.id == "close-btn":
114
+ self.app.pop_screen()
@@ -1,5 +1,11 @@
1
1
  """Events Table Widget with keyboard pagination."""
2
2
 
3
+ import contextlib
4
+ import io
5
+ import json
6
+ import os
7
+ import shutil
8
+ import subprocess
3
9
  from datetime import datetime
4
10
  from typing import Optional, Set
5
11
  from urllib.parse import urlparse
@@ -7,7 +13,7 @@ from urllib.parse import urlparse
7
13
  from textual import events
8
14
  from textual.app import ComposeResult
9
15
  from textual.binding import Binding
10
- from textual.containers import Container, Horizontal
16
+ from textual.containers import Container, Horizontal, Vertical
11
17
  from textual.reactive import reactive
12
18
  from textual.worker import Worker, WorkerState
13
19
  from textual.widgets import Button, DataTable, Static
@@ -44,7 +50,7 @@ class EventsListTable(OpenableDataTable):
44
50
  self.owner.apply_mouse_selection(row_index, shift=event.shift, meta=event.meta)
45
51
 
46
52
 
47
- class EventsTable(Container, can_focus=True):
53
+ class EventsTable(Container):
48
54
  """Table displaying events with keyboard pagination support."""
49
55
 
50
56
  DEFAULT_CSS = """
@@ -54,26 +60,14 @@ class EventsTable(Container, can_focus=True):
54
60
  overflow: hidden;
55
61
  }
56
62
 
57
- EventsTable:focus {
58
- border: solid $accent;
59
- }
60
-
61
- EventsTable .summary-section {
63
+ EventsTable .action-section {
62
64
  height: auto;
63
65
  margin-bottom: 1;
64
- padding: 1;
65
- background: $surface-darken-1;
66
- border: solid $primary-darken-2;
67
- }
68
-
69
- EventsTable .summary-section Static {
70
- width: 1fr;
71
- height: auto;
72
66
  }
73
67
 
74
- EventsTable .summary-section Button {
75
- width: 12;
76
- margin-left: 1;
68
+ EventsTable .action-section Button {
69
+ width: auto;
70
+ margin-bottom: 1;
77
71
  }
78
72
 
79
73
  EventsTable .section-header {
@@ -83,13 +77,15 @@ class EventsTable(Container, can_focus=True):
83
77
 
84
78
  EventsTable .table-container {
85
79
  height: auto;
86
- overflow: hidden;
80
+ overflow-x: auto;
81
+ overflow-y: hidden;
87
82
  }
88
83
 
89
84
  EventsTable DataTable {
90
85
  height: auto;
91
86
  max-height: 100%;
92
- overflow: hidden;
87
+ overflow-x: auto;
88
+ overflow-y: hidden;
93
89
  }
94
90
 
95
91
  EventsTable .pagination-info {
@@ -98,6 +94,12 @@ class EventsTable(Container, can_focus=True):
98
94
  color: $text-muted;
99
95
  text-align: center;
100
96
  }
97
+
98
+ EventsTable .stats-info {
99
+ height: 1;
100
+ color: $text-muted;
101
+ text-align: center;
102
+ }
101
103
  """
102
104
 
103
105
  # Reactive properties
@@ -108,25 +110,42 @@ class EventsTable(Container, can_focus=True):
108
110
  def __init__(self) -> None:
109
111
  super().__init__()
110
112
  self._events: list = []
113
+ self._events_by_id: dict = {} # Index events by id for quick lookup
111
114
  self._total_events: int = 0
112
115
  self._selected_event_ids: Set[str] = set()
113
116
  self._selection_anchor_row: Optional[int] = None
114
117
  self._last_wrap_mode: bool = bool(self.wrap_mode)
115
118
  self._refresh_worker: Optional[Worker] = None
119
+ self._share_export_worker: Optional[Worker] = None
116
120
  self._refresh_timer = None
117
121
  self._active_refresh_snapshot: Optional[tuple[int, int]] = None
118
122
  self._pending_refresh_snapshot: Optional[tuple[int, int]] = None
119
123
 
120
124
  def compose(self) -> ComposeResult:
121
125
  """Compose the events table layout."""
122
- with Horizontal(classes="summary-section"):
123
- yield Static(id="events-summary-text")
124
- yield Button("Load ctx (l)", id="load-context-btn", variant="primary")
125
- yield Button("Import (y)", id="share-import-btn", variant="primary")
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
+ )
126
144
  yield Static(id="section-header", classes="section-header")
127
145
  with Container(classes="table-container"):
128
146
  yield EventsListTable(id="events-table")
129
147
  yield Static(id="pagination-info", classes="pagination-info")
148
+ yield Static(id="stats-info", classes="stats-info")
130
149
 
131
150
  def on_mount(self) -> None:
132
151
  """Set up the table on mount."""
@@ -170,7 +189,9 @@ class EventsTable(Container, can_focus=True):
170
189
  except Exception:
171
190
  pass
172
191
 
173
- def on_openable_data_table_row_activated(self, event: OpenableDataTable.RowActivated) -> None:
192
+ def on_openable_data_table_row_activated(
193
+ self, event: OpenableDataTable.RowActivated
194
+ ) -> None:
174
195
  if event.data_table.id != "events-table":
175
196
  return
176
197
 
@@ -190,7 +211,13 @@ class EventsTable(Container, can_focus=True):
190
211
  def _setup_table_columns(self, table: DataTable) -> None:
191
212
  table.clear(columns=True)
192
213
  table.add_column("✓", key="sel", width=2)
193
- table.add_columns("#", "Event ID", "Title", "Type", "Sessions", "Share", "Created")
214
+ table.add_column("#", key="index", width=4)
215
+ table.add_column("Title", key="title") # Auto width for full title
216
+ table.add_column("Share", key="share", width=12)
217
+ table.add_column("Type", key="type", width=8)
218
+ table.add_column("Sessions", key="sessions", width=8)
219
+ table.add_column("Event ID", key="event_id", width=12)
220
+ table.add_column("Created", key="created", width=10)
194
221
  table.cursor_type = "row"
195
222
 
196
223
  def get_selected_event_ids(self) -> list[str]:
@@ -206,7 +233,9 @@ class EventsTable(Container, can_focus=True):
206
233
  if table.row_count == 0:
207
234
  return
208
235
  try:
209
- event_id = str(table.coordinate_to_cell_key(table.cursor_coordinate)[0].value)
236
+ event_id = str(
237
+ table.coordinate_to_cell_key(table.cursor_coordinate)[0].value
238
+ )
210
239
  except Exception:
211
240
  return
212
241
 
@@ -255,19 +284,28 @@ class EventsTable(Container, can_focus=True):
255
284
  self._selected_event_ids.update(ids_in_range)
256
285
  else:
257
286
  self._selected_event_ids = set(ids_in_range)
258
- elif meta:
287
+ else:
288
+ # Toggle selection on click (no modifier keys needed)
259
289
  if clicked_id in self._selected_event_ids:
260
290
  self._selected_event_ids.remove(clicked_id)
261
291
  else:
262
292
  self._selected_event_ids.add(clicked_id)
263
- else:
264
- self._selected_event_ids = {clicked_id}
265
293
 
266
294
  self._selection_anchor_row = row_index
267
295
  self._refresh_checkboxes_only()
268
296
 
269
297
  def _checkbox_cell(self, event_id: str) -> str:
270
- return "[green]☑[/green]" if event_id in self._selected_event_ids else "☐"
298
+ return (
299
+ "[bold green]●[/bold green]"
300
+ if event_id in self._selected_event_ids
301
+ else "○"
302
+ )
303
+
304
+ def _format_cell(self, value: str, event_id: str) -> str:
305
+ """Format cell value with bold if selected."""
306
+ if event_id in self._selected_event_ids:
307
+ return f"[bold]{value}[/bold]"
308
+ return value
271
309
 
272
310
  def _refresh_checkboxes_only(self) -> None:
273
311
  table = self.query_one("#events-table", DataTable)
@@ -282,20 +320,49 @@ class EventsTable(Container, can_focus=True):
282
320
  continue
283
321
  if not eid:
284
322
  continue
323
+ event = self._events_by_id.get(eid)
324
+ if not event:
325
+ continue
285
326
  try:
327
+ # Update all cells in the row with proper formatting
328
+ share_id = event.get("share_id") or "-"
286
329
  table.update_cell(eid, "sel", self._checkbox_cell(eid))
330
+ table.update_cell(
331
+ eid, "index", self._format_cell(str(event["index"]), eid)
332
+ )
333
+ table.update_cell(eid, "title", self._format_cell(event["title"], eid))
334
+ table.update_cell(eid, "share", self._format_cell(share_id, eid))
335
+ table.update_cell(eid, "type", self._format_cell(event["type"], eid))
336
+ table.update_cell(
337
+ eid, "sessions", self._format_cell(str(event["sessions"]), eid)
338
+ )
339
+ table.update_cell(
340
+ eid, "event_id", self._format_cell(event["short_id"], eid)
341
+ )
342
+ table.update_cell(
343
+ eid, "created", self._format_cell(event["created"], eid)
344
+ )
287
345
  except Exception:
288
346
  continue
289
347
 
290
348
  self._update_summary_widget()
291
349
 
292
350
  def _update_summary_widget(self) -> None:
293
- summary_widget = self.query_one("#events-summary-text", Static)
294
- summary_text = f"[bold]Total Events:[/bold] {self._total_events}"
351
+ # Update stats at the bottom
352
+ stats_widget = self.query_one("#stats-info", Static)
295
353
  selected_count = len(self._selected_event_ids)
354
+
355
+ stats_parts = [f"Total: {self._total_events}"]
296
356
  if selected_count:
297
- summary_text += f" | [bold]Selected:[/bold] {selected_count}"
298
- summary_widget.update(summary_text)
357
+ stats_parts.append(f"Selected: {selected_count}")
358
+
359
+ stats_widget.update(f"[dim]{' | '.join(stats_parts)}[/dim]")
360
+
361
+ # Enable/disable buttons based on selection
362
+ load_btn = self.query_one("#load-context-btn", Button)
363
+ load_btn.disabled = selected_count == 0
364
+ share_btn = self.query_one("#share-event-btn", Button)
365
+ share_btn.disabled = selected_count == 0
299
366
 
300
367
  def _sync_to_available_height(self) -> None:
301
368
  """Recalculate rows per page and reload if the page size changed."""
@@ -342,6 +409,9 @@ class EventsTable(Container, can_focus=True):
342
409
  if callable(action):
343
410
  await action()
344
411
  return
412
+ if button_id == "share-event-btn":
413
+ self._start_share_export()
414
+ return
345
415
 
346
416
  def action_share_import(self) -> None:
347
417
  from ..screens import ShareImportScreen
@@ -402,6 +472,15 @@ class EventsTable(Container, can_focus=True):
402
472
  self.refresh_data()
403
473
 
404
474
  def on_worker_state_changed(self, event: Worker.StateChanged) -> None:
475
+ # Handle share export worker
476
+ if (
477
+ self._share_export_worker is not None
478
+ and event.worker is self._share_export_worker
479
+ ):
480
+ self._on_share_export_worker_changed(event)
481
+ return
482
+
483
+ # Handle refresh worker
405
484
  if self._refresh_worker is None or event.worker is not self._refresh_worker:
406
485
  return
407
486
 
@@ -424,13 +503,182 @@ class EventsTable(Container, can_focus=True):
424
503
  self._total_events = 0
425
504
  try:
426
505
  self._events = list(result.get("events") or [])
506
+ self._events_by_id = {e["id"]: e for e in self._events}
427
507
  except Exception:
428
508
  self._events = []
509
+ self._events_by_id = {}
429
510
  self._update_display()
430
511
 
431
512
  if self._pending_refresh_snapshot is not None:
432
513
  self.refresh_data()
433
514
 
515
+ def _start_share_export(self) -> None:
516
+ """Export selected events as share links."""
517
+ selected_ids = list(self._selected_event_ids)
518
+ if not selected_ids:
519
+ return
520
+
521
+ if (
522
+ self._share_export_worker is not None
523
+ and self._share_export_worker.state
524
+ in (
525
+ WorkerState.PENDING,
526
+ WorkerState.RUNNING,
527
+ )
528
+ ):
529
+ return
530
+
531
+ def work() -> dict:
532
+ # Export the first selected event (for now, support single event)
533
+ event_id = selected_ids[0]
534
+
535
+ stdout_exp = io.StringIO()
536
+ stderr_exp = io.StringIO()
537
+
538
+ with (
539
+ contextlib.redirect_stdout(stdout_exp),
540
+ contextlib.redirect_stderr(stderr_exp),
541
+ ):
542
+ from ...commands import export_shares
543
+
544
+ exp_exit_code = export_shares.export_shares_interactive_command(
545
+ indices=event_id,
546
+ password=None,
547
+ enable_preview=False,
548
+ json_output=True,
549
+ compact=True,
550
+ )
551
+
552
+ exp_output = stdout_exp.getvalue().strip()
553
+ result: dict = {
554
+ "exit_code": exp_exit_code,
555
+ "output": exp_output,
556
+ "stderr": stderr_exp.getvalue().strip(),
557
+ "event_id": event_id,
558
+ }
559
+
560
+ if exp_output:
561
+ # Try to extract JSON from output (may contain other text)
562
+ try:
563
+ result["json"] = json.loads(exp_output)
564
+ except Exception:
565
+ # Try to find JSON object in output
566
+ json_start = exp_output.find("{")
567
+ json_end = exp_output.rfind("}") + 1
568
+ if json_start >= 0 and json_end > json_start:
569
+ try:
570
+ result["json"] = json.loads(exp_output[json_start:json_end])
571
+ except Exception:
572
+ result["json"] = None
573
+ else:
574
+ result["json"] = None
575
+ else:
576
+ result["json"] = None
577
+
578
+ return result
579
+
580
+ self.app.notify("Creating share link...", title="Share", timeout=2)
581
+ self._share_export_worker = self.run_worker(
582
+ work, thread=True, exit_on_error=False
583
+ )
584
+
585
+ def _on_share_export_worker_changed(self, event: Worker.StateChanged) -> None:
586
+ """Handle share export worker state changes."""
587
+ if event.state == WorkerState.ERROR:
588
+ err = (
589
+ self._share_export_worker.error
590
+ if self._share_export_worker
591
+ else "Unknown error"
592
+ )
593
+ self.app.notify(
594
+ f"Share export failed: {err}", title="Share", severity="error"
595
+ )
596
+ return
597
+
598
+ if event.state != WorkerState.SUCCESS:
599
+ return
600
+
601
+ result = self._share_export_worker.result if self._share_export_worker else {}
602
+ if not result:
603
+ result = {}
604
+
605
+ exit_code = int(result.get("exit_code", 1))
606
+
607
+ if exit_code != 0:
608
+ stderr = result.get("stderr", "")
609
+ self.app.notify(
610
+ f"Share export failed: {stderr}" if stderr else "Share export failed",
611
+ title="Share",
612
+ severity="error",
613
+ )
614
+ return
615
+
616
+ payload = result.get("json") or {}
617
+ share_link = payload.get("share_link") or payload.get("share_url")
618
+ slack_message = (
619
+ payload.get("slack_message") if isinstance(payload, dict) else None
620
+ )
621
+ event_id = result.get("event_id")
622
+
623
+ # Try to fetch share_link and slack_message from database
624
+ if event_id:
625
+ try:
626
+ from ...db import get_database
627
+
628
+ db = get_database()
629
+ db_event = db.get_event_by_id(event_id)
630
+ if db_event:
631
+ if not share_link:
632
+ share_link = getattr(db_event, "share_url", None)
633
+ if not slack_message:
634
+ slack_message = getattr(db_event, "slack_message", None)
635
+ except Exception:
636
+ pass
637
+
638
+ if not share_link:
639
+ self.app.notify(
640
+ "Share export completed but no link generated",
641
+ title="Share",
642
+ severity="warning",
643
+ )
644
+ return
645
+
646
+ # Build copy text
647
+ if slack_message:
648
+ copy_text = str(slack_message) + "\n\n" + str(share_link)
649
+ else:
650
+ copy_text = str(share_link)
651
+
652
+ # Copy to clipboard
653
+ copied = False
654
+ if os.environ.get("TMUX") and shutil.which("pbcopy"):
655
+ try:
656
+ copied = (
657
+ subprocess.run(
658
+ ["pbcopy"],
659
+ input=copy_text,
660
+ text=True,
661
+ capture_output=False,
662
+ check=False,
663
+ ).returncode
664
+ == 0
665
+ )
666
+ except Exception:
667
+ copied = False
668
+
669
+ if not copied:
670
+ try:
671
+ self.app.copy_to_clipboard(copy_text)
672
+ copied = True
673
+ except Exception:
674
+ copied = False
675
+
676
+ suffix = " (copied to clipboard)" if copied else ""
677
+ self.app.notify(f"Share link created{suffix}", title="Share", timeout=4)
678
+
679
+ # Refresh to show updated share info
680
+ self.refresh_data()
681
+
434
682
  def _extract_share_id(self, share_url: Optional[str]) -> str:
435
683
  if not share_url:
436
684
  return ""
@@ -555,8 +803,7 @@ class EventsTable(Container, can_focus=True):
555
803
 
556
804
  # Update section header
557
805
  header_widget = self.query_one("#section-header", Static)
558
- mode = "Wrap" if self.wrap_mode else "Compact"
559
- header_widget.update(f"[bold]Events[/bold] [dim]({mode})[/dim]")
806
+ header_widget.update("[bold]Events[/bold]")
560
807
 
561
808
  # Update table
562
809
  table = self.query_one("#events-table", DataTable)
@@ -568,49 +815,44 @@ class EventsTable(Container, can_focus=True):
568
815
  )
569
816
  except Exception:
570
817
  selected_event_id = None
571
- if self.wrap_mode != self._last_wrap_mode:
572
- self._setup_table_columns(table)
573
- self._last_wrap_mode = bool(self.wrap_mode)
574
- else:
575
- table.clear()
818
+ table.clear()
576
819
 
577
- if self.wrap_mode:
578
- table.styles.overflow_x = "auto"
579
- table.show_horizontal_scrollbar = True
580
- else:
581
- table.styles.overflow_x = "hidden"
582
- table.show_horizontal_scrollbar = False
820
+ # Always enable horizontal scrollbar
821
+ table.styles.overflow_x = "auto"
822
+ table.show_horizontal_scrollbar = True
583
823
 
584
824
  for event in self._events:
825
+ eid = event["id"]
826
+ share_id = event.get("share_id") or "-"
585
827
  title = event["title"]
586
- if self.wrap_mode:
587
- title_cell = title
588
- else:
589
- title_cell = title[:50] + "..." if len(title) > 50 else title
590
828
 
591
- share_url = event.get("share_url") or ""
592
- share_id = event.get("share_id") or ""
593
- if self.wrap_mode:
594
- share_cell = str(share_url) if share_url else "-"
595
- else:
596
- share_cell = share_id or "-"
829
+ if not self.wrap_mode and len(title) > 60:
830
+ title = title[:57] + "..."
831
+
832
+ share_val = share_id
833
+ if self.wrap_mode and event.get("share_url"):
834
+ share_val = event.get("share_url")
597
835
 
836
+ # Column order: ✓, #, Title, Share, Type, Sessions, Event ID, Created
598
837
  table.add_row(
599
- self._checkbox_cell(event["id"]),
600
- str(event["index"]),
601
- event["short_id"],
602
- title_cell,
603
- event["type"],
604
- str(event["sessions"]),
605
- share_cell,
606
- event["created"],
607
- key=event["id"],
838
+ self._checkbox_cell(eid),
839
+ self._format_cell(str(event["index"]), eid),
840
+ self._format_cell(title, eid),
841
+ self._format_cell(share_val, eid),
842
+ self._format_cell(event["type"], eid),
843
+ self._format_cell(str(event["sessions"]), eid),
844
+ self._format_cell(event["short_id"], eid),
845
+ self._format_cell(event["created"], eid),
846
+ key=eid,
608
847
  )
609
848
 
610
849
  if table.row_count > 0:
611
850
  if selected_event_id:
612
851
  try:
613
- table.cursor_coordinate = (table.get_row_index(selected_event_id), 0)
852
+ table.cursor_coordinate = (
853
+ table.get_row_index(selected_event_id),
854
+ 0,
855
+ )
614
856
  except Exception:
615
857
  table.cursor_coordinate = (0, 0)
616
858
  else:
@@ -620,7 +862,7 @@ class EventsTable(Container, can_focus=True):
620
862
  total_pages = self._get_total_pages()
621
863
  pagination_widget = self.query_one("#pagination-info", Static)
622
864
  pagination_widget.update(
623
- f"[dim]Page {self.current_page}/{total_pages} ({self._total_events} total) │ (p) prev (n) next (s) wrap[/dim]"
865
+ f"[dim]Page {self.current_page}/{total_pages} ({self._total_events} total) │ (p) prev (n) next[/dim]"
624
866
  )
625
867
 
626
868
  def _shorten_id(self, event_id: str) -> str:
@@ -40,5 +40,5 @@ class AlineHeader(Static):
40
40
  line3 = " ███████║██║ ██║██╔██╗ ██║█████╗ "
41
41
  line4 = " ██╔══██║██║ ██║██║╚██╗██║██╔══╝ "
42
42
  line5 = " ██║ ██║███████╗██║██║ ╚████║███████╗"
43
- info = f"[dim]v{self._version} │ Shared AI Memory[/dim]"
43
+ info = f"[dim]v{self._version} │ Shared Agent Context[/dim]"
44
44
  return f"{line1}\n{line2}\n{line3}\n{line4}\n{line5} {info}"