Surfaced while implementing #457.
The gap
closeTab (src/ui/tabs.ts:132) drops a tab unconditionally — it refuses only to
close the last one. There is no confirmation for a dirty tab, and no
beforeunload handler anywhere in src/. The only "protect unsaved work" UX in the
app is the linked-saved-query conflict chooser (src/ui/conflict-resolution.ts),
which fires on Save, not on close.
#457's acceptance criteria say a dirty variable tab "must use the same
unsaved-change protection as other editable tabs". Taken literally that is parity,
and parity is what shipped: a variable tab shows the same dirty dot and closes the
same way. But there is nothing to inherit.
Why it matters more for a variable tab
The asymmetry is real, not cosmetic:
- a dirty query tab's draft was never expected to persist anywhere — closing it
discards something that only ever lived in that tab;
- a dirty variable tab's draft is an edit to SQL that IS stored
(dashboard.variableConfigs[<name>].sql). Closing it silently discards the user's
only copy of a change to durable configuration, with no prompt and no undo.
The same argument applies to a dirty query tab linked to a saved query
(savedId !== null), which is the other case where the draft diverges from
something persisted.
Goal
Decide and implement one close-time protection for editable tabs, covering both
document kinds rather than bolting one onto variables alone. Options worth weighing:
- confirm on closing any tab whose draft diverges from committed truth (dirty +
either savedId !== null or a dashboard-variable doc);
- confirm only for documents backed by stored content, leaving a never-saved
scratch tab closing freely (probably the least annoying);
- a
beforeunload guard as well, for whole-page navigation.
Why deferred
Designing a close-guard is new UX for BOTH tab kinds and was not specified by #457,
which is a refactor. Building it there would have meant inventing behaviour under a
criterion that reads as "match what exists".
Surfaced while implementing #457.
The gap
closeTab(src/ui/tabs.ts:132) drops a tab unconditionally — it refuses only toclose the last one. There is no confirmation for a dirty tab, and no
beforeunloadhandler anywhere insrc/. The only "protect unsaved work" UX in theapp is the linked-saved-query conflict chooser (
src/ui/conflict-resolution.ts),which fires on Save, not on close.
#457's acceptance criteria say a dirty variable tab "must use the same
unsaved-change protection as other editable tabs". Taken literally that is parity,
and parity is what shipped: a variable tab shows the same dirty dot and closes the
same way. But there is nothing to inherit.
Why it matters more for a variable tab
The asymmetry is real, not cosmetic:
discards something that only ever lived in that tab;
(
dashboard.variableConfigs[<name>].sql). Closing it silently discards the user'sonly copy of a change to durable configuration, with no prompt and no undo.
The same argument applies to a dirty query tab linked to a saved query
(
savedId !== null), which is the other case where the draft diverges fromsomething persisted.
Goal
Decide and implement one close-time protection for editable tabs, covering both
document kinds rather than bolting one onto variables alone. Options worth weighing:
either
savedId !== nullor adashboard-variabledoc);scratch tab closing freely (probably the least annoying);
beforeunloadguard as well, for whole-page navigation.Why deferred
Designing a close-guard is new UX for BOTH tab kinds and was not specified by #457,
which is a refactor. Building it there would have meant inventing behaviour under a
criterion that reads as "match what exists".