Skip to content

An unmirrored tab in its own pane splits the frontend and backend pane trees #370

Description

@deveshk0

The invariant

The frontend and backend pane trees must stay structurally identical. Both reducers mint pane and split ids from the same op stream, so a pane that exists on one side and not the other puts every id minted afterwards permanently out of step — the failure mode src/workspace/persistence.ts's header note exists to prevent.

Tab contents are allowed to differ: a tab that toPersistedTab returns null for is never mirrored, and the backend simply does not have it. That is fine right up until the presence of such a tab changes pane structure on one side only.

The bug

Split a pane, carrying an unmirrored tab into the new pane:

  • the frontend's new pane holds that tab;
  • the backend's new pane is empty, because it has no such tab.

Now open a persisted tab in that pane and close it again. The backend's pane is empty and folds. The frontend's still holds the unmirrored tab and does not. From that point the two trees disagree, and later opens and splits mint ids against different structures.

Mirroring the split with its move_tab_id reaches the same end state by a different route — the backend cannot resolve a tab it never opened, and splits into an empty pane regardless.

Reachable today

Export, import and generate tabs are in NON_PERSISTED_TYPES and have been unmirrored since #326, and they have always been draggable. App.test.tsx's "routes a pane-scoped export handler to the rendered pane's tab" (#97 review Fix 1) splits an Export tab into its own pane, so the test suite creates exactly this state today.

#364 does not introduce it, but makes it ordinary: a tab on a connection the user has not saved is also unmirrored (there is no profile to reconnect it to), and dragging one of those around is a far more everyday thing to do than dragging an export tab.

Why it needs a decision rather than a patch

Three fixes were tried on #369 and each is unsatisfactory in a different way:

Approach Result
Mirror with move_tab_id Backend splits into an empty pane — the current behaviour
Mirror without move_tab_id Same empty pane, reached more tidily
Do not mirror the split Frontend gains a pane the backend never mints — worse, and immediate
Refuse the split entirely Trees stay identical, but splitting an export tab into its own pane stops working, and the #97 test depends on it

Only the last actually holds the invariant, and it removes a real, tested behaviour. That is a product call: is splitting an unmirrored tab into its own pane worth keeping?

Options if it is worth keeping: give the backend a placeholder occupant so pane structure matches, or stop folding emptied panes when the frontend has not folded the corresponding one (which needs the backend to know a pane is locally non-empty).

Suggested first step

Decide whether splitting an unmirrored tab into its own pane is a behaviour to keep. If not, refusing the split is a handful of lines and the #97 test can be re-pointed at a persisted tab, which is what it is really about.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions