diff --git a/.claude/skills/create-core-component/SKILL.md b/.claude/skills/create-core-component/SKILL.md
index 93b7ad3a05..14d22b1408 100644
--- a/.claude/skills/create-core-component/SKILL.md
+++ b/.claude/skills/create-core-component/SKILL.md
@@ -17,17 +17,21 @@ Scaffold a complete, production-ready AEM Core Form Component across all layers
### Phase 1: Gather Requirements
-Before generating any code, collect the following from the user:
-
-1. **Component name** — e.g., `ImageChoice`, `StarRating`, `ColorPicker`
-2. **Component type** — which category:
- - **Simple field** (text-like input) → extends `AbstractFieldImpl` / implements `Field`
- - **Options field** (multiple choices) → extends `AbstractOptionsFieldImpl` / implements `OptionsConstraint`
- - **Container** (holds child components) → extends `AbstractContainerImpl`
- - **Display-only** (non-input: text, image, separator) → extends `AbstractBaseImpl` / implements `Base`
-3. **Custom properties** — any JCR properties beyond what the base class provides
-4. **Widget HTML** — what the interactive element looks like (input, select, custom markup)
+Before generating any code or reading any reference files, collect all of the following from the user in a single message. Do not proceed until all answers are received.
+
+1. **Component name and purpose** — e.g., `ImageChoice` (radio group with image thumbnails), `StarRating` (captures 1–5 star rating)
+2. **Component type** — which category (see `docs/architecture/overview.md` for the base class and interface each one maps to):
+ - **Simple field** (text-like input that captures a single value)
+ - **Options field** (multiple choices: checkboxes, radios, dropdowns)
+ - **Container** (holds child components)
+ - **Step / display** (non-input: text, image, separator)
+ - **Display/text** (renders authored HTML content with a title label) — `sling:resourceSuperType="core/fd/components/form/text/v1/text"`
+3. **Custom properties** — any JCR properties beyond what the base class provides (name, type, default, purpose for each)
+4. **Widget HTML** — what the interactive element looks like (input type, select, custom div, etc.)
5. **Constraints** — which constraint interfaces apply (String, Number, Date, Options, File)
+6. **Target repository and component group** — which repo (`aem-core-forms-components`, or a project overlay) and what `componentGroup` for the author toolbar (e.g., `".core-adaptiveform"`)
+7. **Java backend** — does this component need a custom Sling Model, or can it reuse an existing Core model via `sling:resourceSuperType`? (Reuse is appropriate when the component only adds JS behavior on top of an existing field type; custom Java is required for new authored properties exposed in HTL or JSON)
+8. **FormContainer-level properties** — does this component need any configuration authored on the parent form container rather than on the component instance itself? (e.g., a signing service path set once for the whole form) If yes: what properties, and what tab name should they appear under in the form container dialog?
### Phase 2: Derive Naming Conventions
@@ -44,6 +48,7 @@ From the component name, derive all naming variants. For example, given `ImageCh
| Resource type | `core/fd/components/form/imagechoice/v1/imagechoice` |
| Clientlib category | `core.forms.components.imagechoice.v1.runtime` |
| FormConstant | `RT_FD_FORM_IMAGE_CHOICE_V1` |
+| `fd:viewType` (step only) | `image-choice` |
### Phase 3: Generate Files
@@ -70,9 +75,14 @@ Read `references/templates.md` for the full template set. Read `references/compo
#### 3b. UI AF Apps (HTL, Dialogs, Clientlibs)
-4. **Component definition** — `.content.xml` with `sling:resourceSuperType="core/fd/components/form/base/v1/base"`
+4. **Component definition** — `.content.xml` with `sling:resourceSuperType` set to the appropriate base. Also create `_cq_template.xml` (instance defaults: `jcr:title`, `fieldType`, and `fd:viewType` for step components) and `_cq_editConfig.xml` (author toolbar — use the inplace RTE variant from `references/templates.md` template #16 for display/text components). See `references/templates.md` templates #6, #6b, and #16.
-5. **HTL template** — Follow the standard structure:
+5. **HTL template** — Choose the correct variant from `references/templates.md`:
+ - **Template #7** — field components (captures a value)
+ - **Template #7b** — step/display components (no widget)
+ - **Template #7c** — display/text with label and rich-text value area
+
+ Follow the standard structure for the chosen variant:
- Include shared templates via `data-sly-use` (label, shortDescription, longDescription, errorMessage, questionMark)
- Root `
` with `data-cmp-is`, `data-cmp-visible`, `data-cmp-enabled`, `data-cmp-required`, `data-cmp-readonly`
- Label container with label + question mark
@@ -87,9 +97,9 @@ Read `references/templates.md` for the full template set. Read `references/compo
7. **Dialog XML** — Include base dialog fields via `granite/ui/components/coral/foundation/include`, add component-specific tabs/fields
-8. **Site clientlib** — `.content.xml`, `js.txt`, `css.txt`, view JS, styles. **Before writing the view JS, read `references/runtime-view-js.md`** — it defines the `FormFieldBase` override contract (`super` calls, `updateEmptyStatus`, composite-widget focus guard).
+8. **Site clientlib** — `.content.xml`, `js.txt`, `css.txt`, view JS (`references/templates.md` #11 or #11b), the CSS stub (empty BEM rules in `aem-core-forms-components`) plus the theme SCSS (actual styles in `aem-forms-theme-canvas`). See `references/css-architecture.md` and `references/templates.md` #12. **Before writing the view JS, read `references/runtime-view-js.md`** — it defines the `FormFieldBase` override contract (`super` calls, `updateEmptyStatus`, composite-widget focus guard).
-9. **Editor clientlib** — `.content.xml` with editor category. In editor JS use `textContent` (never `innerHTML`) and don't wrap locale-neutral format strings in `Granite.I18n.get`.
+9. **Editor clientlib** — `.content.xml` with editor category. See `references/editor-clientlib.md` for JS safety rules (`textContent` vs `innerHTML`, i18n) and the conditional-field-visibility pattern.
10. **Register the runtime clientlib** — embed the component's `core.forms.components.{componentname}.v1.runtime` category in `ui.af.apps/.../af-clientlibs/core-forms-components-runtime-all/.content.xml`. Missing this silently breaks the runtime Cypress suite.
@@ -120,15 +130,21 @@ required** (they catch HTL/view-JS/dialog/sync bugs that unit tests cannot). Rea
17. **Example content page** — Page structure with `guideContainer` and sample component instance
18. **Register in example index** — Add entry to `examples/ui.content/.../adaptive-form/.content.xml`
-### Phase 4: Accessibility Verification
+#### 3e. Wiring and Registration
+
+After all files are created, complete the wiring steps from `references/wiring-steps.md`:
-After generating all files, read `references/accessibility-checklist.md` and verify:
+- **Step 1** — Embed the clientlib category in `core-forms-components-runtime-all/.content.xml`
+- **Step 2** — Add `FormConstants` resource type constant (if needed by other Java code)
+- **Step 3** — Add i18n strings to all 10 language files (if new constraint messages were added)
+- **Step 4** — Content migration (see `migrate-foundation-component` skill — not applicable for new components)
+- **Step 5** — Extend FormContainer dialog (if component reads form-level properties)
+- **Step 6** — Register component group in the production template policy
+- **Step 7** — Create integration test content and register it in the IT template policy
+
+### Phase 4: Accessibility Verification
-1. **HTL template** — Every interactive element has `aria-label` or `
`, error div has `aria-live="assertive"`, descriptions have `aria-live="polite"`. For a grouped widget, give the group `aria-label="${component.label.value}"` — **the shared label template emits no `id`, so `aria-labelledby="...__label"` is broken** (see `references/runtime-view-js.md` and the accessibility checklist).
-2. **JS view** — Prefer the inherited `FormFieldBase` handlers. If you override `updateValidity` / `updateEnabled` / `updateReadOnly` / `updateValue`, **call `super.(value, state)` first** so `data-cmp-*` attributes, the error message, and `--filled`/`--empty` stay in sync; only then add component-specific behaviour. Focus/blur handlers call `setActive()`/`setInactive()`. Read `references/runtime-view-js.md` — this is the #1 source of runtime bugs.
-3. **LESS styles** — `:focus` has visible `outline` with `2px` minimum, error states use color + text
-4. **Keyboard** — All interactive elements have `tabindex` if not natively focusable
-5. **BEM** — All class names follow `cmp-adaptiveform-{componentname}__{element}` convention
+After generating all files, work through `references/accessibility-checklist.md` and satisfy every applicable item (labels, ARIA states, live regions, keyboard, focus, BEM). That file is the single source of truth for the WCAG 2.1 AA requirements. It also covers the two highest-risk items: the `FormFieldBase` override contract (if you override `updateValidity` / `updateEnabled` / `updateReadOnly` / `updateValue`, call `super.(value, state)` first — see `references/runtime-view-js.md`, the #1 source of runtime bugs) and the grouped-widget label pitfall (use `aria-label="${component.label.value}"`, not `aria-labelledby`, because the shared label template emits no `id`).
### Phase 5: Validation
@@ -190,28 +206,39 @@ described in `references/runtime-view-js.md`.
## Key References
- `references/component-anatomy.md` — Full file checklist (incl. Cypress + wiring), interface/implementation hierarchy, FormConstants pattern
-- `references/templates.md` — Copy-paste-ready templates for every file type with placeholders
+- `references/templates.md` — Copy-paste-ready templates for every file type with placeholders (including variants 2b, 6b, 7b, 7c, 11b, 11c, 16)
- `references/runtime-view-js.md` — **The `FormFieldBase` override contract** (super calls, `updateEmptyStatus`, composite widgets) — read before writing any view JS
- `references/cypress-tests.md` — Required runtime + authoring specs, wiring, and Cypress gotchas
- `references/accessibility-checklist.md` — WCAG 2.1 AA checklist with AEM-specific ARIA patterns
+- `docs/architecture/overview.md` — Component-layer diagram, runtime data flow, and Java/JS base-class selection guide
+- `references/css-architecture.md` — Two-layer CSS split: stub in `aem-core-forms-components`, SCSS in `aem-forms-theme-canvas`
+- `references/widget-patterns.md` — HTML widget element patterns for each component type
+- `references/wiring-steps.md` — Post-generation wiring: clientlib embed, FormConstants, i18n, FormContainer dialog, template policy
+- `references/verification-checklist.md` — Post-implementation checklist covering structure, wiring, CSS, HTL, JS, Java, and tests
+- `references/common-mistakes.md` — Common implementation mistakes with fixes
+- `references/editor-clientlib.md` — JS-driven conditional field visibility in author dialogs, and editor JS safety rules
+- `references/composite-multifield.md` — Composite multifield patterns for repeatable dialog items
+- `references/datasource-servlet.md` — Dynamic JCR-sourced options for Granite UI selects
- `scripts/validate_component.py` — Automated validator — run after generating all files
## Critical Rules
-1. **Never skip the accessibility checklist** — Every component must satisfy WCAG 2.1 AA
-2. **Always use existing abstract base classes** — Do not reinvent label/description/tooltip/error handling
-3. **Always register in FormConstants** — The resource type constant is required for Sling Model resolution
-4. **Follow BEM strictly** — `cmp-adaptiveform-{componentname}__{element}` with no exceptions
-5. **Use shared field templates** — Label, description, error message, question mark templates from `af-commons`
-6. **JSON export must validate** — The exporter JSON test validates against the Adaptive Form JSON schema
-7. **Match existing copyright headers** — Apache License 2.0 headers on all Java, JS, HTL, and LESS files. Use the **current calendar year** (not a hardcoded year) in `Copyright {year} Adobe`
-8. **Include `data-cmp-data-layer`** — The root `` in HTL must include `data-cmp-data-layer="${{componentname}.data.json}"` for analytics/data layer integration
-9. **Use `@ValueMapValue` with `InjectionStrategy.OPTIONAL`** — Never use `REQUIRED`; missing properties should use defaults
-10. **Test both `Resource` and `SlingHttpServletRequest` adaptation** — The `@Model` annotation must list both `adaptables`
-11. **Widget ID convention** — Always `{componentId}-widget` via `${'{0}-{1}' @ format=[component.id, 'widget']}`
-12. **Call `super` when overriding `update*` handlers** — `FormFieldBase` keeps the root `data-cmp-*` attributes, the error message, and `--filled`/`--empty` in sync; an override that skips `super` silently breaks them. An overridden `updateValue` must also end with `this.updateEmptyStatus()`. See `references/runtime-view-js.md`.
-13. **Cypress specs are mandatory** — ship `{componentname}.runtime.cy.js` + `{componentname}.authoring.cy.js`, the IT sample content, the `formsConstants.js` entry, and the runtime-all embed. Run the runtime spec green against `localhost:4502` before sign-off.
+Terse non-negotiables. Where a rule has a detailed rationale or failure mode, it is documented once in the referenced file — do not restate it here.
+
+1. **Never skip accessibility** — satisfy every applicable item in `references/accessibility-checklist.md` (WCAG 2.1 AA), including the grouped-widget label pitfall (use `aria-label`, not `aria-labelledby`).
+2. **Always extend an existing abstract base class** — do not reinvent label/description/tooltip/error handling. See `docs/architecture/overview.md`.
+3. **Always register in FormConstants** — the resource type constant is required for Sling Model resolution.
+4. **Follow BEM strictly** — `cmp-adaptiveform-{componentname}__{element}`, no exceptions.
+5. **Use shared field templates** from `af-commons` (label, description, error message, question mark).
+6. **JSON export must validate** against the Adaptive Form JSON schema (exporter test).
+7. **Match existing copyright headers** — Apache License 2.0 on all Java/JS/HTL/LESS files, using the current calendar year.
+8. **Include `data-cmp-data-layer`** on the root `
` for data-layer integration.
+9. **Widget ID convention** — always `{componentId}-widget` via `${'{0}-{1}' @ format=[component.id, 'widget']}`.
+10. **`_cq_editConfig.xml` is required** for every component (display/text with inplace editing uses the extended variant — `references/templates.md` template #16).
+11. **CSS is split across two repos** — see `references/css-architecture.md` for what goes where.
+12. **Call `super` when overriding `update*` handlers** — an override that skips `super` silently breaks `data-cmp-*`, the error message, and `--filled`/`--empty`; an overridden `updateValue` must also end with `this.updateEmptyStatus()`. See `references/runtime-view-js.md`.
+13. **Cypress specs are mandatory** — ship `{componentname}.runtime.cy.js` + `{componentname}.authoring.cy.js`, the IT sample content, the `formsConstants.js` entry, and the runtime-all embed. Run the runtime spec green against `localhost:4502` before sign-off. See `references/cypress-tests.md`.
14. **No empty styling or dialogs** — every BEM modifier class you emit must have a CSS rule; never ship an empty `_cq_design_dialog` tab.
-15. **Group label uses `aria-label`** — the shared label template emits no `id`, so `aria-labelledby="...__label"` is broken; use `aria-label="${component.label.value}"` for `role="group"` widgets.
-16. **Composite widgets**: only the hidden combined input carries `name`; never repopulate a sub-input the user is actively editing.
-17. **Editor JS safety** — use `textContent` not `innerHTML`; don't wrap locale-neutral format strings (`'YYYY-MM-DD'`) in `Granite.I18n.get`.
+15. **Composite widgets** — only the hidden combined input carries `name`; never repopulate a sub-input the user is actively editing. See `references/runtime-view-js.md`.
+16. **Editor JS safety** — see `references/editor-clientlib.md` (`textContent` vs `innerHTML`, locale-neutral format strings).
+17. **Read `references/common-mistakes.md` before Phase 3** — it is the source of truth for the recurring failure modes (`@ValueMapValue`/`InjectionStrategy.OPTIONAL`, dual `adaptables`, `data-sly-test` on `__value`, and others); those are not repeated here.
diff --git a/.claude/skills/create-core-component/references/accessibility-checklist.md b/.claude/skills/create-core-component/references/accessibility-checklist.md
index 51c798bc07..896dc491a1 100644
--- a/.claude/skills/create-core-component/references/accessibility-checklist.md
+++ b/.claude/skills/create-core-component/references/accessibility-checklist.md
@@ -63,17 +63,7 @@ Every component MUST satisfy all applicable items below. This checklist is deriv
## BEM Naming Convention for Accessibility Elements
-Every component follows this naming pattern:
-```
-.cmp-adaptiveform-{componentname} — root container
-.cmp-adaptiveform-{componentname}__label — label element
-.cmp-adaptiveform-{componentname}__widget — main interactive widget
-.cmp-adaptiveform-{componentname}__errormessage — error message area
-.cmp-adaptiveform-{componentname}__shortdescription — tooltip
-.cmp-adaptiveform-{componentname}__longdescription — expanded help text
-.cmp-adaptiveform-{componentname}__questionmark — help toggle button
-.cmp-adaptiveform-{componentname}__label-container — label + question mark wrapper
-```
+For the standard BEM element set (`__label`, `__widget`, `__errormessage`, `__shortdescription`, `__longdescription`, `__questionmark`, `__label-container`), see the table in `references/css-architecture.md`. Every one of those elements must be present and correctly linked for accessibility.
## ID Convention for ARIA Linkage
@@ -97,9 +87,9 @@ common source of runtime bugs.
| Method | If you override it… |
|--------|---------------------|
-| `updateValidity(validity, state)` | call `super` (it renders the error message + sets `data-cmp-valid`), then mirror `aria-invalid` onto extra sub-widgets |
-| `updateReadOnly(readOnly, state)` | call `super` (sets `data-cmp-readonly`), then set `aria-readonly`/`readonly` on sub-widgets |
-| `updateEnabled(enabled, state)` | call `super` (sets `data-cmp-enabled`), then toggle `disabled` on sub-widgets |
+| `updateValidity(validity, state)` | call `super` first, then mirror `aria-invalid` onto extra sub-widgets |
+| `updateReadOnly(readOnly, state)` | call `super` first, then set `aria-readonly`/`readonly` on sub-widgets |
+| `updateEnabled(enabled, state)` | call `super` first, then toggle `disabled` on sub-widgets |
| `updateValue(value)` | end with `this.updateEmptyStatus()`; for composite widgets, don't repopulate a focused sub-input |
| `setModel(model)` | register focus/blur listeners for `setActive()`/`setInactive()` |
diff --git a/.claude/skills/create-core-component/references/common-mistakes.md b/.claude/skills/create-core-component/references/common-mistakes.md
new file mode 100644
index 0000000000..f512d83582
--- /dev/null
+++ b/.claude/skills/create-core-component/references/common-mistakes.md
@@ -0,0 +1,62 @@
+# Common Implementation Mistakes
+
+1. **`data-cmp-is` mismatch** — The single most common failure. The string in HTML must equal `static IS` in JS. One extra capital letter breaks the wiring.
+
+2. **Wrong `fieldType` format** — Core uses kebab-case: `text-input`, not `textInput` or `TEXT_INPUT`. Check `references/component-anatomy.md` (FieldType Values section) for the full list.
+
+3. **`@Inject` instead of `@ValueMapValue`** — `@Inject` uses Sling's default injector chain and may pick up values from places other than the resource value map. Always use `@ValueMapValue` for JCR properties.
+
+4. **Missing `InjectionStrategy.OPTIONAL`** — Without it, Sling Model adaptation fails at runtime if the JCR property is absent on an existing content node.
+
+5. **Foundation package imports in Java** — Any import starting with `com.adobe.guides.*` or `com.adobe.fd.guide.*` means you are cross-referencing foundation. Remove it.
+
+6. **Boolean `data-cmp-*` attributes as bare booleans** — `data-cmp-visible="${model.visible}"` outputs `true`/`false` as strings correctly in some HTL versions but fails in others. Always use `${model.visible ? 'true' : 'false'}`.
+
+7. **Missing `aria-describedby`** — All interactive widgets must reference all three IDs (`{id}__errormessage`, `{id}__longdescription`, `{id}__shortdescription`). Omitting any of them breaks screen reader support and fails accessibility audits.
+
+8. **Clientlib category typo** — The category in `runtime/.content.xml` must exactly match what you embed in the `core-forms-components-runtime-all` clientlib. A single character difference means the component's JS/CSS never loads.
+
+9. **Step component extending FormFieldBase** — Steps have no widget and no value. Extending `FormFieldBase` instead of `FormContainer` causes null errors on `getWidget()`.
+
+10. **`fd:viewType` on field components** — `fd:viewType` is for step/display components that share `fieldType=plain-text` but need distinct rendering. Field components (text-input, number-input, etc.) do not need `fd:viewType`.
+
+11. **CSS properties in the core-forms-components stub file** — `{name}view.css` inside `aem-core-forms-components` must have empty rules only. Visual properties go in `aem-forms-theme-canvas`.
+
+12. **Hardcoded colour or size values in theme SCSS** — Always use design tokens from `_variables.scss`. Hardcoded values (`#666`, `40px`) break when the theme is customised. If no suitable token exists, add one to `_variables.scss`.
+
+13. **Forgetting `[data-cmp-valid]` selectors in theme SCSS** — Without these, validation state changes written by the JS view produce no visual feedback. Add both `[data-cmp-valid=false]` and `[data-cmp-valid=true]` selectors for every interactive field component.
+
+14. **Not importing the new SCSS file in `theme.scss`** — The Parcel build only includes what is imported. A missing `@import` means the component has zero styles even after `npm run build`.
+
+15. **`data-sly-test` on the `__value` div** — Never put `data-sly-test` on the outer `__value` wrapper. `getWidget()` in the JS view must always find `.cmp-adaptiveform-{name}__value`. Put the conditional **inside** the div with a `
` wrapper.
+
+16. **Destructive `updateValue` fallback** — Do not set `this.element.innerHTML` when `getWidget()` returns null. That wipes the `__label-container`. Update only `getWidget()`; fix HTL if the widget is missing.
+
+17. **Invented dialog text during migration** *(migration mode only)* — Adding `fieldDescription` to Core dialog fields that had no description in the foundation dialog, or rephrasing foundation `fieldLabel` / option `text` without justification. Rule: copy foundation text verbatim; omit `fieldDescription` when the foundation field had none; only write original text for fields that are genuinely new in Core.
+
+---
+
+## Display/Text Component Mistakes
+
+These apply to any component that uses HTL template 7c and `sling:resourceSuperType="core/fd/components/form/text/v1/text"`.
+
+1. **Title not rendering on display/text components** — Symptom: only rich-text `value` shows but `jcr:title` is set in CRX. Causes and fixes:
+ - HTL used `resource.properties['jcr:title']` instead of `baseModel.label.value` → use HTL template 7c with `data-sly-use.baseModel="…Base"` (see "Sling Model – Display/Text" in `references/templates.md`).
+ - Sling Model `adapters` omitted `Base.class` → add `Base.class` alongside `Text.class`.
+ - Bound only `Text` in HTL for label → `Text` has no `getLabel()`; extend `AbstractBaseImpl` and register `Base.class` in `adapters`.
+ - Parent `text/v1/text` dialog hides Title → re-declare `./jcr:title` and `./hideTitle` in child `_cq_dialog`.
+ - **Verify:** CRX has `jcr:title`; DevTools shows `label.cmp-adaptiveform-{name}__label` inside `__label-container`.
+
+---
+
+## Author Dialog and Coral Interactivity
+
+These apply to any component whose author dialog uses composite multifields or JavaScript-driven conditional field visibility. `references/editor-clientlib.md` is the canonical source for the patterns and fixes below (JS boilerplate, `getSelectValue`, `setVisible`) — this list only flags the symptoms so you recognize them.
+
+1. **Coral listener ordering race** — a `coral-collection:add` handler never fires for a programmatically-added item. See "Listener registration order" in `references/editor-clientlib.md`.
+2. **Both conditional fields visible on new multifield item** — adding an item briefly shows all conditionally-hidden fields before they settle. See "Default-off fields in new items" in `references/editor-clientlib.md`.
+3. **`getSelectValue` returns nothing** — reading `.val()` on a `granite:class`-wrapped element instead of the `coral-select` DOM property. See `getSelectValue` in `references/editor-clientlib.md`.
+4. **`setVisible` hides more than intended** — `.closest(".coral-Form-fieldwrapper")` traverses past the multifield item boundary to a parent container's fieldwrapper. See "`setVisible` boundary" in `references/editor-clientlib.md`.
+5. **Select default not applied when `emptyText` omitted** — the JS may read `val === ""` before Coral finishes upgrading. See "Select Default Behavior" in `references/editor-clientlib.md`.
+
+---
diff --git a/.claude/skills/create-core-component/references/component-anatomy.md b/.claude/skills/create-core-component/references/component-anatomy.md
index ea8e0b0d36..f5feb6deb2 100644
--- a/.claude/skills/create-core-component/references/component-anatomy.md
+++ b/.claude/skills/create-core-component/references/component-anatomy.md
@@ -77,25 +77,7 @@ FormComponent
## Implementation Hierarchy
-```
-AbstractComponentImpl (WCM Core)
- └── AbstractFormComponentImpl (name, fieldType, dataRef, visible, value, events, rules)
- └── AbstractBaseImpl (label, description, tooltip, type, required, enabled)
- └── AbstractFieldImpl (readOnly, default, placeholder, min/max constraints)
- ├── AbstractOptionsFieldImpl (enum, enumNames, enforceEnum)
- └── [Direct subclasses for simple fields]
-```
-
-Choose the appropriate base class:
-- **Simple text/number field** -> extend `AbstractFieldImpl`
-- **Options-based field** (checkboxes, radios, selects) -> extend `AbstractOptionsFieldImpl`
-- **Container/panel** -> extend `AbstractContainerImpl`
-- **Non-input component** (button, text, image) -> extend `AbstractBaseImpl`
-- **Composite / split widget** (e.g. day/month/year, multiple visible inputs feeding
- one value) -> still extend the field base that matches the data type
- (`AbstractFieldImpl` for date), but render **one hidden combined ` `** as the
- value-bearing widget plus the visible sub-inputs. This category has extra runtime
- rules — read `references/runtime-view-js.md` before writing the view JS.
+For the full class hierarchy (Impl classes) and base-class selection guide (including the composite / split-widget case), see `docs/architecture/overview.md` — the "Java Model Hierarchy" section is the single source of truth; do not restate it here.
---
diff --git a/.claude/skills/create-core-component/references/composite-multifield.md b/.claude/skills/create-core-component/references/composite-multifield.md
new file mode 100644
index 0000000000..9ebf07cd8f
--- /dev/null
+++ b/.claude/skills/create-core-component/references/composite-multifield.md
@@ -0,0 +1,93 @@
+# Composite Multifield in Granite UI Dialogs
+
+Use a `composite="true"` multifield when each item in a repeatable list has multiple named sub-fields (e.g., a list of signers each with email, authentication method, and phone number). Each item is stored as a child JCR node under the multifield's named path.
+
+---
+
+## 1. XML Structure
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**Critical rules:**
+- `composite="true"` (not `composite="{Boolean}true"`) — the literal string form is required.
+- The `field` container's `name` must match the multifield's `name` exactly (including the `./` prefix).
+- Sub-field `name` attributes inside the template do NOT use `./` prefix — they are relative to the multifield item node automatically.
+- The `granite:class` on the `field` container provides the JS hook for the whole item row.
+
+---
+
+## 2. JCR Storage
+
+Each item is stored as a numbered child of the multifield node:
+```
+./items/item/item0/title = "First item title"
+./items/item/item0/mode = "A"
+./items/item/item1/title = "Second item title"
+./items/item/item1/mode = "B"
+```
+
+---
+
+## 3. Reading in a Sling Model
+
+```java
+@ChildResource(injectionStrategy = InjectionStrategy.OPTIONAL, name = "items")
+private Resource itemsResource;
+
+@PostConstruct
+private void init() {
+ if (itemsResource != null) {
+ Resource itemContainer = itemsResource.getChild("item");
+ if (itemContainer != null) {
+ List list = new ArrayList<>();
+ for (Resource child : itemContainer.getChildren()) {
+ MyItemModel m = child.adaptTo(MyItemModel.class);
+ if (m != null) list.add(m);
+ }
+ items = Collections.unmodifiableList(list);
+ }
+ }
+}
+```
+
+The item model class must be annotated with `adaptables = { Resource.class }` (not `SlingHttpServletRequest.class`) since it adapts from child resources, not from a request.
+
+---
+
+## 4. JS Selector for Sub-Fields
+
+Inside a `coral-collection:add` handler, `e.detail.item` is the `coral-multifield-item` element. The item div (from `granite:class` on the `field` container) is a child of it — find it with `$(e.detail.item).find(".your-item-class")`. The template HTML is fully rendered (including `granite:class` values) when the event fires, but Coral sub-components (`coral-select`, `coral-radiogroup`) may still be upgrading — use `Coral.commons.ready(e.detail.item, callback)` before reading `coral-select.value`.
+
+**Also see:** `references/editor-clientlib.md` for the full JS boilerplate pattern including listener registration order, default-off field hiding, and `getSelectValue` helpers.
diff --git a/.claude/skills/create-core-component/references/css-architecture.md b/.claude/skills/create-core-component/references/css-architecture.md
new file mode 100644
index 0000000000..bde48d0154
--- /dev/null
+++ b/.claude/skills/create-core-component/references/css-architecture.md
@@ -0,0 +1,70 @@
+# CSS Two-Layer Architecture
+
+## How CSS is Split Between the Two Repos
+
+```
+aem-core-forms-components aem-forms-theme-canvas
+───────────────────────────────────── ──────────────────────────────────────
+ui.af.apps/.../form/{name}/v1/{name}/ src/components/{name}/_{name}.scss
+ clientlibs/
+ runtime/
+ css.txt ← lists {name}view.css src/site/_variables.scss (design tokens)
+ css/
+ {name}view.css ← EMPTY STUBS src/theme.scss ← imports all components
+ dist/theme.css ← compiled output
+ → deployed as AEM clientlib
+```
+
+**Rule**: Never put visual CSS properties in `aem-core-forms-components`. Never put only BEM structure definitions in `aem-forms-theme-canvas`.
+
+## What Goes in Each Layer
+
+| | `aem-core-forms-components` stub CSS | `aem-forms-theme-canvas` SCSS |
+|-|--------------------------------------|-------------------------------|
+| BEM class declarations | All classes, empty `{ }` | Same class names, with properties |
+| Colors, spacing, fonts | Never | Via `$variables` tokens |
+| Validation states | Never | `[data-cmp-valid=false/true]` selectors |
+| Focus / hover / disabled | Never | Pseudo-class selectors |
+| Responsive breakpoints | Never | Media queries |
+| Design tokens | Never | `_variables.scss` |
+| Build step | None — plain CSS | Parcel 2.0 → `npm run build` |
+
+## Standard BEM Elements for Every Field Component
+
+This table is the single source of truth for the standard BEM element set; other references point here.
+
+Every field component's HTL template emits these elements. The stub CSS and theme SCSS must cover all of them:
+
+| BEM element suffix | CSS class | What it wraps |
+|-------------------|-----------|---------------|
+| *(root block)* | `.cmp-adaptiveform-{name}` | Outer container `