This file provides guidance to AI coding assistants when working with this codebase. Designed for OpenAI Codex, GitHub Copilot, Claude, Cursor, and other AI development tools.
YDB Embedded UI is a web-based monitoring and management interface for YDB (Yet another DataBase) clusters. It provides comprehensive tools for viewing database diagnostics, managing storage/nodes/tablets, executing queries, and monitoring cluster health.
The project serves dual purposes:
- Standalone application: Built and deployed as a web UI (embedded into YDB servers or served independently)
- Distributable library: Published as an npm package (
src/lib.tsexports) for embedding into other applications vianpm run package
- Before changing
AGENTS.md, Copilot instructions, or related agent docs, read the current file first and compare it against actual repository conventions. - Do not regenerate agent docs from a generic template when a project-specific file already exists.
- Keep repo-specific rules in repo docs; keep global tool/runtime preferences out of repo docs.
- Framework: React 18.3 with TypeScript 5.x
- State Management: Redux Toolkit 2.x with RTK Query
- UI Components: Gravity UI (
@gravity-ui/uikit7.x,@gravity-ui/components,@gravity-ui/navigation,@gravity-ui/table) - Routing: React Router v5 (not v6)
- Build Tool: Rsbuild (
@rsbuild/core) — configured inrsbuild.config.ts - Code Editor: Monaco Editor 0.52 with
@ydb-platform/monaco-ghostfor ghost text - Syntax Highlighting: Shiki (for non-editor code highlighting)
- Charts:
@gravity-ui/chartkit - Forms:
react-hook-formwith@hookform/resolversand Zod for complex forms; Gravity UI form components for simple forms - Tables: Custom
PaginatedTablecomponent (virtual scrolling);@gravity-ui/table+@tanstack/react-tablefor specialized tables - Keyboard Shortcuts:
hotkeys-js - Split Panes:
react-split - Testing: Jest 30 + React Testing Library (unit), Playwright 1.x (E2E)
- Package Manager: npm
- Node Version: 24+ required (
enginesfield enforces>=24.0)
npm ci # Install dependencies
npm run dev # Start development server (port 3000)npm run build # Standard production build
npm run build:embedded # Build for embedding in YDB servers
npm run build:embedded:archive # Build embedded version + create zip
npm run build:embedded-mc # Build multi-cluster embedded version
npm run analyze # Analyze bundle size with source-map-explorer
npm run package # Build library distribution (output: dist/)npm run lint # Run all linters (JS/TS + CSS + Prettier)
npm run typecheck # TypeScript type checking
npm run unused # Find unused code (uses knip)npm test # Run unit tests (Jest 30)
npm test -- --watch # Run tests in watch mode
npm test -- path/to/file # Run a single test file
npm run test:e2e # Run Playwright E2E tests
npm run test:e2e:update-snapshots # Update E2E snapshots (local browsers)
npm run test:e2e:docker # Run E2E tests in Docker
npm run test:e2e:docker:update-snapshots # Update E2E snapshots in Docker
npm run test:e2e:local # Run E2E against local dev servernpm run typecheck && npm run lint && npm test- Uses Redux Toolkit with RTK Query for API calls
- State organized by feature domains in
src/store/reducers/ - API endpoints injected using RTK Query's
injectEndpointspattern - Each domain has its reducer file (e.g.,
cluster.ts,tenant.ts) - Base RTK Query API created in
src/store/reducers/api.tswithfakeBaseQuery(all endpoints usequeryFn)
Modular API service pattern with domain-specific modules:
YdbEmbeddedAPIis the main API class (src/services/api/index.ts)- Modules:
AuthAPI,CodeAssistAPI,MetaAPI,MetaSettingsAPI,OperationAPI,PDiskAPI,SchemeAPI,StorageAPI,StreamingAPI,TabletsAPI,VDiskAPI,ViewerAPI - API services in
src/services/api/directory - All API calls go through
window.apiglobal object with domain-specific modules
src/
├── assets/ # Static assets (images, icons)
├── components/ # Reusable UI components (~110 components)
├── containers/ # Feature-specific containers (App, Cluster, Tenant, Node, Storage, etc.)
├── services/ # API services and parsers
│ ├── api/ # API module classes
│ └── parsers/ # Response data parsers
├── store/ # Redux store and reducers
│ └── reducers/ # Feature-domain reducers with RTK Query endpoints
├── styles/ # Global styles and theme variables
├── types/ # TypeScript definitions
│ └── api/ # API response types (prefixed with 'T')
├── uiFactory/ # UIFactory pattern for extensibility
└── utils/ # Utility functions, hooks, formatters
├── hooks/ # Custom React hooks
├── i18n/ # Internationalization setup
├── dataFormatters/ # Data formatting utilities
└── monaco/ # Monaco Editor utilities
- Component Registry Pattern: Runtime registration of extensible components via
componentsRegistry - Slots Pattern: Component composition with extensibility points (
AppSlots) - UIFactory Pattern:
configureUIFactory()insrc/uiFactory/uiFactory.tsallows customizing monitoring links, healthcheck views, feature flags, and more when using the library - Feature-based Organization: Features grouped with their state, API, and components
- Separation of Concerns: Clear separation between UI and business logic
- Library Export Pattern:
src/lib.tsexports key components, utilities, and configuration functions for use as an npm package
- ALWAYS dispose Monaco Editor instances:
return () => editor.dispose();in useEffect - NEVER allow memory leaks in long-running components
- Clear timeouts and intervals in cleanup functions
- ALWAYS use
useMemofor expensive computations and object/array creation - ALWAYS use
useCallbackfor functions passed to dependencies - ALWAYS memoize table columns, filtered data, and computed values
- AVOID
useEffectwhen possible - prefer direct approaches withuseCallback - PREFER direct event handlers and callbacks over useEffect for user interactions
// ✅ Memoize computed data
const columns = React.useMemo(
() => getColumns(columnsIds, overrideColumns),
[columnsIds, overrideColumns],
);
// ✅ Direct callback instead of useEffect to clear error on input
const handleTitleChange = React.useCallback((value: string) => {
setNextTitle(value);
setErrorMessage(undefined);
}, []);- ALWAYS provide fallback values:
Number(value) || 0 - NEVER allow division by zero:
capacity > 0 ? value/capacity : 0 - ALWAYS handle null/undefined data gracefully
- ALWAYS use
EMPTY_DATA_PLACEHOLDERfor empty UI values. Do not hardcode em dashes—or en dashes–as placeholders. Hyphen-and dashes may be used as separators in titles/ranges. Before submitting, grep the code for—/–and ensure placeholders useEMPTY_DATA_PLACEHOLDERfromsrc/utils/constants.ts. - Treat empty strings
''as missing UI values unless the feature explicitly distinguishes empty string from absence. - Keep
'',null, andundefinedbehavior consistent across table cells, drawers, detail lists, and badges. - Do not pass unchecked values into date/time formatters. Guard against
'', invalid parse results, andNaNbefore formatting. - When a value is absent, either omit the field or render the shared placeholder consistently with nearby UI.
- NEVER expose authentication tokens in logs or console output
- ALWAYS validate user input before processing
- NEVER skip error handling for async operations
- Sanitize data before displaying in UI components
To test with a local YDB instance:
# Pull and run YDB docker (use specific version or nightly)
docker pull ghcr.io/ydb-platform/local-ydb:nightly
docker run -dp 8765:8765 ghcr.io/ydb-platform/local-ydb:nightly
# Start the UI
npm run dev
# View Swagger API documentation
# Navigate to: http://localhost:8765/viewer/api/Create .env file for custom backend:
REACT_APP_BACKEND=http://your-cluster:8765 # Single cluster mode
- The project uses Husky pre-commit hooks that automatically run
lint-staged - Commit messages must follow conventional commit format (enforced by commitlint)
- PR titles must also follow conventional commit format with lowercase subjects, max 72 characters (e.g.,
fix: update api endpoints,feat: add new component) - Always run
npm run lintandnpm run typecheckto catch issues early
- Do not introduce runtime installs of unpinned latest tool versions in CI when another job or the lockfile defines the expected version.
- Prefer deriving tool versions from
package-lock.jsonorpackage.jsonwhen reproducibility matters. - Avoid commands that may rewrite repo-tracked manifests or lockfiles in CI unless that is the explicit goal.
The following checks run on every PR and merge group (ci.yml):
Job: Verify Files — runs sequentially:
npm run typecheck— TypeScript type checkingnpm run lint— All linters (ESLint + Stylelint + Prettier)npm run build:embedded— Verify embedded build worksnpm run package— Verify library package build works
Job: Unit Tests — runs in parallel with Verify Files:
npm test— Unit tests (Jest 30)
Additional quality checks (quality.yml) — run on PRs and pushes to main:
- Playwright E2E tests (against a
local-ydb:nightlyDocker service, sharded across 8 parallel runners) - Bundle size comparison (current branch vs. main, on PRs only)
- Test report deployment to GitHub Pages
- Automatic PR description updates with test results and bundle size diff
The project uses Gravity UI (@gravity-ui/uikit) as the primary component library. When adding new UI components, prefer using Gravity UI components over custom implementations. Additional Gravity UI packages are available: @gravity-ui/components, @gravity-ui/navigation, @gravity-ui/table, @gravity-ui/date-components.
- ESLint: Flat config (
eslint.config.mjs) based on@gravity-ui/eslint-config, plussrc/.eslintrcfor source-specific rules (React.FC ban, axios import restrictions, lib import restrictions) - Stylelint: Based on
@gravity-ui/stylelint-configwith order and prettier plugins - Prettier: Uses
@gravity-ui/prettier-config
- TypeScript
import type: Must use separate top-level type imports (consistent-type-importswithseparate-type-importsstyle) - React imports: Must use
import React from 'react'— named imports, namespace imports, and non-Reactdefault names are forbidden - Fragments: Must use
React.Fragment— JSX fragment shorthand (<></>) is forbidden - React.FC: Forbidden — do not use
React.FCfor component typing - isAxiosError: Must import from
utils/response, not directly fromaxios - lib imports: Direct imports from
.*/**/libpaths are forbidden — use direct component imports instead
// ✅ Correct — real pattern from the codebase (src/components/SplitPane/SplitPane.tsx)
import React from 'react';
import type {SplitProps} from 'react-split';
// Hooks are accessed via React.* inside component bodies
function SplitPane(props: SplitPaneProps) {
const [innerSizes, setInnerSizes] = React.useState<number[]>();
React.useEffect(() => {
return () => {
saveSizesStringDebounced.cancel();
};
}, [saveSizesStringDebounced]);
const defaultSizePane = React.useMemo(() => {
/* ... */
}, [initialSizes, defaultSizesProp]);
}
// ❌ Wrong — named value imports from 'react' are forbidden by ESLint
import {useState, useEffect} from 'react';
import * as React from 'react';- Unit tests are colocated with source files in
__test__directories - Unit tests use Jest 30 with
babel-jesttransform andjsdomenvironment - E2E tests use Playwright with page objects pattern in
tests/directory- Page models in
tests/models/(BaseModel.ts,PageModel.ts) - Test suites in
tests/suites/
- Page models in
- E2E tests run against both Chromium and Safari projects
- The
data-qaattribute is used for test element selection (configured via Playwright'stestIdAttribute) - When writing tests, follow existing patterns in the codebase
- Test artifacts are stored in
./playwright-artifacts/directory - Environment variables for E2E tests:
PLAYWRIGHT_BASE_URL- Override test URLPLAYWRIGHT_APP_BACKEND- Specify backend for tests
- Uses React Router v5 (not v6)
- Route definitions in
src/routes.ts - Supports both single-cluster and multi-cluster modes
All API calls go through window.api global object with domain-specific modules (e.g., window.api.viewer, window.api.storage, window.api.tablets).
Use PaginatedTable component for data grids with virtual scrolling. Tables require columns, fetchData function, and a unique tableName. For specialized use cases, @gravity-ui/table with @tanstack/react-table is also available.
API endpoints are injected using RTK Query's injectEndpoints pattern. Queries wrap window.api calls and provide hooks with loading states, error handling, and caching. The base API uses fakeBaseQuery — all endpoints must use queryFn.
- Do not interpolate raw user input into SQL/YQL without escaping.
- Parenthesize mixed
ORexpressions before combining them withANDconditions. - When backend schema differs across YDB versions, prefer compatibility-safe query shapes and normalize the response in code.
- If a feature depends on newly added backend fields, verify behavior on older clusters and keep a clear fallback path.
- Notifications: Use
createToastutility for user notifications - Error Display: Use
ResponseErrorcomponent for API errors;PageErrorfor full-page errors;AccessDenied(src/components/Errors/403) for access denied - Loading States: Use
LoaderandTableSkeletoncomponents
Uses BEM naming convention with cn() utility from utils/cn (wraps @bem-react/classname). Create a block function with component name and use it for element and modifier classes.
- ALWAYS prefix new SCSS root blocks and new
cn()block names withydb-(for example,cn('ydb-query-editor')) unless the surrounding feature already uses an established local naming convention that must be preserved for compatibility. - Do not reuse semantically narrow BEM element names for unrelated content just to share visual styles; introduce a generic element or modifier instead.
- API Types: Prefixed with 'T' (e.g.,
TTenantInfo,TClusterInfo) - Located in
src/types/api/directory
- Formatters:
src/utils/dataFormatters/-formatBytes(),formatDateTime() - Parsers:
src/utils/bytesParsers/- Byte value parsing utilities;src/utils/timeParsers/- Time/duration parsing utilities - Query Utils:
src/utils/query.ts- SQL/YQL query helpers - Hooks:
src/utils/hooks/-useTypedSelector,useTypedDispatch,useSetting,useSearchQuery,useTableSort,useSelectedColumns, etc.
See i18n-naming-ruleset.md in the repo root for all i18n conventions (naming and usage).
The i18n system uses @gravity-ui/i18n. Each component with user-facing strings has an i18n/ subdirectory containing:
en.json— English translationsindex.ts— Registers the keyset usingregisterKeysets()fromsrc/utils/i18n
// Real example: src/components/StorageGroupInfo/i18n/index.ts
import {registerKeysets} from '../../../utils/i18n';
import en from './en.json';
const COMPONENT = 'storage-group-info';
export const storageGroupInfoKeyset = registerKeysets(COMPONENT, {en});- Tables use virtual scrolling for large datasets
- Monaco Editor is lazy loaded
- Use
React.memofor expensive renders - Batch API requests when possible
- Simple forms: Use controlled Gravity UI form components with validation. Clear errors on user input and validate before submission.
- Complex forms: Use
react-hook-formwith@hookform/resolversand Zod schemas for validation (e.g.,QuerySettingsDialog,ManagePartitioningDialog).
Complex modals use @ebay/nice-modal-react library. Simple dialogs use Gravity UI Dialog component with proper loading states.
Uses React Router v5 hooks (useHistory, useParams, etc.). Always validate route params exist before using them.
- PREFER
use-query-paramsoverredux-location-statefor new development - ALWAYS use Zod schemas for URL parameter validation with fallbacks
- Use custom
QueryParamConfigobjects for encoding/decoding complex parameters - Use
z.enum([...]).catch(defaultValue)pattern for safe parsing with fallbacks
- When adding or changing table columns, filters, or sorting, update all connected surfaces:
- URL/query-param schema
- sort whitelist and persistence
- shareable links and restored state
- drawer/details rendering
- i18n keys
- unit and E2E coverage where behavior is user-visible
// ✅ Zod schema with fallback (src/containers/Versions/Versions.tsx)
const groupByValueSchema = z.nativeEnum(GroupByValue).catch(GroupByValue.VERSION);
// ✅ Custom QueryParamConfig (src/containers/Tenant/Diagnostics/TopQueries/hooks/useSortParam.ts)
const SortOrderParam: QueryParamConfig<SortOrder[]> = {
encode: (value) => {
if (value === undefined || value === null || !Array.isArray(value)) {
return undefined;
}
return encodeURIComponent(JSON.stringify(value));
},
decode: (value) => {
if (typeof value !== 'string' || !value) {
return [];
}
try {
return JSON.parse(decodeURIComponent(value));
} catch {
return [];
}
},
};
const [urlSortParam, setUrlSortParam] = useQueryParam<SortOrder[]>(paramName, SortOrderParam);The uiFactory in src/uiFactory/uiFactory.ts provides an extensibility layer for customizing the UI when using the project as a library. Use configureUIFactory() to override defaults like monitoring links, healthcheck views, feature flags, and access control.
window.api- Access API methods in browser consolewindow.ydbEditor- Monaco editor instance- Enable request tracing with
DEV_ENABLE_TRACING_FOR_ALL_REQUESTS
- For CSS and layout bugs, verify actual DOM ownership, selector specificity, computed sizing, and overflow behavior before explaining the cause.
- Do not attribute layout issues to specificity or style-order conflicts unless the selectors and computed styles confirm it.
The production build is optimized for embedding in YDB servers:
# Standard web deployment
npm run build
# Embedded deployment (relative paths, no source maps)
npm run build:embedded
# Multi-cluster embedded deployment
npm run build:embedded-mcBuild artifacts are placed in /build directory. For embedded deployments, files should be served from /monitoring path on YDB cluster hosts.
REACT_APP_BACKEND- Backend URL for single-cluster modeREACT_APP_META_BACKEND- Meta backend URL for multi-cluster modeGENERATE_SOURCEMAP- Set tofalsefor production builds
- Swagger API: Available at http://localhost:8765/viewer/api/ in development
- YDB Monitoring Docs: https://ydb.tech/en/docs/maintenance/embedded_monitoring/ydb_monitoring
- Gravity UI Components: https://gravity-ui.com/
- Project Roadmap: See ROADMAP.md in repository root