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
Add a Databases / Dashboards role switcher to the upper-left sidebar and implement a read-only Dashboard hierarchy tree that navigates the full-size Dashboard surface introduced by #425.
This issue establishes reachability and navigation for every stored Dashboard before #427 hides Dashboard-owned queries from the lower list. It must work against the current transitional data model and continue to work unchanged after #427 tightens ownership.
This issue owns:
the upper role switcher;
Dashboard tree derivation and rendering;
Dashboard search;
expansion, scroll and keyboard state;
Dashboard/member navigation;
current-resource styling;
removal of the obsolete header-level SQL Browser/Dashboard switch.
It does not mutate Dashboard membership or query ownership.
when sourceQueryId is present and resolves, the row can open that query;
when sourceQueryId is absent, retain the row as a source-less transitional filter; query-open is unavailable, but Dashboard View/Edit focus navigation remains available;
when sourceQueryId is present but unresolved, render an invalid-reference diagnostic row and disable query-open;
never crash or hide the Dashboard because one filter reference is bad.
After #427, source-backed curated filters are expected to resolve to dedicated filter-role queries. No #426 tree redesign should be required.
Filters are top Dashboard controls and have no tile/layout position.
Panels group
Display:
Panels · <dashboard.tiles.length>
Each row represents one Dashboard tile, keyed by:
<workspaceId>:<dashboardId>:tile:<tileId>
Resolve the query through tile.queryId.
Display-label precedence:
trimmed tile.title when an imported compatibility override exists
otherwise resolved query.spec.name
otherwise "Untitled panel"
Preserve existing tile-title display semantics; do not create or erase overrides.
Before #427 the same query may appear under several members or Dashboards. Render one row per member and never merge rows by query ID or label.
When tile.queryId is unresolved:
retain the tile row;
show an invalid-reference icon/label;
disable query-open;
keep Dashboard View/Edit navigation available so Dashboard diagnostics remain reachable.
Do not create a Dashboard from this tree. Dashboard creation remains outside this issue.
Dashboard search
Dashboard search placeholder:
Search dashboards, filters, panels…
Search case-insensitively across:
Dashboard title and description;
filter label and parameter;
resolved filter source-query name and description;
panel query name and description;
imported tile-local title and description.
Search behaviour:
a child match keeps its Dashboard and group ancestors visible;
show matching descendants plus required ancestors;
temporarily expose matching paths without mutating saved expansion state;
a direct Dashboard match may show its complete hierarchy for context;
preserve pre-search expansion and scroll state;
clearing search restores that state;
search never mutates order or workspace content;
search never loads or executes query data.
Prefer presentation-time forced expansion, as the schema tree does, instead of writing temporary matches into the user's expansion sets.
Interaction arbitration
Do not directly copy the schema tree's immediate-single-click detector for member rows. In the schema tree the first click's action is harmless expansion; in the Dashboard tree it would open a query before the second click opens a Dashboard.
Use one deterministic click arbiter per app/tree instance:
first primary click schedules the row's single-click action for the double-click window;
a second primary click on the same row cancels the scheduled single action and runs exactly one double-click action;
Shift-click cancels a pending single action and runs Edit navigation immediately;
clicking a disclosure chevron stops propagation and expands/collapses immediately;
changing workspace/role or disposing the tree cancels pending click timers;
action-menu/button clicks stop propagation and cancel no unrelated row operation.
This avoids Query -> Dashboard flashes and duplicate browser-history entries.
Mouse gesture contract
Dashboard row
Gesture
Action
Click
Expand/collapse the Dashboard hierarchy
Double-click
openDashboard({ dashboardId, mode: 'view' })
Shift-click
openDashboard({ dashboardId, mode: 'edit' })
A double-click performs only View navigation and must leave expansion exactly as it was before the gesture.
Filters and Panels group rows
Gesture
Action
Click
Expand/collapse the group
Double-click and Shift-click have no separate group action.
#425 currently treats focus as a one-shot render request and rebuilding the already-open Dashboard is acceptable for direct API use. The tree makes repeated same-Dashboard focusing a normal operation, so this issue must add the following two refinements.
In-place same-Dashboard focus
When Dashboard ID and mode are unchanged and a focus target is supplied:
keep the existing Dashboard viewer/editor session;
do not rebuild or rerun the Dashboard;
do not write another browser-history entry;
scroll/focus/highlight the existing tile/filter node through a surface command port;
return a non-destructive diagnostic if the member no longer exists;
generation-check the command so stale surfaces cannot receive focus.
openDashboard() may use this as its same-ID/same-mode fast path. Different Dashboard IDs or mode changes may continue through the normal render transition.
Retained current-member state
The tree must be able to distinguish:
the Dashboard currently open;
the panel/filter most recently navigated to within it;
a pending one-shot DOM focus delivery.
Refactor the Dashboard branch of MainSurfaceState to retain current member identity separately from pending delivery, for example:
key members by Dashboard ID plus member kind and member ID;
never key by array index, title, query ID or displayed label;
prune removed resource IDs;
ordinary mutations preserve state for surviving IDs;
switching back to a workspace may restore its session state;
state is never exported or persisted in StoredWorkspaceV3.
Header cleanup
The current header contains an obsolete two-button application-surface switch:
Altinity® | SQL Browser | Dashboard
After this issue, Dashboard selection lives in the upper-left tree. Replace the switch with non-interactive branding:
Altinity® SQL Browser
Requirements:
remove the surfaceSwitch buttons from the header;
remove the obsolete hidden dashboardNav construction/repaint seam if it has no remaining caller;
retain the Dashboard surface toolbar's Back to query and View/Edit controls;
retain existing keyboard shortcuts that intentionally navigate surfaces;
retain File/workspace controls and connection/user utilities;
update tests, ADR/CHANGELOG and accessible names.
Defensive rendering
The persisted workspace validator should normally reject broken references, but the tree must remain robust while data is stale, imported or concurrently changed.
For invalid rows:
render the member and its ancestors;
show a warning/error icon and safe fallback label;
disable only the operation requiring the missing query;
keep Dashboard View/Edit/focus navigation available when possible;
provide a diagnostic tooltip or accessible description;
never throw while constructing the tree.
Suggested code boundaries
Upper sidebar role host
Owns:
Databases/Dashboards role tabs;
persistent role hosts;
role-specific search and scroll restoration;
upper-pane repaint invalidation.
Dashboard tree model
Pure derivation of:
Dashboard/group/member row identities;
resolved query references;
transitional source-less filters;
labels and counts;
search inclusion/forced expansion;
invalid annotations;
current-resource state.
Dashboard tree view/controller
Owns:
DOM and ARIA rendering;
click arbitration;
expansion;
keyboard traversal and action menus;
scroll/focus restoration.
Application navigation
Owns:
retained current member;
pending focus delivery;
same-Dashboard in-place focus.
The tree must use public application seams and must not directly manipulate the Dashboard renderer DOM.
Tests
Role switcher
Databases and Dashboards tabs render with counts;
Databases behaviour remains unchanged;
each role preserves independent search, scroll and expansion state;
Depends on: #424, #425
Implementation status of dependencies:
Goal
Add a Databases / Dashboards role switcher to the upper-left sidebar and implement a read-only Dashboard hierarchy tree that navigates the full-size Dashboard surface introduced by #425.
This issue establishes reachability and navigation for every stored Dashboard before #427 hides Dashboard-owned queries from the lower list. It must work against the current transitional data model and continue to work unchanged after #427 tightens ownership.
This issue owns:
It does not mutate Dashboard membership or query ownership.
Delivery-order contract
The authoritative epic sequence is:
#426 intentionally precedes #427. Otherwise #427 would hide Dashboard-owned queries from the lower list before any UI existed to open them.
Therefore this issue must not assume that #427 has already run.
Transitional data rules
Until #427 lands, valid workspaces may contain:
DashboardFilterDefinitionV1.sourceQueryIdabsent;The tree is a read-only projection of those references. It must not clone, repair, reject or rewrite them.
After #427:
The same tree implementation should naturally consume the post-#427 workspace without a second redesign.
Lower sidebar scope
The lower sidebar remains unchanged in this issue:
#427 owns the atomic semantic change:
Do not rename the lower tab to Library before its contents have Library semantics.
The final visual grammar after #427 will be:
Upper sidebar architecture
Add an equal-role tab row at the top of the existing upper pane:
Use the lower switcher's visual language, density and count treatment.
Keep two persistent role hosts inside the upper pane:
Switch roles by exposing one host and hiding the other. Do not destroy and reconstruct the schema host merely because the user viewed Dashboards.
This preserves:
Requirements:
Authoritative data source
Derive the Dashboard tree from the complete committed aggregate:
Do not derive it from:
state.dashboardis only the selected/compatibility Dashboard projection introduced for the current surface. It does not contain the collection.The tree must repaint after:
Use an explicit render invalidation signal/seam if needed; do not depend on incidental unrelated signal changes.
Tree hierarchy
Render Dashboards in
workspace.dashboards[]order.Within an expanded Dashboard, render exactly two groups in this order:
Filtersis always beforePanels.Keep both group rows visible while the Dashboard is expanded, including when empty. #428 will use them as stable drop targets.
Dashboard row
Display:
Count:
Filters are not included in the Dashboard-row count.
Display-label precedence:
Do not normally display the Dashboard ID as its title.
Filters group
Display:
Each row represents one
DashboardFilterDefinitionV1, keyed by:Filter display-label precedence:
Current transitional handling:
sourceQueryIdis present and resolves, the row can open that query;sourceQueryIdis absent, retain the row as a source-less transitional filter; query-open is unavailable, but Dashboard View/Edit focus navigation remains available;sourceQueryIdis present but unresolved, render an invalid-reference diagnostic row and disable query-open;After #427, source-backed curated filters are expected to resolve to dedicated filter-role queries. No #426 tree redesign should be required.
Filters are top Dashboard controls and have no tile/layout position.
Panels group
Display:
Each row represents one Dashboard tile, keyed by:
Resolve the query through
tile.queryId.Display-label precedence:
Preserve existing tile-title display semantics; do not create or erase overrides.
Before #427 the same query may appear under several members or Dashboards. Render one row per member and never merge rows by query ID or label.
When
tile.queryIdis unresolved:Counts and empty states
Upper tab counts:
Empty collection:
Empty groups remain visible:
Do not create a Dashboard from this tree. Dashboard creation remains outside this issue.
Dashboard search
Dashboard search placeholder:
Search case-insensitively across:
Search behaviour:
Prefer presentation-time forced expansion, as the schema tree does, instead of writing temporary matches into the user's expansion sets.
Interaction arbitration
Do not directly copy the schema tree's immediate-single-click detector for member rows. In the schema tree the first click's action is harmless expansion; in the Dashboard tree it would open a query before the second click opens a Dashboard.
Use one deterministic click arbiter per app/tree instance:
This avoids Query -> Dashboard flashes and duplicate browser-history entries.
Mouse gesture contract
Dashboard row
openDashboard({ dashboardId, mode: 'view' })openDashboard({ dashboardId, mode: 'edit' })A double-click performs only View navigation and must leave expansion exactly as it was before the gesture.
Filters and Panels group rows
Double-click and Shift-click have no separate group action.
Panel row
tile.idtile.idCalls:
Never focus a panel by query ID.
Filter row
filter.idfilter.idCalls for a source-backed filter:
For a source-less or unresolved filter, primary query-open is disabled; double-click and Shift-click continue to work.
Extend #425 navigation for tree use
#425 currently treats
focusas a one-shot render request and rebuilding the already-open Dashboard is acceptable for direct API use. The tree makes repeated same-Dashboard focusing a normal operation, so this issue must add the following two refinements.In-place same-Dashboard focus
When Dashboard ID and mode are unchanged and a focus target is supplied:
Suggested seam:
openDashboard()may use this as its same-ID/same-mode fast path. Different Dashboard IDs or mode changes may continue through the normal render transition.Retained current-member state
The tree must be able to distinguish:
Refactor the Dashboard branch of
MainSurfaceStateto retain current member identity separately from pending delivery, for example:Equivalent naming is acceptable, but the semantics are required:
currentMemberand requests pending focus;pendingFocus;currentMember;Selection and styling
mainSurface.dashboardId;Keyboard and accessibility model
Implement an actual keyboard tree rather than referring to a schema-tree convention that does not yet exist.
Container:
Rows:
Keys:
Provide a keyboard-reachable trailing action menu for operations otherwise available only through double-click:
Dashboard row:
Panel row:
Filter row:
The menu button must stop propagation, have an accessible label, and not become confused with the row's disclosure chevron.
Touch-specific behaviour remains out of scope.
Tree UI state
Keep state outside workspace JSON and scope it by workspace identity:
Requirements:
filters/panels;StoredWorkspaceV3.Header cleanup
The current header contains an obsolete two-button application-surface switch:
After this issue, Dashboard selection lives in the upper-left tree. Replace the switch with non-interactive branding:
Requirements:
surfaceSwitchbuttons from the header;dashboardNavconstruction/repaint seam if it has no remaining caller;Defensive rendering
The persisted workspace validator should normally reject broken references, but the tree must remain robust while data is stale, imported or concurrently changed.
For invalid rows:
Suggested code boundaries
Upper sidebar role host
Owns:
Dashboard tree model
Pure derivation of:
Dashboard tree view/controller
Owns:
Application navigation
Owns:
The tree must use public application seams and must not directly manipulate the Dashboard renderer DOM.
Tests
Role switcher
Hierarchy
tile.queryId;sourceQueryId;Search
Mouse gestures
#425 integration
Keyboard/accessibility
Invalid data
Header and regressions
Altinity® SQL Browserwithout surface buttons;Acceptance criteria
Non-goals