Skip to content

Persist typed tab identities and restore the ordered roster - #1162

Merged
trissim merged 12 commits into
mainfrom
fix/durable-tab-order
Oct 23, 2026
Merged

trissim merged 12 commits into
mainfrom
fix/durable-tab-order

Conversation

@trissim

@trissim trissim commented Oct 23, 2026 •

Copy link
Copy Markdown

Tabs persist declared TabIdentity values in the existing toad.db. AcpTab, MailboxTab, HistoryTab and PreviewTab own restoration; the shared parent derives Textual names. No counted session-N/comms-N/preview-N identities or mode parsing remain.

TabOrder owns the in-process roster. ApplicationLifetime calls its async load once after the existing off-thread DB.create. names, contains, recent, project and previous read _open only. Mutations await one serialized writer; DB.tab_roster replaces the persisted order in one transaction through DB._transaction (asyncio.to_thread). TabOrder holds its lock until an owned write finishes, including caller cancellation, through the existing join_retirement operation. No frame or sidebar update opens SQLite.

TabTargetGone carries the identity and reason. Missing ACP rows, foreign/replaced mailbox targets, foreign history roots and missing preview files raise it. The startup loop catches only that declared outcome, removes the current roster entry, logs the identity/reason and continues. All other exceptions propagate.

The existing model history callers were migrated to await writes and to initialize/load their fixture DB explicitly. No tests were run for this send-back.

Exact requested grep:

src/toad/tab_order.py:30:            self._open = [row.identity for row in await self._db.tab_list()]
src/toad/db.py:108:    async def tab_list(self) -> list[OpenTabRow]:

The sole roster write is DB.tab_roster; tab_reorder and tab_close were deleted. TabOrder query methods are at tab_order.py:40 (contains), :44 (names), :48 (recent), :62 (project) and :65 (previous). The per-tab TabTargetGone handler is application_lifetime.py:124-129.

Core dependency agent-comms#2896 is merged at da12825c5. Its declaration-derived NOT NULL widening permits Session.agent_session_id to stay absent until the ACP handshake fills it. No Toad carry tool or installer hook.

The earlier authorized DB copy check changed agent_session_id TEXT NOT NULL -> TEXT with sessions rows 0 -> 0; it used a private copy, never the live DB. The commander independently verified two-row preservation and NULL insertion after the Core change.

Assumed / behavior retained:

  • The roster is seeded once per application lifetime. The DB persists that owner; runtime queries never read it back.
  • The lock serializes roster mutations and writes; cancelled callers join their writer before releasing the lock.
  • Gone targets are normal restore outcomes. Storage, codec and programming errors are not swallowed.
  • Agents without load_session support still have their actual ACP row saved at launch. AcpTab.restore passes its non-null provider ID to launch, which binds it as an existing session. AgentSession.run at acp/agent_session.py:308-314 records UnsupportedResumeAgentFail and returns BEFORE load() at :315. Such a tab opens with a visible resume failure; this push does not change that behavior. A row whose handshake has not completed still has None and follows fresh-launch behavior.
  • Welcome is resident, not another persisted tab. Existing input-history namespaces are preserved.

Runtime movement belongs to Drafts in Toad #1159 and agent-comms #2860. This branch does not edit or push #1159.

@trissim trissim changed the title Persist the open-tab order through the existing DB owner Persist typed tab identities and restore the ordered roster Oct 23, 2026
@trissim
trissim marked this pull request as ready for review October 23, 2026 20:37
Copilot AI balanced review requested due to automatic review settings October 23, 2026 20:37

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

trissim and others added 3 commits October 23, 2026 16:46
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ab roster

SessionNavigation.new now takes the tab identity, so every caller names one:
- SessionNavigation.saved owns writing a native session's row and returns its
  AcpTab; launch and the test support's saved_tab both use it.
- Thread tabs in tests pass MailboxTab(incarnation, root), as the restore path does.
- The MCP permission observer called a deleted default_source; it now saves its
  real agent definition.

TabOrder.editing writes the roster only when the open list changed, and the
uncalled TabOrder.reorder is deleted. A saved AcpTab with no session row is now
dropped at startup without a crash; a test covers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@trissim
trissim merged commit eead990 into main Oct 23, 2026
2 of 3 checks passed
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.

2 participants