Skip to content

Dashboard tile removal leaves saved-query star state inconsistent #370

Description

@BorisTyshkevich

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

  1. Save a panel-role query.
  2. Star the query in the Workbench so it is added to the Dashboard.
  3. Open the editable Dashboard.
  4. Press the trash icon on that tile.
  5. 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 favorite to false; 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 StoredWorkspaceV1 aggregate.

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:

  1. remove the selected tile;
  2. remove that tile ID from every explicit filters[].targets array;
  3. check whether another tile still references the same query;
  4. set the query's spec.favorite compatibility flag to that post-delete membership;
  5. normalize the active layout and fallback;
  6. increment the Dashboard revision once;
  7. 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: true without 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.favorite to false in the same workspace commit.
  • Removing one of multiple tile instances keeps spec.favorite true 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: true query with no tile renders as unstarred in the Workbench.
  • Clicking that unstarred query once creates a tile and writes favorite: true atomically.
  • Imported favorite: true queries 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

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