Skip to content

fix(datagrid): release table data via direct call instead of broadcast#1139

Merged
datlechin merged 1 commit into
mainfrom
fix/datagrid-teardown-cross-window-leak
May 9, 2026
Merged

fix(datagrid): release table data via direct call instead of broadcast#1139
datlechin merged 1 commit into
mainfrom
fix/datagrid-teardown-cross-window-leak

Conversation

@datlechin

Copy link
Copy Markdown
Member

Summary

Fixes a real bug in the FK-navigate-with-unsaved-changes flow:

Reproducer:

  1. Open a table, make a value edit / row insert / row delete (don't save)
  2. Click an FK arrow on a cell
  3. New native window tab opens with the FK target table (intentional — preserves the edit buffer)
  4. Close that new tab
  5. Before this PR: original tab's data grid goes blank (columns gone, rows gone) until you Cmd+Tab away and back, or refresh
  6. After this PR: original tab keeps rendering normally

Root cause

MainContentCoordinator.teardown() broadcast a connection-scoped event on AppEvents.shared.mainCoordinatorTeardown. Every TableViewCoordinator listening with .filter { $0.connectionId == myConnectionId } received it — including coordinators in OTHER live windows on the same connection. They ran releaseData() which empties their displayCache, removes their tableView.tableColumns, and calls reloadData() on an empty grid.

Multi-window same-connection wasn't a concern when the broadcast pattern was added, but the FK-with-changes flow now creates exactly that topology. The filter granularity is too coarse for the actual lifecycle.

What changes

Drop the broadcast pattern entirely. Replace with a direct method call from parent to child:

File Change
TablePro/Core/Events/AppEvents.swift Remove mainCoordinatorTeardown PassthroughSubject and MainCoordinatorTeardown struct
TablePro/Views/Main/MainContentCoordinator.swift Replace AppEvents.shared.mainCoordinatorTeardown.send(...) with dataTabDelegate?.tableViewCoordinator?.releaseData()
TablePro/Views/Results/DataGridCoordinator.swift Drop observeTeardown(connectionId:) method, drop teardownCancellable: AnyCancellable? ivar, change private func releaseData() to func releaseData()
TablePro/Views/Results/DataGridView.swift Drop the two observeTeardown call sites in makeNSView and updateNSView

The wiring MainContentCoordinator -> dataTabDelegate.tableViewCoordinator already existed (set during dataGridAttach), so the direct call path was already in place — just unused.

Why this is the Apple-correct pattern

AppKit doesn't use NotificationCenter or Combine for parent-to-child cleanup. The documented pattern is direct method dispatch via the ownership graph:

  • NSViewController.viewWillDisappear -> child cleanup
  • NSWindowController.close -> child window release
  • NSDocument.close -> dismiss editing UI

Combine PassthroughSubject is for streams of events from indeterminate sources (network, user input, timers). Single-producer / single-consumer parent-child cleanup is exactly the case where direct ownership wins. Sendable types and @MainActor boundaries are preserved either way.

Diff stats

  • 5 files, +3 / −30 LoC

Test plan

  • Open table A, make value edits / insert / delete (don't save)
  • Click FK arrow on a cell -> new native window tab opens with the referenced table
  • Close that new tab via Cmd+W or red traffic-light button
  • Original table A's data grid still renders normally (columns, rows, edit indicators visible immediately, no Cmd+Tab refresh needed)
  • No-changes case unchanged: click FK on a clean table -> replaces current tab content in-place
  • Sorted-only case unchanged: click FK on a sorted table (no value changes) -> replaces current tab content in-place
  • Closing a tab with no other tabs of the same connection: behavior unchanged (releaseData runs once on its own tableView)
  • Multi-window same-connection: edit in window A, edit in window B, close window B -> window A's grid still renders
  • App quit / cmd+Q: all tabs tear down cleanly

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin datlechin merged commit 72e18d7 into main May 9, 2026
2 checks passed
@datlechin datlechin deleted the fix/datagrid-teardown-cross-window-leak branch May 9, 2026 14:59
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