Skip to content

feat: heuristic session titles and grouped sub-task tree - #128

Merged
w0wl0lxd merged 14 commits into
mainfrom
feat/session-tree-ui
Jul 27, 2026
Merged

w0wl0lxd merged 14 commits into
mainfrom
feat/session-tree-ui

Conversation

@w0wl0lxd

@w0wl0lxd w0wl0lxd commented Jul 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Derive deterministic, heuristic display_title, kind, and parent_id for every session summary by scanning the first user message.
  • Treat background team, task, and workflow prompts as sub-tasks and group them under the most recent preceding main session.
  • Render the session picker as an expandable tree: main sessions visible by default, sub-tasks indented underneath.
  • Auto-bucket parents with more than 20 direct children into grouped nodes labelled with task count and age range (e.g. "20 tasks · today").
  • Use display_title in the picker instead of the raw persisted title.
  • Right/Left arrows expand/collapse main sessions and groups; Left also jumps to the parent.
  • Cap first-message parsing and cached text to avoid memory/cache bloat on huge initial prompts.
  • Bump the scan cache to scan_cache_v3.json so existing session files are rescanned for the new metadata.

Test plan

  • cargo fmt --all
  • cargo clippy --all --tests -- -D warnings
  • cargo nextest run --workspace
  • stylua --check plugins/sessions/init.lua
  • luac -p plugins/sessions/init.lua

w0wl0lxd added 2 commits July 26, 2026 21:54
…ummaries

- Scan first message from each session file to power deterministic,
  heuristic titles without calling an LLM.
- Add display_title, kind and parent_id to SessionSummary.
- Group sub-tasks (team/task/workflow) under the most recent preceding
  main session using creation-time heuristics.
- Bump scan cache to v3 so existing files are rescanned for the new fields.
- Render the session picker as a tree: main sessions shown by default and
  sub-tasks are indented underneath their inferred parent.
- Use display_title from storage instead of the raw persisted title.
- Right/Left arrows expand or collapse a main session; Left also jumps to
  the parent when already collapsed.
- Preserve expansion state and stable ordering across refreshes.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@w0wl0lxd, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 089ff45b-5d56-420c-85be-7da121dadafb

📥 Commits

Reviewing files that changed from the base of the PR and between fda89b4 and c02e3af.

📒 Files selected for processing (7)
  • n00n-lua/src/api/session.rs
  • n00n-lua/src/api/util/command.rs
  • n00n-lua/tests/plugin_host.rs
  • n00n-storage/src/sessions.rs
  • n00n-ui/src/event_loop.rs
  • plugins/sessions/init.lua
  • site/docs/content/lua-api/_index.md
📝 Walkthrough

Walkthrough

Session scanning now derives display and parent metadata from session content. The /sessions picker uses that metadata to build an expandable tree, preserve ordering and expansion state, and handle grouped rows. Documentation and tests cover the expanded API shape and grouping behavior.

Changes

Session tree and metadata

Layer / File(s) Summary
Session metadata scanning and classification
n00n-storage/src/sessions.rs, n00n-lua/src/api/session.rs, site/docs/content/lua-api/_index.md
Session summaries extract first-message details, classify display titles and kinds, assign parent sessions deterministically, and document the expanded return fields.
Hierarchical sessions picker
plugins/sessions/init.lua, n00n-lua/tests/plugin_host.rs, changelog.d/session-tree-ui.added.md
The picker builds expandable grouped trees with stable ordering, tree-aware navigation and actions, and coverage for groups containing more than twenty children.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SessionScanner
  participant SessionSummary
  participant SessionsPicker
  SessionScanner->>SessionSummary: classify first message and title
  SessionScanner->>SessionSummary: assign parent_id
  SessionSummary->>SessionsPicker: provide grouped session metadata
  SessionsPicker->>SessionsPicker: build and flatten visible tree
Loading

Poem

I’m a rabbit with folders of hops,
Grouping small tasks in neat little crops.
Titles bloom bright from the first task’s cue,
Twenty-one children fit in buckets of two.
Expand, collapse—then back to the burrow,
The session tree grows without any furrow.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: heuristic session titles and grouping sub-tasks into a tree.
Description check ✅ Passed The description accurately matches the session metadata, grouping, UI, cache, and testing changes in the PR.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/session-tree-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

w0wl0lxd added 2 commits July 26, 2026 22:03
- Sort `all` by stable rank/recency before assigning `board.nodes`, so
  filtered (non-tree) results maintain the same order as the tree roots.
- Remove the unused `collect_nodes` helper.
Document the new display_title, kind, parent_id, cwd, and model
fields returned by n00n.session.list().
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Criterion

Details
Benchmark suite Current: c02e3af Previous: 6351b86 Ratio
fib/jit_mlua_hook 6461804 ns/iter (± 111684) 6715029 ns/iter (± 106679) 0.96
fib/jit_watchdog 2442525 ns/iter (± 11745) 2219773 ns/iter (± 5057) 1.10
fib/jit_none 2434861 ns/iter (± 31832) 2215866 ns/iter (± 61366) 1.10
fib/interp_mlua_hook 7711219 ns/iter (± 183085) 8066518 ns/iter (± 41141) 0.96
fib/interp_watchdog 3870892 ns/iter (± 19506) 4289230 ns/iter (± 13656) 0.90
fib/interp_none 3871341 ns/iter (± 22611) 4294011 ns/iter (± 13506) 0.90
buffer_rw/jit_mlua_hook 555021 ns/iter (± 1533) 584109 ns/iter (± 1928) 0.95
buffer_rw/jit_watchdog 167968 ns/iter (± 228) 191602 ns/iter (± 335) 0.88
buffer_rw/jit_none 167969 ns/iter (± 375) 191549 ns/iter (± 423) 0.88
buffer_rw/interp_mlua_hook 1051452 ns/iter (± 11526) 1037627 ns/iter (± 12956) 1.01
buffer_rw/interp_watchdog 621892 ns/iter (± 10401) 588242 ns/iter (± 2255) 1.06
buffer_rw/interp_none 621717 ns/iter (± 3305) 588528 ns/iter (± 1416) 1.06
splash_render_120x40 75520 ns/iter (± 733) 61846 ns/iter (± 5593) 1.22
splash_render_200x60 118980 ns/iter (± 10534) 191757 ns/iter (± 4008) 0.62

This comment was automatically generated by workflow using github-action-benchmark.

w0wl0lxd added 2 commits July 26, 2026 22:24
…y fields

- Add byte limits to first-message parsing and cached text to avoid
  memory and scan-cache bloat on sessions with huge initial prompts.
- Avoid splitting snippets on periods, which cut abbreviations short.
- Cap extracted snippets to a small display budget before title truncation.
- Add serde defaults to new SessionSummary fields for forward compatibility.
- Fall back to updated_at when created_at is missing on older sessions so
  sub-task parent ordering stays sensible.
- Remove the stale scan_cache_v2.json file when writing the v3 cache.
…dling

- Auto-bucket any parent with more than 20 direct children into grouped
  nodes labelled with task count and age range (e.g. "20 tasks · today").
- Preserve expansion state for generated group nodes across refreshes.
- Parse team/workflow/task prefixes from live session titles so background
  sessions are grouped correctly before the storage scan lands.
- Handle group nodes specially: Enter/Right toggles, rename and delete are
  blocked, and the right column shows child counts.
- Keep filtered and tree lists sorted by stable recency rank.
@codecov

codecov Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 56 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
n00n-storage/src/sessions.rs 87.84% 31 Missing ⚠️
n00n-lua/src/api/session.rs 0.00% 13 Missing ⚠️
n00n-ui/src/event_loop.rs 0.00% 12 Missing ⚠️

📢 Thoughts on this report? Let us know!

@w0wl0lxd
w0wl0lxd marked this pull request as ready for review July 27, 2026 03:08

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

Copy link
Copy Markdown

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: fda89b4421

ℹ️ 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 plugins/sessions/init.lua
Comment thread plugins/sessions/init.lua
Comment thread plugins/sessions/init.lua Outdated
Comment thread n00n-storage/src/sessions.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugins/sessions/init.lua (1)

252-268: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Filtered view mixes in group placeholder rows.

board.nodes contains the bucket nodes (they are appended to all by make_bucket), so a query like task matches a group's display_title (21 tasks) and renders a group row whose children are not in the result list. Skip n.is_group in the filter loop.

🐛 Proposed fix
     for _, n in ipairs(board.nodes) do
       n.depth = 0
-      if ListPicker.matches(n.display_title, words) or ListPicker.matches(n.title, words) then
+      if not n.is_group
+        and (ListPicker.matches(n.display_title, words) or ListPicker.matches(n.title, words))
+      then
         table.insert(board.items, n)
       end
     end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/sessions/init.lua` around lines 252 - 268, Update the filtering loop
in apply_filter to skip nodes where n.is_group is true before matching titles
and inserting into board.items, while preserving matching behavior for regular
nodes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@n00n-lua/tests/plugin_host.rs`:
- Around line 2647-2678: The sessions grouping probe only validates bucket
structure, not child link stability. Extend the Lua probe and its Rust
assertions around group_node to verify each child retains its original parent_id
while group membership is tracked separately, then invoke a second
group_node/build_tree pass and assert those relationships remain unchanged.

In `@n00n-storage/src/sessions.rs`:
- Line 1762: Move MAX_FIRST_MESSAGE_BYTES and MSG_RECORD_PREFIX from their
mid-file declarations to the top-level constants section immediately after the
imports, alongside the existing sibling limits. Remove the original declarations
and preserve their values and visibility.
- Around line 571-583: The subtask prefix classification uses inconsistent
casing and is redundantly performed. In n00n-storage/src/sessions.rs:571-583,
lowercase title_norm before matching SUBTASK_TITLE_PREFIXES; in
plugins/sessions/init.lua:412-425, retain the lowercase comparison but remove
the local re-classification and use the stored kind as authoritative for live
rows.
- Around line 592-602: Merge the identical conditional arms in the display
selection logic: combine the title_norm == DEFAULT_TITLE and
title_norm.starts_with("Use the ") conditions into one branch while preserving
the existing snippet_from_text fallback behavior and the title_norm path for all
other titles.
- Line 1664: Update the cleanup around SCAN_CACHE_FILE_V2 to handle
fs::remove_file errors explicitly: ignore only the NotFound case, and log other
failures using an explicitly named fallback with sanitized structured logging.
Do not discard the Result anonymously with let _; preserve cleanup behavior when
the file is absent.
- Around line 483-494: Update generate_title to delegate its final title
truncation and ellipsis handling to truncate_label, removing the duplicated
boundary and word-boundary logic from generate_title while preserving its
existing title-generation behavior.

In `@plugins/sessions/init.lua`:
- Around line 188-199: The group_label function incorrectly assumes slice
endpoints represent the newest and oldest timestamps. Compute the minimum and
maximum age or updated_at values across children[start_idx] through
children[finish], then use those extrema for the label while preserving the
existing singular/plural and equal-age formatting.
- Around line 367-375: Extract the duplicated task-count formatting from the
group branch near the visible session rendering logic into a small reusable
helper, then use it here and at the other task-count construction around the
earlier session code. Preserve the existing singular/plural behavior, returning
“task” only for a count of one and “tasks” otherwise.
- Around line 201-226: Update make_bucket so grouping does not overwrite each
child’s real parent_id; store the bucket relationship in a separate group_id
field instead. Update collapse_or_parent to prefer s.group_id when traversing
grouped nodes, while retaining parent_id as the fallback for ordinary hierarchy
relationships.

---

Outside diff comments:
In `@plugins/sessions/init.lua`:
- Around line 252-268: Update the filtering loop in apply_filter to skip nodes
where n.is_group is true before matching titles and inserting into board.items,
while preserving matching behavior for regular nodes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4231f030-109f-4ef0-935a-cd917c761737

📥 Commits

Reviewing files that changed from the base of the PR and between 4894771 and fda89b4.

📒 Files selected for processing (6)
  • changelog.d/session-tree-ui.added.md
  • n00n-lua/src/api/session.rs
  • n00n-lua/tests/plugin_host.rs
  • n00n-storage/src/sessions.rs
  • plugins/sessions/init.lua
  • site/docs/content/lua-api/_index.md
📜 Review details
⏰ Context from checks skipped due to timeout. (16)
  • GitHub Check: Build (Windows)
  • GitHub Check: Coverage
  • GitHub Check: MSRV (1.97)
  • GitHub Check: Test (macOS)
  • GitHub Check: Rustdoc
  • GitHub Check: Test (Windows)
  • GitHub Check: Lint (macOS)
  • GitHub Check: Lint
  • GitHub Check: Docs
  • GitHub Check: Build
  • GitHub Check: Lint (Windows)
  • GitHub Check: Test
  • GitHub Check: Unused deps
  • GitHub Check: Criterion
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (6)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Non-trivial or multi-file changes should use a dedicated git worktree and new branch; do not modify unrelated user changes, force-push, or push to main.
Ship finished work with a clear Conventional Commit message, a pushed branch, and a draft pull request; never add AI-agent attribution to authored content.
Before investigating unfamiliar failures or third-party behavior, research documented behavior first and distinguish unrelated baseline failures from regressions in the touched surface.
Use structural tools before broad searches: prefer codegraph or arbor for cross-file relationships, index before reading files, targeted reads, parallel calls, and code_execution for filtering large outputs.

Files:

  • changelog.d/session-tree-ui.added.md
  • n00n-lua/src/api/session.rs
  • n00n-lua/tests/plugin_host.rs
  • site/docs/content/lua-api/_index.md
  • plugins/sessions/init.lua
  • n00n-storage/src/sessions.rs
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Workspace Rust lint rules are mandatory: deny unsafe code, production unwrap_used, expect_used, panic, todo!, unimplemented!, dbg!, wildcard imports, and silent-default error handling.
Do not add unsafe code, FFI, global mutable state, static mut, or unchecked transmute-like behavior without written review and an explicit crate-level lint exception.
Use explicit error handling with Result<T, E> rather than panics; propagate typed errors with ?, ok_or_else, and map_err.
Use thiserror for library and domain-specific errors, and color-eyre at binary edges.
Do not silently discard errors with .ok(), unwrap_or, unwrap_or_default, or equivalent defaults; return an error, reject the operation, or use an explicitly named fallback with sanitized structured logging.
Follow Rust idioms, use descriptive variable and function names, avoid unnecessary state and bloat, and keep each line justified.
Import types at the top of the file, use short imported names instead of inline qualified paths, and place constants immediately after imports.
Do not use inline magic numbers or strings; derive Copy only for structs with one primitive field.
Use structured logging with useful fields and provide helpful, sanitized error messages.
Do not commit credentials, API keys, tokens, cookies, authorization headers, or user data, and do not log raw provider payloads, prompts, credentials, or session data.
Validate and authorize HTTP, file, queue, configuration/environment, LLM output, and provider callbacks before mutation or persistence.
Tool execution requires allowlisted tools, scoped credentials, explicit user context, audit events, and refusal or denial tests.
Write meaningful, non-tautological, non-flaky tests; avoid arbitrary sleeps, and define shared constant error/status messages for assertions.

Files:

  • n00n-lua/src/api/session.rs
  • n00n-lua/tests/plugin_host.rs
  • n00n-storage/src/sessions.rs
**/*.{rs,toml}

📄 CodeRabbit inference engine (AGENTS.md)

All crates must opt into the workspace lint configuration with [lints] workspace = true; the root workspace lint configuration is authoritative.

Files:

  • n00n-lua/src/api/session.rs
  • n00n-lua/tests/plugin_host.rs
  • n00n-storage/src/sessions.rs
**/*.{rs,ron,json,toml,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Treat LLM and provider output as untrusted input; validate it against schemas, domain constraints, and source evidence before persistence or action.

Files:

  • n00n-lua/src/api/session.rs
  • n00n-lua/tests/plugin_host.rs
  • n00n-storage/src/sessions.rs
site/docs/**/*

📄 CodeRabbit inference engine (AGENTS.md)

User documentation should be warm, simple, concise, easy for non-native English speakers, story-oriented, and contain no em-dashes, emojis, or AI-like tone.

Files:

  • site/docs/content/lua-api/_index.md
plugins/**/*.lua

📄 CodeRabbit inference engine (AGENTS.md)

Built-in Lua plugins belong under ./plugins and should use the repository's plugin tooling and conventions.

Files:

  • plugins/sessions/init.lua
🪛 markdownlint-cli2 (0.23.0)
changelog.d/session-tree-ui.added.md

[warning] 1-1: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🔇 Additional comments (14)
n00n-storage/src/sessions.rs (6)

37-43: LGTM!

Also applies to: 241-246


496-513: LGTM!


515-565: LGTM!


1605-1653: LGTM!

Also applies to: 1666-1679


1712-1760: LGTM!

Also applies to: 1764-1795, 1827-1833, 1845-1903


2054-2055: LGTM!

Also applies to: 2069-2069, 2120-2121, 3283-3343

n00n-lua/src/api/session.rs (1)

41-42: LGTM!

site/docs/content/lua-api/_index.md (1)

2645-2646: LGTM!

plugins/sessions/init.lua (5)

1-13: LGTM!

Also applies to: 33-47, 56-59


75-94: LGTM!

Also applies to: 107-119, 134-137


148-186: LGTM!

Also applies to: 228-250


278-344: LGTM!


445-511: LGTM!

Also applies to: 524-532, 557-560, 586-590, 614-623, 665-668, 711-712

changelog.d/session-tree-ui.added.md (1)

1-1: LGTM!

Comment thread n00n-lua/tests/plugin_host.rs
Comment thread n00n-storage/src/sessions.rs
Comment thread n00n-storage/src/sessions.rs
Comment thread n00n-storage/src/sessions.rs Outdated
Comment thread n00n-storage/src/sessions.rs Outdated
Comment thread n00n-storage/src/sessions.rs Outdated
Comment thread plugins/sessions/init.lua
Comment thread plugins/sessions/init.lua
Comment thread plugins/sessions/init.lua

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Criterion'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: fda89b4 Previous: 0d313ba Ratio
splash_render_200x60 203672 ns/iter (± 617) 97776 ns/iter (± 9202) 2.08

This comment was automatically generated by workflow using github-action-benchmark.

w0wl0lxd added 5 commits July 27, 2026 03:13
Forward-declare render before navigation callbacks so Luau captures it as
an upvalue, persist kind-prefixed titles in the in-memory stored snapshot
on rename, and rename leftover N00nId uses to n00nId after #145.
@w0wl0lxd
w0wl0lxd merged commit 9f141ea into main Jul 27, 2026
30 checks passed
@linear-code

linear-code Bot commented Jul 27, 2026

Copy link
Copy Markdown

N00N-8

w0wl0lxd added a commit that referenced this pull request Jul 27, 2026
Resolve SessionRequest parent_id + steer/control and storage test imports.
@w0wl0lxd
w0wl0lxd deleted the feat/session-tree-ui branch July 27, 2026 08:56
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