diff --git a/.claude/rules/frontend-react.md b/.claude/rules/frontend-react.md index 571279ec88e3..ca1cfda31ce0 100644 --- a/.claude/rules/frontend-react.md +++ b/.claude/rules/frontend-react.md @@ -8,15 +8,21 @@ paths: "openmetadata-ui/src/main/resources/ui/**/*.{ts,tsx}" Applies to UI `*.{ts,tsx}`. Styling/tokens are in `frontend-styling.md`; component-library choice in `component-library.md`; strings/i18n in `i18n.md`; Playwright in `frontend-playwright.md`. For the **formatting procedure** invoke the `ui-checkstyle` skill — do not hand-edit formatting. -Compliant reference: `openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCardNew/ActivityFeedcardNew.component.tsx` -with its interface in a sibling `*.interface.ts`; forms reference -`openmetadata-ui/src/main/resources/ui/docs/formutils.md`. +**Folder structure and file naming for new code: +`openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md`** — read it before creating any new +file. Forms reference `openmetadata-ui/src/main/resources/ui/docs/formutils.md`. ## Component patterns -- **File naming**: components `ComponentName.component.tsx`, interfaces `ComponentName.interface.ts` - (props interfaces live in the `.interface.ts` file). *(Adherence is partial across the tree; follow - it for new files.)* +- **Folder structure**: layers stay top-level (`components/`, `pages/`, `rest/`, `utils/`, `hooks/`); + inside each, group by `domain/feature/` — `components/governance/glossary/GlossaryList/`. Domains: + `discovery`, `governance`, `observability`, `insights`, `platform`; cross-cutting features + (`lineage`, `data-contract`, `entity`, `activity-feed`) sit at the domain level, not inside one. +- **File naming (new code)**: one stem, suffix = role — `GlossaryList.tsx`, `.types.ts`, `.utils.ts`, + `.constants.ts`, `.style.less`, `.test.tsx`, `.mock.ts`. Pure logic goes in `*.utils.ts` (no React, + no JSX) so it is testable without rendering. + *(Legacy uses `.component.tsx`/`.interface.ts` — don't rename; the suffix marks migration status.)* +- **No `index.ts` barrel** inside a component folder — `no-internal-barrel-imports` reports it. - **Functional components only** — no class components. - **State**: `useState` with proper typing; multiple loading states as one object (`useState>({})`). diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5cb1039b99da..c4d45759bd3a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -568,12 +568,27 @@ make unit_ingestion_dev_env # For Python changes **IMPORTANT: When reviewing UI pull requests, you MUST follow the comprehensive guidelines in [/openmetadata-ui/UI_PR_REVIEW_GUIDELINES.md](../openmetadata-ui/UI_PR_REVIEW_GUIDELINES.md) and [/openmetadata-ui/src/main/resources/ui/playwright/PLAYWRIGHT_DEVELOPER_HANDBOOK.md](../openmetadata-ui/src/main/resources/ui/playwright/PLAYWRIGHT_DEVELOPER_HANDBOOK.md)** +**Folder structure and file naming are specified in +[/openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md](../openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md)** +— consult it for any newly added file under `ui/src/`. + +#### Structure & Naming +- ✅ **REQUIRE**: new files placed as `///` — layers are `components/`, + `pages/`, `rest/`, `utils/`, `hooks/`; domains are `discovery`, `governance`, `observability`, + `insights`, `platform`. Cross-cutting features (`lineage`, `data-contract`, `entity`, + `activity-feed`) sit at the domain level. +- ✅ **REQUIRE**: new files named with one stem + role suffix — `GlossaryList.tsx`, `.types.ts`, + `.utils.ts`, `.constants.ts`, `.style.less`, `.test.tsx`, `.mock.ts` +- ❌ **REJECT**: an `index.ts` barrel inside a component folder (`no-internal-barrel-imports`) +- ⚠️ **DO NOT** ask for existing `.component.tsx` / `.interface.ts` files to be renamed — the suffix + marks migration status + ### Critical UI Standards to Enforce #### Type Safety (Zero Tolerance) - ❌ **REJECT**: Any use of `any` type in TypeScript - ✅ **REQUIRE**: Proper type imports from `generated/` or `@rjsf/utils` -- ✅ **REQUIRE**: Defined interfaces for all component props in `.interface.ts` files +- ✅ **REQUIRE**: Defined types for all component props — `.types.ts` for new files, `.interface.ts` in legacy ones #### Internationalization (Zero Tolerance) - ❌ **REJECT**: Any hardcoded string literals in UI components @@ -599,9 +614,12 @@ make unit_ingestion_dev_env # For Python changes - ✅ **REQUIRE**: Navigation with `useNavigate`, not direct history manipulation #### File Naming (Must Follow) -- ✅ **REQUIRE**: Components named as `ComponentName.component.tsx` -- ✅ **REQUIRE**: Interfaces named as `ComponentName.interface.ts` -- ✅ **REQUIRE**: Custom hooks prefixed with `use` and placed in `src/hooks/` +- ✅ **REQUIRE**: new files use one stem + role suffix — `GlossaryList.tsx`, `GlossaryList.types.ts`, + `GlossaryList.utils.ts`, `GlossaryList.constants.ts`, `GlossaryList.style.less`, + `GlossaryList.test.tsx`, `GlossaryList.mock.ts` +- ⚠️ **DO NOT** ask for existing `.component.tsx` / `.interface.ts` files to be renamed — the suffix + marks migration status +- ✅ **REQUIRE**: Custom hooks prefixed with `use` and placed in `src/hooks//` ### PR Review Checklist diff --git a/CLAUDE.md b/CLAUDE.md index 2b91e4c346ef..d187e12ef32a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -139,6 +139,11 @@ on functionality over education. Do not add unnecessary blank lines between pros for repositories, Factory/Registry for dispatch, Strategy/Adapter/Observer, the ingestion Source→Sink pipeline, …) with the canonical class to copy each from. Extend the established pattern rather than inventing a parallel one. +- `openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md` — **the UI folder structure and file + naming spec.** Read before creating any new file under `openmetadata-ui/.../ui/src/`. Layers stay + top-level (`components/`, `pages/`, `rest/`, `utils/`, `hooks/`) and are grouped inside by + `domain/feature/`; new files use one stem with a role suffix (`GlossaryList.tsx`, `.types.ts`, + `.utils.ts`, `.test.tsx`). Legacy `.component.tsx`/`.interface.ts` files stay as they are. ### Skills (invoke by name; procedures, not rules) diff --git a/docs/index.md b/docs/index.md index 445be0f526f1..ea199ddd0477 100644 --- a/docs/index.md +++ b/docs/index.md @@ -72,6 +72,7 @@ verdict cites an artifact that was checked to still exist): | Doc | Purpose | Read when | Modified | Freshness | |---|---|---|---|---| +| `openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md` | **UI folder structure + file naming spec.** Layers (`components/`, `pages/`, `rest/`, `utils/`, `hooks/`) stay top-level, grouped inside by `domain/feature/`; five domains (`discovery`, `governance`, `observability`, `insights`, `platform`) with cross-cutting features at the domain level. New files use one stem + role suffix (`GlossaryList.tsx`, `.types.ts`, `.utils.ts`, `.test.tsx`); legacy uses `.component.tsx`/`.interface.ts`. Also covers imports, barrels, routing and state locations | **Before creating any new file under `ui/src/`**, or when deciding where code belongs | 2026-08-21 | CURRENT | | `openmetadata-ui/src/main/resources/ui/specs/` | **Machine-readable design system** (41 files). `README.md` declares two stacks — **go-forward = UntitledUI + Tailwind (`tw:`)**, **legacy (deprecated) = Ant Design + Less** — plus `foundations/*` (color, spacing, typography, radius, elevation, motion), `tokens/*` (Tailwind-utility + master token reference), `untitled/*` (go-forward component specs), and legacy `components/*` | **Before writing or modifying any UI code** — start at `specs/README.md`, then the `foundations`/`tokens` and the `untitled/.md` (or legacy `components/*`) spec for what you touch | 2026-07-27 | CURRENT ⁶ | | `openmetadata-ui/src/main/resources/ui/docs/colors.md` | Semantic color-token system (`tw:bg-primary`, `tw:text-fg-*`, `tw:border-*`) with light/dark values + the mandatory `ring`→`border` migration (§2.3.1) | Before writing/reviewing any Tailwind color class or dark-mode styling, or when tempted to use `ring-*` or a raw hex | 2026-07-23 | CURRENT | | `openmetadata-ui/src/main/resources/ui/docs/formutils.md` | The modern react-hook-form + react-aria form stack (`FieldProp`, `getField`/`FormFields`/`HookForm`) vs the legacy antd `@utils/formUtils` API | Before building/modifying any UI form — which API to use, and wiring to `useFormDrawerWithHook` + a pure transform | 2026-07-15 | CURRENT | diff --git a/openmetadata-ui/UI_PR_REVIEW_GUIDELINES.md b/openmetadata-ui/UI_PR_REVIEW_GUIDELINES.md index cf10e9c391f0..f4802870fd6a 100644 --- a/openmetadata-ui/UI_PR_REVIEW_GUIDELINES.md +++ b/openmetadata-ui/UI_PR_REVIEW_GUIDELINES.md @@ -6,17 +6,28 @@ This document outlines the standards and best practices that must be followed wh - [ ] **No `any` types**: Code must never use `any` type - use proper types or `unknown` with type guards - [ ] **Proper type imports**: All types imported from existing definitions (e.g., `RJSFSchema` from `@rjsf/utils`, types from `generated/`) -- [ ] **Interface definitions**: All component props have defined interfaces in `.interface.ts` files +- [ ] **Prop types defined**: All component props have declared types — `.types.ts` for new files, `.interface.ts` in legacy ones - [ ] **Type assertions**: Avoid type assertions unless absolutely necessary and well-justified - [ ] **Discriminated unions**: Use discriminated unions for action types and state variants ## React Component Standards ### File Structure and Naming -- [ ] **Component files**: Named as `ComponentName.component.tsx` -- [ ] **Interface files**: Named as `ComponentName.interface.ts` + +Full spec: [`src/main/resources/ui/DEVELOPER_HANDBOOK.md`](src/main/resources/ui/DEVELOPER_HANDBOOK.md). + +- [ ] **Placement**: new files live at `///` — layers are `components/`, + `pages/`, `rest/`, `utils/`, `hooks/`; domains are `discovery`, `governance`, `observability`, + `insights`, `platform`. Cross-cutting features (`lineage`, `data-contract`, `entity`, + `activity-feed`) sit at the domain level +- [ ] **New file names**: one stem + role suffix — `GlossaryList.tsx`, `.types.ts`, `.utils.ts`, + `.constants.ts`, `.style.less`, `.test.tsx`, `.mock.ts` +- [ ] **Legacy files untouched**: do not ask for existing `.component.tsx` / `.interface.ts` files to + be renamed — the suffix marks migration status +- [ ] **No `index.ts` barrel** inside a component folder (`no-internal-barrel-imports`) +- [ ] **Business logic in `*.utils.ts`**: pure functions, no React or JSX, unit-testable without rendering - [ ] **Functional components only**: No class components -- [ ] **Custom hooks**: Prefixed with `use`, placed in `src/hooks/`, return typed objects +- [ ] **Custom hooks**: Prefixed with `use`, placed in `src/hooks//`, return typed objects ### Component Implementation - [ ] **State management**: Uses `useState` with proper typing diff --git a/openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md b/openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md index a5b35daed35a..3cd0f7916dfb 100644 --- a/openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md +++ b/openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md @@ -2,118 +2,281 @@ ## 1. Folder Structure +The top-level folders are **layers** (`components`, `pages`, `rest`, `utils`, …). Inside each layer, +code is grouped by **domain**, then by **feature**. The same domain and feature names repeat in every +layer, so `governance/glossary` is one coordinate you can use to find anything about glossaries. + ``` @src/ │ -├── assets/ # Static files (images, fonts, icons, etc.) -├── components/ # Reusable UI components (buttons, modals, etc.) -│ └── [ComponentName]/ -│ ├── index.tsx -│ ├── [ComponentName].tsx -│ ├── [ComponentName].test.tsx -│ ├── [ComponentName].style.less -│ └── [ComponentName].types.ts # (if needed, see below) -├── constants/ # App-wide constants and enums -├── context/ # React context providers -├── hooks/ # Custom React hooks -├── interface/ # TypeScript interfaces and types (shared) -├── pages/ # Top-level pages/views (mapped to routes) -│ └── [PageName]/ -│ ├── index.tsx -│ ├── [PageName].tsx -│ ├── [PageName].test.tsx -│ ├── [PageName].style.less -│ └── [PageName].types.ts # (if needed) -├── routes/ # Route definitions and helpers -├── store/ # State management (Redux, Zustand, etc.) -├── api/ # API service functions and clients -├── utils/ # Utility/helper functions -├── styles/ # Global styles, variables, mixins (LESS) -├── App.tsx # Main app component -├── index.tsx # Entry point -└── ... # Other config files, setup, etc. +├── components/ # Reusable UI components +│ ├── discovery/ # domain +│ │ ├── explore/ # feature +│ │ │ ├── ExploreTree/ # component +│ │ │ └── ExploreSearchBar/ +│ │ ├── search/ +│ │ ├── my-data/ +│ │ └── data-marketplace/ +│ ├── governance/ +│ │ ├── glossary/ +│ │ │ ├── GlossaryList/ +│ │ │ ├── GlossaryHeader/ +│ │ │ └── GlossaryTermCard/ +│ │ ├── classification/ +│ │ ├── domain/ +│ │ └── data-product/ +│ ├── observability/ +│ │ ├── data-quality/ +│ │ ├── incident-manager/ +│ │ ├── profiler/ +│ │ └── alerts/ +│ ├── insights/ +│ │ ├── data-insight/ +│ │ └── kpi/ +│ ├── platform/ +│ │ ├── settings/ +│ │ ├── services/ +│ │ ├── ingestion/ +│ │ └── auth/ +│ ├── lineage/ # cross-cutting — belongs to no single domain +│ ├── data-contract/ +│ ├── entity/ +│ ├── activity-feed/ +│ └── common/ # generic, domain-agnostic UI +│ +├── pages/ # Route-level views +│ ├── governance/ +│ │ └── glossary/ +│ │ ├── GlossaryListPage/ +│ │ └── GlossaryDetailPage/ +│ ├── discovery/ +│ ├── observability/ +│ ├── insights/ +│ ├── platform/ +│ └── lineage/ +│ +├── rest/ # API clients +│ ├── governance/ +│ │ ├── glossary.api.ts +│ │ └── classification.api.ts +│ └── … # same domains +│ +├── utils/ # Helpers +│ ├── governance/ +│ │ ├── glossary.utils.ts +│ │ └── glossary.utils.test.ts +│ ├── … # same domains +│ └── common/ # date, string, formatting +│ +├── hooks/ +│ ├── governance/ +│ │ └── useGlossaryList.ts +│ └── common/ +│ +├── constants/ # grouped by domain, plus common/ +├── interface/ # grouped by domain, plus common/ +├── context/ # React context providers (note: a stray `contexts/` also +│ # exists — use `context/`, fold the other in) +├── generated/ # generated from openmetadata-spec — never hand-edit +├── assets/ # images, fonts, icons +├── locale/ # i18n resources +├── styles/ # global styles, variables, mixins (LESS) +├── App.tsx +└── index.tsx +``` + +One feature, fully expanded across the layers: + +``` +components/governance/glossary/GlossaryList/ +pages/governance/glossary/GlossaryListPage/ +rest/governance/glossary.api.ts +utils/governance/glossary.utils.ts +hooks/governance/useGlossaryList.ts +constants/governance/glossary.constants.ts +interface/governance/glossary.interface.ts +``` + +**Trade-off to be aware of:** because layers stay at the top, one feature spans several trees — adding +a glossary field can touch four folders. That is the price of keeping the core layers stable and the +lint layer-rules working (see §6). Keep the domain and feature names identical across layers so the +files are still trivially greppable. + +## 2. Domains + +Five domains, matching the product surface: + +| Domain | Owns | +|---|---| +| `discovery` | Explore, search, my data, data marketplace, knowledge center | +| `governance` | Glossary, classification, domains, data products, policies, roles, certification | +| `observability` | Data quality, incident manager, profiler, test library, alerts, service insights | +| `insights` | Data insight, KPI, web analytics | +| `platform` | Settings, services, ingestion, auth, bots, applications, personas, customization | + +**Cross-cutting features sit at the domain level, not inside a domain** — `lineage/`, +`data-contract/`, `entity/`, `activity-feed/`. + +Use one test to decide: **if two domains would both reasonably claim a feature, it has no domain** — +put it at the domain level. Lineage is discovery *and* governance *and* observability, so it stays +flat. This is what stops every new folder from reopening a taxonomy argument. + +Do not add a sixth domain without team agreement — the value of the scheme is that the same five +names appear in every layer. + +## 3. File Naming Conventions + +One stem per unit; the suffix carries the role. Fuzzy-finding `GlossaryList` returns a set of files +that label themselves. + +| File | Holds | +|---|---| +| `GlossaryList.tsx` | the component | +| `GlossaryList.types.ts` | props and local types | +| `GlossaryList.utils.ts` | pure logic, no React, no JSX | +| `GlossaryList.constants.ts` | literals, enums | +| `GlossaryList.style.less` | component-scoped styles | +| `GlossaryList.test.tsx` | unit test | +| `GlossaryList.mock.ts` | test fixtures | + +Casing: + +- **kebab-case** for domain and feature folders — they are paths, not things: `governance/`, + `data-contract/` +- **PascalCase** for component folders and component files — the import name then matches the + filename with no mental mapping: `GlossaryList/GlossaryList.tsx` +- **camelCase** for non-component modules: `useGlossaryList.ts`, `glossary.api.ts`, + `glossary.utils.ts` +- **UPPER_CASE** for constant values (not filenames): `API_URL` + +Never rename a file by case alone. macOS is case-insensitive by default and git will not track it +cleanly — rename via an intermediate name if you must. + +> Legacy files use `.component.tsx` and `.interface.ts`. New code uses the table above. The +> divergence is intentional: the suffix tells you at a glance whether a file has been migrated. + +## 4. Component Structure + +Each component gets a folder containing only the files it needs — do not scaffold empty ones: + +``` +components/governance/glossary/GlossaryList/ + GlossaryList.tsx + GlossaryList.types.ts + GlossaryList.utils.ts + GlossaryList.test.tsx + GlossaryList.style.less ``` -## 2. File Naming Conventions +- **Keep business logic out of the component.** Data shaping, filtering, and derivation go in + `*.utils.ts` as pure functions — no React, no JSX. They are then unit-testable without rendering. +- **No `index.ts` barrel files inside a component folder.** A barrel re-export pulls every sibling + module into the graph and defeats tree-shaking; the `no-internal-barrel-imports` lint rule reports + it. Import the deep path instead. -- Use PascalCase for components, pages, and their folders: `UserProfile`, `UserProfile.tsx` -- Use camelCase for hooks and utility functions: `useFetchData.ts`, `formatDate.ts` -- Use UPPER_CASE for constants: `API_URL` -- Test files: `[ComponentName].test.tsx` or `[util].test.ts` -- Styles: `[ComponentName].style.less` for component-scoped styles -- Types: `[ComponentName].types.ts` for component-specific types (see below) +## 5. Component-Specific Types -## 3. Component Structure +- Simple, single-use types: define at the top of the component file. +- Complex or reused types: `[ComponentName].types.ts` beside the component. +- Types shared across a whole feature: `interface/[domain]/[feature].interface.ts`. +- **Never redefine an API type** — import it from `generated/`, which is generated from + `openmetadata-spec/` (see `.claude/rules/schema-first.md`). -Each component/page should have its own folder containing: -- Main component file (`.tsx`) -- Test file (`.test.tsx`) -- Styles (`.style.less`) -- Index file for clean imports -- Types file (`.types.ts`) if types are complex or reused +## 6. Imports -Example: +**Within a feature, use relative imports.** + +```ts +import { formatTerms } from './GlossaryList.utils'; +import { GlossaryHeader } from '../GlossaryHeader/GlossaryHeader'; ``` -components/ - Button/ - Button.tsx - Button.test.tsx - Button.style.less - Button.types.ts - index.ts + +If you need more than one `../`, the file is probably in the wrong feature. Reach for an absolute +import rather than climbing. + +**Across features or layers, use absolute imports from the layer root.** + +```ts +import { GlossaryList } from 'components/governance/glossary/GlossaryList/GlossaryList'; +import { getGlossaryTerms } from 'rest/governance/glossary.api'; +import { formatTerms } from 'utils/governance/glossary.utils'; ``` -## 4. Component-Specific Types +> **Setup required.** Absolute imports currently resolve in Jest only +> (`jest.config.js` → `moduleDirectories: ['node_modules', 'src']`). Vite and `tsc` do not resolve +> them yet — `tsconfig.json` has no `baseUrl`, and `vite.config.ts` aliases only `@`. Before using +> absolute imports in application code, add `"baseUrl": "./src"` to `tsconfig.json` and matching +> entries to the Vite `resolve.alias` block. Until then, application code must keep using relative +> paths; only tests can use the absolute form. + +Enforced import rules (custom ESLint plugins in `eslint-rules/`, run on every PR): -- For simple or small components, define types/interfaces at the top of the component file. -- For complex components or when types are reused elsewhere, create a separate file: - - `[ComponentName].types.ts` -- **Rule of Thumb:** - - If the type is only used in one file and is simple, keep it in the same file. - - If the type is reused or complex, use a separate `.types.ts` file. +- **No app-internal barrel imports** — import the deep path, not an `index.ts`. +- **Pages must not import other pages.** Move shared code down into components, hooks, interfaces, or + pure utilities. +- **REST clients and hooks must not import UI.** Dependencies point one way — `pages` → `components` + → `hooks`/`rest` → `utils`. +- **Pure utilities must not contain JSX** or import React, pages, or REST. +- **Routers must lazy-load pages** with `import()`, never a static import. +- **No lodash default or namespace imports** — `import { isEmpty } from 'lodash';`. -## 5. API Layer +These rules derive a file's layer from the first path segment after `src/`, which is why the layer +folders stay at the top level. -- All API calls go in `@src/api/` -- Group by resource (e.g., `userApi.ts`, `projectApi.ts`) -- Use TypeScript interfaces from `@src/interface/` -- Keep API logic separate from UI logic +## 7. API Layer -## 6. State Management +- All API calls live in `@src/rest/`, grouped by domain: `rest/governance/glossary.api.ts` +- One file per feature, not per endpoint +- Request and response types come from `generated/` +- Keep API logic free of UI concerns — no React, no toasts, no navigation -- Use `@src/store/` for global state (Redux, Zustand, etc.) -- Organize by feature/domain -- Keep actions, reducers, and selectors together +## 8. State Management -## 7. Hooks +- Zustand stores for global state. They live in `@src/hooks/` alongside other hooks, not in a separate + `store/` folder — `useApplicationStore`, `useSearchStore`, `useDomainStore`, `useWelcomeStore` +- New stores go in `hooks/[domain]/`, named `use[Thing]Store.ts` +- Keep component state local with `useState` when possible +- Context providers for feature-shared state live in `@src/context/` -- Place all custom hooks in `@src/hooks/` -- Prefix with `use` (e.g., `useAuth.ts`) -- Keep hooks generic and reusable +## 9. Hooks -## 8. Interfaces & Types +- Feature hooks: `hooks/[domain]/useThing.ts` +- Generic hooks: `hooks/common/` +- Always prefix with `use`; return typed objects +- Hooks must not import UI components -- All shared types/interfaces go in `@src/interface/` -- Group by domain or feature -- Import types from here in components, API, and store +## 10. Interfaces & Types -## 9. Testing +- Shared types go in `@src/interface/[domain]/` +- Component-local types stay in `[ComponentName].types.ts` (see §5) +- API types always come from `generated/` — never hand-written + +## 11. Testing - Use Jest + React Testing Library - Test files live next to the code they test - Cover components, hooks, utils, and API logic -- **IMPORTANT**: Always write unit tests for utility functions in `@src/utils/` +- **IMPORTANT**: Always write unit tests for utility functions - Every utility function should have corresponding test coverage - Test edge cases, error conditions, and expected behavior - - Place test files next to the utility: `utilName.test.ts` + - Place test files next to the utility: `glossary.utils.test.ts` +- Because business logic lives in `*.utils.ts` (§4), most logic is testable without rendering — prefer + that over deep component tests - Follow [this](./playwright/PLAYWRIGHT_DEVELOPER_HANDBOOK.md) guide for e2e testing. -## 10. Routing +## 12. Routing -- Define all routes in `@src/routes/` -- Use a central file for route paths and lazy loading +- Routers live in `@src/components/AppRouter/`, which is the app's routing composition root — there is + no `src/routes/` folder +- Each feature that owns a route subtree gets its own router there: `GlossaryRouter`, `DomainRouter`, + `ClassificationRouter`, `SettingsRouter`, `EntityRouter` +- Route path constants go in `constants/[domain]/[feature].routes.ts` +- Route modules must load pages with `React.lazy` + `import()`, never a static import — enforced by + the `no-eager-page-imports` rule. Pass every lazy component to a helper from + `components/AppRouter/withSuspenseFallback`, or render it in a real `Suspense` boundary with an + explicit `fallback` -## 11. Assets +## 13. Assets - Place images, SVGs, fonts, etc. in `@src/assets/` - Organize by type or feature @@ -122,12 +285,12 @@ components/ - ✅ Good: `` - This allows flexible sizing via CSS/props without hardcoded dimensions -## 12. Styles +## 14. Styles - Use LESS style for component styles (`.style.less`) - Place global styles, variables, and mixins in `@src/styles/` as `.less` files -## 13. Translations +## 15. Translations - **i18n Translation Rule**: DO NOT use i18n translation functions outside of React components - Only use `t()` from `useTranslation()` hook inside components @@ -136,7 +299,7 @@ components/ - ✅ Good: Store the key `message: 'label.name'`, then translate in component using `t('label.name')` - This prevents initialization order issues and keeps non-component code pure -## 14. UI Library (@openmetadata/ui-core-components) and Icons (@untitledui/icons) +## 16. UI Library (@openmetadata/ui-core-components) and Icons (@untitledui/icons) - Primary UI library: `@openmetadata/ui-core-components`, built on Untitled UI patterns with `react-aria-components` as the accessibility foundation. - Icon source: @untitledui/icons. @@ -150,13 +313,13 @@ Usage guidance: - Use CSS custom properties (design tokens) defined in `openmetadata-ui-core-components` for colors instead of hardcoded values. - Use `.style.less` only for component-specific static styles/layout that predate the Tailwind migration. - Icons: - - Import icons directly to keep bundle size small. Example: - - import { IconName } from '@untitledui/icons'; - - Create a single re-export file for commonly used icons: - - @src/assets/icons/index.ts — re-export the subset of icons used across app to centralize changes. + - Import icons directly to keep bundle size small: `import { IconName } from '@untitledui/icons';` - For rarely used icons, import directly where needed to enable tree-shaking. - - Wrap icons in an Icon component if you need consistent sizing, color, or additional behavior: - - @src/components/ui/Icon/Icon.tsx + - If you need consistent sizing, color, or extra behavior, wrap icons in a shared component at + `@src/components/common/Icon/Icon.tsx` (create it when the need arises — it does not exist yet). + - Do **not** create an `assets/icons/index.ts` re-export barrel. It is the exact case the + `no-internal-barrel-imports` rule exists to prevent: one import pulls the whole icon set into the + module graph and defeats tree-shaking. - Accessibility and ARIA: - Rely on `react-aria-components`' accessible primitives (already wired into `openmetadata-ui-core-components`). - Ensure icons used as interactive elements have accessible names or aria-hidden when decorative. @@ -164,16 +327,15 @@ Usage guidance: - Prefer named imports to maintain tree-shaking icon packages. - Keep the icon re-export file limited to used icons; avoid exporting entire icon packs. -## 15. General Rules +## 17. General Rules - Keep files small and focused - Prefer composition over inheritance -- Use TypeScript everywhere -- Write clear, descriptive comments and JSDoc where needed -- Use absolute imports from `@src/` (configure `tsconfig.json` paths) +- Use TypeScript everywhere; never `any` +- Write clear, descriptive comments and JSDoc where needed — explain *why*, never restate the code - Keep third-party code and wrappers in a separate folder if needed -## 16. Forms +## 18. Forms - New forms use the `react-hook-form` + `react-aria` stack from `@openmetadata/ui-core-components` (`getField`/`FieldProp`/`FieldTypes`/`HookForm`/`FormFields`): a form is `FieldProp[]` config objects + RHF state + a pure values→payload transform. - Full reference: [docs/formutils.md](./docs/formutils.md) — field types, validation, composition, escape hatches, and the drawer caller pattern. @@ -181,4 +343,19 @@ Usage guidance: --- +## Adding a new feature — checklist + +1. Pick the domain (§2). If two domains claim it, put it at the domain level. +2. Create the folders you need, in each layer you touch: + - `components/[domain]/[feature]/[ComponentName]/` + - `pages/[domain]/[feature]/[PageName]/` + - `rest/[domain]/[feature].api.ts` + - `utils/[domain]/[feature].utils.ts` +3. Name files by role (§3): `.tsx`, `.types.ts`, `.utils.ts`, `.test.tsx`. +4. Put business logic in `*.utils.ts` and unit-test it. +5. Import API types from `generated/` — never redeclare them. +6. Run `yarn ui-checkstyle:changed` and `npx tsc --noEmit`. + +--- + This handbook ensures a clean, scalable, and consistent codebase for all developers. diff --git a/skills/agents/frontend-reviewer.md b/skills/agents/frontend-reviewer.md index 876fddbf1d8e..979c45b00045 100644 --- a/skills/agents/frontend-reviewer.md +++ b/skills/agents/frontend-reviewer.md @@ -223,9 +223,24 @@ Beyond CI checks, review for these patterns: - Avoid type assertions (`as Type`) unless absolutely necessary - Use discriminated unions for action types and state variants -### 10. Component Patterns +### 10. Structure, Naming & Component Patterns + +Full spec: `openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md` — read it before reviewing +any newly added file. + +- **Placement**: layers stay top-level (`components/`, `pages/`, `rest/`, `utils/`, `hooks/`), grouped + inside by `domain/feature/` — `components/governance/glossary/GlossaryList/`. Domains are + `discovery`, `governance`, `observability`, `insights`, `platform`; cross-cutting features + (`lineage`, `data-contract`, `entity`, `activity-feed`) sit at the domain level. Flag a new file + dropped straight into `components/` or `utils/` without a domain folder. +- **File naming (new files)**: one stem, suffix = role — `GlossaryList.tsx`, `.types.ts`, `.utils.ts`, + `.constants.ts`, `.style.less`, `.test.tsx`, `.mock.ts`. Do NOT flag legacy + `.component.tsx`/`.interface.ts` files or ask for them to be renamed — the suffix marks migration + status, and case-only/suffix renames churn history. +- **Business logic in `*.utils.ts`** as pure functions (no React, no JSX) so it is unit-testable + without rendering. +- **No `index.ts` barrel** inside a component folder — `no-internal-barrel-imports` reports it. - Functional components only, no class components -- File naming: `ComponentName.component.tsx`, interfaces in `ComponentName.interface.ts` - `useCallback` for event handlers passed to children - `useMemo` for expensive computations - `useEffect` with correct dependency arrays — no missing deps, no over-fetching diff --git a/skills/openmetadata-workflow/SKILL.md b/skills/openmetadata-workflow/SKILL.md index 29caa3eef895..6bfe805a6dd4 100644 --- a/skills/openmetadata-workflow/SKILL.md +++ b/skills/openmetadata-workflow/SKILL.md @@ -19,7 +19,7 @@ This skill is loaded automatically at session start. It ensures you follow the r | Bug fix | `/systematic-debugging` then `/tdd` (write regression test) then `/verification` | | New API endpoint | `/planning` then `/tdd` then `/test-enforcement` (must include integration test) | | New connector | `/connector-standards` then `/connector-building` then `/test-enforcement` | -| UI component | `/ui-core-components` + `/react-best-practices` + `/composition-patterns` **before writing**, then `/tdd`, then `/web-design-guidelines` **after**, then `/test-enforcement` (must include Jest + Playwright if user-facing) | +| UI component | **Read `openmetadata-ui/src/main/resources/ui/DEVELOPER_HANDBOOK.md` first** (folder structure + file naming for any new file), then `/ui-core-components` + `/react-best-practices` + `/composition-patterns` **before writing**, then `/tdd`, then `/web-design-guidelines` **after**, then `/test-enforcement` (must include Jest + Playwright if user-facing) | | UI a11y / UX / design audit | `/web-design-guidelines` | | React perf work (re-renders, waterfalls, bundle) | `/react-best-practices` | | Code review / PR review | `/code-review` then `/test-enforcement` |