Skip to content

fix(app): finish the state-surfaces follow-ups - #4018

Merged
datlechin merged 8 commits into
mainfrom
fix/state-surfaces-followups
Nov 3, 2026
Merged

datlechin merged 8 commits into
mainfrom
fix/state-surfaces-followups

Conversation

@datlechin

@datlechin datlechin commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Summary

Finishes what #4001, #4005, #4007 and #4009 left open.

1. Restore Previous Values is disabled when nothing is kept (from #4007)

  • The Edit menu, the toolbar's Actions menu and the history drawer dim Restore Previous Values… when no saved values are kept for the target, instead of answering "Nothing to Restore".
  • QueryHistoryStorage keeps a revision and publishes the kept tables and history ids after every snapshot write, delete, clear, retention prune and connection delete. The read uses plain columns, no decrypt.
  • RewindAvailabilityCache on the main actor feeds the synchronous menu and toolbar checks and only takes newer revisions, so a snapshot written after the drawer loaded enables the item without a reload (the refactor(ui): native notices and status labels on every surface #4007 round 4 staleness).
  • "Nothing to Restore" stays for one case: the save is pruned, or its payload cannot be decrypted, between the item being enabled and the click. Knowing that up front means a decrypt on every menu validation.
  • The drawer's "Another Connection" alert is gone: the drawer already disables another connection's saves, so the guard only logs.

2. Export and table transfer stop only when their owner goes away (from #4009)

  • SheetWorkScope and SheetJob: a job is listed for the closers and stopped by MainContentCoordinator.teardown() (window close, ConnectionWorkspace.teardown(), a released session), never by onDisappear. A connection switch or an Agent mode toggle keeps it running.
  • This covers disconnects that never ask: AppleScript and MCP, connect-error and sample-failure window closes, disconnectAll.
  • The data file window stops its running export when it closes.
  • An export is listed only for its own window, so closing another window on the same connection no longer stops it. A transfer stays listed for any window on its source or destination, since closing the destination's last window disconnects it.

3. Agent mode and finished results (from #4009)

  • In Agent mode a finished tab result counts as off screen: it marks the tab and can notify.
  • showSelectedContentPanes clears the selected tab's mark when Browse shows again, and when its connection is selected. The second case was a stale mark on main too.

4. MCP disconnect refuses instead of skipping the ask (from #4009)

  • A client cannot answer the running-work alert, so disconnect returns denied when TablePro's own Disconnect would ask: running work, unsaved changes or an open transaction on that connection. The message names kinds only, never a tab, file or connection.
  • The check runs in DatabaseAccessBridge.disconnect on the main actor, re-read after the transaction probe with no suspension before the disconnect.
  • transactionSafety(for:) tells idle from holding from unknown. A busy lane, a 1 s timeout or an unknown state is refused. This narrows the tool: on Dameng, KingbaseES, libSQL, Snowflake, Spanner, StarRocks and Teradata, which never report their state, the user disconnects in TablePro.

5. Tunnel creation races (from #4001)

  • TunnelCreationGenerations: only the newest createTunnel per connection adopts its tunnel; a superseded one closes what it built. In SSH, SOCKS, Cloudflare, Cloud SQL, tunnel command and remote SQLite.
  • closeTunnel and closeAllTunnels also cancel a creation still dialing, so nothing is adopted after a close returns.
  • Cloud SQL writes one key file per attempt; every attempt that is not adopted deletes its own.

6. A real SSH tunnel drop (verification #4001 could not do)

  • Postgres behind an sshd container, connected through SSH with a key. docker stop on sshd: the pane bar read "Reconnecting to orders-prod (4 of 10)" with "SSH tunnel disconnected." and Cancel; docker start: the session came back with its tabs. Stopped again: after the 10th attempt the window showed "Disconnected from orders-prod" with Reconnect. Screenshots below.

7. iOS NULL toggle in edit mode (from #4007)

  • Shot by rendering RowDetailView and pressing its Edit bar button in a hosted window.
  • The shot showed a defect: the NULL toggle jumped from the trailing edge to just after the NULL label when turned on. It now stays on the trailing edge.

8. Selected rail entry's status badge (from #4001)

  • The lost or failed badge on the selected (emphasized) rail entry drew a plain white disc. NSTableCellView passes the emphasized style to the mark's image view, and a template symbol then draws in the selection white and ignores contentTintColor.
  • The symbol is now a non-template image tinted at draw time. A palette symbol would fill the "!" cut-out of the triangle with the same red.
Defect on main Fixed by
Restore Previous Values enabled with nothing kept, then an info-only alert Availability published by the history store
Export and transfer kept running with no UI after a disconnect that never asks SheetWorkScope stopped by teardown
Closing one window stopped another window's export on the same connection Window id on the export's running work
Agent mode results counted as on screen; a background connection's mark never cleared Resolver checks Browse, pane show clears
MCP disconnect skipped the running-work ask denied with kinds only
Two creations for one connection leaked a tunnel; a close mid-creation adopted it afterwards; Cloud SQL could delete a newer attempt's key Creation generations
iOS NULL toggle moved when turned on Leading content fills the row
Selected rail entry's badge was a white disc Non-template tinted symbol

Tests changed on purpose:

  • CloudSQLProxyManagerTests "service account key is written 0600 and removed on close" reads the per-attempt path from --credentials-file.
  • WritePolicyFixture's driver answers .idle, so the write-policy matrix disconnect cells reach a known-safe session.
  • WorkspaceRailCellView+Label renderedTint reads displayedTint, since contentTintColor no longer carries the tint.

Decisions made without the maintainer:

  • MCP disconnect refuses rather than raising the alert on the Mac: a remote client should not put a modal in front of the user at an arbitrary time.
  • An unknown transaction state refuses the MCP disconnect, even on engines that never report one.
  • "This Save Cannot Be Restored" stays an alert for a kept save whose rows are all refused: the reason is in the encrypted payload, and a dimmed item would hide it.

Deliberately not fixed here

  • Item 2, import Stop rollback (from refactor(running-work): ask once about running work and show one progress sheet #4009). Built and then cut back to main after five Codex rounds, each finding a new defect in the previous round's code:
    • round 1: a Stop during the last write still committed while promising a rollback;
    • round 2: a batch under Stop and Commit committed earlier files when a write failed after the Stop; a SQL Server GO batch could hide a COMMIT;
    • round 3: a file's own COMMIT ran while the promise still showed;
    • round 4: the confirmation alert kept its promise while the last write committed behind it;
    • round 5: the pause added for that skipped a statement and reported success, and a batch mixing InnoDB and MyISAM tables reported rolled-back files as kept.
    • Telling the truth per case needs a stop protocol between the sheet and the runners (pause while the question is up, cancellation that the SQL importer propagates, per-table outcomes in a batch). The Stop keeps "Anything already imported may stay." on every engine, which is true in every case.
  • A menu command that replaces the active sheet (for example Edit > Restore Previous Values while an export sheet is up) swaps the export dialog out; the job keeps running, stays listed and still stops on teardown. It needs a sheet guard in every command's validation.

Test plan

  • Full Unit plan on the tree merged with main (4030434): 28,677 executed, 28,676 passed, 2 skipped, 1 failed: GutterNumberOffsetTests/windowGutterKeepsItsMargin, which fails on main whenever the Mac is in Dark Mode (it was); no editor code changes here.
  • New suites: RewindAvailabilityTests, CoordinatorRewindAvailabilityTests, SheetWorkTests, SheetWorkLifetimeTests, AgentModeResultVisibilityTests, TunnelCreationRaceTests, TunnelCloseDuringCreationTests, plus new cases in DisconnectToolTests, CloudSQLProxyManagerTests and WorkspaceRailCellTextTests.
  • Fail on main, pass here: TunnelCreationRaceTests (11 issues on main: the older creation adopted its tunnel and the newer leaked); the selected-row badge case in WorkspaceRailCellTextTests (all 4 cases failed on selected > 0).
  • Touched suites together: 279 of 279 at integration; disconnect and write-policy suites 16 of 16 after the probe fence.
  • iOS: TableProMobile built with the RowDetailView change and the render harness ran on an iPhone Duo simulator, iOS 27.1 (the only runtime installed).
  • Lint: 0 violations on every changed Swift file. verify.sh l10n passes; 8 strings added through localization.py extracted --add.
  • Codex: an adversarial round and five reviews. Fixed: stale and unknown transaction probes in MCP disconnect, a close during tunnel creation, superseded Cloud SQL key files, transfers missing from the destination window's close. The import rollback findings led to the cut above. Round 6 found nothing.
  • Not verified: macOS 13 to 15 and iOS 18 to 26 (no such OS here); the import Stop confirmation text has no screenshot (unchanged from main after the cut).

Screenshots

Shot from a second, sandboxed copy of the Debug build (the user's own TablePro was running, so XCUITest could not), captured by window id. The SSH states are a real drop: Postgres behind an sshd container, docker stop and docker start on sshd. The iOS shots render RowDetailView in a hosted window and press its Edit button. Before is main at 4030434 for the menu; for the rail badge and the iOS toggle it is this branch before those fixes.

Before After
Edit menu on a table with no kept values (the menu follows the system appearance) Restore Previous Values enabled Restore Previous Values dimmed
Light Dark
Before: selected rail entry, lost Selected rail entry with a plain white disc Selected rail entry with a plain white disc, dark
After: selected rail entry, lost Selected rail entry with an orange bolt.slash badge Selected rail entry with an orange bolt.slash badge, dark
SSH connected Connected through SSH Connected through SSH, dark
sshd stopped Reconnecting to orders-prod (3 of 10), SSH tunnel disconnected, Cancel Reconnecting, dark
sshd back Session back, no bar Session back, dark
sshd down past 10 attempts Disconnected from orders-prod, Reconnecting failed after 10 attempts, Reconnect Lost, dark
Before: iOS row editor, NULL toggle moves when on NULL toggle next to the NULL label NULL toggle next to the NULL label, dark
After: iOS row editor, NULL toggle stays trailing NULL toggle on the trailing edge NULL toggle on the trailing edge, dark

@datlechin
datlechin marked this pull request as ready for review November 3, 2026 15:01
@datlechin
datlechin enabled auto-merge (squash) November 3, 2026 15:01
@datlechin
datlechin merged commit b7fb05f into main Nov 3, 2026
9 of 10 checks passed
@datlechin
datlechin deleted the fix/state-surfaces-followups branch November 3, 2026 16:29
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