Skip to content

Commit f48cb40

Browse files
authored
Merge pull request #112 from SoundBlaster/feature/FU-P11-T2-3-reorder-sessions-last-to-first
FU-P11-T2-3: Reorder sessions from the last to the first
2 parents a1bf784 + 990b3a9 commit f48cb40

9 files changed

Lines changed: 248 additions & 33 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# FU-P11-T2-3: Reorder sessions from the last to the first
2+
3+
**Version:** 1.0.0
4+
**Status:** IN PROGRESS
5+
**Priority:** P2
6+
**Dependencies:** P11-T2 ✅
7+
**Created:** 2026-02-28
8+
9+
---
10+
11+
## Overview
12+
13+
Make the Session Timeline newest-first by returning sessions in descending start-time order.
14+
This ensures the latest activity appears at the top and is labeled as `Session 1`.
15+
16+
---
17+
18+
## Background
19+
20+
The backend session detector currently returns sessions chronologically (oldest-to-newest).
21+
`renderTimeline()` uses the returned array order for labels, so the oldest session is rendered as
22+
`Session 1`. This makes fresh activity harder to find during active debugging.
23+
24+
---
25+
26+
## Deliverables
27+
28+
| File | Change |
29+
|------|--------|
30+
| `src/mcpbridge_wrapper/webui/sessions.py` | Return sessions newest-first while preserving deterministic session IDs and tool ordering inside each session |
31+
| `tests/unit/webui/test_sessions.py` | Update ordering expectations and add explicit newest-first assertions |
32+
| `tests/unit/webui/test_server.py` | Add/adjust API/WebSocket assertions to verify newest-first session ordering |
33+
34+
---
35+
36+
## Acceptance Criteria
37+
38+
- [ ] `GET /api/sessions` returns sessions ordered by latest start time first
39+
- [ ] Timeline labels show the newest group as `Session 1`
40+
- [ ] Refresh and live updates keep the same newest-first ordering
41+
- [ ] Tests cover ordering with at least two sessions at different timestamps
42+
- [ ] `pytest` passes
43+
- [ ] `ruff check src/` passes
44+
- [ ] `mypy src/` passes
45+
- [ ] `pytest --cov` reports ≥ 90% coverage
46+
47+
---
48+
49+
## Implementation Plan
50+
51+
### 1. Update session ordering at source (`sessions.py`)
52+
53+
- Build sessions in chronological order as today (to keep gap grouping logic simple).
54+
- Reverse the completed session list before returning.
55+
- Reindex session IDs after reversal so `session_0` maps to newest session and labels remain intuitive.
56+
- Keep each session's `tools` list in chronological order to preserve intra-session call flow.
57+
58+
### 2. Validate API and WebSocket behavior (`test_server.py`)
59+
60+
- Add endpoint-level assertion that multi-session responses are newest-first.
61+
- Add WebSocket assertion that pushed `sessions` payload is newest-first.
62+
63+
### 3. Validate detector behavior (`test_sessions.py`)
64+
65+
- Update existing multi-session tests to assert newest-first ordering.
66+
- Add explicit test ensuring `session_0` corresponds to newest session.
67+
68+
---
69+
70+
## Risks and Mitigations
71+
72+
- **Risk:** Existing tests or UI logic rely on oldest-first order.
73+
- **Mitigation:** Update tests and keep tool ordering within a session unchanged.
74+
75+
- **Risk:** Session IDs could become unstable after reordering.
76+
- **Mitigation:** Reindex IDs after final ordering so IDs are deterministic and label-safe.
77+
78+
---
79+
80+
## Test Plan
81+
82+
```bash
83+
pytest
84+
ruff check src/
85+
mypy src/
86+
pytest --cov
87+
```
88+
89+
---
90+
**Archived:** 2026-02-28
91+
**Verdict:** PASS
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Validation Report: FU-P11-T2-3
2+
3+
**Task:** Reorder sessions from the last to the first
4+
**Date:** 2026-02-28
5+
**Verdict:** PASS
6+
7+
---
8+
9+
## Changes Made
10+
11+
| File | Change |
12+
|------|--------|
13+
| `src/mcpbridge_wrapper/webui/sessions.py` | Return sessions in newest-first order and reindex session IDs so `session_0` is the newest session |
14+
| `tests/unit/webui/test_sessions.py` | Updated ordering expectations and added explicit newest-first assertions |
15+
| `tests/unit/webui/test_server.py` | Updated API ordering assertions and WebSocket newest-first ordering test |
16+
17+
---
18+
19+
## Acceptance Criteria
20+
21+
- [x] `GET /api/sessions` returns sessions ordered by latest start time first
22+
- [x] Timeline labels show the newest group as `Session 1`
23+
- [x] Refresh and live updates keep the same newest-first ordering
24+
- [x] Tests cover ordering with at least two sessions at different timestamps
25+
26+
---
27+
28+
## Quality Gates
29+
30+
| Gate | Result |
31+
|------|--------|
32+
| `PYTHONPATH=src pytest` | ✅ 661 passed, 5 skipped |
33+
| `ruff check src/` | ✅ All checks passed |
34+
| `mypy src/` | ✅ Success: no issues found in 18 source files |
35+
| `PYTHONPATH=src pytest --cov` | ✅ 91.55% (≥ 90% required) |
36+
37+
---
38+
39+
## New/Updated Tests
40+
41+
`tests/unit/webui/test_sessions.py`
42+
- Updated multi-session expectations to newest-first order
43+
- Added `test_multi_session_output_is_newest_first`
44+
- Extended zero-gap test to assert descending session start order
45+
46+
`tests/unit/webui/test_server.py`
47+
- Updated mixed-order sessions endpoint test to assert newest-first output
48+
- Updated WebSocket test to assert newest-first two-session payload ordering

SPECS/ARCHIVE/INDEX.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# mcpbridge-wrapper Tasks Archive
22

3-
**Last Updated:** 2026-02-25 (BUG-T9_Orphaned_Web_UI_server_process_blocks_port_after_MCP_client_disconnect_or_config_change)
3+
**Last Updated:** 2026-02-28 (FU-P11-T2-3_Reorder_sessions_from_the_last_to_the_first)
44

55
## Archived Tasks
66

77
| Task ID | Folder | Archived | Verdict |
88
|---------|--------|----------|---------|
9+
| FU-P11-T2-3 | [FU-P11-T2-3_Reorder_sessions_from_the_last_to_the_first/](FU-P11-T2-3_Reorder_sessions_from_the_last_to_the_first/) | 2026-02-28 | PASS |
910
| BUG-T18 | [BUG-T18_Error_Breakdown_full_width_layout_fix/](BUG-T18_Error_Breakdown_full_width_layout_fix/) | 2026-02-26 | PASS |
1011
| BUG-T9 | [BUG-T9_Orphaned_Web_UI_server_process_blocks_port_after_MCP_client_disconnect_or_config_change/](BUG-T9_Orphaned_Web_UI_server_process_blocks_port_after_MCP_client_disconnect_or_config_change/) | 2026-02-25 | PASS |
1112
| BUG-T20 | [BUG-T20_Session_Timeline_can_show_negative_duration_due_to_incorrect_entry_ordering/](BUG-T20_Session_Timeline_can_show_negative_duration_due_to_incorrect_entry_ordering/) | 2026-02-25 | PASS |
@@ -211,6 +212,7 @@
211212
| [REVIEW_P12-T3_error_classification_categorization.md](P12-T3_Add_Error_Classification_and_Categorization/REVIEW_P12-T3_error_classification_categorization.md) | Review report for P12-T3 |
212213
| [REVIEW_P12-T4_data_storage_documentation.md](P12-T4_Add_documentation_about_data_storage/REVIEW_P12-T4_data_storage_documentation.md) | Review report for P12-T4 |
213214
| [REVIEW_P12-T2_param_frequency_analysis.md](P12-T2_Add_Tool_Parameter_Frequency_Analysis/REVIEW_P12-T2_param_frequency_analysis.md) | Review report for P12-T2 |
215+
| [REVIEW_fu_p11_t2_3_session_ordering.md](_Historical/REVIEW_fu_p11_t2_3_session_ordering.md) | Review report for FU-P11-T2-3 |
214216
| [REVIEW_FU-P11-T2-2_limit_query_param.md](_Historical/REVIEW_FU-P11-T2-2_limit_query_param.md) | Review report for FU-P11-T2-2 |
215217
| [REVIEW_FU-P11-T1-1_fake_webuiconfig_refactor.md](_Historical/REVIEW_FU-P11-T1-1_fake_webuiconfig_refactor.md) | Review report for FU-P11-T1-1 |
216218
| [REVIEW_FU-P12-T2-1_stacking_click_listeners.md](_Historical/REVIEW_FU-P12-T2-1_stacking_click_listeners.md) | Review report for FU-P12-T2-1 |
@@ -269,6 +271,8 @@
269271

270272
| Date | Task ID | Action |
271273
|------|---------|--------|
274+
| 2026-02-28 | FU-P11-T2-3 | Archived REVIEW_fu_p11_t2_3_session_ordering report |
275+
| 2026-02-28 | FU-P11-T2-3 | Archived Reorder_sessions_from_the_last_to_the_first (PASS) |
272276
| 2026-02-26 | BUG-T18 | Archived REVIEW_bug_t18_error_breakdown_layout report |
273277
| 2026-02-26 | BUG-T18 | Archived Error_Breakdown_full_width_layout_fix (PASS) |
274278
| 2026-02-25 | BUG-T9 | Archived REVIEW_bug_t9_orphaned_webui_process report |
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
## REVIEW REPORT — FU-P11-T2-3 Session Ordering
2+
3+
**Scope:** origin/main..HEAD
4+
**Files:** 8
5+
6+
### Summary Verdict
7+
- [x] Approve
8+
- [ ] Approve with comments
9+
- [ ] Request changes
10+
- [ ] Block
11+
12+
### Critical Issues
13+
- None.
14+
15+
### Secondary Issues
16+
- None.
17+
18+
### Architectural Notes
19+
- Session ordering is now enforced at the detector layer (`detect_sessions`), so both REST (`GET /api/sessions`) and WebSocket (`/ws/metrics`) paths inherit the same newest-first behavior.
20+
- Session ID reindexing after reversal keeps timeline labels deterministic (`session_0` == newest session).
21+
22+
### Tests
23+
- Verified by updated unit tests in `tests/unit/webui/test_sessions.py` and `tests/unit/webui/test_server.py`.
24+
- Quality gates executed during EXECUTE:
25+
- `PYTHONPATH=src pytest` → 661 passed, 5 skipped
26+
- `ruff check src/` → pass
27+
- `mypy src/` → pass
28+
- `PYTHONPATH=src pytest --cov` → 91.55% coverage (>= 90%)
29+
30+
### Next Steps
31+
- No actionable findings. FOLLOW-UP step can be skipped.

SPECS/INPROGRESS/next.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
## Recently Archived
44

5+
- **FU-P11-T2-3** — Reorder sessions from the last to the first (2026-02-28, PASS)
56
- **BUG-T9** — Orphaned Web UI server process blocks port after MCP client disconnect or config change (2026-02-25, PASS)
67
- **BUG-T18** — Error Breakdown widget must be full width streatched (2026-02-26, PASS)
7-
- **BUG-T20** — Session Timeline can show negative duration due to incorrect entry ordering (2026-02-25, PASS)
88

99
## Suggested Next Tasks
1010

11-
- BUG-T4 — Repeated Xcode permission prompts for each short-lived MCP client process
12-
- BUG-T1 — Kimi CLI MCP Connection Failure
11+
- None (all workplan tasks are complete)

SPECS/Workplan.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2080,7 +2080,7 @@ Phase 9 Follow-up Backlog
20802080

20812081
---
20822082

2083-
#### ⬜️ FU-P11-T2-3: Reorder sessions from the last to the first
2083+
#### FU-P11-T2-3: Reorder sessions from the last to the first
20842084
- **Description:** Fix session ordering so the Session Timeline shows the most recent session first (newest-to-oldest). Current behavior shows the oldest session first, which makes fresh activity harder to find.
20852085
- **Priority:** P2
20862086
- **Dependencies:** P11-T2
@@ -2097,6 +2097,23 @@ Phase 9 Follow-up Backlog
20972097

20982098
---
20992099

2100+
#### ⬜️ FU-P11-T2-4: Add one-command Web UI restart workflow
2101+
- **Description:** Add a simple restart workflow for developers and users that reliably frees the configured Web UI port and starts a fresh dashboard process after updates.
2102+
- **Priority:** P2
2103+
- **Dependencies:** P11-T2
2104+
- **Parallelizable:** yes
2105+
- **Outputs/Artifacts:**
2106+
- Updated `src/mcpbridge_wrapper/__main__.py` and/or helper script — support restart semantics (`stop stale listener on port`, then `start`)
2107+
- Updated `Makefile` — add a `webui-restart` target
2108+
- Updated `docs/troubleshooting.md` and `Sources/XcodeMCPWrapper/Documentation.docc/Troubleshooting.md` — document one-step restart command
2109+
- **Acceptance Criteria:**
2110+
- [ ] A single documented command restarts Web UI on a chosen port without manual PID hunting
2111+
- [ ] Restart flow attempts graceful stop first, then force-kill only if needed
2112+
- [ ] Works for both local/dev install and uvx usage
2113+
- [ ] Tests cover restart behavior and port-occupied edge case(s) where practical
2114+
2115+
---
2116+
21002117
#### ✅ P11-T3: Add Dashboard Theme Toggle (Dark/Light)
21012118
- **Description:** Implement CSS-variable-based theme system with a toggle button in the header. Refactor all hardcoded colors in `dashboard.css` to CSS custom properties on `:root`. Add `[data-theme="light"]` overrides. Store user preference in `localStorage`. Update Chart.js color defaults on theme toggle.
21022119
- **Priority:** P2

src/mcpbridge_wrapper/webui/sessions.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,13 @@ def detect_sessions(
3737
Must be >= 0. A value of 0 puts each call in its own session.
3838
3939
Returns:
40-
List of session dicts ordered chronologically. Each dict has:
40+
List of session dicts ordered newest-first by session start time.
41+
Each dict has:
4142
4243
.. code-block:: python
4344
4445
{
45-
"id": "session_0", # zero-based index string
46+
"id": "session_0", # zero-based index string (newest session)
4647
"start": 1234567890.0, # timestamp of first tool call
4748
"end": 1234567890.5, # timestamp of last tool call
4849
"tool_count": 3,
@@ -83,7 +84,7 @@ def detect_sessions(
8384
if current_tools:
8485
sessions.append(_build_session(len(sessions), current_tools))
8586

86-
return sessions
87+
return _newest_first_sessions(sessions)
8788

8889

8990
def _extract_tool(entry: Dict[str, Any]) -> Dict[str, Any]:
@@ -109,3 +110,11 @@ def _build_session(index: int, tools: List[Dict[str, Any]]) -> Dict[str, Any]:
109110
"error_count": error_count,
110111
"tools": tools,
111112
}
113+
114+
115+
def _newest_first_sessions(sessions: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
116+
"""Return sessions in newest-first order with deterministic IDs."""
117+
newest_first = list(reversed(sessions))
118+
for index, session in enumerate(newest_first):
119+
session["id"] = f"session_{index}"
120+
return newest_first

tests/unit/webui/test_server.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def test_sessions_endpoint_sorts_entries_chronologically(self, client, audit, mo
177177
def test_sessions_endpoint_handles_mixed_order_and_preserves_latest_event(
178178
self, client, audit, monkeypatch
179179
):
180-
"""Sessions API yields monotonic boundaries for mixed-order audit rows."""
180+
"""Sessions API yields monotonic boundaries and newest-first session ordering."""
181181
mixed_entries = [
182182
{
183183
"timestamp": 600.0,
@@ -217,12 +217,14 @@ def test_sessions_endpoint_handles_mixed_order_and_preserves_latest_event(
217217
assert len(sessions) == 2
218218

219219
first, second = sessions
220-
assert first["start"] == 100.0
221-
assert first["end"] == 600.0
222-
assert [t["request_id"] for t in first["tools"]] == ["req-1", "req-2", "req-3"]
223-
assert second["start"] == 1400.0
224-
assert second["end"] == 1400.0
225-
assert second["tools"][-1]["request_id"] == "req-4"
220+
assert first["id"] == "session_0"
221+
assert first["start"] == 1400.0
222+
assert first["end"] == 1400.0
223+
assert first["tools"][-1]["request_id"] == "req-4"
224+
assert second["id"] == "session_1"
225+
assert second["start"] == 100.0
226+
assert second["end"] == 600.0
227+
assert [t["request_id"] for t in second["tools"]] == ["req-1", "req-2", "req-3"]
226228
assert all(session["start"] <= session["end"] for session in sessions)
227229

228230
def test_export_audit_json(self, client, audit):
@@ -387,14 +389,14 @@ def test_websocket_metrics_update_includes_sessions(self, client, audit):
387389
assert "sessions" in message
388390
assert isinstance(message["sessions"], list)
389391

390-
def test_websocket_sessions_are_sorted_chronologically(self, client, audit, monkeypatch):
391-
"""WebSocket payload session windows use non-decreasing start/end timestamps."""
392+
def test_websocket_sessions_are_newest_first(self, client, audit, monkeypatch):
393+
"""WebSocket payload keeps the newest session at index 0."""
392394
reverse_entries = [
393395
{
394-
"timestamp": 300.0,
395-
"timestamp_iso": "2026-02-25T10:03:00Z",
396+
"timestamp": 600.0,
397+
"timestamp_iso": "2026-02-25T10:10:00Z",
396398
"tool": "LatestTool",
397-
"request_id": "req-3",
399+
"request_id": "req-2",
398400
"direction": "response",
399401
},
400402
{
@@ -412,8 +414,10 @@ def test_websocket_sessions_are_sorted_chronologically(self, client, audit, monk
412414
message = websocket.receive_json()
413415

414416
sessions = message["sessions"]
415-
assert sessions
416-
assert sessions[0]["start"] <= sessions[0]["end"]
417+
assert len(sessions) == 2
418+
assert [session["id"] for session in sessions] == ["session_0", "session_1"]
419+
assert [session["start"] for session in sessions] == [600.0, 100.0]
420+
assert all(session["start"] <= session["end"] for session in sessions)
417421

418422

419423
class TestAuth:

0 commit comments

Comments
 (0)