You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue supersedes #429's previous split between:
Phase 4: Panel metadata pencil;
Phase 5: Panel delete;
the Dashboard/Panel ⋯ overflow-menu action chrome.
The row chrome, edit command, delete command and focus navigation must land as one vertical slice so the tree is never left with two competing interaction models.
Remove the ⋯ overflow icon from both Dashboard and Panel rows.
Dashboard and Panel rows should expose their important operations directly, following the Library Query row's pencil/trash vocabulary instead of hiding actions in a menu.
Dashboard row
Target
Action
Disclosure chevron
Expand/collapse the Dashboard hierarchy only
Dashboard icon/name
Open the exact Dashboard in View mode
Shift-click / Shift+Enter on the primary row
Open the exact Dashboard in Edit mode; retain the existing shortcut
Pencil icon from Phase 3
Edit Dashboard title and description
Trash icon
Remove the Dashboard and all its dedicated owned queries atomically after confirmation
There is no ⋯ button and no Dashboard row action menu. Removing the menu must not change chevron, primary-row, drag/drop or keyboard-tree behaviour.
Panel row
Target
Action
Panel icon/name / primary row
Open the panel's dedicated query in the Query surface, unchanged
Open-in-Dashboard icon
Open the owning Dashboard in View mode, focus the exact tile and scroll it into view
Pencil icon
Edit the dedicated query's name and description
Trash icon
Remove the tile and its dedicated owned query atomically
Keep the existing panel-row double-click and Shift-click shortcuts unless removing them is required by shared-code simplification:
double-click: open Dashboard View and focus the tile;
Shift-click / Shift+Enter: open Dashboard Edit and focus the tile.
The direct Open-in-Dashboard button is the discoverable, keyboard-operable path; the existing gestures may remain as shortcuts but must not be the only path.
There is no ⋯ button and no Panel row action menu.
Direct action presentation
Use the Library Query row as the visual precedent:
Icon.pencil() for metadata edit;
Icon.trash() for delete;
Icon.expand() for Open in Dashboard/focus panel, unless an already-shipped icon is demonstrably clearer in the rendered sidebar.
Required presentation:
real <button type="button"> elements;
compact trailing action cluster;
order: Open in Dashboard → Edit → Delete, keeping the destructive action rightmost;
buttons exist in the DOM whenever the operation is part of that row's vocabulary;
reveal on row hover and :focus-within, matching Query-row action discoverability;
an open dialog/confirmation keeps its trigger visible;
narrow sidebar layout must preserve the label's usable width and ellipsis rather than overlapping or wrapping the controls;
each button has an independent focus indicator;
delete uses the destructive hover/focus treatment;
do not add a fourth overflow control.
Accessible names and tooltips:
Open <panel name> in dashboard
Edit <panel name>
Remove <panel name> from dashboard
Dashboard pencil: Edit <dashboard name>
The tooltip may be shorter, but the accessible name must identify the target resource.
Event isolation
Every nested action control must be isolated from the row's own gestures.
For mouse and keyboard activation:
activating an icon must not run the row click, double-click or Shift-click action;
it must not toggle a Dashboard/group disclosure;
it must not disturb another row's pending click arbitration;
it must cancel only a pending action for its own row when such an action exists;
Enter/Space on a nested button must not bubble into the tree-level Enter handler;
native button activation must occur exactly once.
Do not rely only on onclick.stopPropagation(): explicitly cover the keydown path used by the tree's delegated keyboard handler.
when an imported override masks query metadata, retain established display precedence and show a compatibility note rather than claiming the rendered title will immediately change;
validate the complete workspace candidate;
commit through the existing serialized read-latest workspace mutation path;
do not increment Dashboard revision for a query-metadata-only change unless the existing aggregate contract requires it;
refresh the tree, any linked Query tab and the rendered Dashboard after success;
Cancel and Escape commit nothing.
If the owned query cannot be proven, the pencil remains rendered but unavailable with aria-disabled="true" and a concise reason. Do not guess a query from the name or another tile.
Panel trash action
Show the existing destructive confirmation pattern:
Remove panel “<panel name>” from “<dashboard name>”?
This also deletes its dedicated query copy.
On confirmation, execute one atomic workspace mutation. The pure command may retain the parent issue's proposed name:
remove its placement from the flow fallback where applicable;
normalise through the active layout plugin;
regenerate the grafana-grid flow fallback where required;
remove the dedicated query from workspace.queries[];
increment the target Dashboard revision exactly once;
replace only the target Dashboard;
validate the complete workspace candidate;
commit once through app.mutateWorkspace.
Variables are inferred from panel SQL. If this was the last declaration of a variable, retain any variableConfigs entry as an orphan; do not delete it as a side effect.
After success:
remove the Panel row;
preserve Dashboard and Panels-group expansion state;
move tree keyboard focus to the next Panel row, otherwise the previous Panel row, otherwise the Panels group;
if the tile was the current Dashboard member, keep the Dashboard open and clear the member focus rather than retargeting another tile;
if its query was open in the Query surface, use the existing deleted-query reconciliation behaviour;
do not modify a Library source query or favourite state.
Cancel/Escape commit nothing.
Dashboard trash action
like Panel's trash action - confirmation and recursive delete of all nested queries.
Ownership and malformed-data safety
Edit/delete is enabled only when the ownership index proves exactly one expected owner: this Dashboard + this tile.
For a missing query, multiple owners, wrong owner kind or mismatched Dashboard/member identity:
Open in Dashboard remains available when the tile itself exists;
pencil and trash remain visible but unavailable;
expose aria-disabled="true" plus a concise tooltip/reason;
activating an unavailable control changes nothing and may surface the existing integrity diagnostic;
never cascade-delete based on a guessed owner.
Repairing malformed imported data remains with #429's repair-planner phase.
Concurrency and lifecycle
Follow the Phase 3 dialog/mutation pattern:
dialogs capture workspace/generation and stable resource IDs, not mutable object references as truth;
save/delete re-resolve the target and ownership inside the queued read-latest transform;
workspace switch, route change, sign-out or disposal makes a stale dialog inert;
if the target was deleted or ownership changed while a dialog was open, commit nothing, close or reconcile the stale UI and report a targeted diagnostic;
a successful mutation refreshes the current surface only when its captured surface generation is still current;
no detached row/button/dialog may receive late focus after a repaint;
concurrent unrelated workspace changes are preserved;
repeated activation cannot open duplicate dialogs or submit the same mutation twice.
Model and cleanup requirements
src/application/dashboard-tree-model.ts remains the source of structural decisions. Represent direct action availability/commands in the model; do not re-derive ownership or row capability from DOM classes in src/ui/dashboard-tree.ts.
After Dashboard and Panel overflow menus are removed:
remove their menu command entries;
remove DashboardTreeMenuItem / buildMenuButton / menu CSS if no remaining production row uses them;
remove dead menu-only tests and comments rather than keeping an unused abstraction;
preserve the orphan-variable direct trash control;
keep explicit row accessible names so nested button labels are not folded into the treeitem name;
update CHANGELOG.md because the deployed interaction model changes.
Likely files include:
src/application/dashboard-tree-model.ts
src/ui/dashboard-tree.ts
src/styles.css
a pure Dashboard panel mutation module under src/dashboard/application/
the existing saved-query metadata mutation/dialog seams
Open-in-Dashboard opens View mode and scrolls/focuses the exact tile by stable ID on grafana-grid and flow layouts.
Pencil edits only the owned query's name/description and preserves SQL, ownership, presentation and extension data.
Trash atomically removes the tile, all layout placements and exactly its dedicated owned query.
Dashboard Trash atomically removes all tiles and the correct dashboard
Malformed ownership never causes guessed or cascading edits/deletes.
Direct buttons are independently named, keyboard-operable and isolated from row/disclosure gestures.
Tree expansion, focus ownership and Query/Dashboard surfaces reconcile correctly after every action.
Unit and Playwright tests cover mouse, keyboard, accessibility and stale state. Layout-engine coverage is the pure delete transform's (grafana-grid placements + the flow fallback); the browser scroll/focus half rides with the deferred focus button.
CHANGELOG.md documents the new direct-action UX.
Verification
The implementation PR must report:
npm test
npx tsc --noEmit
npm run build
npm run test:e2e
Also report the focused #438/Panel-navigation browser specs separately so the scroll/focus contract is visible rather than hidden inside the full matrix.
Delivery status (2026-07-27)
Everything above except the Open-in-Dashboard focus button is implemented; see
the ship log on #429 for the handoff.
The focus button is held back deliberately, per the owner decision taken when this
phase started: it dispatches openDashboard({ dashboardId, mode: 'view', focus: { kind: 'tile', id } }), which #438 proves is a no-op on flow-layout KPI tiles (the focus target is a display: contents host with no box). This issue's own dependency section says not
to ship a known no-op focus action, so the button — and the two acceptance bullets
that describe it — wait for #438.
Note that the capability itself is not a regression: it shipped inside the ⋯
menu's Open Dashboard in View/Edit and focus panel items, and those gestures
remain available on a Panel row as double-click and Shift-click. What is deferred
is promoting them to a discoverable button while they are still broken on one
layout engine.
Part of #429 — revised Phase 4.
This issue supersedes #429's previous split between:
⋯overflow-menu action chrome.The row chrome, edit command, delete command and focus navigation must land as one vertical slice so the tree is never left with two competing interaction models.
Dependencies
UX decision
Remove the
⋯overflow icon from both Dashboard and Panel rows.Dashboard and Panel rows should expose their important operations directly, following the Library Query row's pencil/trash vocabulary instead of hiding actions in a menu.
Dashboard row
There is no
⋯button and no Dashboard row action menu. Removing the menu must not change chevron, primary-row, drag/drop or keyboard-tree behaviour.Panel row
Keep the existing panel-row double-click and Shift-click shortcuts unless removing them is required by shared-code simplification:
The direct Open-in-Dashboard button is the discoverable, keyboard-operable path; the existing gestures may remain as shortcuts but must not be the only path.
There is no
⋯button and no Panel row action menu.Direct action presentation
Use the Library Query row as the visual precedent:
Icon.pencil()for metadata edit;Icon.trash()for delete;Icon.expand()for Open in Dashboard/focus panel, unless an already-shipped icon is demonstrably clearer in the rendered sidebar.Required presentation:
<button type="button">elements;:focus-within, matching Query-row action discoverability;Accessible names and tooltips:
Open <panel name> in dashboardEdit <panel name>Remove <panel name> from dashboardEdit <dashboard name>The tooltip may be shorter, but the accessible name must identify the target resource.
Event isolation
Every nested action control must be isolated from the row's own gestures.
For mouse and keyboard activation:
Do not rely only on
onclick.stopPropagation(): explicitly cover the keydown path used by the tree's delegated keyboard handler.Open-in-Dashboard command
The focus button must dispatch by stable IDs:
Requirements:
Panel pencil action
Open the same metadata editing experience used for Library queries, adapted to a Dashboard-owned query. Prefill:
Canonical fields:
Requirements:
panel;tile.titleortile.descriptionfor normal authoring;If the owned query cannot be proven, the pencil remains rendered but unavailable with
aria-disabled="true"and a concise reason. Do not guess a query from the name or another tile.Panel trash action
Show the existing destructive confirmation pattern:
On confirmation, execute one atomic workspace mutation. The pure command may retain the parent issue's proposed name:
Required transformation:
workspace.queries[];app.mutateWorkspace.Variables are inferred from panel SQL. If this was the last declaration of a variable, retain any
variableConfigsentry as an orphan; do not delete it as a side effect.After success:
Cancel/Escape commit nothing.
Dashboard trash action
like Panel's trash action - confirmation and recursive delete of all nested queries.
Ownership and malformed-data safety
Edit/delete is enabled only when the ownership index proves exactly one expected owner: this Dashboard + this tile.
For a missing query, multiple owners, wrong owner kind or mismatched Dashboard/member identity:
aria-disabled="true"plus a concise tooltip/reason;Repairing malformed imported data remains with #429's repair-planner phase.
Concurrency and lifecycle
Follow the Phase 3 dialog/mutation pattern:
Model and cleanup requirements
src/application/dashboard-tree-model.tsremains the source of structural decisions. Represent direct action availability/commands in the model; do not re-derive ownership or row capability from DOM classes insrc/ui/dashboard-tree.ts.After Dashboard and Panel overflow menus are removed:
menucommand entries;DashboardTreeMenuItem/buildMenuButton/ menu CSS if no remaining production row uses them;treeitemname;CHANGELOG.mdbecause the deployed interaction model changes.Likely files include:
src/application/dashboard-tree-model.tssrc/ui/dashboard-tree.tssrc/styles.csssrc/dashboard/application/tests/unit/dashboard-tree-model.test.tstests/unit/dashboard-tree.test.tstests/e2e/dashboard-tree.spec.jsThe agent must audit current
mainbefore choosing exact module names; do not duplicate an existing mutation or dialog primitive.Required tests
Model
Rendering and interaction
.dash-tree-menu-btnis rendered for Dashboard or Panel rows;focus-withinreveal the direct controls;type="button"and independent focus treatment;Focus navigation
openDashboard({ dashboardId, mode: 'view', focus: { kind: 'tile', id: tileId } })dispatch;Edit
Delete
Browser accessibility
Use real keyboard events, not
.click()substitutes:Non-goals
Acceptance criteria
⋯action icon or overflow menu.⋯action icon or overflow menu.CHANGELOG.mddocuments the new direct-action UX.Verification
The implementation PR must report:
Also report the focused #438/Panel-navigation browser specs separately so the scroll/focus contract is visible rather than hidden inside the full matrix.
Delivery status (2026-07-27)
Everything above except the Open-in-Dashboard focus button is implemented; see
the ship log on #429 for the handoff.
The focus button is held back deliberately, per the owner decision taken when this
phase started: it dispatches
openDashboard({ dashboardId, mode: 'view', focus: { kind: 'tile', id } }), which#438 proves is a no-op on flow-layout KPI tiles (the focus target is a
display: contentshost with no box). This issue's own dependency section says notto ship a known no-op focus action, so the button — and the two acceptance bullets
that describe it — wait for #438.
Note that the capability itself is not a regression: it shipped inside the
⋯menu's Open Dashboard in View/Edit and focus panel items, and those gestures
remain available on a Panel row as double-click and Shift-click. What is deferred
is promoting them to a discoverable button while they are still broken on one
layout engine.