Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions docs/todos/2026-06-18-issue-525-viewer-timeline-sort/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Viewer Timeline Sort Toggle Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add a minimal timeline sort order toggle to the viewer timeline tab.

**Architecture:** Keep this viewer-only. Store sort order in `state.timeline.sortOrder`, render a third toolbar `select`, sort a copy of observations by timestamp before type filtering and pagination, and bind the select to reset pagination and re-render.

**Tech Stack:** TypeScript test suite with Vitest; viewer is a static HTML/CSS/JavaScript document rendered through `src/viewer/document.ts`.

---

## File Structure

- Modify: `src/viewer/index.html`
- Add `state.timeline.sortOrder`.
- Add timeline toolbar select `#tl-sort-order`.
- Add sort helper logic inside timeline rendering path.
- Bind sort changes.
- Create: `test/viewer-timeline-sort.test.ts`
- VM-load the viewer script, seed timeline observations, and assert toolbar/default/newest/oldest behavior.
- Modify: `docs/todos/2026-06-18-issue-525-viewer-timeline-sort/todo.md`
- Update progress, verification evidence, and review notes.

## Task 1: Focused Failing Test

- [ ] **Step 1: Create `test/viewer-timeline-sort.test.ts` with a VM harness.**

The test should extract the viewer script from `renderViewerDocument()`, evaluate it in a minimal DOM sandbox, seed `state.timeline.observations`, call `renderTimelineToolbar([])` and `renderObservations()`, then assert:

```ts
expect(getElement("view-timeline").innerHTML).toContain('id="tl-sort-order"');
expect(getElement("view-timeline").innerHTML).toContain("Newest First");
expect(getElement("view-timeline").innerHTML).toContain("Oldest First");
expect(titles(getElement("tl-content").innerHTML)).toEqual(["Newest", "Middle", "Oldest"]);
sandbox.state.timeline.sortOrder = "oldest";
sandbox.renderObservations();
expect(titles(getElement("tl-content").innerHTML)).toEqual(["Oldest", "Middle", "Newest"]);
```

- [ ] **Step 2: Run the focused test and verify RED.**

Run: `corepack pnpm exec vitest run test/viewer-timeline-sort.test.ts`

Expected: FAIL because `tl-sort-order` and `state.timeline.sortOrder` do not exist.

## Task 2: Minimal Viewer Implementation

- [ ] **Step 1: Add timeline sort state.**

Change the `state.timeline` object to include `sortOrder: 'newest'`.

- [ ] **Step 2: Add the toolbar select.**

In `renderTimelineToolbar`, after the importance select, render:

```js
html += '<select id="tl-sort-order">';
html += '<option value="newest"' + (state.timeline.sortOrder === 'newest' ? ' selected' : '') + '>Newest First</option>';
html += '<option value="oldest"' + (state.timeline.sortOrder === 'oldest' ? ' selected' : '') + '>Oldest First</option>';
html += '</select>';
```

- [ ] **Step 3: Bind sort changes.**

Add a `change` listener for `#tl-sort-order` that validates `oldest`; all other values fall back to `newest`, resets `state.timeline.page = 0`, then calls `renderObservations()`.

- [ ] **Step 4: Sort before type filtering and pagination.**

In `renderObservations`, start from a sorted copy:

```js
var obs = state.timeline.observations.slice().sort(function(a, b) {
var ac = (a && a.timestamp) || '';
var bc = (b && b.timestamp) || '';
return state.timeline.sortOrder === 'oldest' ? ac.localeCompare(bc) : bc.localeCompare(ac);
});
```

Use that sorted `obs` for min-importance filtering, type counts, pagination, and rendering.

- [ ] **Step 5: Run focused test and verify GREEN.**

Run: `corepack pnpm exec vitest run test/viewer-timeline-sort.test.ts`

Expected: PASS.

## Task 3: Targeted And Full Verification

- [ ] **Step 1: Run targeted viewer tests.**

Run: `corepack pnpm exec vitest run test/viewer-timeline-sort.test.ts test/viewer-session-id.test.ts test/viewer-memories-sort.test.ts test/viewer-i18n.test.ts`

Expected: PASS.

- [ ] **Step 2: Run repo checks.**

Run: `corepack pnpm test`, `corepack pnpm run build`, and `git diff --check`.

Expected: all pass.

- [ ] **Step 3: Run required security gates before commit.**

Run: `semgrep scan --config p/default --error --metrics=off .`, `osv-scanner scan source .`, stage intended files, then `gitleaks protect --staged --redact`.

Expected: all pass or any blocker is recorded and fixed/accepted before commit.

## Plan Review

- Spec source: GitHub Issue #525 plus the delegated current-turn request; no separate `spec.md`.
- Scope is viewer-only and does not require API, storage, dependency, auth, or routing changes.
- Verification covers the new behavior with focused VM tests and existing project-native checks.
83 changes: 83 additions & 0 deletions docs/todos/2026-06-18-issue-525-viewer-timeline-sort/todo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Issue 525 Viewer Timeline Sort Toggle

Scope: repository worktree `/Users/A1538552/.codex/worktrees/3eb6/agentmemory`, branch `issue/525-viewer-timeline-sort`, target `origin/main`.

## Sprint Contract

Goal: add a minimal viewer timeline sort control so timeline observations can be displayed newest-first or oldest-first.

Scope:
- `src/viewer/index.html` timeline state, toolbar, observation sorting, and event wiring.
- Focused viewer tests covering default newest-first ordering and the oldest-first toggle.
- This task record and implementation plan.

Non-goals:
- No REST, MCP, storage, schema, auth, persistence, routing, or dependency changes.
- No PRs or references targeting `rohitg00/agentmemory`; all remote workflow targets `wbugitlab1/agentmemory`.

Acceptance criteria:
- Timeline toolbar exposes `Newest First` and `Oldest First` choices.
- Default timeline rendering is newest-first.
- Observations are sorted by timestamp before pagination.
- Changing the sort order resets timeline pagination to page 0 and re-renders existing observations.
- Tests cover toolbar rendering and both sort directions.

Intended verification:
- `corepack pnpm exec vitest run test/viewer-timeline-sort.test.ts`
- `corepack pnpm test`
- `corepack pnpm run build`
- `git diff --check`
- Required security gates before commit: `semgrep scan --config p/default --error --metrics=off .`, `osv-scanner scan source .`, and `gitleaks protect --staged --redact`.

Known boundaries:
- Viewer-only frontend behavior; no server contract changes.
- Remote write actions are within the delegated GitHub flow only: push branch, PR against `wbugitlab1/agentmemory` `main`, merge after checks if possible, then close/link issue.
- `github-push-prepare` skill was required by the router skill but is not installed or exposed in this Codex environment; its boundaries are being followed manually.

Stop conditions:
- A required scanner reports findings that are not fixed or explicitly accepted.
- The viewer change requires API/schema/auth/persistence changes.
- Existing unrelated changes appear in task-owned files and cannot be safely separated.

## Feature / Verification Matrix

| Change | Verification method | Status | Evidence |
| --- | --- | --- | --- |
| Timeline sort state defaults newest-first | Focused VM test | Pass | `corepack pnpm exec vitest run test/viewer-timeline-sort.test.ts` passed; first test asserts `Newest`, `Middle`, `Oldest` from unsorted fixture. |
| Toolbar renders sort toggle choices | Focused VM test | Pass | Focused test asserts `#tl-sort-order`, `Newest First`, and `Oldest First` render. |
| Observations sorted before pagination in both directions | Focused VM test | Pass | Focused tests assert default newest-first, oldest-first, and page reset with visible `Page 1 of 3` rerender after the control changes. |
| Existing repo behavior remains healthy | Full test/build/security gates | Pass | `corepack pnpm test` passed: 195 files, 2731 tests. `corepack pnpm run build` passed. `git diff --check` passed. Semgrep passed with 0 findings. OSV found no issues after the repo waiver. Staged Gitleaks found no leaks. |

## Subagent Ledger

| Workstream | Scope | Edits allowed | Expected output | Result | Residual risk |
| --- | --- | --- | --- | --- | --- |
| Read-only viewer timeline validation | `src/viewer/index.html`, viewer tests, issue #525 context | No | Valid/stale/duplicate conclusion with files, commands, evidence | Valid issue; no sort state/control existed and observations were not sorted before pagination | Did not inspect upstream PR code; fork issue and local repo evidence were sufficient. |
| Pre-implementation plan review | Task record, implementation plan, viewer files/tests | No | Accept or evidence-backed High/Medium findings | ACCEPT; plan covered issue #525 requirements | No tests run by reviewer. |
| Final security review | Current diff and supporting auth/render paths | No | Accept or High/Medium security findings | ACCEPT; no High/Medium security findings | Diff-scoped review; Semgrep, OSV, and staged Gitleaks also passed. |
| Final test coverage review | Viewer timeline sort tests and implementation | No | Accept or coverage gaps | Found valid pagination rerender gap; fixed with paginated fixture and rerender assertion; re-review ACCEPT | Browser UI not separately exercised. |
| Final maintainability review | Shared VM helper, viewer tests, task record | No | Accept or maintainability gaps | Found valid duplicated harness and stale task-record gaps; harness extracted to `test/helpers/viewer-sandbox.ts`; task record updated with final evidence. | No remaining code maintainability issue; task-record stale finding fixed here. |

## Progress

- 2026-06-18: Issue #525 inspected with `gh issue view 525 --repo wbugitlab1/agentmemory --json ...`; issue is open and tracks upstream PR 672.
- 2026-06-18: Worktree inspected; clean detached checkout at `4d5fbe20f5cd8273687da459ec1128dfaf4f5226`, remote `https://github.com/wbugitlab1/agentmemory.git`.
- 2026-06-18: Created branch `issue/525-viewer-timeline-sort`.
- 2026-06-18: No repo-local `docs/lessons` directory; Agentmemory lesson recall for viewer timeline returned no lessons.
- 2026-06-18: Wrote failing focused VM tests for timeline toolbar rendering, default newest-first ordering, oldest-first ordering, and pagination reset.
- 2026-06-18: Implemented viewer-only timeline `sortOrder` state, toolbar select, change handler, and timestamp sorting before pagination.
- 2026-06-18: Extracted shared viewer VM harness to `test/helpers/viewer-sandbox.ts` after maintainability review found duplicated test scaffolding.
- 2026-06-18: Verification passed: `corepack pnpm exec vitest run test/viewer-timeline-sort.test.ts test/viewer-session-id.test.ts test/viewer-memories-sort.test.ts test/viewer-i18n.test.ts` (4 files, 28 tests).
- 2026-06-18: Verification passed: `corepack pnpm test` (195 files, 2731 tests).
- 2026-06-18: Verification passed: `corepack pnpm run build`.
- 2026-06-18: Verification passed: `git diff --check`.
- 2026-06-18: Security gate passed: `semgrep scan --config p/default --error --metrics=off .` (0 findings).
- 2026-06-18: Security gate passed: `osv-scanner scan source .` (no issues found; existing OpenTelemetry waiver applied).
- 2026-06-18: Security gate passed: `gitleaks protect --staged --redact` (no leaks found).

## Review Notes

- Security review ACCEPT: no auth, persistence, dependency, network, or storage changes; sort control values are fixed and clamped, and existing observation rendering continues to escape untrusted fields.
- Coverage review initial Medium finding fixed: pagination reset test now proves the visible timeline re-renders to page 1 after sort control change.
- Maintainability review initial Medium finding fixed: viewer VM setup is centralized in `test/helpers/viewer-sandbox.ts` and reused by viewer tests.
- Final staged diff review confirmed intended files only: viewer implementation, shared VM test helper, focused timeline sort tests, and task docs.
17 changes: 15 additions & 2 deletions src/viewer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,7 @@ <h1>agentmemory</h1>
dashboard: { loaded: false, health: null, sessions: [], memories: [], graphStats: null, recentAudit: [], lessons: [], crystals: [], insights: [] },
graph: { loaded: false, nodes: [], edges: [], stats: null, filters: {}, selectedNode: null, queryError: null, truncated: false, totalNodes: 0, totalEdges: 0 },
memories: { loaded: false, items: [], search: '', typeFilter: '', total: 0, searching: false, requestId: 0 },
timeline: { loaded: false, observations: [], sessionId: '', minImportance: 0, page: 0, pageSize: 50 },
timeline: { loaded: false, observations: [], sessionId: '', minImportance: 0, sortOrder: 'newest', page: 0, pageSize: 50 },
sessions: { loaded: false, items: [], selectedId: null },
audit: { loaded: false, entries: [], opFilter: '' },
activity: { loaded: false, observations: [], sessions: [], typeFilter: '' },
Expand Down Expand Up @@ -2891,6 +2891,10 @@ <h1>agentmemory</h1>
for (var i = 1; i <= 9; i++) {
html += '<option value="' + i + '"' + (state.timeline.minImportance === i ? ' selected' : '') + '>&ge; ' + i + '</option>';
}
html += '</select>';
html += '<select id="tl-sort-order">';
html += '<option value="newest"' + (state.timeline.sortOrder === 'newest' ? ' selected' : '') + '>Newest First</option>';
html += '<option value="oldest"' + (state.timeline.sortOrder === 'oldest' ? ' selected' : '') + '>Oldest First</option>';
html += '</select></div>';
html += '<div id="tl-content"></div>';
el.innerHTML = html;
Expand All @@ -2904,6 +2908,11 @@ <h1>agentmemory</h1>
state.timeline.minImportance = parseInt(this.value);
renderObservations();
});
document.getElementById('tl-sort-order').addEventListener('change', function() {
state.timeline.sortOrder = this.value === 'oldest' ? 'oldest' : 'newest';
state.timeline.page = 0;
renderObservations();
});
}

async function loadObservations() {
Expand All @@ -2924,7 +2933,11 @@ <h1>agentmemory</h1>
function renderObservations() {
var content = document.getElementById('tl-content');
if (!content) return;
var obs = state.timeline.observations;
var obs = state.timeline.observations.slice().sort(function(a, b) {
var ac = (a && a.timestamp) || '';
var bc = (b && b.timestamp) || '';
return state.timeline.sortOrder === 'oldest' ? ac.localeCompare(bc) : bc.localeCompare(ac);
});
var minImp = state.timeline.minImportance;
var filtered = minImp > 0 ? obs.filter(function(o) { return (o.importance || 0) >= minImp; }) : obs;

Expand Down
Loading
Loading