Skip to content

feat(dashboard): task & skill ledger views (#865)#955

Merged
shaun0927 merged 7 commits into
feat/855-task-ledgerfrom
feat/865-dashboard-ledger-views
May 13, 2026
Merged

feat(dashboard): task & skill ledger views (#865)#955
shaun0927 merged 7 commits into
feat/855-task-ledgerfrom
feat/865-dashboard-ledger-views

Conversation

@shaun0927
Copy link
Copy Markdown
Owner

@shaun0927 shaun0927 commented May 12, 2026

Progress / Review status

Auto-refreshed 2026-05-13 — owner comments cleaned up to reduce review noise.

Field Value
Branch feat/865-dashboard-ledger-viewsfeat/855-task-ledger
Draft no
CI
Mergeable ✅ MERGEABLE
Review decision
Codex (latest) 💡 suggestions posted
Other reviewers (latest) chatgpt-codex-connector: commented
Head bb73160 — fix(dashboard): decode skill domain before constructing store
Commits 4

Owner comment cleanup: 1 issue + 0 inline review comments deleted. Outstanding feedback from automated/external reviewers above is unchanged.


Summary

Adds two read-only ANSI views to the existing terminal dashboard. Stacked on top of #911 (task ledger). PR base is feat/855-task-ledger so the diff shows only F's lines; once #911 merges to develop this branch can be re-targeted at develop (or fast-forwarded by GitHub).

What's in this PR

P-principle compliance

  • P2os.homedir() + path.join everywhere; no platform-specific paths.
  • P4 — views never mutate ledger files; snapshot readers only open files for read.
  • P5 — no new native deps; package.json is untouched.

Tests (6 new)

  • TasksView: header + mixed-status rows render; empty-state; error message surfaces.
  • SkillsView: rows + PASS/FAIL replay badges; empty-state.
  • readSkillsSnapshot tolerates a missing skill-memory directory and does not write any state.

Coordination

Test plan (post-merge)

Closes #865

Part of the mcp-browser-use adoption series (F).

🤖 Generated with Claude Code

Adds two read-only ANSI views to the existing terminal dashboard:

- TasksView (src/dashboard/views/tasks-view.ts) — surfaces the task
  ledger from #855. Reads ~/.openchrome/tasks/ (or OPENCHROME_TASK_ROOT).
  Renders task_id, kind, status, age, duration_ms. Status colour-coded:
  RUNNING cyan, COMPLETED green, FAILED red, CANCELLED yellow.

- SkillsView (src/dashboard/views/skills-view.ts) — walks
  ~/.openchrome/skill-memory/<domain>/skills.json across all known
  domains. Renders domain, skill name, use count, replay badge
  (PASS/FAIL/—), and relative last-used timestamp. The replay badge
  reads the optional lastReplayPassedAt/lastReplayFailedAt fields
  defensively so this PR builds on either A (#855) alone or the
  full A+B stack — the cast becomes redundant once #856 is in develop.

Wiring (src/dashboard/index.ts)
- ViewMode extended with 'tasks' | 'skills' (src/dashboard/types.ts).
- Two new view instances initialised next to the existing four.
- Switch case in the render dispatcher routes to the new views.
- Main-view keys: 'j' → tasks ledger, 'k' → skills ledger.
  (Existing 't' is already taken for tabs; 's' for sessions.)
- Ledger-view key handler: 'escape' returns to activity.
- Ledger snapshot is re-read on entry; subsequent dashboard ticks
  refresh via refreshLedgers(). Read-only — IO errors surface in
  the view's errorMessage field, never thrown.

Per portability-harness contract
- P2: os.homedir() + path.join everywhere; no platform-specific paths.
- P4: views never mutate ledger files; the snapshot readers only
  open files for read.
- P5: no new native deps. package.json is untouched.

Tests (6 new)
- TasksView: header + mixed-status rows render; empty-state; error
  message surfaces.
- SkillsView: rows + PASS/FAIL replay badges; empty-state.
- readSkillsSnapshot tolerates a missing skill-memory directory and
  does not write any state.

Stacked on top of #855 (feat/855-task-ledger). PR base will be
feat/855-task-ledger; once #855 merges to develop this branch will
fast-forward.

Closes #865
@gemini-code-assist
Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@qodo-code-review
Copy link
Copy Markdown

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad4e26c474

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/dashboard/index.ts Outdated
Comment on lines +239 to +241
this.currentView = 'tasks';
this.selectedIndex = 0;
void this.refreshLedgers();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Refresh ledger snapshots on every dashboard tick

The new ledger views only invoke refreshLedgers() when j/k is pressed, so once the view is open the periodic loop continues to call refresh() without re-reading disk state; this leaves task/skill rows stale until the user exits and re-enters the view. In practice, status transitions and newly written records won't appear live despite the in-code comment claiming tick-based updates, so the ledger can show outdated operational state.

Useful? React with 👍 / 👎.

Comment thread src/dashboard/views/skills-view.ts Outdated
Comment on lines +161 to +164
const encodedDomain = entry.name;
const domain = encodedDomain;
try {
const store = new SkillMemoryStore({ domain });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid double-encoding skill domain directory names

entry.name here is already the encoded on-disk domain folder name, but passing it as domain into new SkillMemoryStore({ domain }) causes encodeDomain in src/core/skill-memory/store.ts to run again; domains containing encoded characters (for example %2F, %3A, etc.) become %252F, %253A, so list() reads a non-existent directory and valid skills are silently omitted from the ledger.

Useful? React with 👍 / 👎.

Surface read-only TaskRun NEEDS_HELP metadata and active handoff context in the terminal task view so operators can see why automation paused and how to resume without digging through JSON files.

Constraint: Stack on #955 dashboard task view and read #1039/#1040 metadata defensively from disk because those branches may merge in a different order.
Rejected: Dashboard resume/cancel controls | recovery state must remain read-only and MCP tools own mutation.
Confidence: high
Scope-risk: moderate
Directive: Keep dashboard recovery readers best-effort and non-throwing; never add writes or hotkey mutations to this pane.
Tested: npm test -- --runTestsByPath tests/dashboard/views/ledger-views.test.ts --runInBand
Tested: npm run build
Tested: npm run lint:changed
Co-authored-by: OmX <omx@oh-my-codex.dev>
shaun0927 and others added 2 commits May 13, 2026 04:07
Refresh task and skill ledger snapshots from the dashboard timer path so read-only ledger views continue to reflect task status and skill rows without leaving the view.

Constraint: PR #955 promised entry plus tick refresh, but the timer only repainted cached data.

Rejected: Polling ledgers from every dashboard view | non-ledger views do not need disk reads on each tick.

Confidence: high

Scope-risk: narrow

Directive: Keep ledger readers read-only and guarded against overlapping async filesystem walks.

Tested: npm test -- --runInBand tests/dashboard/views/ledger-views.test.ts; ./node_modules/.bin/tsc -p tsconfig.json --pretty false

Not-tested: Interactive TTY dashboard smoke.

Co-authored-by: OmX <omx@oh-my-codex.dev>
Routes task/skill view entry refreshes and dashboard timer refreshes through one guarded scheduler so snapshot reads cannot overlap while a ledger view remains open.

Constraint: Re-review found entry-triggered refreshes bypassed the non-overlap guard added for timer ticks.

Rejected: Making refreshLedgers silently reuse state | a small scheduler keeps the reader method simple and the guard explicit.

Confidence: high

Scope-risk: narrow

Directive: Keep ledger refresh scheduling single-path for entry keys and timer ticks.

Tested: npm test -- --runInBand tests/dashboard/views/ledger-views.test.ts; ./node_modules/.bin/tsc -p tsconfig.json --pretty false

Not-tested: Interactive TTY dashboard smoke.

Co-authored-by: OmX <omx@oh-my-codex.dev>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98ca862289

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/dashboard/views/skills-view.ts Outdated
Comment on lines +162 to +164
const domain = encodedDomain;
try {
const store = new SkillMemoryStore({ domain });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use decoded domain when constructing skill stores

Fresh evidence from this commit: readSkillsSnapshot still passes entry.name directly as domain into SkillMemoryStore, and the store constructor re-applies domain encoding. When a persisted domain directory already contains percent escapes (for example evil%2F..%2Fescape), this becomes double-encoded (%252F...), so store.list() reads the wrong path and valid skills for that domain disappear from the ledger view.

Useful? React with 👍 / 👎.

Resolve the stacked dashboard test conflict by preserving both recovery-pane read-only coverage and ledger refresh timer coverage.

Constraint: PR #1119 targets the dashboard-ledger-views branch, not develop, and only the shared ledger-views test file conflicted.
Rejected: Taking either side wholesale | that would drop either recovery state coverage or dashboard refresh guard coverage.
Confidence: high
Scope-risk: narrow
Directive: Keep dashboard stack conflict fixes test-only unless source conflicts require owner review.
Tested: npx jest tests/dashboard/views/ledger-views.test.ts --runInBand --forceExit; git diff --check for touched file.
Not-tested: Full GitHub Actions matrix.
Co-authored-by: OmX <omx@oh-my-codex.dev>
SkillMemoryStore encodes the domain internally via encodeDomain(). Passing
entry.name — which is the already-encoded filesystem basename — caused the
store to look at a double-encoded directory and surface no skills.

Decode the directory name with decodeURIComponent() so the store sees the
raw domain it expects.

Addresses Codex P2 finding on PR #955.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb73160841

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// encodeDomain() inside SkillMemoryStore. Decode it back to the raw
// domain string before constructing the store, which re-encodes
// internally — passing an already-encoded name would double-encode it.
const domain = decodeURIComponent(entry.name);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard domain decoding when scanning skill directories

decodeURIComponent(entry.name) can throw URIError for malformed percent escapes (for example a directory named bad%), but this call currently happens before the per-entry try/catch, so a single bad directory aborts the whole snapshot and returns a global error instead of skipping just that entry. That contradicts the intended best-effort behavior in this loop and can hide all otherwise valid skills until the filesystem is cleaned up.

Useful? React with 👍 / 👎.

feat(dashboard): surface recovery state in task view (#1043)
@shaun0927
Copy link
Copy Markdown
Owner Author

Merge rationale (stack consolidation)

Intent. Adds two read-only ANSI views (TasksView + SkillsView) to the existing terminal dashboard, stacked on top of #911 (task ledger).

Why this is correct.

CI. Targets the task-ledger feature branch; CI workflow only runs on main/develop PRs.

@shaun0927 shaun0927 merged commit 6a4536e into feat/855-task-ledger May 13, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5d1af7b0b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

async function readTaskRuns(root: string, limit: number): Promise<TaskRunRecovery[]> {
const entries = await readdirSafe(root);
const rows: TaskRunRecovery[] = [];
for (const entry of entries.slice(0, limit)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply task-run limit after sorting by requested time

readTaskRuns slices entries before reading metadata and sorting, so with more than limit run directories you only rank an arbitrary filesystem subset instead of the most recent requests. In environments with many task runs, newer NEEDS_HELP records can be omitted from the dashboard entirely because they were outside the first entries.slice(0, limit) batch.

Useful? React with 👍 / 👎.

async function readHandoffs(root: string, limit: number): Promise<HandoffRecovery[]> {
const entries = await readdirSafe(root);
const rows: HandoffRecovery[] = [];
for (const entry of entries.slice(0, limit)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply handoff limit after sorting by expiration time

readHandoffs also truncates directory entries before parsing and sorting, which means the returned rows are not guaranteed to be the most relevant ACTIVE handoffs when the handoff directory contains more than limit entries. Because directory iteration order is not time-ordered, active/soon-expiring handoffs can be dropped from the recovery pane even though they should be visible.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant