Repository navigation
Dashboard tile removal leaves saved-query star state inconsistent #370
Description
Summary
Removing a query tile with the Dashboard trash icon updates dashboard.tiles[] but does not update the saved query's compatibility spec.favorite flag.
This leaves one persisted workspace containing two contradictory membership states:
{
"queries": [
{
"id": "q1",
"spec": {
"favorite": true
}
}
],
"dashboard": {
"tiles": []
}
}After reloading the Workbench, the query still appears starred even though it has no Dashboard tile. The user must unstar and then star it again to restore membership.
This issue owns membership semantics and the Dashboard trash workflow. General cross-tab refresh-before-write consistency remains tracked by #343.
Reproduction
- Save a panel-role query.
- Star the query in the Workbench so it is added to the Dashboard.
- Open the editable Dashboard.
- Press the trash icon on that tile.
- Reload the Workbench.
Actual result
- The tile is absent from
dashboard.tiles[]. - The saved query still contains
spec.favorite: true. - The Workbench renders the star as enabled.
- Clicking the star once only changes
favoritetofalse; clicking it a second time recreates the tile.
Expected result
Removing the final Dashboard tile for a query also makes that query unstarred.
If another tile instance still references the same query, the query remains starred.
Current workflow
The Workbench star path performs an atomic dual-write:
- toggles
spec.favorite; - adds or removes Dashboard tile membership;
- commits the complete
StoredWorkspaceV1aggregate.
The Dashboard trash path currently dispatches only:
{ type: 'remove-tile', tileId }The command removes the tile from dashboard.tiles[], but the committed candidate preserves latest.queries unchanged. Therefore spec.favorite remains true.
The existing DashboardAuthoringSession.toggleMembership() already demonstrates the intended compatibility behavior: after changing membership, it updates spec.favorite to match whether the query still has a tile. The live Dashboard UI bypasses that semantic operation and calls raw remove-tile.
Canonical membership rule
During the compatibility period:
Dashboard membership source of truth: dashboard.tiles[]
Compatibility mirror: query.spec.favorite
For a panel-role query:
const member = dashboard.tiles.some(tile => tile.queryId === query.id);The Workbench star UI should reflect canonical tile membership rather than trusting a stale spec.favorite value.
spec.favorite should still be written to match canonical membership until the compatibility field is retired.
Required delete behavior
Deleting a Dashboard tile must atomically:
- remove the selected tile;
- remove that tile ID from every explicit
filters[].targetsarray; - check whether another tile still references the same query;
- set the query's
spec.favoritecompatibility flag to that post-delete membership; - normalize the active layout and fallback;
- increment the Dashboard revision once;
- validate and commit the complete workspace.
Examples:
Delete the only tile for q1
→ q1.favorite = false
Delete one of multiple tile instances for q1
→ q1.favorite = true
Deleting a tile does not delete the saved query from the Library.
Legacy inconsistent state
Do not automatically recreate tiles for every persisted query with favorite: true and no tile.
That state can also result from an intentionally imported query collection, because imported favorite flags do not automatically create Dashboard tiles.
Instead:
- render the Workbench star from canonical
dashboard.tiles[]membership; - treat
favorite: truewithout a tile as unstarred; - one click should add the tile and repair the compatibility flag in the same commit.
This repairs existing affected workspaces without guessing whether a missing tile should be recreated automatically.
Read-before-write consistency
Every membership mutation must be expressed as a semantic transform over the latest workspace loaded at write time, not as a complete candidate built from an older UI snapshot.
The broader two-tab refresh-before-write behavior is tracked by #343. This issue does not require protection against two users committing during the same read/write instant; it only requires that ordinary sequential edits from the Workbench and Dashboard are based on the latest persisted workspace.
Acceptance criteria
- Removing the final tile for a query sets
spec.favoritetofalsein the same workspace commit. - Removing one of multiple tile instances keeps
spec.favoritetrue while another instance remains. - Removing a tile deletes its ID from every explicit Dashboard filter target list.
- Removing a tile increments the Dashboard revision exactly once.
- A persisted
favorite: truequery with no tile renders as unstarred in the Workbench. - Clicking that unstarred query once creates a tile and writes
favorite: trueatomically. - Imported
favorite: truequeries do not automatically gain Dashboard tiles on load. - Workbench star add/remove and Dashboard trash use one shared membership mutation primitive or equivalent identical semantics.
- Failed validation or persistence leaves both the query flag and Dashboard document unchanged.
- Unit tests cover final-instance deletion, multi-instance deletion, filter-target cleanup, legacy inconsistent state, and failed commits.
- A browser test covers star in Workbench → delete in Dashboard → reload Workbench.
Related
- Cross-tab workspace consistency: refresh before write and invalidate stale tabs #343 — Dashboard/Workbench cross-tab read-before-write consistency and stale render invalidation
- Dashboard tile ignores saved
view: "table"and auto-renders a chart #368 — Dashboard tile presentation bug; the original second report was split into this issue - fix(#365): reconcile saved-query persistence and dirty state #367 — creates the first Dashboard/tile when starring a query from a workspace with
dashboard: null
Activity
fix(#370): keep dashboard membership and stars consistent (#380)
docs(Altinity#544): reconcile the ⋯ decision across ADR-0003, CHANGEL…