diff --git a/.agents/rules/mendix-widget.md b/.agents/rules/mendix-widget.md new file mode 100644 index 0000000000..8da24d65cd --- /dev/null +++ b/.agents/rules/mendix-widget.md @@ -0,0 +1,48 @@ +# Mendix Widget Development Rules + +## API Guards + +- ALWAYS check `actionValue.canExecute` before calling `execute()` +- NEVER render widget content while value status is `"loading"` — show loading/placeholder state instead +- Use `editableValue.setValue()` for two-way binding, never mutate the value directly +- Handle all three `EditableValue` states: `"available"`, `"loading"`, `"unavailable"` + +## XML ↔ TypeScript + +- XML property keys MUST be lowerCamelCase and MUST match TypeScript prop names exactly +- When adding an XML property: update `.xml`, rebuild to regenerate `typings/Props.d.ts`, update `editorConfig.ts`, and `editorPreview.tsx` +- Each widget MUST have a unique widget ID in `package.xml` — never duplicate across widgets + +## Versioning + +- Any change to runtime behavior, XML schema, or public API REQUIRES: + - Semver bump in `package.json` + - CHANGELOG.md entry (Keep a Changelog format) + - Run `pnpm -w changelog` or update manually +- Refactor, test, or docs-only changes: no bump required + +## Styling + +- SCSS only — no inline styles for static design +- Prefer Atlas UI classes (`btn`, `btn-primary`, `badge`, etc.) for common elements +- Custom classes MUST use the widget-name prefix: `.widget--` +- NEVER override core Atlas UI classes — wrap in a widget-specific selector if custom styling is needed + +## Testing + +- Run tests from the widget package dir: `cd packages/pluggableWidgets/-web && pnpm test` +- Use `@mendix/widget-plugin-test-utils` builders for mocking Mendix API values: + ```ts + import { dynamicValue, EditableValueBuilder } from "@mendix/widget-plugin-test-utils"; + const mockAttr = new EditableValueBuilder().withValue("test").build(); + ``` +- New features require unit tests; bug fixes require regression tests +- User-visible behavior changes require E2E updates in `e2e/*.spec.js` +- E2E tests MUST include `test.afterEach` session logout to avoid Mendix license limit issues + +## Constraints + +- Never modify `dist/`, generated typings, or `pnpm-lock.yaml` +- No tree-shaking-hostile imports — use named imports for large dependencies +- No core Atlas class overrides +- `dangerouslySetInnerHTML` is forbidden unless the content is explicitly sanitized diff --git a/.agents/rules/react-patterns.md b/.agents/rules/react-patterns.md new file mode 100644 index 0000000000..da3dc224e4 --- /dev/null +++ b/.agents/rules/react-patterns.md @@ -0,0 +1,58 @@ +# React & MobX Patterns for Mendix Widgets + +## Hooks + +- `useEffect` deps array MUST include all referenced variables — no stale closures +- Async effects MUST guard against state updates after unmount: + ```ts + useEffect(() => { + let active = true; + (async () => { + const data = await load(); + if (active) setData(data); + })(); + return () => { + active = false; + }; + }, [load]); + ``` +- No side effects in render — use `useEffect` for side effects +- Cleanup subscriptions, timers, and event listeners on unmount + +## State + +- Use functional updates when reading previous state: `setCount(c => c + 1)` +- Do NOT store computed values in `useState` if they can be derived from props +- Source of truth priority: Mendix props → MobX store → React state +- Controlled vs. uncontrolled inputs: never mix `value` and `defaultValue` on the same element + +## Keys & Lists + +- Stable, unique `key` props on all list items — NEVER use array index for dynamic lists +- For large lists, consider virtualization + +## MobX + +- Stores: use `makeAutoObservable(this)` or explicit `makeObservable(this, { ... })` in the constructor +- State mutations: inside `action` or `runInAction` — never mutate observables outside an action +- Derived values: `computed` — no side effects allowed inside computed properties +- React integration: `observer()` HOC from `mobx-react-lite` + `useSubscribe()` from `@mendix/widget-plugin-mobx-kit` +- Use `reaction()` for side effects triggered by state changes, not `autorun()` in most cases + +## Performance + +- `useMemo`/`useCallback` only where re-render cost is measurable — incorrect deps are worse than no memoization +- Avoid creating new objects/arrays/functions inline in JSX props passed to child components (new reference on every render forces child re-render) + +## Accessibility + +- Semantic HTML first — ARIA only when native elements don't convey the right semantics +- WCAG 2.2 AA: 4.5:1 contrast for normal text, 3:1 for large text and UI elements +- Keyboard navigation: Arrow keys (menus/lists), Enter/Space (activation), Escape (dismiss), Tab (focus order) +- Use Floating UI accessibility hooks for floating elements: `useRole`, `useDismiss`, `useListNavigation`, `FloatingFocusManager` +- `dangerouslySetInnerHTML`: never — if unavoidable, sanitize with a trusted library + +## Props + +- Do NOT spread unknown props onto DOM nodes — filter non-HTML attributes before spreading +- Prefer composition over prop drilling; use React Context for cross-tree state when prop chains exceed 2-3 levels diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d85a277c43..ee5e6118c5 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -35,6 +35,17 @@ Use this guide to review both code and workflow. Focus on Mendix pluggable widge - If refactor/docs/tests-only: bump not required (ask author to confirm). - Multiple changed packages: each needs its own bump and changelog entry. +### OpenSpec change proposals + +- If the PR introduces a new feature, behavior change, or XML property change in a widget that has `openspec/` initialized: + - Check if `openspec/changes//` exists with a `proposal.md`. + - If no proposal exists: "No OpenSpec proposal found. For behavior/XML changes, consider `/opsx:propose` to document intent. This is optional for small changes." + - If a proposal **does** exist, verify: + - `proposal.md` clearly states the why and lists XML property impact. + - Delta specs in `specs/` use Given/When/Then scenarios and cover loading/empty/error states. + - `tasks.md` has a **Versioning** group with CHANGELOG and semver tasks. +- Generally ignore `openspec/changes/archive/` — that's completed history. + ## Code quality – Mendix pluggable widgets and React ### Mendix-specific @@ -48,7 +59,6 @@ Use this guide to review both code and workflow. Focus on Mendix pluggable widge ### React code-logic best practices - **Hooks and effects** - - Correct `useEffect`/`useMemo`/`useCallback` dependencies; avoid stale closures. - No side effects in render. Cleanup subscriptions/timers on unmount. - Guard async effects to avoid setting state after unmount: @@ -67,7 +77,6 @@ Use this guide to review both code and workflow. Focus on Mendix pluggable widge - Avoid deriving state directly from props unless necessary; prefer computing from props or synchronize carefully (watch for loops). - **State management** - - Use functional updates when reading previous state: ```ts setCount(c => c + 1); @@ -76,27 +85,22 @@ Use this guide to review both code and workflow. Focus on Mendix pluggable widge - **MobX stores** for complex cross-component state; **React state** for simple UI state; **Mendix props** as source of truth for persistent data. - **Rendering and lists** - - Use stable, unique `key`s (avoid array index unless list is static). - Avoid heavy computations in render; memoize when there's proven benefit. - For large lists/tables, consider virtualization. - **Performance hygiene** - - Limit `useCallback`/`useMemo` to cases with measurable re-render cost; ensure dependency arrays are correct. - Avoid creating new objects/arrays/styles inline when passed to children repeatedly; memoize where needed. - **Composition and props** - - Prefer composition over prop drilling; consider Context when appropriate. - Don't spread unknown props onto DOM nodes (avoid React unknown prop warnings). Validate/filter props before spreading. - **Accessibility** - - Semantic elements, proper ARIA, focus management, and keyboard navigation. - **Error handling and robustness** - - Handle null/undefined from Mendix props; safe optional chaining; avoid non-null assertions unless justified. - Guard external data parsing; provide graceful fallbacks. - Avoid `dangerouslySetInnerHTML`; if unavoidable, sanitize input. @@ -320,6 +324,9 @@ This repository uses a comprehensive three-tier testing strategy: - E2E (dev): `pnpm e2edev` (with GUI debugger) - E2E (CI): `pnpm e2e` (headless) - Prepare changelog/version (workspace): `pnpm -w changelog`, `pnpm -w version` +- OpenSpec propose (in widget dir): `/opsx:propose ""` +- OpenSpec apply: `/opsx:apply` +- OpenSpec archive: `/opsx:archive` ## Tone and format for comments diff --git a/AGENTS.md b/AGENTS.md index 65b206d1f1..9cb1016cf8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,6 +60,43 @@ Reference (consult on demand for specific tasks): - docs/requirements/implementation-plan.md — New widget guide + PR template - docs/requirements/widget-to-module.md — Widget-to-module conversion guide +## OpenSpec Workflow + +We use [OpenSpec](https://github.com/Fission-AI/OpenSpec) for spec-driven development. Install globally: `npm install -g @fission-ai/openspec@latest`. + +**When to use:** Before implementing new widget features, behavior changes, or XML property changes. Not required for bug fixes, refactors, or test-only changes. + +**Per-widget structure (opt-in):** Each widget that has been initialized has: +``` +packages/pluggableWidgets/-web/ +└── openspec/ + ├── specs/spec.md ← current behavior spec (source of truth) + ├── changes/ ← active change proposals + └── config.yaml ← widget-specific context +``` + +**Starting a change:** +``` +cd packages/pluggableWidgets/-web +/opsx:propose "" +``` +This creates `openspec/changes//` with `proposal.md`, `specs/`, `design.md`, `tasks.md`. + +**The workflow:** `/opsx:propose` → `/opsx:apply` → `/opsx:archive` + +**Schema:** `mendix-widget` (default in `openspec/config.yaml`). Provides Mendix-aware templates with XML impact tracking, Mendix API guidance, and versioning tasks. + +**Monorepo conventions spec:** `openspec/specs/conventions/spec.md` — source of truth for shared widget development requirements. + +**Initializing a new widget:** +``` +cd packages/pluggableWidgets/-web +openspec init +# Then edit openspec/config.yaml with widget-specific context +``` + +**Agent rules:** `.agents/rules/mendix-widget.md` and `.agents/rules/react-patterns.md` contain concise, tool-agnostic coding rules for this repo. + ## Agent-Specific Instructions - **Claude Code** — See `CLAUDE.md` for hooks and auto-imported documentation diff --git a/CLAUDE.md b/CLAUDE.md index 82b55f9f37..0f34b22905 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,3 +17,13 @@ @docs/requirements/app-flow.md @docs/requirements/backend-structure.md @docs/requirements/project-requirements-document.md +@openspec/specs/conventions/spec.md +@openspec/schemas/mendix-widget/schema.yaml + +## OpenSpec + +Per-widget `openspec/` directories exist inside widget packages that have been initialized +(e.g., `packages/pluggableWidgets/datagrid-web/openspec/`). When working inside a widget +directory, check for `openspec/specs/spec.md` as the source of truth for current behavior, +and `openspec/changes/` for any active change proposals. Use `/opsx:propose` before +implementing non-trivial behavior changes. diff --git a/openspec/README.md b/openspec/README.md new file mode 100644 index 0000000000..53d1106318 --- /dev/null +++ b/openspec/README.md @@ -0,0 +1,98 @@ +# OpenSpec in This Repository + +Spec-driven development for Mendix pluggable widgets. We use +[OpenSpec](https://github.com/Fission-AI/OpenSpec) to align on what to build +before any code is written. + +## Quick Start + +```bash +# Install the CLI (one-time) +npm install -g @fission-ai/openspec@latest + +# Navigate to a widget you want to change +cd packages/pluggableWidgets/-web + +# Propose a change +/opsx:propose "" + +# Implement the generated tasks +/opsx:apply + +# Archive when done (merges delta specs into source of truth) +/opsx:archive +``` + +## Structure + +``` +openspec/ # Monorepo-level +├── specs/ +│ └── conventions/spec.md # Shared widget dev conventions (source of truth) +├── schemas/ +│ └── mendix-widget/ # Custom schema for widget development +│ ├── schema.yaml # Artifact definitions + instructions +│ └── templates/ # AI generation templates +│ ├── proposal.md +│ ├── spec.md +│ ├── design.md +│ └── tasks.md +└── config.yaml # Monorepo context + default schema + +packages/pluggableWidgets/-web/ # Per-widget (opt-in) +└── openspec/ + ├── specs/spec.md # Widget behavior source of truth + ├── changes/ # Active proposals (one folder per change) + │ └── / + │ ├── proposal.md + │ ├── specs/spec.md # Delta spec (ADDED/MODIFIED/REMOVED) + │ ├── design.md + │ └── tasks.md + └── config.yaml # Widget-specific context + +.agents/rules/ # Tool-agnostic coding rules +├── mendix-widget.md # Mendix API guards, versioning, styling +└── react-patterns.md # React hooks, MobX, accessibility +``` + +## Pilot Widgets + +These widgets have OpenSpec initialized and baseline specs: + +| Widget | Spec | Status | +| -------------- | ------------------------------------------------------------------------------------------ | -------- | +| `datagrid-web` | [openspec/specs/spec.md](../packages/pluggableWidgets/datagrid-web/openspec/specs/spec.md) | baseline | +| `combobox-web` | [openspec/specs/spec.md](../packages/pluggableWidgets/combobox-web/openspec/specs/spec.md) | baseline | +| `gallery-web` | [openspec/specs/spec.md](../packages/pluggableWidgets/gallery-web/openspec/specs/spec.md) | baseline | + +## Initializing a New Widget + +```bash +cd packages/pluggableWidgets/-web +openspec init +``` + +Then edit `openspec/config.yaml` to add widget-specific context (architecture, +key XML properties, data dependencies). Use the pilot widgets as reference. + +## When to Use OpenSpec + +| Situation | Use OpenSpec? | +| ----------------------------------- | --------------------------- | +| New feature or behavior change | Yes — `/opsx:propose` first | +| XML property added/modified/removed | Yes — `/opsx:propose` first | +| Bug fix | No | +| Refactor (no behavior change) | No | +| Test-only change | No | +| Docs update | No | + +## The `mendix-widget` Schema + +The custom schema (`openspec/schemas/mendix-widget/`) generates four artifacts: + +1. **`proposal.md`** — Intent, Mendix XML impact, scope, release checklist +2. **`specs//spec.md`** — Delta specs (ADDED/MODIFIED/REMOVED) in Given/When/Then +3. **`design.md`** — Mendix API usage, component hierarchy, file changes +4. **`tasks.md`** — Grouped checklist: XML/Types → Implementation → Tests → Versioning + +Dependency order: `proposal → specs + design → tasks → implement` diff --git a/openspec/config.yaml b/openspec/config.yaml new file mode 100644 index 0000000000..1b122a1431 --- /dev/null +++ b/openspec/config.yaml @@ -0,0 +1,65 @@ +schema: mendix-widget + +context: | + Mendix pluggable widgets monorepo (TypeScript, React, SCSS, Rollup). + 52 widget packages under packages/pluggableWidgets/*-web. + + ## Mendix API + - EditableValue: read/write bound attribute values (setValue(), status, readOnly) + - ActionValue: executable actions — ALWAYS check canExecute before execute() + - ListValue: paginated data source with items and setLimit() + - DynamicValue: dynamic expressions evaluated at runtime + - Render loading/empty states until values are available (status === "loading") + + ## Tech stack + - TypeScript strict mode, React functional components + hooks + - SCSS for styling, Atlas UI classes preferred (btn, badge, etc.) + - BEM-like class naming with widget prefix (e.g., .widget-datagrid-column) + - Rollup via @mendix/pluggable-widgets-tools + - Jest + React Testing Library for unit tests (src/**/__tests__/*.spec.ts) + - Playwright for E2E (e2e/*.spec.js) + - pnpm workspaces + Turbo + + ## Widget anatomy + - .tsx — entry point with Mendix ContainerProps + - .xml — property schema (lowerCamelCase keys, must match TS types) + - .editorConfig.ts — Studio Pro design-mode config + - components/ — React components + - ui/ — SCSS files + - src/**/__tests__/ — unit tests + - e2e/ — Playwright E2E tests + + ## Conventions + - Check ActionValue.canExecute before execute() + - XML keys: lowerCamelCase, must match TypeScript props exactly + - Semver bump + CHANGELOG.md update required for runtime/XML/behavior changes + - Conventional commits: type(scope): description + - Atlas UI classes preferred over custom styles + - Never override core Atlas UI classes + + ## Testing + - Unit tests: pnpm run test (from widget dir, NOT root) + - E2E: pnpm e2e (headless) or pnpm e2edev (with GUI debugger) + - Builders from @mendix/widget-plugin-test-utils for mocking Mendix APIs + +rules: + proposal: + - Explicitly state whether XML properties change (new/modified/removed) + - Identify if changes require a CHANGELOG.md entry and semver bump + - Note if Studio Pro UI (editorConfig/editorPreview) needs updating + - Call out any Atlas UI class usage or design-system implications + specs: + - Use Given/When/Then format for all scenarios + - Reference existing Mendix API behavior (canExecute, status checks) where applicable + - Cover: happy path, loading state, empty state, error state, accessibility + - Do not include implementation details (component names, class names) + design: + - Describe Mendix API usage (which EditableValue/ActionValue/ListValue props are involved) + - List files to add/modify (src/, ui/, *.xml) + - Include component hierarchy if new components are introduced + - Note MobX store usage if applicable + tasks: + - Group under headings: 1. XML & TypeScript types, 2. Implementation, 3. Tests, 4. Versioning + - Include unit test tasks for every new behavior + - Include E2E task if user-visible behavior changes + - Final task must be "Update CHANGELOG.md and bump semver" (if applicable) diff --git a/openspec/schemas/mendix-widget/schema.yaml b/openspec/schemas/mendix-widget/schema.yaml new file mode 100644 index 0000000000..f602eebcdc --- /dev/null +++ b/openspec/schemas/mendix-widget/schema.yaml @@ -0,0 +1,67 @@ +name: mendix-widget +version: 1 +description: Spec-driven workflow for Mendix pluggable widget development + +artifacts: + - id: proposal + generates: proposal.md + description: Feature intent, Mendix XML impact, and scope + template: proposal.md + instruction: | + Create a proposal for this widget change. Focus on: + - WHY this change is needed (user problem or technical improvement) + - WHAT Mendix XML properties are added, modified, or removed + - SCOPE: what's in scope, what's explicitly out of scope + - Whether a CHANGELOG.md entry and semver bump will be required + Keep it concise. Avoid implementation details. + requires: [] + + - id: specs + generates: specs/**/*.md + description: Behavior requirements and scenarios for the widget + template: spec.md + instruction: | + Create delta specs describing the behavioral changes to this widget. + Use ADDED/MODIFIED/REMOVED sections. + Each requirement MUST have at least one Given/When/Then scenario. + Cover: happy path, loading states, empty states, error states, accessibility. + Do NOT reference component names, class names, or implementation details. + Specs should be testable — a QA engineer should be able to verify them manually. + requires: + - proposal + + - id: design + generates: design.md + description: Technical implementation approach + template: design.md + instruction: | + Create a technical design for this widget change. Include: + - Which Mendix API values are involved (EditableValue, ActionValue, etc.) + - Component hierarchy changes (new components, modified components) + - SCSS changes (new classes, modified styles) + - XML property changes (new properties, modified defaults) + - MobX store changes (if applicable) + - List of files to add/modify + Keep implementation decisions justified and pragmatic. + requires: + - proposal + + - id: tasks + generates: tasks.md + description: Implementation checklist for this widget change + template: tasks.md + instruction: | + Create an implementation checklist. Group tasks under these headings: + 1. XML & TypeScript types (XML changes, typings, editorConfig) + 2. Implementation (component changes, hooks, stores) + 3. Tests (unit tests, E2E tests) + 4. Versioning (CHANGELOG.md, semver bump) — only if runtime/XML/behavior changes + Use hierarchical numbering (1.1, 1.2, 2.1, etc.). + Each task should be completable in one focused coding session. + requires: + - specs + - design + +apply: + requires: [tasks] + tracks: tasks.md diff --git a/openspec/schemas/mendix-widget/templates/design.md b/openspec/schemas/mendix-widget/templates/design.md new file mode 100644 index 0000000000..f0567007f2 --- /dev/null +++ b/openspec/schemas/mendix-widget/templates/design.md @@ -0,0 +1,61 @@ +## Technical Approach + + + +## Mendix API Usage + + + +## Component Changes + + + +## XML Changes + + + +## SCSS Changes + + + +## MobX / State Changes + + + +## File Changes + +``` +src/components/NewComponent.tsx (new) +src/components/ExistingComponent.tsx (modify) +src/ui/widget-name.scss (modify) +src/WidgetName.xml (modify) +src/WidgetName.editorConfig.ts (modify if needed) +``` diff --git a/openspec/schemas/mendix-widget/templates/proposal.md b/openspec/schemas/mendix-widget/templates/proposal.md new file mode 100644 index 0000000000..5c2921b7e1 --- /dev/null +++ b/openspec/schemas/mendix-widget/templates/proposal.md @@ -0,0 +1,36 @@ +## Intent + + + +## Mendix XML Impact + + + +## Scope + +**In scope:** + +- **Out of scope:** + +- + +## Release Impact + + + +- [ ] Requires `CHANGELOG.md` update +- [ ] Requires semver bump — patch / minor / major (delete inapplicable) +- [ ] Requires `editorConfig.ts` / `editorPreview.tsx` update (Studio Pro UI) +- [ ] Requires docs PR in mendix/docs (XML or behavior changes visible to Mendix users) diff --git a/openspec/schemas/mendix-widget/templates/spec.md b/openspec/schemas/mendix-widget/templates/spec.md new file mode 100644 index 0000000000..ce9c5b88e6 --- /dev/null +++ b/openspec/schemas/mendix-widget/templates/spec.md @@ -0,0 +1,51 @@ +# — Spec Delta + +## Purpose + + + +--- + +## ADDED Requirements + + + +## MODIFIED Requirements + + + +## REMOVED Requirements + + diff --git a/openspec/schemas/mendix-widget/templates/tasks.md b/openspec/schemas/mendix-widget/templates/tasks.md new file mode 100644 index 0000000000..38265d65b9 --- /dev/null +++ b/openspec/schemas/mendix-widget/templates/tasks.md @@ -0,0 +1,21 @@ +# Tasks + +## 1. XML & TypeScript Types + +- [ ] 1.1 +- [ ] 1.2 + +## 2. Implementation + +- [ ] 2.1 +- [ ] 2.2 + +## 3. Tests + +- [ ] 3.1 Add unit tests for in `src/**/__tests__/.spec.ts` +- [ ] 3.2 Update E2E test in `e2e/` (if user-visible behavior changes) + +## 4. Versioning + +- [ ] 4.1 Update `CHANGELOG.md` with the change description +- [ ] 4.2 Bump semver in `package.json` (patch / minor / major) diff --git a/openspec/specs/conventions/spec.md b/openspec/specs/conventions/spec.md new file mode 100644 index 0000000000..40f61a2ed1 --- /dev/null +++ b/openspec/specs/conventions/spec.md @@ -0,0 +1,170 @@ +# Mendix Web Widgets — Development Conventions + +## Purpose + +Shared behavioral contracts and process standards for all widget packages in this +monorepo. These requirements describe expected developer-facing behavior that every +widget MUST comply with. They serve as the source of truth for code review and +agent-assisted development. + +--- + +## Requirements + +### Requirement: XML ↔ TypeScript Alignment + +Widget XML property keys SHALL use lowerCamelCase and MUST match the corresponding +TypeScript prop names exactly. + +#### Scenario: New XML property added + +- GIVEN a developer adds a new property to `.xml` +- WHEN the property has any supported Mendix type (string, integer, boolean, action, attribute, datasource, etc.) +- THEN the corresponding TypeScript prop interface MUST be updated to match +- AND the prop key MUST be identical to the XML key in lowerCamelCase + +#### Scenario: XML property renamed + +- GIVEN a developer renames an existing XML property +- WHEN the change is merged +- THEN the TypeScript prop name MUST be updated to match the new XML key +- AND all usages of the old prop name in `.tsx` and `editorConfig.ts` MUST be updated + +--- + +### Requirement: ActionValue Guard + +The widget SHALL check `ActionValue.canExecute` before calling `execute()` on any action prop. + +#### Scenario: Action triggered by user interaction + +- GIVEN a widget has an `ActionValue` prop +- WHEN a user triggers the action (click, keypress, programmatic trigger) +- THEN the widget MUST verify `canExecute === true` before calling `execute()` +- AND if `canExecute` is false, the interaction SHALL be silently ignored or the trigger element disabled + +--- + +### Requirement: Loading State Rendering + +The widget SHALL render a loading or placeholder state until all required Mendix API values are available. + +#### Scenario: Data source loading on mount + +- GIVEN a widget depends on a `ListValue` or `EditableValue` +- WHEN the widget mounts and the value's `status` is `"loading"` +- THEN the widget SHALL NOT render its full interactive content +- AND a loading indicator or empty placeholder SHALL be displayed + +#### Scenario: Value becomes available + +- GIVEN a widget was showing a loading state +- WHEN the value's `status` transitions to `"available"` +- THEN the widget SHALL render its full content using the now-available data + +--- + +### Requirement: Semver Versioning + +Each widget package MUST receive a semver bump whenever runtime code, public API, +XML schema, or observable behavior changes. + +#### Scenario: Behavior change merged + +- GIVEN a PR modifies runtime widget behavior, XML properties, or the public API +- WHEN the PR is reviewed and approved +- THEN `package.json` version MUST be incremented (patch / minor / major as appropriate) +- AND `CHANGELOG.md` MUST contain an entry in Keep-a-Changelog format describing the change + +#### Scenario: Refactor or test-only change + +- GIVEN a PR modifies only internal implementation, tests, or documentation (no observable behavior change) +- WHEN the PR is reviewed +- THEN a version bump is NOT required +- AND the author MUST confirm no behavior change in the PR description + +--- + +### Requirement: Atlas UI Class Usage + +Widgets SHALL use Atlas UI classes for common UI elements and MUST NOT override core Atlas classes. + +#### Scenario: Button rendered inside a widget + +- GIVEN a widget renders a clickable button element +- WHEN the button requires primary, secondary, or other standard styling +- THEN Atlas classes (e.g., `btn`, `btn-primary`, `btn-secondary`) SHALL be used +- AND custom CSS MUST NOT redefine or override core Atlas class definitions + +#### Scenario: Custom styling needed beyond Atlas + +- GIVEN a widget requires visual styling that Atlas classes do not cover +- WHEN the developer writes custom SCSS +- THEN the custom styles MUST wrap the element in a widget-specific selector +- AND MUST NOT modify global Atlas class definitions + +--- + +### Requirement: SCSS Class Naming + +Custom CSS classes MUST be prefixed with the widget name using BEM-like conventions. + +#### Scenario: Widget renders a custom DOM element with a class + +- GIVEN a widget adds a custom CSS class to a DOM element +- WHEN the class is not a standard Atlas UI class +- THEN the class name MUST start with the widget name prefix (e.g., `widget-datagrid-`, `widget-combobox-`) + +--- + +### Requirement: Unit Test Coverage + +New features and bug fixes MUST include corresponding unit tests using Jest and React Testing Library. + +#### Scenario: Feature added to a widget + +- GIVEN a developer implements a new feature in a widget +- WHEN the feature is submitted as a PR +- THEN unit tests MUST exist in `src/**/__tests__/*.spec.ts` covering the new behavior +- AND tests MUST use `@mendix/widget-plugin-test-utils` builders for Mendix API mocks +- AND tests MUST NOT use Enzyme — use RTL (`render`, `screen`) instead + +#### Scenario: Bug fix submitted + +- GIVEN a developer fixes a bug +- WHEN the fix is submitted as a PR +- THEN a regression test MUST be added that would have caught the original bug + +--- + +### Requirement: Conventional Commits + +All commits MUST follow the Conventional Commits specification. + +#### Scenario: Developer commits code + +- GIVEN a developer commits code to the monorepo +- WHEN the commit message is written +- THEN it MUST follow the pattern: `type(scope): description` +- AND `type` MUST be one of: `feat`, `fix`, `refactor`, `chore`, `docs`, `test`, `perf`, `ci` +- AND `scope` SHOULD be the widget package name (e.g., `datagrid`, `combobox`) + +--- + +### Requirement: OpenSpec Change Proposal + +Non-trivial changes to widget behavior, XML schema, or public API SHOULD be preceded +by an OpenSpec change proposal before implementation begins. + +#### Scenario: Developer starts a new feature + +- GIVEN a developer plans to implement a new feature or behavior change in a widget +- WHEN the change affects the widget's observable behavior, XML schema, or public API +- THEN `/opsx:propose ` SHOULD be run in the widget's directory first +- AND the generated `openspec/changes//proposal.md` SHOULD be reviewed before coding starts + +#### Scenario: Bug fix or minor refactor + +- GIVEN a developer is fixing a bug or doing a minor refactor +- WHEN the change does not add new user-visible behavior +- THEN an OpenSpec proposal is NOT required diff --git a/package.json b/package.json index 5f8488a6e3..1d3173b93e 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "verify": "turbo run verify --continue --concurrency 1" }, "devDependencies": { + "@fission-ai/openspec": "latest", "husky": "^8.0.3", "turbo": "^2.5.4" }, diff --git a/packages/pluggableWidgets/combobox-web/AGENTS.md b/packages/pluggableWidgets/combobox-web/AGENTS.md index 492e5e7b10..9c78cedf9a 100644 --- a/packages/pluggableWidgets/combobox-web/AGENTS.md +++ b/packages/pluggableWidgets/combobox-web/AGENTS.md @@ -1,5 +1,21 @@ # Combobox-Web Widget — Agent Context +## OpenSpec + +This widget has OpenSpec initialized. Before implementing any new feature, +behavior change, or XML property change, run: + +``` +cd packages/pluggableWidgets/combobox-web +/opsx:propose "" +``` + +- **Current behavior spec:** `openspec/specs/spec.md` +- **Active changes:** `openspec/changes/` (check here first before starting work) +- **Schema:** `mendix-widget` (see `openspec/config.yaml` for widget-specific context) + +--- + ## Overview Configurable dropdown widget supporting single/multi-selection across 3 data source modes: **context** (association, enum, boolean), **database**, and **static**. Uses React + TypeScript with Downshift for accessible combobox behavior. No MobX — pure React state + class-based selectors/providers. diff --git a/packages/pluggableWidgets/combobox-web/openspec/changes/.gitkeep b/packages/pluggableWidgets/combobox-web/openspec/changes/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/pluggableWidgets/combobox-web/openspec/config.yaml b/packages/pluggableWidgets/combobox-web/openspec/config.yaml new file mode 100644 index 0000000000..319f19a0e8 --- /dev/null +++ b/packages/pluggableWidgets/combobox-web/openspec/config.yaml @@ -0,0 +1,31 @@ +schema: mendix-widget + +context: | + Widget: combobox-web (Combo Box) + Version: 2.8.0 | Min Mendix: 10.22.0 + Package: @mendix/widget-combobox-web + + ## Domain + Configurable dropdown widget supporting single and multi-selection across + 3 data source modes: context (association, enum, boolean), database, and static. + Uses Downshift for accessible combobox behavior. No MobX — pure React state + + class-based Selector/Provider pattern. + + ## Key XML Properties + - dataSourceType (enum: "context" | "database" | "static") — selection mode + - optionsSourceAssociation / optionsSourceDatabaseAssociation — association value + - optionsSourceDatabaseItemCaption (DynamicValue) — item display text + - selectedItemsStyle (enum: "text" | "boxes") — multi-select display mode + - filterType (enum: "contains" | "startsWith" | "none") — search behavior + - clearable (bool) — show clear button + - lazyLoading (bool) — defer option loading until dropdown opens + - multiSelect (bool) — enable multi-selection + - onChangeAction (ActionValue) — triggered on selection change + - readOnly (bool) — read-only mode + + ## Architecture + - Entry: src/Combobox.tsx — calls useGetSelector(props) → SingleSelection or MultiSelection + - No MobX; uses React state + class hierarchy (Selector, Provider) + - Key abstractions: Selector (data access), Provider (data source adapter) + - Accessibility via Downshift hooks (useCombobox, useMultipleSelection) + - Key deps: downshift, match-sorter, classnames diff --git a/packages/pluggableWidgets/combobox-web/openspec/specs/spec.md b/packages/pluggableWidgets/combobox-web/openspec/specs/spec.md new file mode 100644 index 0000000000..8dea9c01c1 --- /dev/null +++ b/packages/pluggableWidgets/combobox-web/openspec/specs/spec.md @@ -0,0 +1,175 @@ +# Combo Box — Behavior Specification + +## Purpose + +Configurable dropdown selection widget for Mendix applications. Supports single +and multi-selection from three data source modes (context, database, static), +with optional search/filter, lazy loading, clear, and accessibility via keyboard. + +--- + +## Requirements + +### Requirement: Single Selection + +The widget SHALL allow selecting exactly one option at a time from the configured +data source. + +#### Scenario: User opens dropdown and selects an item + +- GIVEN the widget is in single-select mode and the datasource has items +- WHEN the user clicks the input or arrow to open the dropdown +- THEN the option list SHALL be displayed +- AND when the user clicks an option +- THEN that option SHALL become the selected value +- AND the dropdown SHALL close + +#### Scenario: Selected value displayed in input + +- GIVEN an option is selected +- WHEN the widget renders +- THEN the selected option's caption SHALL be shown in the input field + +--- + +### Requirement: Multi-Selection + +The widget SHALL allow selecting multiple options simultaneously when configured +for multi-select mode. + +#### Scenario: User selects multiple options + +- GIVEN the widget is in multi-select mode +- WHEN the user clicks multiple options in the dropdown +- THEN all clicked options SHALL be added to the selection +- AND the dropdown SHALL remain open between selections + +#### Scenario: Selected items displayed as badges + +- GIVEN multiple items are selected and selectedItemsStyle is "boxes" +- WHEN the widget renders +- THEN each selected item SHALL be displayed as a removable badge/tag + +#### Scenario: Select All in multi-select + +- GIVEN multi-select mode is active and a "Select All" option is available +- WHEN the user clicks "Select All" +- THEN all currently visible options SHALL be selected + +--- + +### Requirement: Search / Filter + +The widget SHALL filter visible options based on user-typed input in the search field. + +#### Scenario: User types in search field + +- GIVEN filterType is "contains" or "startsWith" +- WHEN the user types characters in the input +- THEN the option list SHALL update to show only matching options +- AND non-matching options SHALL be hidden + +#### Scenario: No matching options + +- GIVEN the user typed a search term that matches no options +- WHEN the option list renders +- THEN a "no options" placeholder SHALL be displayed + +#### Scenario: Filter disabled + +- GIVEN filterType is "none" +- WHEN the user types in the input +- THEN the option list SHALL NOT change — all options remain visible + +--- + +### Requirement: Clear Selection + +The widget SHALL allow clearing the current selection via a dedicated clear button +when clearable is enabled. + +#### Scenario: Clear button visible with selection + +- GIVEN clearable is true and at least one item is selected +- WHEN the widget renders +- THEN a clear button SHALL be visible + +#### Scenario: User clicks clear + +- GIVEN the clear button is visible +- WHEN the user clicks it +- THEN the selection SHALL be reset to empty +- AND the clear button SHALL disappear + +--- + +### Requirement: Lazy Loading + +The widget SHALL defer loading of database options until the dropdown is first +opened when lazyLoading is enabled. + +#### Scenario: Dropdown opens with lazy loading enabled + +- GIVEN lazyLoading is true and the datasource is a database datasource +- WHEN the widget mounts +- THEN the datasource SHALL NOT be fetched immediately +- AND when the user first opens the dropdown +- THEN options SHALL be loaded and displayed + +--- + +### Requirement: Read-Only Mode + +The widget SHALL display the current selection without allowing interaction when +in read-only mode. + +#### Scenario: Widget rendered read-only + +- GIVEN readOnly is true +- WHEN the widget renders +- THEN the selected value SHALL be displayed +- AND the dropdown SHALL NOT open on click +- AND the clear button SHALL NOT be visible + +--- + +### Requirement: Change Action + +The widget SHALL execute the configured `onChangeAction` when the selection changes. + +#### Scenario: Selection changes + +- GIVEN `onChangeAction` is configured and `canExecute` is true +- WHEN the user selects or deselects an option +- THEN the action SHALL be executed + +--- + +### Requirement: Keyboard Navigation + +The widget SHALL support full keyboard navigation for accessibility. + +#### Scenario: Open with keyboard + +- GIVEN the input is focused +- WHEN the user presses the down arrow or Enter key +- THEN the dropdown SHALL open and focus the first option + +#### Scenario: Navigate options + +- GIVEN the dropdown is open +- WHEN the user presses the down or up arrow keys +- THEN focus SHALL move to the next or previous option respectively + +#### Scenario: Select with keyboard + +- GIVEN an option is focused in the dropdown +- WHEN the user presses Enter or Space +- THEN that option SHALL be selected (or toggled in multi-select) + +#### Scenario: Close with Escape + +- GIVEN the dropdown is open +- WHEN the user presses Escape +- THEN the dropdown SHALL close +- AND focus SHALL return to the input diff --git a/packages/pluggableWidgets/datagrid-web/AGENTS.md b/packages/pluggableWidgets/datagrid-web/AGENTS.md new file mode 100644 index 0000000000..d00627196e --- /dev/null +++ b/packages/pluggableWidgets/datagrid-web/AGENTS.md @@ -0,0 +1,45 @@ +# Data Grid 2 — Agent Context + +## OpenSpec + +This widget has OpenSpec initialized. Before implementing any new feature, +behavior change, or XML property change, run: + +``` +cd packages/pluggableWidgets/datagrid-web +/opsx:propose "" +``` + +- **Current behavior spec:** `openspec/specs/spec.md` +- **Active changes:** `openspec/changes/` (check here first before starting work) +- **Schema:** `mendix-widget` (see `openspec/config.yaml` for widget-specific context) + +--- + +## Overview + +Complex data grid widget. Renders rows from a `ListValue` datasource with +configurable columns, pagination, sorting, row selection, column resizing/reordering, +filtering integration, and data export. + +- **Version:** 3.10.0 | **Min Mendix:** 10.0.0 +- **Architecture:** MobX stores + brandi-react DI container +- **Key deps:** MobX, mobx-react-lite, brandi-react, @mendix/widget-plugin-filtering + +--- + +## Key Patterns + +- Entry point: `src/Datagrid.tsx` — wraps everything in a brandi DI `ContainerProvider` +- All state lives in MobX stores, not React state +- Feature modules under `src/features/` are self-contained slices (pagination, selection, export, etc.) +- Pure presentational components in `src/components/` +- Use `runInAction` for async state mutations in stores + +--- + +## Constraints + +- Never use React state for data-layer concerns — use the existing MobX stores +- Column configuration is immutable after mount — do not mutate column descriptors +- Filtering is owned by child filter widgets; the grid only consumes the filter result diff --git a/packages/pluggableWidgets/datagrid-web/openspec/changes/.gitkeep b/packages/pluggableWidgets/datagrid-web/openspec/changes/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/pluggableWidgets/datagrid-web/openspec/config.yaml b/packages/pluggableWidgets/datagrid-web/openspec/config.yaml new file mode 100644 index 0000000000..30d465b01b --- /dev/null +++ b/packages/pluggableWidgets/datagrid-web/openspec/config.yaml @@ -0,0 +1,36 @@ +schema: mendix-widget + +context: | + Widget: datagrid-web (Data Grid 2) + Version: 3.10.0 | Min Mendix: 10.0.0 + Package: @mendix/widget-datagrid-web + + ## Domain + Tabular data display widget. Renders rows from a ListValue datasource with + configurable columns, sorting, filtering (via child filter widgets), pagination, + row selection, column resizing/reordering, and data export. + + ## Key XML Properties + - datasource (ListValue) — the data source providing rows + - columns (list) — column definitions: attribute, caption, width, sortable, resizable, visible + - pagination (enum: "buttons" | "virtualScrolling" | "loadMore" | "off") + - pageSize (int) — rows per page + - selectionMode (enum: "None" | "Single" | "Multi") — row selection behavior + - sortingType (enum: "server" | "client") + - filtering (bool) — whether filter bar is shown + - exportEnabled (bool) — whether data export button is shown + - emptyMessageWidget (widget slot) — content when datasource returns no rows + - onClickRow (ActionValue) — row click action + + ## Architecture + - Entry: src/Datagrid.tsx (ContainerProvider with brandi-react DI container) + - State: MobX stores (not pure React state) + - Key stores: pagination, selection, column configuration, data export + - Features split under src/features/ (pagination, selection, data-export, etc.) + - Components under src/components/ (pure React presentational) + + ## Dependencies + - brandi-react (DI container) + - MobX + mobx-react-lite + - @mendix/widget-plugin-platform + - @mendix/widget-plugin-filtering (filter integration) diff --git a/packages/pluggableWidgets/datagrid-web/openspec/specs/spec.md b/packages/pluggableWidgets/datagrid-web/openspec/specs/spec.md new file mode 100644 index 0000000000..22476fe3f6 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-web/openspec/specs/spec.md @@ -0,0 +1,189 @@ +# Data Grid 2 — Behavior Specification + +## Purpose + +Tabular data display and interaction for Mendix applications. Renders rows from +a configured ListValue datasource with support for column configuration, sorting, +pagination, row selection, filtering, column resizing and reordering, and data export. + +--- + +## Requirements + +### Requirement: Tabular Data Rendering + +The widget SHALL render datasource items as rows in a table structure, with each +column mapped to a configured attribute or custom content slot. + +#### Scenario: Data available on mount + +- GIVEN a datasource is configured with items +- WHEN the widget mounts and the datasource status is `"available"` +- THEN all items SHALL be displayed as rows +- AND each column SHALL display the attribute value or content for each item + +#### Scenario: Loading state + +- GIVEN a datasource is configured +- WHEN the datasource status is `"loading"` +- THEN the widget SHALL display a loading indicator +- AND no data rows SHALL be rendered + +#### Scenario: Empty datasource + +- GIVEN a datasource returns zero items +- WHEN the widget renders +- THEN the empty message widget SHALL be displayed in place of data rows + +--- + +### Requirement: Column Configuration + +The widget SHALL support per-column configuration including caption, content, +visibility, width, sortability, and resizability. + +#### Scenario: Column with static width + +- GIVEN a column is configured with a fixed width +- WHEN the widget renders +- THEN the column SHALL occupy that fixed width regardless of container size + +#### Scenario: Column hidden by default + +- GIVEN a column has its visible property set to false +- WHEN the widget renders +- THEN the column SHALL NOT appear in the grid header or rows + +--- + +### Requirement: Server-Side Sorting + +The widget SHALL support sorting rows by a column's attribute value, delegating +sort order to the datasource. + +#### Scenario: User clicks a sortable column header once + +- GIVEN a column is configured as sortable +- WHEN the user clicks the column header +- THEN the datasource SHALL be sorted ascending by that column's attribute +- AND a visual indicator SHALL appear showing ascending sort direction + +#### Scenario: User clicks the same sortable column header again + +- GIVEN the column is already sorted ascending +- WHEN the user clicks the header again +- THEN the sort order SHALL toggle to descending +- AND the visual indicator SHALL update accordingly + +#### Scenario: User clicks a non-sortable column header + +- GIVEN a column is configured as not sortable +- WHEN the user clicks the header +- THEN no sort change SHALL occur + +--- + +### Requirement: Pagination + +The widget SHALL support paginating through datasource items, with configurable +page size and pagination control style. + +#### Scenario: Next page navigation + +- GIVEN pagination is enabled and the current page is not the last +- WHEN the user clicks the "next page" control +- THEN the next page of items SHALL be loaded and displayed +- AND the current page indicator SHALL update + +#### Scenario: Page size change + +- GIVEN the page size control is visible +- WHEN the user selects a different page size +- THEN the datasource SHALL reload with the new limit +- AND the display SHALL return to the first page + +--- + +### Requirement: Row Selection + +The widget SHALL support row selection in Single and Multi modes when configured. + +#### Scenario: Single-mode row click + +- GIVEN selectionMode is "Single" +- WHEN the user clicks a row +- THEN that row SHALL become selected +- AND any previously selected row SHALL be deselected +- AND the row's visual state SHALL reflect selection + +#### Scenario: Multi-mode checkbox selection + +- GIVEN selectionMode is "Multi" with checkbox column enabled +- WHEN the user checks a row's checkbox +- THEN that row SHALL be added to the selection set +- AND already-selected rows SHALL remain selected + +#### Scenario: Selection disabled + +- GIVEN selectionMode is "None" +- WHEN the user clicks a row +- THEN no selection state change SHALL occur + +--- + +### Requirement: Row Click Action + +The widget SHALL execute the configured `onClickRow` action when a row is clicked, +subject to `canExecute`. + +#### Scenario: Row clicked with valid action + +- GIVEN `onClickRow` is configured and `canExecute` is true +- WHEN the user clicks a row +- THEN the action SHALL be executed with that row's object as context + +--- + +### Requirement: Column Resizing + +The widget SHALL allow users to drag column dividers to resize column widths +when resizing is enabled for a column. + +#### Scenario: Column resize drag + +- GIVEN a column has resizing enabled +- WHEN the user drags the column's resize handle +- THEN the column width SHALL update in real time to match the drag position + +--- + +### Requirement: Column Visibility Toggle + +The widget SHALL provide a column selector allowing users to show or hide +individual columns at runtime. + +#### Scenario: User hides a column + +- GIVEN the column selector is open +- WHEN the user unchecks a column +- THEN that column SHALL disappear from the grid + +#### Scenario: User re-shows a column + +- GIVEN a column is hidden via the selector +- WHEN the user checks it in the column selector +- THEN the column SHALL reappear in its original position + +--- + +### Requirement: Data Export + +The widget SHALL support exporting visible data to a downloadable format when +export is enabled. + +#### Scenario: Export triggered + +- GIVEN exportEnabled is true +- WHEN the user clicks the export button +- THEN the currently displayed data SHALL be exported +- AND a file download SHALL be initiated diff --git a/packages/pluggableWidgets/gallery-web/AGENTS.md b/packages/pluggableWidgets/gallery-web/AGENTS.md new file mode 100644 index 0000000000..c6a5c723fc --- /dev/null +++ b/packages/pluggableWidgets/gallery-web/AGENTS.md @@ -0,0 +1,34 @@ +# Gallery — Agent Context + +## OpenSpec + +This widget has OpenSpec initialized. Before implementing any new feature, +behavior change, or XML property change, run: + +``` +cd packages/pluggableWidgets/gallery-web +/opsx:propose "" +``` + +- **Current behavior spec:** `openspec/specs/spec.md` +- **Active changes:** `openspec/changes/` (check here first before starting work) +- **Schema:** `mendix-widget` (see `openspec/config.yaml` for widget-specific context) + +--- + +## Overview + +Grid-based data display widget. Renders items from a `ListValue` datasource as +a configurable multi-column grid with pagination, sorting, filtering integration, +and item click/selection actions. + +- **Version:** 3.10.0 | **Min Mendix:** 10.0.0 +- **Key deps:** @mendix/widget-plugin-filtering, @mendix/widget-plugin-sorting + +--- + +## Key Patterns + +- Entry point: `src/Gallery.tsx` — functional component with `ContainerProps` +- Shares filtering and pagination patterns with datagrid via shared plugin packages +- Item rendering uses Mendix widget content slots (not inline React) diff --git a/packages/pluggableWidgets/gallery-web/openspec/changes/.gitkeep b/packages/pluggableWidgets/gallery-web/openspec/changes/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/pluggableWidgets/gallery-web/openspec/config.yaml b/packages/pluggableWidgets/gallery-web/openspec/config.yaml new file mode 100644 index 0000000000..d2b96f7299 --- /dev/null +++ b/packages/pluggableWidgets/gallery-web/openspec/config.yaml @@ -0,0 +1,29 @@ +schema: mendix-widget + +context: | + Widget: gallery-web (Gallery) + Version: 3.10.0 | Min Mendix: 10.0.0 + Package: @mendix/widget-gallery-web + + ## Domain + Grid-based data display widget. Renders items from a ListValue datasource as + a configurable grid with pagination, sorting, filtering (via child filter widgets), + and item click/selection actions. + + ## Key XML Properties + - datasource (ListValue) — the data source providing items + - content (widget slot per item) — item template rendered per row + - itemClass (DynamicValue) — dynamic CSS class per item + - pagination (enum: "buttons" | "virtualScrolling" | "loadMore" | "off") + - pageSize (int) — items per page + - numberOfColumns (int) — grid column count + - selectionMode (enum: "None" | "Single" | "Multi") + - onClickItem (ActionValue) — item click action + - emptyMessageWidget (widget slot) — content when datasource returns no items + - sortingType (enum: "server" | "client") + - filtering (bool) — whether filter bar is shown + + ## Architecture + - Entry: src/Gallery.tsx (functional component with ContainerProps) + - Uses similar data plugin pattern as datagrid (filtering, pagination, selection via shared packages) + - Components under src/components/ diff --git a/packages/pluggableWidgets/gallery-web/openspec/specs/spec.md b/packages/pluggableWidgets/gallery-web/openspec/specs/spec.md new file mode 100644 index 0000000000..ddbbf77492 --- /dev/null +++ b/packages/pluggableWidgets/gallery-web/openspec/specs/spec.md @@ -0,0 +1,115 @@ +# Gallery — Behavior Specification + +## Purpose + +Grid-based data display widget for Mendix applications. Renders items from a +ListValue datasource as a configurable multi-column grid with pagination, sorting, +filtering (via child filter widgets), and item click/selection actions. + +--- + +## Requirements + +### Requirement: Grid Data Rendering + +The widget SHALL render datasource items as a grid of content slots, with a +configurable number of columns. + +#### Scenario: Items available on mount + +- GIVEN a datasource is configured with items +- WHEN the widget mounts and the datasource status is `"available"` +- THEN all items SHALL be rendered in a grid layout +- AND each item SHALL use the configured content template + +#### Scenario: Loading state + +- GIVEN a datasource is configured +- WHEN the datasource status is `"loading"` +- THEN the widget SHALL display a loading indicator +- AND no items SHALL be rendered + +#### Scenario: Empty datasource + +- GIVEN the datasource returns zero items +- WHEN the widget renders +- THEN the empty message widget SHALL be displayed in place of the grid + +--- + +### Requirement: Column Count + +The widget SHALL lay out items across a configurable number of columns. + +#### Scenario: Custom column count + +- GIVEN numberOfColumns is set to N +- WHEN the widget renders with items +- THEN items SHALL be arranged in N columns per row +- AND the grid SHALL be responsive within the configured column count + +--- + +### Requirement: Pagination + +The widget SHALL support paginating through datasource items when pagination is +configured. + +#### Scenario: Load more pagination + +- GIVEN pagination is "loadMore" and there are more items than pageSize +- WHEN the user clicks the "Load More" button +- THEN the next page of items SHALL be appended to the existing items +- AND the button SHALL disappear when all items are loaded + +#### Scenario: Button pagination — next page + +- GIVEN pagination is "buttons" and the current page is not the last +- WHEN the user clicks the next-page control +- THEN the next page of items SHALL replace the current display + +--- + +### Requirement: Item Click Action + +The widget SHALL execute the configured `onClickItem` action when an item is +clicked, subject to `canExecute`. + +#### Scenario: Item clicked with valid action + +- GIVEN `onClickItem` is configured and `canExecute` is true +- WHEN the user clicks an item +- THEN the action SHALL be executed with that item's object as context + +--- + +### Requirement: Item Selection + +The widget SHALL support selecting items in Single and Multi modes when configured. + +#### Scenario: Single-mode item click + +- GIVEN selectionMode is "Single" +- WHEN the user clicks an item +- THEN that item SHALL become selected +- AND any previously selected item SHALL be deselected +- AND the item's visual state SHALL reflect selection + +#### Scenario: Multi-mode item click + +- GIVEN selectionMode is "Multi" +- WHEN the user clicks multiple items +- THEN all clicked items SHALL be added to the selection set + +--- + +### Requirement: Dynamic Item Class + +The widget SHALL apply a dynamically evaluated CSS class to each item when +`itemClass` is configured. + +#### Scenario: Dynamic class applied + +- GIVEN `itemClass` is a dynamic expression returning a string +- WHEN the widget renders items +- THEN each item's wrapper element SHALL have the evaluated class applied