Skip to content

feat(dashboards): views prototype (demo-only, chained on #2379)#2382

Draft
alex-fedotyev wants to merge 14 commits into
alex/dashboards-multi-select-tag-filterfrom
alex/dashboards-smart-views-tag-only
Draft

feat(dashboards): views prototype (demo-only, chained on #2379)#2382
alex-fedotyev wants to merge 14 commits into
alex/dashboards-multi-select-tag-filterfrom
alex/dashboards-smart-views-tag-only

Conversation

@alex-fedotyev
Copy link
Copy Markdown
Contributor

@alex-fedotyev alex-fedotyev commented May 30, 2026

[demo-only, do-not-merge] PR-2 is now a prototype branch demonstrating the full Views story. Final production PRs will be split by topic after the UX demo lands agreement.

Summary

Save reusable filter combinations as named "views" pinned to a
left-rail sidebar on the Dashboards listing page. The PR is being
repurposed as a UX prototype that demonstrates the end-state
story before the production split.

This PR is chained on #2379. Please review #2379 first; the
Vercel preview here shows the cumulative UX (multi-select chips +
ListViews sidebar working together). After #2379 merges I'll
rebase this onto main.

What this prototype demos

  • Rename to "View". The model is ListView end-to-end; the
    user-facing copy is just "view" / "Views".
  • Six rule kinds in the schema: tag-includes, tag-excludes,
    untagged, updated-within-days, has-active-alerts, created-by-me.
    Stored as an additive Zod discriminated union.
  • Quick-filter pills on the listing for the three non-tag
    rule kinds. Each pill syncs to its own nuqs URL key so a
    filtered listing stays shareable.
  • Filters-first save flow. The primary "save a view" entry
    lives next to the chips and pills, not in the sidebar header.
    A modal converts the active filter state into a rules array
    and routes to ?view= on save.
  • Default system views. Four pinned non-editable rows above
    the user's saved views: My dashboards, Recently updated, With
    active alerts, Untagged. Counts update in lockstep with the
    grid.
  • Advanced editor drawer stays accessible via the kebab on
    the Views section header for hand-written rule lists.

Why a throwaway prototype branch

Tier-4 by the classifier (+800 / -250 LOC on top of the existing
1775 / 230). Acceptable because the goal here is to evaluate the
integrated UX in one Vercel preview, then disassemble. After
UX agreement lands:

  1. PR-2-final: schema + CRUD + sidebar + drawer + rename +
    filters-first save. Fresh branch from main.
  2. PR-3 production: any rule kinds + system views that didn't
    fold into PR-2-final.
  3. This branch closes without merging.

What's deferred

  • isShared / team-shared views (schema-only).
  • Most-viewed rule (no view-tracking yet).
  • has-tile-type rule (PR-3 production scope).
  • External API v2 + OpenAPI + MCP parity (PR-4).
  • Customer docs (PR-5).
  • Saved Searches sidebar parity (PR-6).

Test plan

  • yarn jest on @hyperdx/app (33 tests covering the
    evaluator, listing pills, save modal, default views).
  • yarn jest on @hyperdx/common-utils (1169 pass).
  • yarn tsc --noEmit clean on api + app.
  • yarn ci:lint clean on api + app.
  • api integration tests
    (packages/api/src/routers/api/__tests__/listViews.test.ts):
    will run in CI; covers POST/GET round-trips for tag and
    non-tag rules, 404s, validation rejection.
  • Local dev stack walkthrough on the cumulative preview
    (feat(dashboards): multi-select tag filter on Dashboards and Saved Searches #2379 + this PR): light + dark theme, seed dashboards
    with mixed tagging, alerts, and createdBy; exercise each
    pill independently and combined; save a view via the new
    modal and verify it lands in the sidebar with the right
    count and round-trips through edit + delete; click each
    system view and confirm the ?view=system:* URL shape.
  • Narrow viewport.

Alex Fedotyev and others added 5 commits May 30, 2026 00:12
…iscriminator)

Adds the `SmartView*` schemas to common-utils ahead of the model,
router, and UI work. The rule discriminated union is intentionally
narrow in v1 (`tag-includes`, `tag-excludes`, `untagged`) so the
storage + sidebar plumbing can ship without dragging in non-tag rule
machinery; a follow-up widens the union with recency / has-alerts /
created-by-me / provisioned / has-tile-type kinds and existing
documents keep parsing because the extension is additive.

The `resource` discriminator already includes `savedSearch` so the
Saved Searches sidebar parity work drops in without a schema change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mongoose model mirrors `favorite.ts` for the per-user index pattern
and `dashboard.ts` for the Mixed-typed JSON column that stores the
rules array. The Zod schema in `@hyperdx/common-utils/dist/types`
is the source of truth for rule shape; widening the rule union
later does not require a model migration.

Controller exposes `getSmartViews(userId, teamId, resource?)`,
`getSmartView`, `createSmartView`, `updateSmartView`,
`deleteSmartView`, all scoped by `{ owner, team }`. Cross-user and
cross-team access fall through to a 404 in the router.

Router mounted at `/smart-views` next to `favoritesRouter` and
`savedSearchRouter`. Body and query validation via
zod-express-middleware against the Zod schemas. Tests round-trip
POST -> GET, exercise the resource discriminator filter, and confirm
that another user on the same team cannot patch or delete the
view (both 404).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`packages/app/src/smartView.ts` exposes `useSmartViews(resource)`,
`useCreateSmartView`, `useUpdateSmartView`, `useDeleteSmartView`.
React Query keys are `['smart-views', resource]` so a mutation that
changes one resource's views does not invalidate the other's cache.

`packages/app/src/utils/evaluateSmartView.ts` is a pure function
that takes `{ rules, combinator }` and an item with a `tags` array
and returns a boolean. An empty rule list matches every item.
Combinator `all` requires every rule to pass; `any` short-circuits
on the first success. The switch over rule kinds is exhaustive for
the tag-only v1 set; the rule-widening follow-up extends the switch.

Unit tests cover every rule kind, empty rules, both combinators,
multi-tag items, untagged items, and an `any` combinator that
combines an `untagged` rule with a `tag-includes` rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`SmartViewsSidebar` renders a 240px-wide rail with the current
user's smart views for a given resource. The empty state nudges
toward the "+ New Smart View" affordance; the populated state shows
each view as an UnstyledButton with optional icon, name, and a
kebab Menu (Edit / Delete). The Delete flow uses the existing
`useConfirm` for parity with the dashboard / saved-search delete
flows on the same page.

`SmartViewEditorDrawer` is a Mantine Drawer that opens on the right
and accepts: name, optional icon (free text, intended for an emoji
or short symbol), top-level combinator (`all` | `any`), and a
dynamic rule list. Each rule row picks a kind from the discriminated
union and (for the tag-includes / tag-excludes kinds) a tag from
the available-tags pool. Saving calls `useCreateSmartView` for a
fresh view and `useUpdateSmartView` for an existing one. Draft
rules with an empty tag are dropped on save, so the editor stays
forgiving while the persisted shape stays clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…o the listing page

DashboardsListPage now pulls smart views for the dashboard resource
and renders the sidebar in a left rail next to the existing listing
column. A new nuqs `?view=<id>` state drives the active view;
clicking a sidebar entry sets it and clicking the active entry
again clears it (shareable URL).

The `filteredDashboards` memo factors the active view's rules
through `evaluateSmartView` AFTER the manual tag and search
filters, so manual chips and the view's rule list AND-combine. The
existing favorites + preset sections sit above the listing and are
unchanged.

`SmartViewEditorDrawer` renders at the page level via
`useDisclosure`; the sidebar's "+ New" and per-item Edit open it
with the right initial state. The drawer reuses the page's
`allTags` memo as the tag pool so a user only picks from tags
already in their dashboards.

Extends the existing RTL test with a fourth case: when the mocked
`useQueryState('view')` returns a known smart-view id, only items
that pass that view's rules show up in the grid.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 30, 2026

🦋 Changeset detected

Latest commit: a06531b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Minor
@hyperdx/api Minor
@hyperdx/otel-collector Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented May 30, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Jun 3, 2026 5:15am
hyperdx-storybook Ready Ready Preview, Comment Jun 3, 2026 5:15am

Request Review

…AL_MODE builds work

useSmartViews / useCreateSmartView / useUpdateSmartView /
useDeleteSmartView all unconditionally hit the API. On the Vercel
preview and any `IS_LOCAL_MODE` build there is no `/smart-views`
backend, so GETs 504 (sidebar shows "Loading..." for ~7s while
React Query retries before falling through to the empty state) and
POST/PATCH/DELETE 504 too (the editor drawer shows "Failed to
create smart view" but keeps the user staring at a useless modal).

Mirror the pattern used by `favorites.ts` and `dashboard.ts`:
each hook short-circuits to `createEntityStore<SmartView>(
'hdx-local-smart-views')` when `IS_LOCAL_MODE` is true. The
listing is filtered + sorted by `ordering` on the read path. The
React Query invalidation logic is unchanged so the sidebar
refreshes on create / update / delete.

Drive-by: switch the editor drawer's Cancel button from
`variant="default"` to `variant="secondary"` to satisfy
`agent_docs/code_style.md`'s Button variant rule (caught by
`no-restricted-syntax` after rebuild).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@alex-fedotyev
Copy link
Copy Markdown
Contributor Author

Pushed a fix in commit 60e8871f. Root cause was that the four smartView.ts hooks (useSmartViews, useCreateSmartView, useUpdateSmartView, useDeleteSmartView) called the API unconditionally. The Vercel preview runs in IS_LOCAL_MODE with no backend, so every GET 504'd and React Query retried for ~7 seconds before falling through to the empty state, and every POST/PATCH/DELETE 504'd with a vague "Failed to create smart view" notification while the drawer stayed open.

Fix mirrors the pattern in favorites.ts and dashboard.ts: each hook short-circuits to createEntityStore<SmartView>('hdx-local-smart-views') when IS_LOCAL_MODE is true. The listing path filters by resource and sorts by ordering on the client; React Query invalidation logic stays the same so the sidebar refreshes on every mutation.

Verified on the redeployed preview:

  • Empty sidebar renders instantly (no /api/smart-views requests at all).
  • Create: type a name, click Create, the view shows up in the sidebar with a kebab menu.
  • Apply: click the view, URL gains ?view=<id>; click again, URL clears.
  • Delete: kebab -> Delete -> confirm; the view disappears AND the active ?view clears if the deleted view was the active one.

Drive-by: switched the editor drawer's Cancel button from variant="default" to variant="secondary" to satisfy agent_docs/code_style.md's Button rule (caught by no-restricted-syntax after rebuild).

…ed view does not crash the listing

Before this commit, a SmartView document with `rules` undefined or
null (server response that dropped a field, localStorage entry
written by an earlier draft, a `Mixed` Mongoose column that
returned a stray shape) crashed the listing in three places:

- `evaluateSmartView(view, item)` called `view.rules.length` and
  `view.rules.every`, throwing "Cannot read properties of
  undefined / null".
- `SmartViewEditorDrawer` seeded its draft from
  `existingView.rules.length`, throwing on open of the Edit menu.
- The unrelated nuqs URL state would still leave `?view=<id>`
  pointing at a view whose rules now blow up the filter memo,
  taking the entire page down with the Next.js client-side
  exception boundary.

Coerce defensively at every entry point:

- `evaluateSmartView` accepts `rules?: SmartViewRule[] | null` and
  treats anything non-array as empty (-> match-all). Same for
  `combinator`, which defaults to `all` if missing. Entries inside
  the array that are not objects with a `kind` field are filtered
  out before evaluation.
- `SmartViewEditorDrawer` seeds its draft via the same
  `Array.isArray` check + null-entry filter; missing `combinator`
  also defaults to `all`.
- `smartView.ts` exposes a private `normalizeSmartView` that runs
  on both the local-mode and server-mode read paths so the data
  React Query hands back to consumers always has the canonical
  shape (id/name/resource/combinator/ordering present, rules
  guaranteed array, isShared optional).

Added a regression test on `evaluateSmartView` for the
empty-view (rules undefined) and the explicit-null-rules cases.

Root-cause notes: the user's repro stack pointed at
`Cannot read properties of null (reading 'value')` inside a
useState chain. The exact `value` access lives inside Mantine's
Drawer / Select internals when a rule prop becomes null mid-render;
the upstream cause is the same `rules` shape mismatch fixed here.
@alex-fedotyev
Copy link
Copy Markdown
Contributor Author

alex-fedotyev commented May 30, 2026

Pushed 4ff23496. The earlier IS_LOCAL_MODE fix only covered the network-hang case; a SmartView whose rules field is undefined, null, or any non-array still crashed the listing.

Repro (verified on the redeployed preview before the fix): pre-seed [{ id: 'bad-1', name: 'Bad shape', resource: 'dashboard', ordering: 0 }] (no rules, no combinator) in localStorage.hdx-local-smart-views, navigate to /dashboards/list, kebab -> Edit. Page goes down with TypeError: Cannot read properties of undefined (reading 'length') via Next.js's client-side exception boundary. The same shape produces the earlier Cannot read properties of null (reading 'value') report when the URL carries ?view=<id> and Mantine's Select renders against a null rule prop further down the tree.

Three call sites called view.rules.length / .every unconditionally: evaluateSmartView, the editor drawer's seed effect, and the listing's filter memo.

Fix is defensive parsing on every entry point:

  1. evaluateSmartView now accepts rules?: SmartViewRule[] | null and combinator?: SmartViewCombinator | null, coerces non-array rules to [] (matches all), and filters out non-object rule entries before evaluation. Combinator defaults to all.
  2. SmartViewEditorDrawer seeds the draft via Array.isArray(existingView.rules) + a null-entry filter; missing combinator defaults to all.
  3. smartView.ts runs both the local-mode and server-mode read paths through a normalizeSmartView helper so React Query never hands a malformed view to consumers (id / name / resource / combinator / ordering always present, rules guaranteed array, isShared kept optional).
  4. Added a regression test on evaluateSmartView for rules: undefined and rules: null.

Reproduced and verified on the Vercel preview after the fix:

  • Pre-seeded a SmartView in localStorage with no rules and no combinator, navigated to /dashboards/list, opened the kebab -> Edit: no crash, drawer opens with one default tag-includes rule and combinator all.
  • Created a new view, removed the only rule, clicked Create (rules: []): no crash, view appears in sidebar.
  • Activated the rules-empty view (URL gains ?view=<id>), opened its kebab -> Edit: no crash.

…e synthetic-event crash

Typing into the smart-view editor's Name or Icon field crashed the
listing page after a few keystrokes with `TypeError: Cannot read
properties of null (reading 'value')` inside a useState update.

Root cause: both TextInput onChange handlers closed over the
synthetic event INSIDE a `setDraft(d => ({...d, name:
e.currentTarget.value}))` updater. React 18 nulls out
`event.currentTarget` after the event handler returns (and React
Compiler / concurrent rendering routinely defers / re-runs the
updater function). When the updater finally executes the
event-detach has already happened and `e.currentTarget` is null;
reading `.value` on it throws and Next.js's client-side exception
boundary takes the whole page down.

Fix: capture `e.currentTarget.value` into a local const inside the
synchronous event handler, then pass the const to the updater.

Reproducer that crashed reliably on the Vercel preview before this
commit: open `/dashboards/list`, click "+ New Smart View", type
into the Name field via real keystrokes (not a single setValue);
the page shows the Next.js client-side exception screen after a
few characters. Same pattern crashed the Icon field.

Documented in a code comment so the next person who writes
`onChange={e => setX(prev => ({...prev, foo: e.currentTarget.value}))}`
in this file doesn't re-introduce the same bug.
@alex-fedotyev
Copy link
Copy Markdown
Contributor Author

Pushed 0c7b2e8f. The earlier two fixes covered the IS_LOCAL_MODE hang and the malformed-view crash, but a third bug remained on the typing path.

Reproducer: open /dashboards/list -> "+ New Smart View" -> type into the Name field via real keystrokes (the bug requires multiple sequential input events; a single programmatic setValue does not trigger it). After a few characters the page goes down with TypeError: Cannot read properties of null (reading 'value') matching the original report.

Root cause: both TextInput onChange handlers in SmartViewEditorDrawer closed over the synthetic event INSIDE the setDraft updater:

onChange={e => setDraft(d => ({ ...d, name: e.currentTarget.value }))}

React 18 nulls out event.currentTarget once the synthetic-event handler returns, and React Compiler / concurrent rendering routinely defers or re-runs the updater function. When the updater finally executes the event has been detached and e.currentTarget is null; reading .value on it throws and Next.js's client-side exception boundary takes the whole page down.

Fix: capture e.currentTarget.value into a local const synchronously inside the event handler, then pass the const into the updater. Same change on both TextInputs (Name and Icon). Inline comment in the file so the pattern does not get re-introduced.

Verified on the redeployed preview: typed Test view name with several characters into the Name field via per-character keystrokes, then typed an emoji into the Icon field, clicked Create. View appears in the sidebar as "🚀 Test view name with several characters". Zero console errors.

…, accent active state

Polish for scale to hundreds of dashboards.

- Default `All Dashboards` row at the top of the sidebar. Always
  active when no smart view is selected, single-click clears any
  active view, and shows the total dashboard count so the catalog
  scope is legible at a glance.
- Count badge per smart view (`<name>  <count>`) computed against
  the same `dashboards` reference that drives the grid, so the
  badge and the visible result set move together.
- Stronger active state: 3px inset accent bar on the left edge
  (matches the AppNav rail) plus a subtle background tint and the
  label switches to weight 600. Reads as `you are here` from
  several feet away rather than a fragile bold-only cue.
- Quieter empty state: drop the `No smart views yet. Pin a tag
  filter combination to jump back to it.` paragraph. Now the
  sidebar shows the `SMART VIEWS` section header with its inline
  `+` affordance and a single subtle row-shaped `+ New Smart
  View` button. Nothing nags the user when no rules are
  configured.
- Density bump: 6/10px padding per row, 220px rail width (down
  from 240). Tighter than the previous gap-heavy layout and
  closer to professional catalog rails (Linear, Notion, Datadog
  monitor lists).
- Layout alignment: the page now wraps the sidebar + main column
  in a single `Container maw={1440}` so the rail no longer floats
  far-left while the content sits in a separately-centered 1200px
  column. Removes the visual `gap of dead space` between the two.

Sidebar is now a pure presentation component: counts and total
come in as props from the listing page, so the same evaluator
output drives both the grid and the badges. Keeps the component
reusable for Saved Searches in the followup PR-6 without an
internal `dashboards` import.
@alex-fedotyev
Copy link
Copy Markdown
Contributor Author

Pushed 5428dd85: catalog-clean polish on the sidebar so it scales to hundreds of dashboards without feeling noisy.

What changed:

  • All Dashboards default entry at the top of the rail. Always there, single-click clears any active smart view, shows the total dashboard count next to the label. The catalog scope is legible at a glance and users no longer have to know that "clicking the active view again clears it" is the way to deselect.
  • Per-view count badges. Each smart view shows <name> <count> where the count is computed off the same dashboards reference that drives the grid, so the badge and the result set never drift. Tabular numerals so columns of counts align cleanly.
  • Stronger active state. 3px inset accent bar on the left edge (mirrors AppNav's active-link pattern) plus a subtle background tint and the label switches to weight 600. Reads as "you are here" from across the screen rather than fragile bold-only emphasis.
  • Quiet empty state. No more "No smart views yet. Pin a tag filter combination to jump back to it." paragraph. The SMART VIEWS section header with its inline + is the primary affordance; a single subtle row-shaped + New Smart View button below it is the fallback. Nothing nags when zero views are configured.
  • Density bump. 6/10px padding per row, 220px rail width (down from 240). Closer to the catalog rails users expect from Linear, Notion, Datadog.
  • Layout alignment. The whole page is now inside a single Container maw={1440}, so the sidebar and the dashboard grid share one centered max-width instead of the sidebar floating far-left while the content sat in a separately-centered 1200px column. Closes the dead-space gap that was visible in the previous screenshot.

Sidebar is now a pure presentation layer. Counts and total come in as props from the listing page (totalCount, viewCounts: Record<string, number>), so the component stays reusable for the Saved Searches sidebar parity work without an internal dashboards import.

Out-of-scope for this PR but worth flagging for the catalog-at-scale direction:

  • Default view mode should probably flip to list when item count crosses a threshold (~25); the current grid eats a lot of vertical space at 100+.
  • Preset Dashboards (Services / ClickHouse / Kubernetes) is a top-of-fold three-card row that doesn't scale; folding those into a sidebar System section alongside the pre-built smart views feels right when the system-views work lands.
  • Virtualization or pagination on the team-dashboard grid once the listing endpoint returns hundreds.

Verified on the redeployed preview with a 5-dashboard / 2-smart-view seed mirroring the screenshot you shared.

alex-fedotyev and others added 5 commits June 3, 2026 04:47
Mechanical rename. No behavior change. Same routes (renamed to
/list-views), same wire shape, same combinator + tag rule kinds.

* common-utils: SmartView* Zod symbols become ListView*. The
  intermediate SmartViewTagRuleSchema alias collapses into
  ListViewRuleSchema directly so PR-3's union widening edits one
  symbol instead of two.
* api: smartView.ts model and controllers rename to listView.ts;
  router file rename to listViews.ts and mount point change to
  /list-views. Mongoose model name becomes 'ListView' so the
  default collection name becomes 'listviews'.
* app: smartView.ts hook file, evaluateSmartView util, and
  SmartViewsSidebar component directory all rename. React Query
  keys and localStorage keys swap to 'list-views' / 'hdx-local-
  list-views'. User-visible copy ("Smart View", "smart view")
  becomes "View" / "view"; the sidebar header reads "Views" and
  the empty-state button reads "+ New View".

Existing tests round-trip unchanged after path / symbol updates.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ive-alerts / created-by-me

The discriminated union picks up three non-tag kinds:

* updated-within-days (numeric `days`, 1-365): matches items whose
  `updatedAt` is younger than the window. Editor renders a
  NumberInput with 1 / 7 / 30 preset pills.
* has-active-alerts: zero-config rule. The evaluator stays pure;
  the listing precomputes per-item alert state and passes it in
  via `context.itemHasActiveAlerts`.
* created-by-me: zero-config rule. Matches on the current user's
  `_id` or, if missing, on email. The listing pulls identity from
  `api.useMe()` and feeds it into the evaluator context.

The widening is additive; existing tag-only documents keep parsing
because every rule still carries a discriminant `kind`. The
Mongoose model stores `rules` as Mixed, so the schema flows
through with no migration.

The router round-trip test now covers POST -> GET for a mixed-kind
view in one toMatchObject assertion against a canonical config, and
rejects out-of-range `days`. Evaluator gains 9 new unit tests
across the three kinds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three quick-filter pills sit between the search / tag row and the
listing. Each independently AND-combines with the rest of the
filter chain (search, tag chips, active list view).

* Recently updated: pill opens a popover with 1 / 7 / 30 presets.
  Default to 7 days when first toggled on. URL: ?recentDays=<n>.
* With active alerts: zero-config toggle. URL: ?withAlerts=1.
* Created by me: zero-config toggle. URL: ?createdByMe=1.

The semantics route through evaluateListView so the pill behavior
matches what the save flow (next commit) will persist into a
ListView rule. Empty state copy widens to include any active pill
so a no-match state still reads as "no matches" rather than "no
dashboards yet". Two new unit tests cover the created-by-me and
recently-updated pills filtering the visible grid.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Active-filter row now ends with a "Save as view" button. Disabled
until the user has at least one chip, pill, or quick filter
active; tooltip explains why. Click opens a modal that converts
the current filter state into a ListView rule array:

* Each tag chip becomes a tag-includes rule.
* recentDays becomes updated-within-days.
* withAlerts becomes has-active-alerts.
* createdByMe becomes created-by-me.
* Search query is intentionally NOT persisted (transient).
* Combinator is `all` (chips + pills both narrow); the advanced
  drawer remains the entry point for `any`.

On save the modal clears the transient filter state and routes to
?view=<new-id>, so the user sees the saved view applied rather
than the now-duplicated raw filters.

The sidebar's inline "+" primary button is gone. A kebab on the
Views section header keeps the path to the advanced editor drawer
(hand-written rule lists) for power users.

5 new unit tests cover buildRulesFromFilters end-to-end across
every combination of inputs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A new "Suggested" section in the sidebar pins four non-editable
system views above the user's saved views:

* My dashboards (created-by-me)
* Recently updated (updated-within-days = 7)
* With active alerts (has-active-alerts)
* Untagged

System view ids carry a `system:` prefix so they never collide
with API-returned ids. The listing's lookup checks system ids
first, then falls through to the user-views response.

View counts on the sidebar now include the system rail in the
same pass so the suggested counts stay in sync with whatever the
grid is rendering.

Saved-search system views drop `has-active-alerts` until PR-6
lands the saved-search alert analogue; otherwise the set matches
the dashboard rail.

8 new unit tests on getDefaultListViews + isSystemViewId.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@alex-fedotyev alex-fedotyev changed the title feat(dashboards): smart views sidebar with tag-only rules (chained on #2379) feat(dashboards): views prototype (demo-only, chained on #2379) Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant