Skip to content

Refactor chat thread lifecycle and listing into dedicated Convex modules - #159

Merged
EricTsai83 merged 3 commits into
mainfrom
feature/refactor-thread-lifecycle
Jun 24, 2026
Merged

Refactor chat thread lifecycle and listing into dedicated Convex modules#159
EricTsai83 merged 3 commits into
mainfrom
feature/refactor-thread-lifecycle

Conversation

@EricTsai83

@EricTsai83 EricTsai83 commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • Extracted thread lifecycle responsibilities from convex/chat/threads.ts into a new convex/chat/threadLifecycle.ts module (create/move/delete/archive/recover/reset, share updates, stream/message cleanup orchestration, and bounded continuation scheduling).
  • Added a dedicated convex/chat/threadListing.ts module to centralize active-thread list ordering (pinned-first, then recent, deduplicated) for both repository-scoped and repoless scopes.
  • Updated convex/chat/chatTurnIntake.ts to use shared lifecycle reset logic for single-turn thread prep before new turns, reducing inline cleanup orchestration.
  • Updated convex/chat/context.ts and related call sites to consume new lifecycle exports (including repoless agent detection).
  • Kept higher-level API shape stable in threads.ts while delegating heavy orchestration to the new modules and preserving existing mutation/query behavior.
  • Added/updated tests for ordering contract and lifecycle flows in convex/chat/threads.test.ts, convex/lib/chat-composer-session.test.ts, and convex/repositories-delete.test.ts.
  • Adjusted client-side composer/workspace session hooks and repository ownership adapters to match the refactored thread state flow.

Testing

  • Not run: bun run format.
  • Not run: bun run lint.
  • Not run: bun run typecheck.
  • Not run: bun run test.

Summary by CodeRabbit

  • New Features

    • Added a more unified repository workspace experience, bringing thread selection, panels, dialogs, and chat actions together.
    • Improved composer grounding behavior so library and sandbox choices stay in sync with availability and access changes.
    • Thread lists now consistently show pinned items first, followed by recent items without duplicates.
  • Bug Fixes

    • Improved handling of thread resets, archiving, deletion, and repository changes for more reliable chat and repository workflows.
    • Refined repository cleanup and deletion behavior for better consistency.

- Move thread creation, repository transfer, single-turn reset, archive, and delete logic from `convex/chat/threads.ts` into new `convex/chat/threadLifecycle.ts`
- Update `startChatTurnInExistingThread` to use `resetSingleTurnThreadForNextTurn` before new turns in single-turn repoless threads
- Route agent/thread profile resolution and context imports to the new lifecycle module to reduce cross-file duplication
- Centralize thread listing into a shared scope-aware helper with pinned-first ordering and regression tests
- Introduce adapter-driven repository deletion lifecycle with ordered groups and retry policies
- Add explicit sandbox list pagination and propagate hardened cleanup result handling
- Move chat composer grounding state to reducer-based session state for stable updates
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
systify Ready Ready Preview, Comment Jun 24, 2026 10:10am

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@EricTsai83, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 37 minutes. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

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 credits.

🚦 How do rate 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 see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f778ddea-ebfb-4266-9f9e-be6c2a0c379b

📥 Commits

Reviewing files that changed from the base of the PR and between 2dc5f9c and cc43fc5.

📒 Files selected for processing (5)
  • convex/chat/threadLifecycle.ts
  • convex/daytona.ts
  • src/components/chat-shell-shared/use-chat-composer-session.ts
  • src/components/chat-shell-shared/use-repository-workspace-state.ts
  • src/components/repository-shell.tsx
📝 Walkthrough

Walkthrough

This PR extracts ~600 lines of inline thread mutation logic from convex/chat/threads.ts into a new threadLifecycle.ts module and a threadListing.ts helper. It introduces typed adapter/retry-policy infrastructure for repository-owned data deletion. On the frontend, it adds a ComposerSessionState reducer for grounding selection, extracts RepositoryShell orchestration into a useRepositoryWorkspaceState hook, and fixes Daytona sandbox listing to use explicit pagination.

Changes

Backend: Thread Lifecycle and Repository Deletion Refactors

Layer / File(s) Summary
Thread listing helper and ordering tests
convex/chat/threadListing.ts, convex/chat/threads.ts, convex/chat/threads.test.ts
Adds listActiveThreadsForScope returning pinned-first de-duplicated threads; listThreads/listRepolessThreads delegate to it; new ordering-contract tests assert pinned-first-then-recent with no duplicates for both repo and repoless scopes.
threadLifecycle.ts: constants, types, creation, and repo-attach
convex/chat/threadLifecycle.ts
Introduces batch-size/length constants, ThreadMessageArtifactDrainResult type, agent profile normalization, regular and library-ask thread creation lifecycles, and repository attach/detach/share-scope-update lifecycle functions.
threadLifecycle.ts: repoless agent profile and single-turn reset
convex/chat/threadLifecycle.ts, convex/chat/chatTurnIntake.ts, convex/chat/context.ts
Implements updateRepolessThreadAgentProfileLifecycle, continueRepolessSingleTurnResetLifecycle, and resetSingleTurnThreadForNextTurn; chatTurnIntake.ts delegates single-turn reset to the new helper; context.ts re-sources resolveRepolessAgentEnabled from threadLifecycle.
threadLifecycle.ts: archive, restore, delete, and orphan cleanup
convex/chat/threadLifecycle.ts
Implements bulk and single-thread archive/restore/delete lifecycle functions, permanent deletion with staged draining and share-mapping cleanup, orphaned message/stream cleanup with per-pass chunk budgets, and drainThreadMessageArtifacts.
threads.ts: delegate all mutations to lifecycle helpers
convex/chat/threads.ts
Replaces all inline mutation logic with single-line await *Lifecycle(ctx, args) calls, removing ~550 lines of implementation from the file.
Repository-owned data adapter types and de-exported helpers
convex/lib/repositoryOwnedDataAdapters.ts
Adds RepositoryOwnedDataDrainContext/RepositoryOwnedDataDrainResult exported types, removes exports from five drain helpers, adds clearOwnerViewerPreference and repositoryRoot adapter operations, and short-circuits ownerViewerState when ownerTokenIdentifier is absent.
Repository-owned data lifecycle: retry policy and execution-group-driven deletion
convex/lib/repositoryOwnedDataLifecycle.ts, convex/repositories-delete.test.ts
Introduces RepositoryOwnedDataRetryPolicy, extends registry entries with adapterKey/order/retryPolicy, replaces the fixed drain sequence with an adapter-loop computing effective retry delay, and updates tests to assert registry entry structure.
Daytona sandbox listing explicit pagination
convex/daytona.ts
Replaces async-iterator drain with explicit page/totalPages loop.

Frontend: Composer Session Reducer and Workspace State Extraction

Layer / File(s) Summary
ComposerSessionState model, reducer, and snapshot
src/lib/chat-composer-session.ts, src/lib/chat-composer-session.test.ts
Adds ComposerSessionGroundingState, ComposerSessionInputs, ComposerSessionState, and ComposerSessionAction types; exports createComposerSessionState, reduceComposerSession, and getComposerSessionSnapshot with auto-rules for disabling unavailable grounding; 125 lines of new tests cover initialization, repository-switch clearing, and auto-clear behavior.
useChatComposerSession migrated to reducer
src/components/chat-shell-shared/use-chat-composer-session.ts
Removes local GroundingState/useState/useEffect and replaces with useReducer(reduceComposerSession); dispatches sync on input changes; buildChatSendRequest and grounding-tool wiring now consume composerSession.* snapshot fields.
useRepositoryWorkspaceState hook
src/components/chat-shell-shared/use-repository-workspace-state.ts
New 563-line hook encapsulating repository/thread ID derivation, capability checks, panel/dialog state, navigation callbacks, archive/delete lifecycle wiring, and composer session construction previously inline in RepositoryShell. Exports RepositoryWorkspaceState type.
RepositoryShell consuming useRepositoryWorkspaceState
src/components/repository-shell.tsx
Removes ~360 lines of inline orchestration; all sidebar, top-bar, drawer, dialog, and panel bindings now read from workspace.* returned by useRepositoryWorkspaceState.

Sequence Diagram(s)

sequenceDiagram
  participant RepositoryShell
  participant useRepositoryWorkspaceState
  participant useChatComposerSession
  participant reduceComposerSession
  participant threadLifecycle

  RepositoryShell->>useRepositoryWorkspaceState: urlRepositoryId, urlThreadId
  useRepositoryWorkspaceState->>useChatComposerSession: thread, repository, capabilities
  useChatComposerSession->>reduceComposerSession: dispatch sync(composerSessionInputs)
  reduceComposerSession-->>useChatComposerSession: ComposerSessionState
  useChatComposerSession-->>useRepositoryWorkspaceState: composerSession snapshot
  useRepositoryWorkspaceState-->>RepositoryShell: RepositoryWorkspaceState

  RepositoryShell->>useRepositoryWorkspaceState: handlers.archiveThread(thread)
  useRepositoryWorkspaceState->>threadLifecycle: archiveThreadLifecycle(ctx, { thread })
  threadLifecycle-->>useRepositoryWorkspaceState: void
  useRepositoryWorkspaceState-->>RepositoryShell: dialogs.archiveThread updated
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • EricTsai83/systify#81: Both touch the setThreadRepository path — the prior PR added swappedFromRepositoryId return value, and this PR moves that logic into setThreadRepositoryLifecycle.
  • EricTsai83/systify#144: This PR refactors the repoless single-turn reset drain + scheduling introduced in #144 by delegating it to threadLifecycle.resetSingleTurnThreadForNextTurn.
  • EricTsai83/systify#153: Both PRs refactor repository-shell.tsx and related chat UI composition, with overlapping changes to composer/grounding wiring.

Poem

🐇 I hopped through the threads, so tangled before,
Pulled lifecycle helpers out onto the floor.
The shell lost its clutter, the reducer took hold,
Adapters now carry their retry policies bold.
With pagination fixed and grounding made clear—
The warren runs cleaner, hop hop, never fear! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main refactor: moving chat thread lifecycle and listing logic into dedicated Convex modules.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/refactor-thread-lifecycle

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.

@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: 5

🤖 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 `@convex/chat/threadLifecycle.ts`:
- Around line 439-446: Avoid removing archive-scope membership twice in
deleteArchivedThreadLifecycle and deleteThreadLifecycle. The archived thread is
already removed from archive scope before the permanent delete, so update
deleteThreadLifecycle to skip recordThreadRemovedFromArchiveScope when the
thread is being permanently deleted from an archived state, or centralize the
removal so it happens only once. Use the deleteArchivedThreadLifecycle and
deleteThreadLifecycle flow to ensure the archive decrement is performed a single
time for archived-thread deletes.
- Around line 290-302: The single-turn reset flow in threadLifecycle should also
clear lastAssistantMessageAt whenever the reset completes, not only in the
synchronous path. Update the reset handling around enablingSingleTurn and the
pending-reset continuation so that once drainThreadMessageArtifacts finishes
removing all remaining messages/streams, the thread patch removes or nulls
lastAssistantMessageAt alongside singleTurnResetPending. Use the existing reset
logic in threadLifecycle as the entry point and ensure both the immediate
completion path and the deferred completion path apply the same metadata
cleanup.

In `@src/components/chat-shell-shared/use-chat-composer-session.ts`:
- Around line 106-118: The composer session currently renders with stale reducer
state before the sync effect corrects it, which can drive route and send payload
from the previous thread/repo/access context. Update the logic in
use-chat-composer-session, especially around useReducer,
getComposerSessionSnapshot, and the sync effect, so the first render after
threadId/repositoryId/access input changes derives the new composer session
state immediately instead of waiting for the effect to dispatch a fix-up. Ensure
the grounding selection and any derived route/payload values always reflect the
latest baseComposerSessionInputs on that same render.

In `@src/components/chat-shell-shared/use-repository-workspace-state.ts`:
- Around line 159-160: The artifact panel can be enabled via an attached
repository, but `handleSelectArtifact` and related artifact navigation still
rely only on `currentRepositoryId`, causing clicks to fail for repoless or moved
threads. Update the artifact-selection flow in `useRepositoryWorkspaceState` to
derive an effective repository id from `capabilities.attachedRepository?.id ??
currentRepositoryId`, and use that wherever artifact navigation or downstream
panel state is computed so the panel never receives a null repo id when an
attached repository is available.
- Around line 274-291: The repoless thread selection handler in
handleSelectThread drops the selected thread when currentRepositoryId is null by
navigating to DEFAULT_AUTHENTICATED_PATH instead of a thread-specific route.
Update the null-repository branch to route through the repoless thread/new-chat
path so AppSidebarLeft actions remain routable, and keep the selected
threadId/new-thread flow intact for both thread selection and thread creation
paths.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: edec2837-b17d-45a2-9721-41c48da5caec

📥 Commits

Reviewing files that changed from the base of the PR and between 2d448f0 and 2dc5f9c.

📒 Files selected for processing (15)
  • convex/chat/chatTurnIntake.ts
  • convex/chat/context.ts
  • convex/chat/threadLifecycle.ts
  • convex/chat/threadListing.ts
  • convex/chat/threads.test.ts
  • convex/chat/threads.ts
  • convex/daytona.ts
  • convex/lib/repositoryOwnedDataAdapters.ts
  • convex/lib/repositoryOwnedDataLifecycle.ts
  • convex/repositories-delete.test.ts
  • src/components/chat-shell-shared/use-chat-composer-session.ts
  • src/components/chat-shell-shared/use-repository-workspace-state.ts
  • src/components/repository-shell.tsx
  • src/lib/chat-composer-session.test.ts
  • src/lib/chat-composer-session.ts

Comment thread convex/chat/threadLifecycle.ts
Comment thread convex/chat/threadLifecycle.ts Outdated
Comment thread src/components/chat-shell-shared/use-chat-composer-session.ts
Comment thread src/components/chat-shell-shared/use-repository-workspace-state.ts
Comment thread src/components/chat-shell-shared/use-repository-workspace-state.ts
- Clear assistant timestamp on single-turn re-enables and avoid duplicate archive-scope deletion
- Use async-iterator-based Daytona sandbox pagination for cleaner listing
- Resolve artifact repository and thread navigation for repo-less flows, and stabilize composer session snapshots
@EricTsai83
EricTsai83 merged commit 2ccb70c into main Jun 24, 2026
4 checks passed
@EricTsai83
EricTsai83 deleted the feature/refactor-thread-lifecycle branch June 24, 2026 10:14
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