diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index abfb6134970c..08ad44a5cffe 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -168,8 +168,7 @@ pnpm nx run-many -t lint # all packages pnpm run lint # devextreme package: js, ts, dts, texts pnpm run lint-js -- --fix # auto-fix JS -# Regenerate (after changes to generators, TS declarations, or devextreme-internal-tools) -pnpm run regenerate-all +# Regenerate TS reexports and bundle (after changes to public .d.ts) pnpm run update-ts-reexports # from packages/devextreme pnpm run update-ts-bundle # from packages/devextreme @@ -183,7 +182,7 @@ pnpm nx clean:artifacts devextreme # build artifacts only ``` packages/ devextreme/ # core library: ui/, viz/, core/, data/, __internal/ - devextreme-{angular,react,vue}/ # framework wrappers (generated) + devextreme-{angular,react,vue}/ # framework wrappers (hand/AI-maintained) devextreme-scss/ # SCSS themes devextreme-themebuilder/ # theme builder devextreme-metadata/ # metadata for wrapper generation @@ -209,14 +208,14 @@ clean (`devextreme-nx-infra-plugin:clean` preserving CSS and npm metadata) → l - Use `pnpm nx ` rather than raw npm scripts so Nx caching and the dependency graph stay correct. - Build before testing: `pnpm nx build:dev devextreme`; QUnit and TestCafe both require an up-to-date build. -- Run `pnpm run regenerate-all` after editing wrapper generators, TypeScript declarations, or `devextreme-internal-tools`. -- Edit source files only under `packages/devextreme/js/**`, `packages/devextreme-scss/scss/**`, and `packages/devextreme-metadata/**`. +- The Angular/React/Vue wrappers are maintained by hand (with AI agents), not regenerated from metadata. When public API in `packages/devextreme/js/{ui,viz}/**/*.d.ts` changes, update all three wrappers in the same PR following `.github/instructions/wrapper-{angular,react,vue}.instructions.md` and `.github/instructions/public-api-wrappers.instructions.md`. +- Edit source files under `packages/devextreme/js/**`, `packages/devextreme-scss/scss/**`, `packages/devextreme-metadata/**`, and the wrapper `packages/devextreme-{angular,react,vue}/src/**` (except each package's `src/core/**`). - Match the Node and pnpm versions declared in `package.json` (`engines`, `packageManager`); mismatched versions cause CI failure. - Set `DEVEXTREME_TEST_CI=true` for test-mode builds and `BUILD_TEST_INTERNAL_PACKAGE=true` for wrapper test prep. ## Constraints -- NEVER edit generated wrappers under `packages/devextreme-{angular,react,vue}/src/` (templates excepted); update the generators and run `pnpm run regenerate-all` instead. +- The wrappers under `packages/devextreme-{angular,react,vue}/src/` are maintained by hand — keep them in sync with the public API per the wrapper instructions; NEVER edit each package's `src/core/**` (hand-written base classes) to accommodate a single component. - NEVER hand-edit `packages/devextreme/js/__internal/core/localization/{default_messages.ts,cldr-data/**}`; regenerate via the localization executor. - NEVER run `pnpm install` without `--frozen-lockfile`; use `pnpm install --frozen-lockfile` to match CI. @@ -224,7 +223,7 @@ clean (`devextreme-nx-infra-plugin:clean` preserving CSS and npm metadata) → l - Always install with `pnpm install --frozen-lockfile`; never plain `pnpm install`. - Build before test: `pnpm nx build:dev devextreme`. -- Generated wrappers under `packages/devextreme-{angular,react,vue}/src/` are read-only — modify generators and run `pnpm run regenerate-all`. +- Wrappers under `packages/devextreme-{angular,react,vue}/src/` are hand/AI-maintained — update them per the wrapper instructions when public API changes; leave each `src/core/**` alone. - Prefer `pnpm nx ` over direct npm scripts for caching. - Consult @.github/instructions/ for file-specific coding rules before editing. diff --git a/.github/instructions/public-api-wrappers.instructions.md b/.github/instructions/public-api-wrappers.instructions.md new file mode 100644 index 000000000000..c56fa466e999 --- /dev/null +++ b/.github/instructions/public-api-wrappers.instructions.md @@ -0,0 +1,64 @@ +--- +applyTo: "**/packages/devextreme/js/{ui,viz}/**/*.d.ts" +--- + +# Public API changes must be propagated to the framework wrappers + +The Angular, React, and Vue wrappers are **no longer regenerated** from +`devextreme-metadata` / `devextreme-internal-tools`. They are maintained by hand (with the +help of local AI agents). This means **every public API change in a wrapped component's +`.d.ts` must be applied, in the same pull request, to all three wrappers**. + +This file applies to the public API surface (`js/ui/**/*.d.ts`, `js/viz/**/*.d.ts`). Use it +both when editing the API and when reviewing an API change. + +## What counts as a wrapper-affecting change + +A change to any of the following in a component's options interface (e.g. `dxButtonOptions`, +its `Properties` alias, or a nested option type such as `dxDataGridColumn`) affects the +wrappers: + +- **Options** — a property added, removed, renamed, or retyped on the options interface. +- **Events** — an `onX` callback added, removed, renamed, or whose event type changed. +- **Event types** — a `*Event` type (e.g. `ClickEvent`) whose shape changed. +- **Nested option types** — a collection/object option type (e.g. `columns`, `items`, + `toolbar`) added, removed, renamed, or restructured. +- **Template options** — a `template` option added or removed. + +Purely internal changes (implementation in `js/__internal/**`, JSDoc-only edits that do not +change types or names) do **not** require a wrapper change. + +## Where each change lands in the wrappers + +| Public API element | Angular | React | Vue | +|---|---|---|---| +| Option `foo?: T` | `@Input() get foo()/set foo()` in `ui//index.ts` | field in the component's options type (`IXOptions`) | entry in `props` (correct runtime type + `PropType`) and `emits` `update:foo` | +| Event `onX?: (e: XEvent) => void` | `@Output() onX: EventEmitter` + `{ subscribe, emit }` entry | narrowed field in `IXOptionsNarrowedEvents` + entry in `independentEvents` | `onX` prop typed `Function as PropType<(e: XEvent) => void>` | +| Nested option type | do **not** edit `ui/nested/**` (deprecated, removed in a parallel PR) | `NestedOption` config component | configuration component via `prepareConfigurationComponentConfig` | +| Template option | template input | entry in `templateProps` | `template` prop | + +The per-framework anatomy and rules live in +[wrapper-angular.instructions.md](./wrapper-angular.instructions.md), +[wrapper-react.instructions.md](./wrapper-react.instructions.md), and +[wrapper-vue.instructions.md](./wrapper-vue.instructions.md). + +## Reviewer checklist + +When a PR changes a wrapped component's public `.d.ts`, verify and comment inline if any is +missing: + +1. **Completeness** — every added, removed, or renamed option/event is reflected in + **all three** wrappers (`devextreme-angular/src`, `devextreme-react/src`, + `devextreme-vue/src`). A change present in one or two wrappers but not the third is a + defect — flag the missing wrapper(s) by name. +2. **Type fidelity** — the wrapper carries the same type as the API (including union + members, generics, and the correct Vue runtime type + `PropType`). +3. **Breaking changes** — explicitly label these as **breaking** in a review comment: + - an option or event **removed** or **renamed**; + - an option/event **type narrowed** or otherwise changed incompatibly; + - a nested option type removed or restructured. + Confirm the wrappers reflect the same removal/rename so they stay in sync; do not let a + breaking API change land with stale wrapper surface. + +Only comment on the wrappers and the API contract. Do not propose regenerating the wrappers +or restoring the metadata pipeline — that workflow is retired. diff --git a/.github/instructions/wrapper-angular.instructions.md b/.github/instructions/wrapper-angular.instructions.md new file mode 100644 index 000000000000..61243bdeb679 --- /dev/null +++ b/.github/instructions/wrapper-angular.instructions.md @@ -0,0 +1,96 @@ +--- +applyTo: "**/packages/devextreme-angular/src/**" +--- + +# Maintaining the DevExtreme Angular wrapper by hand + +The files under `packages/devextreme-angular/src/` are **no longer generated**. Edit them +directly to keep them in sync with the public API in +`packages/devextreme/js/{ui,viz}/**/*.d.ts`. See +[public-api-wrappers.instructions.md](./public-api-wrappers.instructions.md) for when a +wrapper change is required and what the reviewer checks. + +## Do not edit + +- `packages/devextreme-angular/src/core/**` — hand-written base classes + (`DxComponent`, `NestedOptionHost`, template/integration modules). Never change these to + accommodate a single component. +- `packages/devextreme-angular/src/ui/nested/**` — the legacy nested option components + ("old nesteds"). This folder is **deprecated and being removed in a parallel PR**. Do not + add, edit, or reference files here, even when a nested/collection option changes. + +## Adding a new component + +Fastest path: **clone the closest existing wrapper and rename**, rather than writing a file +from scratch. For a plain Widget-based component copy `ui/load-indicator/index.ts`; for one +with many options/events copy `ui/button/index.ts`; for a container with nested options copy +`ui/sortable/` (the component plus its `nested/` folder). + +Files to create and register (keep every list alphabetical): + +- `packages/devextreme-angular/src/ui//index.ts` — the component (see anatomy below). +- `packages/devextreme-angular/src/ui//ng-package.json` — + `{ "lib": { "entryFile": "index.ts" } }`. +- `packages/devextreme-angular/src/index.ts` — add + `export { DxComponent, DxModule } from 'devextreme-angular/ui/';`. +- `packages/devextreme-angular/src/ui/all.ts` — add the `import` **and** add `DxModule` + to **both** the declarations and exports arrays (the two arrays are identical; anchor on the + neighboring entries to place it in each). +- For a component with nested/object options, also create the `ui//nested/` folder + (cloning `ui/sortable/nested/` is the fastest way) and wire its module into the component's + `@NgModule` plus `export * from 'devextreme-angular/ui//nested';`. + +Do **not** create anything under `src/metadata/generated/**` — it is gitignored generated +output, not source. + +## Component file anatomy (`ui//index.ts`) + +Use the widget's options interface (e.g. `dxButtonOptions` and its `WidgetOptions` base) as +the source of truth. A component file contains, in order: + +1. **Imports** — Angular symbols (`Component`, `Input`, `Output`, `EventEmitter`, + `NgModule`, `OnDestroy`, …), `type` imports of the event types and enum/union types from + `devextreme/ui/` and `devextreme/common`, the default widget import + (`import DxButton from 'devextreme/ui/button'`), and base classes from + `devextreme-angular/core`. +2. **`@Component` decorator** — `selector: 'dx-'`, `template` (`` for + container widgets, empty otherwise), `imports: [ DxIntegrationModule ]`, and the standard + `providers` (`DxTemplateHost`, `WatcherHelper`, `NestedOptionHost`). +3. **Class** `DxComponent extends DxComponent implements OnDestroy` with + `instance: Dx = null;`. +4. **One `@Input()` getter/setter pair per option**, in the same order as the options + interface (including inherited `WidgetOptions`/`DOMComponentOptions`): + ```ts + @Input() + get text(): string { return this._getOption('text'); } + set text(value: string) { this._setOption('text', value); } + ``` + The getter/setter type must match the option's type exactly (keep `| undefined` and + union members). +5. **Events** — one `@Output()` per `onX` callback, named exactly as the option and typed + with the event type: + ```ts + @Output() onClick: EventEmitter; + ``` +6. **Two-way binding outputs** — one `@Output()