RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Diff/grafana-grafana-e2e-playwright-dashboard-new-layouts-agents ↔ grafana-grafana-agents

Comparison

A · AGENTS.md · grafana/grafanaB · AGENTS.md · grafana/grafana
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections012250%
Commands01380%
Section tags32725%

What each file covers

Sections

0 shared · 12 only in A · 25 only in B
  • − dashboard-new-layouts E2E Tests — Agent Guide
  • − Purpose
  • − Page Objects Reference
  • − Base class & constructor
  • − How to Write a New Test
  • − Conventions
  • − Page objects
  • − Selector scoping
  • − Specs
  • − Adding a method to a page object
  • − Canonical Example
  • − Migration Status
  • + AGENTS.md
  • + Project Overview
  • + Principles
  • + Comments
  • + Human Review Gates
  • + Commands
  • + Build & Run
  • + Test
  • + Backend
  • + Frontend
  • + E2E
  • + Lint & Format
  • + Code Generation
  • + Dev Environment
  • + Architecture
  • + Backend (`pkg/`)
  • + Frontend (`public/app/`)
  • + Shared Packages (`packages/`)
  • + Backend Apps (`apps/`)
  • + Plugin Workspaces
  • + Key Notes
  • + Cursor Cloud specific instructions
  • + Prerequisites
  • + Running services
  • + Testing gotchas

Commands

0 shared · 1 only in A · 38 only in B
  • − yarn e2e:pw --project dashboard-new-layouts --reporter list --repeat-each=3 -- <spec-filename>
  • + make run
  • + make build-backend
  • + yarn start
  • + yarn build
  • + go test -run TestName ./pkg/services/myservice/
  • + make test-go-unit
  • + make test-go-integration
  • + yarn test path/to/file
  • + yarn test -t "pattern"
  • + yarn test -u
  • + yarn e2e:playwright path/to/test.spec.ts
  • + make lint-go
  • + yarn lint
  • + yarn lint:fix
  • + yarn prettier:write
  • + yarn typecheck
  • + make gen-go
  • + make gen-cue
  • + make gen-apps
  • + make swagger-gen
  • + make gen-feature-toggles
  • + make i18n-extract
  • + make update-workspace
  • + yarn install --immutable
  • + make devenv sources=influxdb
  • + make devenv-down
  • + make lefthook-install
  • + git push
  • + yarn workspace @grafana-plugins/<name> dev
  • + go.work
  • + make devenv sources=postgres_tests,mysql_tests
  • + make test-go-integration-postgres
  • + node
  • + yarn
  • + jest
  • + go.mod
  • + yarn test
  • + yarn jest --no-watch

Section tags

3 shared · 2 only in A · 7 only in B
  • − code-style
  • − database
  • + setup
  • + build
  • + lint-format
  • + architecture
  • + git-pr
  • + dependencies
  • + docs
  •   test
  •   testing-strategy
  •   agent-behaviour

Line diff

+123 added−141 removed47 unchanged25.0% identical
grafana/grafana · e2e-playwright/dashboard-new-layouts/AGENTS.md
@@ −1 @@
1# dashboard-new-layouts E2E Tests — Agent Guide
2 
3## Purpose
4 
5This suite contains Playwright E2E tests for the V2 dashboard layout system. Tests use **page objects** to wrap raw selector chains behind user-intent methods. The full rationale is in [`_page_objects_strategy.md`](./_page_objects_strategy.md).
6 
7## Page Objects Reference
8 
9All page objects live in `page-objects/`. Only the top-level ones (`Controls`, `Sidebar`, `Panels`, `Rows`, `Tabs`, `Canvas`) are re-exported from `page-objects/index.ts` — import those in specs. Sidebar panes (`Toolbar`, `AddOptions`, `DashboardOptions`, `PanelOptions`, `TabOptions`, `VariableOptions`, `ContentOutline`) and shared sub-options (`ConditionalRenderingOptions`, `RepeatOptions` under `sidebar/shared/`) are not exported; reach them via `sidebar.*` (e.g. `sidebar.toolbar`, `sidebar.panelOptions.repeatOptions`). Every page object extends the abstract `PageObject` base class (`PageObject.ts`), which holds the shared `page`, `dashboardPage`, `selectors`, and `components` dependencies as `protected` fields.
 
 
 
10 
11| Class | File | UI Region | Key Methods / Getters |
12| ----------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13| `PageObject` | `PageObject.ts` | _(abstract base — not used directly)_ | Shared constructor taking a `PageObjectArgs` object (`page`, `dashboardPage`, `selectors`, `components`) |
14| `Controls` | `Controls.ts` | Top nav bar (edit, save, timepicker, share, ...) and variable submenu | `getContainer()`, `enterEditMode()`, `exitEditMode()`, `saveDashboard(title?)`, `clickBackToDashboard()`, `openControlsMenu()`, `openShareSnapshotDrawer()`; `timeRange` sub-object: `set(from, to)`, `selectPreset(presetLabel)`; `variables` sub-object: `getLabel(variableLabel)`, `getInput(variableLabel)`, `setValue(variableLabel, text)`, `getDropdownTrigger(variableLabel)`, `openDropdown(variableLabel)`, `getOption(optionLabel)`, `selectOption(variableLabel, optionLabel)`, `deselectOption(variableLabel, optionLabel)`, `addFilter(variableLabel, [label, operator, value])` |
15| `Sidebar` | `sidebar/Sidebar.ts` | Whole sidebar region (toolbar + open pane) | `.toolbar`, `.addOptions`, `.dashboardOptions`, `.panelOptions`, `.tabOptions`, `.variableOptions`, `.contentOutline` sub-objects; `getContainer()`, `clickGoBackButton()`, `getDockToggle()`, `clickCloseButton()`, `clickDeleteButton({ confirm? })` |
16| `Toolbar` | `sidebar/Toolbar.ts` | Icon strip — accessed via `sidebar.toolbar` | `getButton(name)`, `clickButton(name)`, `getVisibilityToggle()` |
17| `AddOptions` | `sidebar/AddOptions.ts` | "Add" pane (default pane on new dashboards) — via `sidebar.addOptions` | `clickNewPanelButton()`, `clickAddTabButton()`, `clickNewVariableButton()` |
18| `ContentOutline` | `sidebar/ContentOutline.ts` | Content outline pane — via `sidebar.contentOutline` | `getTree()`, `clickItem(name)`, `toggleNode(name)` |
19| `DashboardOptions` | `sidebar/DashboardOptions.ts` | Dashboard options pane — via `sidebar.dashboardOptions` | `getTitleInput()`, `getDescriptionTextarea()`, `switchLayout(layoutType, { confirm? })` |
20| `PanelOptions` | `sidebar/PanelOptions.ts` | Panel options pane — via `sidebar.panelOptions` | `.conditionalRenderingOptions`, `.repeatOptions`; `getTitleInput()`, `setTitle(title)`, `getDescriptionTextarea()`, `toggleTransparentBackground()` |
21| `TabOptions` | `sidebar/TabOptions.ts` | Tab options pane — via `sidebar.tabOptions` | `.conditionalRenderingOptions`, `.repeatOptions` |
22| `ConditionalRenderingOptions` | `sidebar/shared/ConditionalRenderingOptions.ts` | Shared conditional rendering rules — via `*.conditionalRenderingOptions` | `selectVisibility('show' \| 'hide')`, `selectMatch('all' \| 'any')`, `addVariableRule(name, operator, value)`, `addTimeRangeRule(lessThan)` |
23| `RepeatOptions` | `sidebar/shared/RepeatOptions.ts` | Shared repeat options — via `*.repeatOptions` | `repeatByVariable(variableName)`, `disableRepeatByVariable()` |
24| `VariableOptions` | `sidebar/VariableOptions.ts` | Variable sidebar — via `sidebar.variableOptions` | `selectVariableType(type)`, `setName(name)`, `setLabel(label)`, `selectDisplay(displayLabel)`; type-specific sub-objects: `datasource.selectType(dsType)`, `datasource.setNameFilter(filter)`, `custom.openEditor()`, `custom.selectFormat(format)`, `custom.setValues(values)`, `custom.getPreviewOfValues()`, `custom.getPreviewTable()`, `custom.clickApplyButton()`, `groupby.selectDatasource(ds)`, `adhoc.selectDatasource(ds)`, `query.openEditor()`, `query.selectTargetDatasource(ds)`, `query.setTestDataQuery(query)`, `query.runQuery()`, `query.getPreviewOfValues()`, `query.clickApplyButton()`, `constant.setValue(value)`, `textbox.setValue(value)`, `interval.toggleAuto()` |
25| `Panels` | `Panels.ts` | The dashboard panels in the edit canvas | `getPanels(title, scope?)`, `getPanel(title, scope?)`, `getHeaders(title?, scope?)` — string titles match exactly, RegExp filters by text, no argument returns all headers; `getHeader(title, scope?)`, `getBodies()`, `selectByTitle(title \| RegExp \| Array<title \| RegExp>)`, `selectByIndex(index)`, `selectMenuItem(panelTitle, menuPath[])` |
26| `Rows` | `Rows.ts` | A row of a rows layout in the dashboard canvas | `getTitle(rowTitle)`, `getContent(rowTitle)` — content wrapper (grid or nested tabs) right after the row header |
27| `Tabs` | `Tabs.ts` | Tab bar of a tabs layout (top-level or nested in a row) | `getTitle(tabTitle, scope?)` — pass `rows.getContent(rowTitle)` as `scope` to look up a tab inside a specific row; `getContent(tabTitle)` — the layout container holding the tab's content; `select(tabTitle)` — click the tab title |
28| `Canvas` | `Canvas.ts` | Edit canvas add-actions strip (per grid, revealed on hover) | `getContainer()`, `addPanel(panelsContainer?)`, `addTab()`, `addRow()`, `groupPanels('row' \| 'tab', panelsContainer?)` — pass `panelsContainer` (e.g. `tabs.getContent(...)`, `rows.getContent(...)`) to target the add-actions strip of a nested grid |
29 
30> The show/hide visibility toggle is a **Toolbar** control (`sidebar.toolbar.getVisibilityToggle()`), even though its selector lives under `components.Sidebar.*`. `Toolbar.getButton(name)` resolves buttons by accessible name, scoped to the sidebar container.
31 
32> This table grows as specs are migrated — only methods needed by migrated specs exist.
33 
34### Base class & constructor
 
 
 
 
35 
36All page objects inherit from `PageObject`, which provides the shared constructor. It takes a single `PageObjectArgs` object:
37 
38```typescript
39// page-objects/PageObject.ts
40export interface PageObjectArgs {
41 page: Page;
42 dashboardPage: DashboardPage;
43 selectors: E2ESelectorGroups;
44 components: Components;
45}
46 
47export abstract class PageObject {
48 constructor({ page, dashboardPage, selectors, components }: PageObjectArgs) {
49 // assigned to protected fields
50 }
51}
52```
53 
54Simple page objects (e.g. `Controls`, `Toolbar`) inherit the constructor directly — no override needed. Page objects that compose sub-objects (e.g. `Sidebar`) declare `constructor(args: PageObjectArgs)`, call `super(args)`, and pass the same `args` to their children.
55 
56All four dependencies come from the Playwright test arguments:
57 
58```typescript
59test('example', async ({ gotoDashboardPage, selectors, page, components }) => {
60 const dashboardPage = await gotoDashboardPage({ uid: 'some-uid' });
61 const controls = new Controls({ page, dashboardPage, selectors, components });
62 // ...
63});
 
64```
65 
66## How to Write a New Test
67 
681. **Identify which page objects you need.** Check the table above. If the interaction you need isn't covered, add the method to the appropriate page object — only what the new test requires.
 
 
 
 
69 
702. **Scaffold the spec** following this structure:
 
 
 
71 
72```typescript
73import { test, expect } from '@grafana/plugin-e2e';
 
74 
75import { Controls, Sidebar } from './page-objects';
76 
77test.describe(
78 'Feature name',
79 {
80 tag: ['@dashboards'],
81 },
82 () => {
83 test('describes the user-visible behavior', async ({ gotoDashboardPage, selectors, page, components }) => {
84 const dashboardPage = await gotoDashboardPage({ uid: 'dashboard-uid' });
85 
86 const controls = new Controls({ page, dashboardPage, selectors, components });
87 const sidebar = new Sidebar({ page, dashboardPage, selectors, components });
88 
89 await controls.enterEditMode();
90 // ... test body using page objects (the toolbar is reached via sidebar.toolbar)
91 });
92 }
93);
 
 
 
94```
95 
961. **Verify locally:**
97 
98```bash
99yarn e2e:pw --project dashboard-new-layouts --reporter list --repeat-each=3 -- <spec-filename>
 
 
 
100```
101 
102## Conventions
103 
104### Page objects
105 
106- **Locator getters** (e.g. `getTitleInput()`) return a Playwright `Locator` — for elements that specs assert on (or both act on and assert on). The test owns the assertion — never the page object.
107- **Action methods** (e.g. `enterEditMode()`, `clickCloseButton()`) wrap interactions — multi-step flows or single clicks on act-only elements — and use `test.step()` so the HTML report shows named steps.
108- **When a spec needs both**, pair them: the action method delegates to the getter (see `Toolbar.getButton()` / `clickButton()`).
109- **No speculative methods.** Only add methods needed by the spec being migrated.
110- **Plural vs singular getters** (e.g. `getPanels()` / `getPanel()`): plural getters return every match — assert counts or narrow (`.first()`, `.nth()`) in the spec; singular getters return the first match.
111- **Scoped lookups**: getters with a `scope?: Locator` parameter search inside that container — pass `rows.getContent(...)` or `tabs.getContent(...)` to look up elements in a specific row or tab.
112- **No waits or retries inside page objects** unless the pre-refactor code had them. Keep `toPass()` retries, drag-and-drop, scroll logic, and `boundingBox()` in the spec or in `utils.ts`.
 
 
 
113 
114### Selector scoping
115 
116- **Scope lookups to the owning container.** A bare `page.getByRole(...)` searches the whole page and can match an unrelated element with the same role and name — if not today, then after an unrelated UI change. Elements that belong to a region with a page object must be looked up through that region's container: e.g. radio buttons in the sidebar are `sidebar.getContainer().getByRole('radio', { name: '...' })`, the same way `Toolbar.getButton()` scopes button names to `Sidebar.container`. Inside a page object, chain from the container selector (`this.dashboardPage.getByGrafanaSelector(this.selectors.components.Sidebar.container).getByRole(...)`).
117- **Portalled UI is the only exception.** Select/Combobox option lists, modals, tooltips, and toasts render in a portal at the document root, outside their logical parent, so they cannot be scoped to it. Anchor them to the portal's own root instead: `page.getByRole('listbox').getByRole('option', { name })` (see `RepeatOptions`), or `page.getByRole('dialog', { name: 'Delete panel?' })`. A bare `page.getByRole('option', ...)` with no anchor is still too broad.
118 
119### Specs
 
 
 
 
 
 
120 
121- **One raw `getByGrafanaSelector` is allowed** for one-off assertions that aren't reusable interactions (e.g. a breadcrumb check).
122- **Timing-sensitive mechanics stay inline** — `toPass()`, `mouse` sequences, `page.evaluate()`.
123- **Test setup stays in the spec** — API calls, dashboard provisioning, navigation via `gotoDashboardPage()`.
124- **Each spec is fully migrated or untouched.** No file should mix page-object calls and raw selectors for the same UI region.
125 
126### Adding a method to a page object
127 
1281. Find the raw selector chain in the spec you're migrating.
1292. Copy it into the appropriate page object class — mechanical extraction, no rewrites. New page objects must extend `PageObject` from `PageObject.ts`.
1303. For interactions (multi-step flows or single clicks on act-only elements), wrap in `test.step('Human-readable name', async () => { ... })`.
1314. For elements the spec asserts on, return a `Locator` (getter pattern, no `test.step` needed).
1325. Run `--repeat-each=3` on the migrated spec.
133 
134## Canonical Example
135 
136`dashboards-title-description.spec.ts` — the seed spec demonstrating the full pattern:
137 
138```typescript
139await controls.enterEditMode();
140await sidebar.toolbar.clickButton('Options');
141 
142const titleInput = sidebar.dashboardOptions.getTitleInput();
143await expect(titleInput).toHaveValue('Annotation filtering');
144 
145const newTitle = 'New dashboard title';
146await titleInput.fill(newTitle);
147await expect(titleInput).toHaveValue(newTitle);
148```
149 
150## Migration Status
151 
152**26 of 30 specs migrated.** Non-migrated specs are listed first by descending selectors usage count (a rough proxy for migration effort). "Selectors usage count" is the number of times the spec accesses the `selectors` object (`selectors.components...`, `selectors.pages...`, etc.).
 
 
 
 
 
 
 
 
153 
154| Spec | Status | Lines of code | Selectors usage count |
155| ----------------------------------------------------- | ----------- | ------------- | --------------------- |
156| `dashboard-group-panels.spec.ts` | Not started | 918 | 224 |
157| `dashboards-repeats-tabs-layout.spec.ts` | Not started | 482 | 73 |
158| `dashboards-panel-layouts.spec.ts` | Not started | 425 | 70 |
159| `dashboard-repeats-row-layout.spec.ts` | Not started | 551 | 61 |
160| `dashboards-repeats-custom-grid.spec.ts` | Migrated | — | — |
161| `dashboards-repeats-auto-grid.spec.ts` | Migrated | — | — |
162| `dashboards-title-description.spec.ts` | Migrated | — | — |
163| `dashboards-edit-panel-title-description.spec.ts` | Migrated | — | — |
164| `dashboards-edit-panel-transparent-bg.spec.ts` | Migrated | — | — |
165| `dashboard-mobile-sidebar.spec.ts` | Migrated | — | — |
166| `dashboard-hide-sidebar.spec.ts` | Migrated | — | — |
167| `dashboards-remove-panel.spec.ts` | Migrated | — | — |
168| `dashboard-duplicate-panel.spec.ts` | Migrated | — | — |
169| `dashboard-sidepane.spec.ts` | Migrated | — | — |
170| `dashboard-outline.spec.ts` | Migrated | — | — |
171| `dashboards-conditional-rendering.spec.ts` | Migrated | — | — |
172| `dashboards-add-panel.spec.ts` | Migrated | — | — |
173| `dashboards-edit-variables.spec.ts` | Migrated | — | — |
174| `dashboard-tabs-scroll.spec.ts` | Migrated | — | — |
175| `dashboards-repeats-snapshots.spec.ts` | Migrated | — | — |
176| `dashboards-move-panel.spec.ts` | Migrated | — | — |
177| `dashboard-conditional-rendering-load-change.spec.ts` | Migrated | — | — |
178| `dashboards-edit-custom-variables.spec.ts` | Migrated | — | — |
179| `dashboards-edit-query-variables.spec.ts` | Migrated | — | — |
180| `dashboard-keybindings.spec.ts` | Migrated | — | — |
181| `dashboards-edit-adhoc-variables.spec.ts` | Migrated | — | — |
182| `dashboards-edit-group-by-variables.spec.ts` | Migrated | — | — |
183| `dashboards-edit-datasource-variables.spec.ts` | Migrated | — | — |
184| `dashboard-url-syncing.spec.ts` | Migrated | — | — |
185| `dashboard-tabs-drag-drop.spec.ts` | Migrated | — | — |
186 
187See [`_page_objects_strategy.md`](./_page_objects_strategy.md) for the full migration plan.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188 
grafana/grafana · AGENTS.md
@@ +1 @@
1# AGENTS.md
2 
3<!-- version: 2.0.0 -->
4 
5This file provides guidance to AI agents when working with code in the Grafana repository.
6 
7**Directory-scoped agent files exist for specialized areas — read them when working in those directories:**
8 
9- `docs/AGENTS.md` — Documentation style guide (for work under `docs/`)
10- `public/app/features/alerting/unified/AGENTS.md` — Alerting squad patterns
11- `pkg/storage/unified/AGENTS.md` — Unified storage/search compatibility rules (for work under `pkg/storage/unified/`)
12- `public/app/core/journeys/AGENTS.md` — Critical User Journey instrumentation
13 
14## Project Overview
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15 
16Grafana is a monitoring and observability platform. Go backend, TypeScript/React frontend, monorepo with Yarn workspaces (frontend) and Go workspaces (backend).
17 
18## Principles
19 
20- Follow existing patterns in the surrounding code
21- Write tests for new functionality
22- Keep changes focused — avoid over-engineering
23- Separate PRs for frontend and backend changes (deployed at different cadences)
24- Security: prevent XSS, SQL injection, command injection
25 
26## Comments
27 
28- Only add a comment when it explains **why** something is done or reveals non-obvious logic that a reader must know to safely change the code. If the code is self-explanatory, no comment is needed.
29- Never include links (Slack, GitHub, Jira, etc.) in code comments.
 
 
 
 
 
 
30 
31## Human Review Gates
 
 
 
 
 
32 
33Before running `git push`, stop and get explicit human approval. When changes are ready, show a summary of changes and wait for instruction. "Open a PR" in a task description is intent, not permission to push without review.
34 
35## Commands
36 
37### Build & Run
38 
39```bash
40make run # Backend with hot reload (localhost:3000, admin/admin)
41make build-backend # Backend only
42yarn start # Frontend dev server (watches for changes)
43yarn build # Frontend production build
44```
45 
46### Test
47 
48```bash
49# Backend
50go test -run TestName ./pkg/services/myservice/ # Specific test
51make test-go-unit # All unit tests
52make test-go-integration # Integration tests
53 
54# Frontend
55yarn test path/to/file # Specific file
56yarn test -t "pattern" # By name pattern
57yarn test -u # Update snapshots
58 
59# E2E
60yarn e2e:playwright path/to/test.spec.ts # Specific test
61```
62 
63### Lint & Format
64 
65```bash
66make lint-go # Go linter
67yarn lint # ESLint
68yarn lint:fix # ESLint auto-fix
69yarn prettier:write # Prettier auto-format
70yarn typecheck # TypeScript check
71```
 
72 
73### Code Generation
 
74 
75```bash
76make gen-go # Wire DI (after changing service init)
77make gen-cue # CUE schemas (after changing kinds/)
78make gen-apps # App SDK apps
79make swagger-gen # OpenAPI/Swagger specs
80make gen-feature-toggles # Feature flags (pkg/services/featuremgmt/)
81make i18n-extract # i18n strings
82make update-workspace # Go workspace (after adding modules)
83```
84 
85### Dev Environment
86 
87```bash
88yarn install --immutable # Install frontend deps
89make devenv sources=influxdb # Start backing services
90make devenv-down # Stop backing services
91make lefthook-install # Pre-commit hooks
92```
93 
94## Architecture
95 
96### Backend (`pkg/`)
97 
98| Directory | Purpose |
99| ----------------- | ----------------------------------------------------------- |
100| `pkg/api/` | HTTP API handlers and routes |
101| `pkg/services/` | Business logic by domain (alerting, dashboards, auth, etc.) |
102| `pkg/server/` | Server init and Wire DI setup (`wire.go`) |
103| `pkg/tsdb/` | Time series database query backends |
104| `pkg/plugins/` | Plugin system and loader |
105| `pkg/infra/` | Logging, metrics, database access |
106| `pkg/middleware/` | HTTP middleware |
107| `pkg/setting/` | Configuration management |
108 
109**Patterns**: Wire DI (regenerate with `make gen-go`), services implement interfaces in same package, business logic in `pkg/services/<domain>/` not in API handlers, database via `sqlstore`, plugin communication via gRPC/protobuf.
110 
111### Frontend (`public/app/`)
 
112 
113| Directory | Purpose |
114| ---------------------- | ----------------------------------------------------- |
115| `public/app/core/` | Shared services, components, utilities |
116| `public/app/features/` | Feature code by domain (dashboard, alerting, explore) |
117| `public/app/plugins/` | Built-in plugins (many are Yarn workspaces) |
118| `public/app/types/` | TypeScript type definitions |
119| `public/app/store/` | Redux store configuration |
120 
121**Patterns**: Redux Toolkit with slices (not old Redux), function components with hooks, Emotion CSS-in-JS via `useStyles2`, RTK Query for data fetching, React Testing Library for tests.
 
 
 
122 
123### Shared Packages (`packages/`)
124 
125`@grafana/data` (data structures), `@grafana/ui` (components), `@grafana/runtime` (runtime services), `@grafana/schema` (CUE-generated types), `@grafana/scenes` (dashboard framework).
 
 
 
 
126 
127### Backend Apps (`apps/`)
128 
129Standalone Go apps using Grafana App SDK: `apps/dashboard/`, `apps/folder/`, `apps/alerting/`.
130 
131### Plugin Workspaces
 
 
132 
133These built-in plugins require separate build steps: `azuremonitor`, `loki`, `grafana-testdata-datasource`.
 
134 
135Build a specific plugin: `yarn workspace @grafana-plugins/<name> dev`
 
 
 
136 
137## Key Notes
138 
139- **Wire DI**: Backend service init changes require `make gen-go`. Wire catches circular deps at compile time.
140- **CUE schemas**: Dashboard/panel schemas in `kinds/` generate both Go and TS code via `make gen-cue`.
141- **Feature toggles**: Defined in `pkg/services/featuremgmt/`, auto-generate code. Run `make gen-feature-toggles` after changes.
142- **Go workspace**: Defined in `go.work`. Run `make update-workspace` when adding Go modules.
143- **Build tags**: `oss` (default), `enterprise`, `pro`.
144- **Config**: Defaults in `conf/defaults.ini`, overrides in `conf/custom.ini`.
145- **Database migrations**: Live in `pkg/services/sqlstore/migrations/`. Test with `make devenv sources=postgres_tests,mysql_tests` then `make test-go-integration-postgres`.
146- **CI sharding**: Backend tests use `SHARD`/`SHARDS` env vars for parallelization.
147- **Service compatibility**: Unified storage/search (`pkg/storage/unified/`) can be deployed as separate services at a different cadence than the Grafana API layer. Changes spanning API-layer callers and `pkg/storage/unified/` must be backwards compatible in both directions — see `pkg/storage/unified/AGENTS.md`.
148 
149## Cursor Cloud specific instructions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150 
151### Prerequisites
152 
153- **Node.js** — version pinned in `.nvmrc` (check that file for the exact version). Installed via nvm and set as the nvm default. **PATH gotcha:** the infra injects `/exec-daemon/node` ahead of nvm, so the plain non-login shell may resolve `node` to an older version — check it satisfies the `engines` range in `package.json` (it does today, so builds/tests work), but it is not the pinned version. Login shells (tmux sessions, `bash -lc '...'`) get the pinned version because `~/.bashrc` prepends the nvm bin. Run `yarn` / `yarn start` / `jest` / webpack via a login shell (tmux or `bash -lc`) to use the pinned Node.
154- **Go** — version pinned in `go.mod` (check that file for the exact version), installed at `/usr/local/go` and symlinked to `/usr/local/bin/go`. The distro `/usr/bin/go` is older; `/usr/local/bin` wins in PATH so `go` resolves correctly. If `go.mod` bumps Go, reinstall a matching toolchain into `/usr/local/go`.
155- **Yarn** via corepack — version pinned by `package.json` `packageManager` (check that field for the exact version). Run `corepack enable` if `yarn` is not found. `.yarnrc.yml` sets `enableScripts: false`, so dependency build/lifecycle scripts are disabled by default.
156- **GCC** required for CGo/SQLite compilation of the backend.
157- Repos in this environment live under `/agent/repos/<repo>` (e.g. `/agent/repos/grafana`); this is a multi-repo workspace, not the single `~/grafana` layout described in `grafana-enterprise/AGENTS.md`.
158 
159### Running services
160 
161- **Backend**: `make run` — builds and starts Grafana backend with hot-reload (air) on `localhost:3000`. Default login: `admin`/`admin`. First build takes ~3 minutes due to debug symbols (`-gcflags all=-N -l`); subsequent hot-reload rebuilds are faster.
162- **Frontend**: `yarn start` — starts webpack dev server that watches for changes. The backend proxies to it. First compile takes ~45s.
163- No external databases required — Grafana uses embedded SQLite by default.
164 
165### Testing gotchas
166 
167- **Frontend tests**: The `yarn test` script includes `--watch` by default. Always use `yarn jest --no-watch` or add `--watchAll=false` to run tests once and exit.
168- **Backend tests**: Some packages (e.g. `pkg/api/`) have slow test compilation (~2 min) due to large dependency graphs. Use targeted test runs with `-run TestName` where possible.
169- All standard build/test/lint commands are documented in the Commands section above.
170 
@@ −1 +1 @@
1−# dashboard-new-layouts E2E Tests — Agent Guide
1+# AGENTS.md
22  
3−## Purpose
3+<!-- version: 2.0.0 -->
44  
5−This suite contains Playwright E2E tests for the V2 dashboard layout system. Tests use **page objects** to wrap raw selector chains behind user-intent methods. The full rationale is in [`_page_objects_strategy.md`](./_page_objects_strategy.md).
5+This file provides guidance to AI agents when working with code in the Grafana repository.
66  
7−## Page Objects Reference
7+**Directory-scoped agent files exist for specialized areas — read them when working in those directories:**
88  
9−All page objects live in `page-objects/`. Only the top-level ones (`Controls`, `Sidebar`, `Panels`, `Rows`, `Tabs`, `Canvas`) are re-exported from `page-objects/index.ts` — import those in specs. Sidebar panes (`Toolbar`, `AddOptions`, `DashboardOptions`, `PanelOptions`, `TabOptions`, `VariableOptions`, `ContentOutline`) and shared sub-options (`ConditionalRenderingOptions`, `RepeatOptions` under `sidebar/shared/`) are not exported; reach them via `sidebar.*` (e.g. `sidebar.toolbar`, `sidebar.panelOptions.repeatOptions`). Every page object extends the abstract `PageObject` base class (`PageObject.ts`), which holds the shared `page`, `dashboardPage`, `selectors`, and `components` dependencies as `protected` fields.
9+- `docs/AGENTS.md` — Documentation style guide (for work under `docs/`)
10+- `public/app/features/alerting/unified/AGENTS.md` — Alerting squad patterns
11+- `pkg/storage/unified/AGENTS.md` — Unified storage/search compatibility rules (for work under `pkg/storage/unified/`)
12+- `public/app/core/journeys/AGENTS.md` — Critical User Journey instrumentation
1013  
11−| Class | File | UI Region | Key Methods / Getters |
12−| ----------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
13−| `PageObject` | `PageObject.ts` | _(abstract base — not used directly)_ | Shared constructor taking a `PageObjectArgs` object (`page`, `dashboardPage`, `selectors`, `components`) |
14−| `Controls` | `Controls.ts` | Top nav bar (edit, save, timepicker, share, ...) and variable submenu | `getContainer()`, `enterEditMode()`, `exitEditMode()`, `saveDashboard(title?)`, `clickBackToDashboard()`, `openControlsMenu()`, `openShareSnapshotDrawer()`; `timeRange` sub-object: `set(from, to)`, `selectPreset(presetLabel)`; `variables` sub-object: `getLabel(variableLabel)`, `getInput(variableLabel)`, `setValue(variableLabel, text)`, `getDropdownTrigger(variableLabel)`, `openDropdown(variableLabel)`, `getOption(optionLabel)`, `selectOption(variableLabel, optionLabel)`, `deselectOption(variableLabel, optionLabel)`, `addFilter(variableLabel, [label, operator, value])` |
15−| `Sidebar` | `sidebar/Sidebar.ts` | Whole sidebar region (toolbar + open pane) | `.toolbar`, `.addOptions`, `.dashboardOptions`, `.panelOptions`, `.tabOptions`, `.variableOptions`, `.contentOutline` sub-objects; `getContainer()`, `clickGoBackButton()`, `getDockToggle()`, `clickCloseButton()`, `clickDeleteButton({ confirm? })` |
16−| `Toolbar` | `sidebar/Toolbar.ts` | Icon strip — accessed via `sidebar.toolbar` | `getButton(name)`, `clickButton(name)`, `getVisibilityToggle()` |
17−| `AddOptions` | `sidebar/AddOptions.ts` | "Add" pane (default pane on new dashboards) — via `sidebar.addOptions` | `clickNewPanelButton()`, `clickAddTabButton()`, `clickNewVariableButton()` |
18−| `ContentOutline` | `sidebar/ContentOutline.ts` | Content outline pane — via `sidebar.contentOutline` | `getTree()`, `clickItem(name)`, `toggleNode(name)` |
19−| `DashboardOptions` | `sidebar/DashboardOptions.ts` | Dashboard options pane — via `sidebar.dashboardOptions` | `getTitleInput()`, `getDescriptionTextarea()`, `switchLayout(layoutType, { confirm? })` |
20−| `PanelOptions` | `sidebar/PanelOptions.ts` | Panel options pane — via `sidebar.panelOptions` | `.conditionalRenderingOptions`, `.repeatOptions`; `getTitleInput()`, `setTitle(title)`, `getDescriptionTextarea()`, `toggleTransparentBackground()` |
21−| `TabOptions` | `sidebar/TabOptions.ts` | Tab options pane — via `sidebar.tabOptions` | `.conditionalRenderingOptions`, `.repeatOptions` |
22−| `ConditionalRenderingOptions` | `sidebar/shared/ConditionalRenderingOptions.ts` | Shared conditional rendering rules — via `*.conditionalRenderingOptions` | `selectVisibility('show' \| 'hide')`, `selectMatch('all' \| 'any')`, `addVariableRule(name, operator, value)`, `addTimeRangeRule(lessThan)` |
23−| `RepeatOptions` | `sidebar/shared/RepeatOptions.ts` | Shared repeat options — via `*.repeatOptions` | `repeatByVariable(variableName)`, `disableRepeatByVariable()` |
24−| `VariableOptions` | `sidebar/VariableOptions.ts` | Variable sidebar — via `sidebar.variableOptions` | `selectVariableType(type)`, `setName(name)`, `setLabel(label)`, `selectDisplay(displayLabel)`; type-specific sub-objects: `datasource.selectType(dsType)`, `datasource.setNameFilter(filter)`, `custom.openEditor()`, `custom.selectFormat(format)`, `custom.setValues(values)`, `custom.getPreviewOfValues()`, `custom.getPreviewTable()`, `custom.clickApplyButton()`, `groupby.selectDatasource(ds)`, `adhoc.selectDatasource(ds)`, `query.openEditor()`, `query.selectTargetDatasource(ds)`, `query.setTestDataQuery(query)`, `query.runQuery()`, `query.getPreviewOfValues()`, `query.clickApplyButton()`, `constant.setValue(value)`, `textbox.setValue(value)`, `interval.toggleAuto()` |
25−| `Panels` | `Panels.ts` | The dashboard panels in the edit canvas | `getPanels(title, scope?)`, `getPanel(title, scope?)`, `getHeaders(title?, scope?)` — string titles match exactly, RegExp filters by text, no argument returns all headers; `getHeader(title, scope?)`, `getBodies()`, `selectByTitle(title \| RegExp \| Array<title \| RegExp>)`, `selectByIndex(index)`, `selectMenuItem(panelTitle, menuPath[])` |
26−| `Rows` | `Rows.ts` | A row of a rows layout in the dashboard canvas | `getTitle(rowTitle)`, `getContent(rowTitle)` — content wrapper (grid or nested tabs) right after the row header |
27−| `Tabs` | `Tabs.ts` | Tab bar of a tabs layout (top-level or nested in a row) | `getTitle(tabTitle, scope?)` — pass `rows.getContent(rowTitle)` as `scope` to look up a tab inside a specific row; `getContent(tabTitle)` — the layout container holding the tab's content; `select(tabTitle)` — click the tab title |
28−| `Canvas` | `Canvas.ts` | Edit canvas add-actions strip (per grid, revealed on hover) | `getContainer()`, `addPanel(panelsContainer?)`, `addTab()`, `addRow()`, `groupPanels('row' \| 'tab', panelsContainer?)` — pass `panelsContainer` (e.g. `tabs.getContent(...)`, `rows.getContent(...)`) to target the add-actions strip of a nested grid |
14+## Project Overview
2915  
30−> The show/hide visibility toggle is a **Toolbar** control (`sidebar.toolbar.getVisibilityToggle()`), even though its selector lives under `components.Sidebar.*`. `Toolbar.getButton(name)` resolves buttons by accessible name, scoped to the sidebar container.
16+Grafana is a monitoring and observability platform. Go backend, TypeScript/React frontend, monorepo with Yarn workspaces (frontend) and Go workspaces (backend).
3117  
32−> This table grows as specs are migrated — only methods needed by migrated specs exist.
18+## Principles
3319  
34−### Base class & constructor
20+- Follow existing patterns in the surrounding code
21+- Write tests for new functionality
22+- Keep changes focused — avoid over-engineering
23+- Separate PRs for frontend and backend changes (deployed at different cadences)
24+- Security: prevent XSS, SQL injection, command injection
3525  
36−All page objects inherit from `PageObject`, which provides the shared constructor. It takes a single `PageObjectArgs` object:
26+## Comments
3727  
38−```typescript
39−// page-objects/PageObject.ts
40−export interface PageObjectArgs {
41− page: Page;
42− dashboardPage: DashboardPage;
43− selectors: E2ESelectorGroups;
44− components: Components;
45−}
28+- Only add a comment when it explains **why** something is done or reveals non-obvious logic that a reader must know to safely change the code. If the code is self-explanatory, no comment is needed.
29+- Never include links (Slack, GitHub, Jira, etc.) in code comments.
4630  
47−export abstract class PageObject {
48− constructor({ page, dashboardPage, selectors, components }: PageObjectArgs) {
49− // assigned to protected fields
50− }
51−}
52−```
31+## Human Review Gates
5332  
54−Simple page objects (e.g. `Controls`, `Toolbar`) inherit the constructor directly — no override needed. Page objects that compose sub-objects (e.g. `Sidebar`) declare `constructor(args: PageObjectArgs)`, call `super(args)`, and pass the same `args` to their children.
33+Before running `git push`, stop and get explicit human approval. When changes are ready, show a summary of changes and wait for instruction. "Open a PR" in a task description is intent, not permission to push without review.
5534  
56−All four dependencies come from the Playwright test arguments:
35+## Commands
5736  
58−```typescript
59−test('example', async ({ gotoDashboardPage, selectors, page, components }) => {
60− const dashboardPage = await gotoDashboardPage({ uid: 'some-uid' });
61− const controls = new Controls({ page, dashboardPage, selectors, components });
62− // ...
63−});
37+### Build & Run
38+ 
39+```bash
40+make run # Backend with hot reload (localhost:3000, admin/admin)
41+make build-backend # Backend only
42+yarn start # Frontend dev server (watches for changes)
43+yarn build # Frontend production build
6444 ```
6545  
66−## How to Write a New Test
46+### Test
6747  
68−1. **Identify which page objects you need.** Check the table above. If the interaction you need isn't covered, add the method to the appropriate page object — only what the new test requires.
48+```bash
49+# Backend
50+go test -run TestName ./pkg/services/myservice/ # Specific test
51+make test-go-unit # All unit tests
52+make test-go-integration # Integration tests
6953  
70−2. **Scaffold the spec** following this structure:
54+# Frontend
55+yarn test path/to/file # Specific file
56+yarn test -t "pattern" # By name pattern
57+yarn test -u # Update snapshots
7158  
72−```typescript
73−import { test, expect } from '@grafana/plugin-e2e';
59+# E2E
60+yarn e2e:playwright path/to/test.spec.ts # Specific test
61+```
7462  
75−import { Controls, Sidebar } from './page-objects';
63+### Lint & Format
7664  
77−test.describe(
78− 'Feature name',
79− {
80− tag: ['@dashboards'],
81− },
82− () => {
83− test('describes the user-visible behavior', async ({ gotoDashboardPage, selectors, page, components }) => {
84− const dashboardPage = await gotoDashboardPage({ uid: 'dashboard-uid' });
65+```bash
66+make lint-go # Go linter
67+yarn lint # ESLint
68+yarn lint:fix # ESLint auto-fix
69+yarn prettier:write # Prettier auto-format
70+yarn typecheck # TypeScript check
71+```
8572  
86− const controls = new Controls({ page, dashboardPage, selectors, components });
87− const sidebar = new Sidebar({ page, dashboardPage, selectors, components });
73+### Code Generation
8874  
89− await controls.enterEditMode();
90− // ... test body using page objects (the toolbar is reached via sidebar.toolbar)
91− });
92− }
93−);
75+```bash
76+make gen-go # Wire DI (after changing service init)
77+make gen-cue # CUE schemas (after changing kinds/)
78+make gen-apps # App SDK apps
79+make swagger-gen # OpenAPI/Swagger specs
80+make gen-feature-toggles # Feature flags (pkg/services/featuremgmt/)
81+make i18n-extract # i18n strings
82+make update-workspace # Go workspace (after adding modules)
9483 ```
9584  
96−1. **Verify locally:**
85+### Dev Environment
9786  
9887 ```bash
99−yarn e2e:pw --project dashboard-new-layouts --reporter list --repeat-each=3 -- <spec-filename>
88+yarn install --immutable # Install frontend deps
89+make devenv sources=influxdb # Start backing services
90+make devenv-down # Stop backing services
91+make lefthook-install # Pre-commit hooks
10092 ```
10193  
102−## Conventions
94+## Architecture
10395  
104−### Page objects
96+### Backend (`pkg/`)
10597  
106−- **Locator getters** (e.g. `getTitleInput()`) return a Playwright `Locator` — for elements that specs assert on (or both act on and assert on). The test owns the assertion — never the page object.
107−- **Action methods** (e.g. `enterEditMode()`, `clickCloseButton()`) wrap interactions — multi-step flows or single clicks on act-only elements — and use `test.step()` so the HTML report shows named steps.
108−- **When a spec needs both**, pair them: the action method delegates to the getter (see `Toolbar.getButton()` / `clickButton()`).
109−- **No speculative methods.** Only add methods needed by the spec being migrated.
110−- **Plural vs singular getters** (e.g. `getPanels()` / `getPanel()`): plural getters return every match — assert counts or narrow (`.first()`, `.nth()`) in the spec; singular getters return the first match.
111−- **Scoped lookups**: getters with a `scope?: Locator` parameter search inside that container — pass `rows.getContent(...)` or `tabs.getContent(...)` to look up elements in a specific row or tab.
112−- **No waits or retries inside page objects** unless the pre-refactor code had them. Keep `toPass()` retries, drag-and-drop, scroll logic, and `boundingBox()` in the spec or in `utils.ts`.
98+| Directory | Purpose |
99+| ----------------- | ----------------------------------------------------------- |
100+| `pkg/api/` | HTTP API handlers and routes |
101+| `pkg/services/` | Business logic by domain (alerting, dashboards, auth, etc.) |
102+| `pkg/server/` | Server init and Wire DI setup (`wire.go`) |
103+| `pkg/tsdb/` | Time series database query backends |
104+| `pkg/plugins/` | Plugin system and loader |
105+| `pkg/infra/` | Logging, metrics, database access |
106+| `pkg/middleware/` | HTTP middleware |
107+| `pkg/setting/` | Configuration management |
113108  
114−### Selector scoping
109+**Patterns**: Wire DI (regenerate with `make gen-go`), services implement interfaces in same package, business logic in `pkg/services/<domain>/` not in API handlers, database via `sqlstore`, plugin communication via gRPC/protobuf.
115110  
116−- **Scope lookups to the owning container.** A bare `page.getByRole(...)` searches the whole page and can match an unrelated element with the same role and name — if not today, then after an unrelated UI change. Elements that belong to a region with a page object must be looked up through that region's container: e.g. radio buttons in the sidebar are `sidebar.getContainer().getByRole('radio', { name: '...' })`, the same way `Toolbar.getButton()` scopes button names to `Sidebar.container`. Inside a page object, chain from the container selector (`this.dashboardPage.getByGrafanaSelector(this.selectors.components.Sidebar.container).getByRole(...)`).
117−- **Portalled UI is the only exception.** Select/Combobox option lists, modals, tooltips, and toasts render in a portal at the document root, outside their logical parent, so they cannot be scoped to it. Anchor them to the portal's own root instead: `page.getByRole('listbox').getByRole('option', { name })` (see `RepeatOptions`), or `page.getByRole('dialog', { name: 'Delete panel?' })`. A bare `page.getByRole('option', ...)` with no anchor is still too broad.
111+### Frontend (`public/app/`)
118112  
119−### Specs
113+| Directory | Purpose |
114+| ---------------------- | ----------------------------------------------------- |
115+| `public/app/core/` | Shared services, components, utilities |
116+| `public/app/features/` | Feature code by domain (dashboard, alerting, explore) |
117+| `public/app/plugins/` | Built-in plugins (many are Yarn workspaces) |
118+| `public/app/types/` | TypeScript type definitions |
119+| `public/app/store/` | Redux store configuration |
120120  
121−- **One raw `getByGrafanaSelector` is allowed** for one-off assertions that aren't reusable interactions (e.g. a breadcrumb check).
122−- **Timing-sensitive mechanics stay inline** — `toPass()`, `mouse` sequences, `page.evaluate()`.
123−- **Test setup stays in the spec** — API calls, dashboard provisioning, navigation via `gotoDashboardPage()`.
124−- **Each spec is fully migrated or untouched.** No file should mix page-object calls and raw selectors for the same UI region.
121+**Patterns**: Redux Toolkit with slices (not old Redux), function components with hooks, Emotion CSS-in-JS via `useStyles2`, RTK Query for data fetching, React Testing Library for tests.
125122  
126−### Adding a method to a page object
123+### Shared Packages (`packages/`)
127124  
128−1. Find the raw selector chain in the spec you're migrating.
129−2. Copy it into the appropriate page object class — mechanical extraction, no rewrites. New page objects must extend `PageObject` from `PageObject.ts`.
130−3. For interactions (multi-step flows or single clicks on act-only elements), wrap in `test.step('Human-readable name', async () => { ... })`.
131−4. For elements the spec asserts on, return a `Locator` (getter pattern, no `test.step` needed).
132−5. Run `--repeat-each=3` on the migrated spec.
125+`@grafana/data` (data structures), `@grafana/ui` (components), `@grafana/runtime` (runtime services), `@grafana/schema` (CUE-generated types), `@grafana/scenes` (dashboard framework).
133126  
134−## Canonical Example
127+### Backend Apps (`apps/`)
135128  
136−`dashboards-title-description.spec.ts` — the seed spec demonstrating the full pattern:
129+Standalone Go apps using Grafana App SDK: `apps/dashboard/`, `apps/folder/`, `apps/alerting/`.
137130  
138−```typescript
139−await controls.enterEditMode();
140−await sidebar.toolbar.clickButton('Options');
131+### Plugin Workspaces
141132  
142−const titleInput = sidebar.dashboardOptions.getTitleInput();
143−await expect(titleInput).toHaveValue('Annotation filtering');
133+These built-in plugins require separate build steps: `azuremonitor`, `loki`, `grafana-testdata-datasource`.
144134  
145−const newTitle = 'New dashboard title';
146−await titleInput.fill(newTitle);
147−await expect(titleInput).toHaveValue(newTitle);
148−```
135+Build a specific plugin: `yarn workspace @grafana-plugins/<name> dev`
149136  
150−## Migration Status
137+## Key Notes
151138  
152−**26 of 30 specs migrated.** Non-migrated specs are listed first by descending selectors usage count (a rough proxy for migration effort). "Selectors usage count" is the number of times the spec accesses the `selectors` object (`selectors.components...`, `selectors.pages...`, etc.).
139+- **Wire DI**: Backend service init changes require `make gen-go`. Wire catches circular deps at compile time.
140+- **CUE schemas**: Dashboard/panel schemas in `kinds/` generate both Go and TS code via `make gen-cue`.
141+- **Feature toggles**: Defined in `pkg/services/featuremgmt/`, auto-generate code. Run `make gen-feature-toggles` after changes.
142+- **Go workspace**: Defined in `go.work`. Run `make update-workspace` when adding Go modules.
143+- **Build tags**: `oss` (default), `enterprise`, `pro`.
144+- **Config**: Defaults in `conf/defaults.ini`, overrides in `conf/custom.ini`.
145+- **Database migrations**: Live in `pkg/services/sqlstore/migrations/`. Test with `make devenv sources=postgres_tests,mysql_tests` then `make test-go-integration-postgres`.
146+- **CI sharding**: Backend tests use `SHARD`/`SHARDS` env vars for parallelization.
147+- **Service compatibility**: Unified storage/search (`pkg/storage/unified/`) can be deployed as separate services at a different cadence than the Grafana API layer. Changes spanning API-layer callers and `pkg/storage/unified/` must be backwards compatible in both directions — see `pkg/storage/unified/AGENTS.md`.
153148  
154−| Spec | Status | Lines of code | Selectors usage count |
155−| ----------------------------------------------------- | ----------- | ------------- | --------------------- |
156−| `dashboard-group-panels.spec.ts` | Not started | 918 | 224 |
157−| `dashboards-repeats-tabs-layout.spec.ts` | Not started | 482 | 73 |
158−| `dashboards-panel-layouts.spec.ts` | Not started | 425 | 70 |
159−| `dashboard-repeats-row-layout.spec.ts` | Not started | 551 | 61 |
160−| `dashboards-repeats-custom-grid.spec.ts` | Migrated | — | — |
161−| `dashboards-repeats-auto-grid.spec.ts` | Migrated | — | — |
162−| `dashboards-title-description.spec.ts` | Migrated | — | — |
163−| `dashboards-edit-panel-title-description.spec.ts` | Migrated | — | — |
164−| `dashboards-edit-panel-transparent-bg.spec.ts` | Migrated | — | — |
165−| `dashboard-mobile-sidebar.spec.ts` | Migrated | — | — |
166−| `dashboard-hide-sidebar.spec.ts` | Migrated | — | — |
167−| `dashboards-remove-panel.spec.ts` | Migrated | — | — |
168−| `dashboard-duplicate-panel.spec.ts` | Migrated | — | — |
169−| `dashboard-sidepane.spec.ts` | Migrated | — | — |
170−| `dashboard-outline.spec.ts` | Migrated | — | — |
171−| `dashboards-conditional-rendering.spec.ts` | Migrated | — | — |
172−| `dashboards-add-panel.spec.ts` | Migrated | — | — |
173−| `dashboards-edit-variables.spec.ts` | Migrated | — | — |
174−| `dashboard-tabs-scroll.spec.ts` | Migrated | — | — |
175−| `dashboards-repeats-snapshots.spec.ts` | Migrated | — | — |
176−| `dashboards-move-panel.spec.ts` | Migrated | — | — |
177−| `dashboard-conditional-rendering-load-change.spec.ts` | Migrated | — | — |
178−| `dashboards-edit-custom-variables.spec.ts` | Migrated | — | — |
179−| `dashboards-edit-query-variables.spec.ts` | Migrated | — | — |
180−| `dashboard-keybindings.spec.ts` | Migrated | — | — |
181−| `dashboards-edit-adhoc-variables.spec.ts` | Migrated | — | — |
182−| `dashboards-edit-group-by-variables.spec.ts` | Migrated | — | — |
183−| `dashboards-edit-datasource-variables.spec.ts` | Migrated | — | — |
184−| `dashboard-url-syncing.spec.ts` | Migrated | — | — |
185−| `dashboard-tabs-drag-drop.spec.ts` | Migrated | — | — |
149+## Cursor Cloud specific instructions
186150  
187−See [`_page_objects_strategy.md`](./_page_objects_strategy.md) for the full migration plan.
151+### Prerequisites
152+ 
153+- **Node.js** — version pinned in `.nvmrc` (check that file for the exact version). Installed via nvm and set as the nvm default. **PATH gotcha:** the infra injects `/exec-daemon/node` ahead of nvm, so the plain non-login shell may resolve `node` to an older version — check it satisfies the `engines` range in `package.json` (it does today, so builds/tests work), but it is not the pinned version. Login shells (tmux sessions, `bash -lc '...'`) get the pinned version because `~/.bashrc` prepends the nvm bin. Run `yarn` / `yarn start` / `jest` / webpack via a login shell (tmux or `bash -lc`) to use the pinned Node.
154+- **Go** — version pinned in `go.mod` (check that file for the exact version), installed at `/usr/local/go` and symlinked to `/usr/local/bin/go`. The distro `/usr/bin/go` is older; `/usr/local/bin` wins in PATH so `go` resolves correctly. If `go.mod` bumps Go, reinstall a matching toolchain into `/usr/local/go`.
155+- **Yarn** via corepack — version pinned by `package.json` `packageManager` (check that field for the exact version). Run `corepack enable` if `yarn` is not found. `.yarnrc.yml` sets `enableScripts: false`, so dependency build/lifecycle scripts are disabled by default.
156+- **GCC** required for CGo/SQLite compilation of the backend.
157+- Repos in this environment live under `/agent/repos/<repo>` (e.g. `/agent/repos/grafana`); this is a multi-repo workspace, not the single `~/grafana` layout described in `grafana-enterprise/AGENTS.md`.
158+ 
159+### Running services
160+ 
161+- **Backend**: `make run` — builds and starts Grafana backend with hot-reload (air) on `localhost:3000`. Default login: `admin`/`admin`. First build takes ~3 minutes due to debug symbols (`-gcflags all=-N -l`); subsequent hot-reload rebuilds are faster.
162+- **Frontend**: `yarn start` — starts webpack dev server that watches for changes. The backend proxies to it. First compile takes ~45s.
163+- No external databases required — Grafana uses embedded SQLite by default.
164+ 
165+### Testing gotchas
166+ 
167+- **Frontend tests**: The `yarn test` script includes `--watch` by default. Always use `yarn jest --no-watch` or add `--watchAll=false` to run tests once and exit.
168+- **Backend tests**: Some packages (e.g. `pkg/api/`) have slow test compilation (~2 min) due to large dependency graphs. Use targeted test runs with `-run TestName` where possible.
169+- All standard build/test/lint commands are documented in the Commands section above.
188170  
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack