diff --git a/skills/celements-component/SKILL.md b/skills/celements-component/SKILL.md index a260e82..3a5a095 100644 --- a/skills/celements-component/SKILL.md +++ b/skills/celements-component/SKILL.md @@ -1,17 +1,11 @@ --- name: celements-component -description: Use when working in Celements code, including Celements-based Progon code, that touches the mixed XWiki component and Spring bean system, especially when refactoring legacy org.xwiki.component.annotation.Component classes to Spring @Component/@Service beans, reviewing ComponentManager/Utils.getComponent/BeanFactory lookups, handling @ComponentRole/@Requirement/@Inject, component hints, META-INF/components.txt, or diagnosing bean wiring and lookup pitfalls. +description: Use when changing Celements XWiki/Spring component registration, lookup, hints, or injection. --- -# Celements Component +# Celements Components -## Overview - -Celements runs a hybrid component system: legacy XWiki components are loaded into the Spring bean factory, while legacy XWiki lookup APIs are shimmed back onto Spring. Use the actual bridge code as the source of truth before judging whether a refactor from XWiki component annotations to Spring annotations is safe. - -## First Checks - -Inspect these classes when behavior is unclear in the `celements-base/celements-component` module: +Celements combines legacy XWiki components with Spring beans. Verify behavior in the bridge code located in `celements-base/celements-component`before changing registration or lookup: - `com.celements.spring.context.CelSpringContext` - `com.celements.spring.context.SpringShimComponentManager` @@ -22,176 +16,50 @@ Inspect these classes when behavior is unclear in the `celements-base/celements- - `org.xwiki.component.annotation.ComponentDescriptorFactory` - `org.xwiki.component.descriptor.ComponentDescriptor` -Prefer live code over memory when reviewing a branch, because this bridge is central infrastructure and details may change. - ## Runtime Model -`CelSpringContext` creates an `XWikiShimBeanFactory`, configures Spring component scanning, then loads XWiki descriptors from every `META-INF/components.txt` resource and registers them as Spring bean definitions. +`CelSpringContext` scans Spring components and registers XWiki descriptors from `META-INF/components.txt` as Spring bean definitions. -Legacy `org.xwiki.component.annotation.Component` classes listed in `META-INF/components.txt` become Spring beans using XWiki role and hint naming. The bean name is normally: +Legacy XWiki beans use an encoded role/hint name: ```java roleClassName + "|||" + roleHint ``` -Spring-native beans have normal Spring bean names unless explicitly named. With -`org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGenerator`, scanned Spring -components normally get their fully qualified implementation class name as bean name. That is a -different namespace from XWiki's role-and-hint model. - -`SpringShimComponentManager` implements XWiki `ComponentManager` by delegating lookups to the Spring `BeanFactory`. This means legacy calls such as `Utils.getComponent(MyRole.class)` or `componentManager.lookup(MyRole.class)` may still resolve plain Spring beans by type. - -`XWikiShimBeanPostProcessor` keeps old `@Requirement` injection and `Initializable.initialize()` support working for Spring-managed beans. A bean can therefore be Spring-managed and still receive legacy XWiki requirements. - -For code that cannot use injection and needs static access, use `SpringContextProvider` for direct -Spring context access and legacy `Utils.getComponent(...)` for XWiki component lookup. -`SpringContextProvider` exposes `getSpringContext()`, `getBeanFactory()`, and -`getEventPublisher()`. - -## Hints And Bean Names - -XWiki components are addressed by role plus hint: - -```java -componentManager.lookup(MyRole.class, "myHint") -``` - -Spring beans are addressed by bean name plus type: - -```java -beanFactory.getBean("myBeanName", MyRole.class) -``` - -Those are not naturally equivalent. A Spring bean named `"myHint"` is not the same as an XWiki -component with role `MyRole.class` and hint `"myHint"` unless the bridge maps between them. - -The bridge handles this in two directions: - -- XWiki descriptors are registered into Spring with the encoded role/hint bean name: - `MyRole.class.getName() + "|||" + hint` -- `XWikiShimBeanFactory` can translate a requested hint/name into that encoded bean name, but only - when the requested type is annotated with `@ComponentRole` - -This means a refactor from XWiki component to Spring bean has two different compatibility questions: - -- Plain role lookup: usually OK if there is exactly one Spring bean assignable to the role. -- Hinted lookup: not automatically OK; preserve the XWiki role/hint contract or update all callers. - -If an implementation used `@org.xwiki.component.annotation.Component("myHint")`, converting it to -`@Service` changes the naming contract unless you deliberately preserve the bean name or remove all -hinted callers. For XWiki compatibility, prefer keeping `@ComponentRole` on the role and checking -all hinted lookup sites before removing descriptor registration. - -## Lookup Rules - -For `ComponentManager.lookup(role)`: +Spring beans normally use Spring bean names. `SpringShimComponentManager` delegates XWiki lookups to the Spring `BeanFactory`, so plain `lookup(Role.class)` can resolve a Spring bean by type. `XWikiShimBeanPostProcessor` preserves `@Requirement` injection and `Initializable.initialize()` for Spring-managed beans. -- first tries the XWiki role/hint bean name -- then falls back to plain type lookup with `beanFactory.getBean(type)` -- therefore a Spring `@Component` implementing a role can satisfy plain role lookup even if the role no longer has `@ComponentRole` +## Lookup Compatibility -For hinted lookup, named lookup, or fallback from a hint to the XWiki role/hint bean name: - -- `XWikiShimBeanFactory` only applies the XWiki hint fallback when the requested type is annotated with `@ComponentRole` -- removing `@ComponentRole` is risky if callers use custom hints, `@Named`, `@Qualifier`, `lookup(role, hint)`, or `getBean(hint, role)` - -For list and map lookup: - -- `lookupList(role)` and Spring `List` injection use beans assignable to the role -- XWiki components listed in `META-INF/components.txt` are included because descriptors are registered into Spring -- check whether a refactor removes the only mechanism that registers the implementation - -## Refactoring To Spring Beans - -When converting a legacy XWiki component implementation to Spring: - -1. Replace `org.xwiki.component.annotation.Component` on the implementation with Spring `@Component` or `@Service`. -2. Prefer constructor injection with `javax.inject.Inject` or Spring-compatible injection. -3. Remove the implementation from `META-INF/components.txt` when it is now discovered by Spring component scanning. -4. Keep the role interface if callers use it as the injection or lookup type. -5. Keep `@ComponentRole` on the role when hinted lookup compatibility matters. -6. Check callers for `lookup(Role.class, hint)`, `Utils.getComponent(Role.class, hint)`, `@Named`, `@Qualifier`, and `getBean(hint, Role.class)` before removing role metadata. -7. Keep `@Requirement` only where a broader refactor is out of scope; otherwise prefer constructor injection. -8. If the component used `Initializable`, prefer `@PostConstruct` for Spring-native components. - -Do not remove `@ComponentRole` just because the implementation is now a Spring bean. It is role metadata, not implementation registration. It may still be part of hint compatibility. - -## When Removing `components.txt` Entries Is Safe - -Usually safe: - -- the class is under a package covered by Spring component scanning, such as `com.celements`, `org.xwiki`, or `com.xpn.xwiki` in the configured Celements context -- the class has a Spring stereotype annotation -- there are no callers depending on the XWiki descriptor by custom hint -- tests and runtime modules use `CelSpringContext` or the Spring shim - -Risky: - -- the class is outside scanned packages -- the implementation only had XWiki `@Component` and no Spring stereotype -- the role/hint name is part of public lookup behavior -- tests or bootstrapping paths still use an embeddable XWiki component manager without the Spring shim -- component overrides rely on `META-INF/component-overrides.txt` - -## Annotation Guidance - -Use Spring annotations for new or refactored implementations: - -```java -@Service -public class MyService implements MyRole { - - private final Dependency dependency; - - @Inject - public MyService(Dependency dependency) { - this.dependency = dependency; - } -} -``` - -Use XWiki role annotations selectively: - -```java -@ComponentRole -public interface MyRole { -} -``` +Plain and hinted lookups are not equivalent: -Keep `@ComponentRole` when role/hint lookup is part of the contract. Removing it can break hint fallback even if plain type lookup still works. +- Plain role lookup usually works when exactly one assignable Spring bean exists. +- Hinted lookup depends on the XWiki role/hint contract. +- `XWikiShimBeanFactory` applies role/hint fallback only when the requested type has `@ComponentRole`. +- `lookupList(Role.class)` and Spring `List` injection include all registered assignable beans. -## Review Checklist +Before changing a component, search for: -- Identify whether each changed class is an implementation, a role interface, or both. -- Search for `Utils.getComponent`, `componentManager.lookup`, `webUtilsService.lookup`, `BeanFactory.getBean`, `@Named`, and `@Qualifier` usages of the changed role. -- Check whether lookups are plain type lookups or hinted/named lookups. -- Verify whether the implementation is discovered by Spring scanning after removing a `components.txt` entry. -- Verify `List` or `lookupList(Role.class)` injection if the component is an extension/listener/converter. -- Check test harness assumptions. In tests extending `AbstractComponentTest`, use the parent - class' `getBeanFactory().getBean(...)` helper instead of static lookup through - `SpringContextProvider` or `Utils.getComponent(...)`. -- Avoid changing runtime behavior by converting too many related components in one PR. +- `Utils.getComponent` +- `ComponentManager.lookup` +- `BeanFactory.getBean` +- `@Named` and `@Qualifier` +- `List` and `lookupList(Role.class)` -## Common Pitfalls +Do not remove `@ComponentRole` solely because the implementation becomes a Spring bean. It may still be required for hinted lookup. -- Treating `@ComponentRole` as obsolete registration metadata. It still controls XWiki hint fallback. -- Removing a `components.txt` entry from a class that has no Spring stereotype or is outside scanned packages. -- Assuming `lookup(role)` and `lookup(role, hint)` have the same compatibility behavior. -- Forgetting that XWiki `@Requirement` is still injected by `XWikiShimBeanPostProcessor`. -- Replacing `Initializable.initialize()` without preserving initialization timing. -- Breaking listener/converter registration by changing component names or hints. -- Using static lookup through `SpringContextProvider` or `Utils.getComponent(...)` in tests instead - of the parent `getBeanFactory()` helper provided by `AbstractComponentTest`. +## Migrating To Spring -## Practical Review Language +1. Replace the XWiki `@Component` annotation with Spring `@Component` or `@Service`. +2. Prefer constructor injection. +3. Remove the class from `META-INF/components.txt` only after confirming component scanning covers it. +4. Preserve role interfaces used by callers. +5. Preserve `@ComponentRole` when hinted lookup remains supported. +6. Replace `Initializable` with `@PostConstruct` only when initialization timing remains equivalent. -When a PR removes `@ComponentRole`, be precise: +Removing a `components.txt` entry is unsafe when the class lacks a Spring stereotype, is outside scanned packages, depends on descriptor naming or overrides, or runs in a bootstrap path without the Spring shim. -- Plain type lookup is usually still covered by `SpringShimComponentManager`. -- Hinted lookup compatibility may still require `@ComponentRole`. -- The finding should cite actual hinted/named callers, not just the existence of legacy `Utils.getComponent(Role.class)` plain lookups. +## Tests And Review -When a PR removes `components.txt` entries, be precise: +In `AbstractComponentTest`, use the inherited `getBeanFactory().getBean(...)` helper rather than static `SpringContextProvider` or `Utils.getComponent(...)` lookup. -- The removal is fine if Spring scanning now registers the class. -- It is not fine if the class remains only an XWiki component or relies on descriptor-based role/hint registration. +Review the actual caller contract. A valid finding identifies a broken hinted/named lookup, missing registration, list membership change, or initialization change; the mere presence of legacy annotations or plain lookup is not enough. diff --git a/skills/celements-component/agents/openai.yaml b/skills/celements-component/agents/openai.yaml deleted file mode 100644 index f792edd..0000000 --- a/skills/celements-component/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Celements Component" - short_description: "Celements XWiki/Spring component bridge guidance." - default_prompt: "Use this skill when refactoring Celements legacy XWiki components to Spring beans, including in Progon, or when reviewing component lookup, hints, and bean wiring." diff --git a/skills/celements-struct/SKILL.md b/skills/celements-struct/SKILL.md index e438bd3..37e92e8 100644 --- a/skills/celements-struct/SKILL.md +++ b/skills/celements-struct/SKILL.md @@ -1,188 +1,75 @@ --- name: celements-struct -description: Use when working with celements-struct or Celements structured data editor layouts, including DB/XAR layout cells, StructEditFieldClass bindings, StructuredDataEditor/StructuredDataView page types, form field page types, object filters, ObjectList/Table layouts, SelectTagAutocomplete, struct Java services, Velocity templates, and structEditJS behavior. +description: Use when changing Celements structured editor layouts, field bindings, object filters, object lists, autocomplete, or tables. --- # Celements Struct -## Overview +Celements Struct maps XWiki page-layout cells to document fields and XObjects. Treat Java and Velocity code as authoritative; exported layouts only illustrate possible DB content. -Use celements-struct as a bridge between XWiki page-layout documents and structured data editing. Most behavior is configured in XWiki documents: layout cells choose a page type, attach Celements cell/menu objects, and add struct-specific objects that bind the cell to document fields or XObjects. - -Treat code as the source of truth and exported layouts as examples of possible DB content. Do not hard-code assumptions from one exported layout into reusable code. - -## First Checks - -Start with these files before guessing: +## Start Here - `component/src/main/java/com/celements/struct/DefaultStructDataService.java` -- `component/src/main/java/com/celements/structEditor/DefaultStructuredDataEditorService.java` -- `component/src/main/java/com/celements/structEditor/StructuredDataEditorScriptService.java` - `component/src/main/java/com/celements/struct/StructDataScriptService.java` -- `component/src/main/java/com/celements/structEditor/StructuredDataEditorNavigationConfigurator.java` +- `component/src/main/java/com/celements/structEditor/DefaultStructuredDataEditorService.java` +- `component/src/main/java/com/celements/structEditor/*ScriptService.java` - `component/src/main/java/com/celements/structEditor/fields/*PageType.java` - `component/src/main/java/com/celements/structEditor/classes/*EditorClass.java` - `component/src/main/java/com/celements/struct/classes/*Class.java` - `component/src/main/java/com/celements/struct/table/*PresentationType.java` -- `component/src/main/resources/META-INF/components.txt` - `web-module/src/main/webapp/templates/celTemplates/StructuredDataEditorView.vm` - `web-module/src/main/webapp/templates/celMacros/struct/renderEditorLayout.vm` -- `web-module/src/main/webapp/templates/celTemplates/*View.vm` - `web-module/src/main/webapp/templates/celTemplates/fieldTypeEdit/*.vm` - `web-module/src/main/webapp/resources/structEditJS/*.mjs` -Use `example-layouts/` only to understand the shape of exported XWiki documents and the range of authoring patterns. - -## Runtime Model - -`StructuredDataEditor` and `StructuredDataView` are page types rendered through `StructuredDataEditorView.vm`. The view resolves an editor layout space, includes layout-specific JS/CSS, sets struct context-menu classes, then renders the page layout. - -Layout-space resolution is important: - -1. Prefer `Celements.StructLayoutClass` / `layoutSpace` on the current document if it points to an existing layout. -2. Otherwise compute from the current page type config name: `-EditFields` when the page type has an edit template, or `-StructData` otherwise. -3. Fall back to the central wiki when the local layout does not exist. - -A struct layout is a page-layout tree. Each cell is an XWiki document with normal layout objects such as `Celements.CellClass` and menu ordering/parenting, plus a page type such as `InputTag`, `SelectTag`, `Table`, or `ObjectList`. The struct field page types render their view template for normal editor output and use `StructDataFieldEdit.vm` when editing the field configuration itself. - -The public Velocity script services are: - -- `$services.structuredDataEditor` for cell attributes, values, labels, possible values, object streams, autocomplete metadata, and field metadata. -- `$services.structData` for table config loading, table rendering, and JS files configured on the layout config document. - -## Field Binding - -The central binding object is `Celements.StructEditFieldClass`: - -- `edit_field_class_fullname`: XClass reference for XObject-backed fields. -- `edit_field_name`: property name, or special document fields `title` and `content`. -- `multilingual`: restrict object selection to the request/default language field when the target class has a language field. -- `computed_obj_nb`: Velocity-evaluated object number fallback. - -Attribute names are built from the configured class reference, selected object number, and field name. For a normal XObject field this produces names shaped like `Space.Class_0_field`. If no class reference is configured, the field name can stand alone, which is used for document-level fields and plain hidden/form control fields. - -Object number resolution is contextual. Check these sources in order when a field shows or saves the wrong object: - -1. Request parameters: `objNb`, `objNb_`, and class/filter-specific keys. -2. Execution context object number keys from cell rendering. -3. `computed_obj_nb` on the cell. -4. First matching object, with multilingual filtering if enabled. -5. A negative create object number cached per request by class and key/value filters. - -Request values win over stored values. Stored values come from the selected XObject field, or from translated document `title` / `content` when those special field names are configured. - -## Object Filters - -Struct filters are `Classes.KeyValueClass` objects on the layout cell. The key and value are Velocity-evaluated. - -- `struct-obj-filter` and `struct-obj-filter-and` are AND filters. -- `struct-obj-filter-or` is an OR filter. - -Filters affect object selection, object streams, request object-number keys, and generated negative object numbers for new objects. For CelTag fields, a filter with key `type` can also define the tag type used to load possible tag values. - -## Field Types - -Prefer current page types when adding new layouts: - -- `FormField`: renders a form with configured action, method, optional multipart encoding, prefix, validation class, and autocomplete off. -- `InputTag`: renders a text input with generated name/value. `NumberTag` extends it with `type=number`. -- `TextAreaTag`: renders a textarea with rows/cols and optional TinyMCE classes. -- `SelectTag`: renders a select from XClass possible values or rendered child option cells; supports bootstrap and multiselect config. -- `OptionTag`: renders option content/attributes and can mark selected/disabled options. -- `SelectTagAutocomplete`: renders a Select2-backed select using an `AutocompleteRole`. -- `DateTime`: renders the current custom date/time element and includes date-time JS. -- `HiddenTag`: renders explicit hidden name/value fields or falls back to the struct field binding and request value. -- `DisplayField`: displays the bound value. -- `LabelTag`: displays the resolved pretty name. -- `LanguageSelector`: renders the language selector macro. -- `SubmitLink`: renders the standard save label. -- `ObjectList`: repeats an object row layout for all matching XObjects. -- `Table`: renders document/object/object-link tables. - -Legacy aliases still exist: `InputField`, `TextAreaField`, and `DateTimePicker`. Prefer `InputTag`, `TextAreaTag`, and `DateTime` for new work unless the surrounding layout already depends on the old names. - -Field-type-specific configuration lives in editor classes such as `FormFieldEditorClass`, `SelectTagEditorClass`, `SelectTagAutocompleteEditorClass`, `TextAreaFieldEditorClass`, `HiddenTagEditorClass`, `OptionTagEditorClass`, and `DateTimePickerEditorClass`. The edit UI for those objects is in `web-module/src/main/webapp/templates/celTemplates/fieldTypeEdit/`. - -## Autocomplete - -`SelectTagAutocomplete` uses `SelectTagAutocompleteEditorClass` plus an `AutocompleteRole` component. The default implementation: - -- uses web search configured on the cell document, -- returns JSON through `templates/celAjax/struct/autocomplete/search.vm`, -- can render result names and result HTML from Velocity fields on the cell, -- can expose an add-new URL from the cell config, -- loads Select2, i18n files, and `structEditJS/autocomplete.mjs`. - -For custom autocomplete, implement `AutocompleteRole` with a distinct component hint/name, provide search and selected-value behavior, and include any extra JS through `getJsFilePath()`. - -## Tables +## Layout And Binding -A `Table` cell is configured by `Celements.StructTableClass` and optional `Celements.StructTableColumnClass` objects. +`StructuredDataEditor` and `StructuredDataView` resolve a layout space, include its resources, and render its page-layout tree. Layout resolution is: -Table config fields: +1. Existing `Celements.StructLayoutClass.layoutSpace` on the current document. +2. `-EditFields` for page types with an edit template, otherwise `-StructData`. +3. The same layout in the central wiki when local content is absent. -- `type`: `DOC`, `OBJ`, or `OBJLINK`. -- `query`: Velocity-evaluated Lucene query for `DOC` tables; empty query falls back to web search. -- `sort_fields`, `result_limit`, `css_id`, `css_classes`. -- `header_layout` and `row_layout` for layout-driven rows. +`Celements.StructEditFieldClass` binds a cell to an XObject property or document `title`/`content` through: -Column config fields: +- `edit_field_class_fullname` +- `edit_field_name` +- `multilingual` +- `computed_obj_nb` -- `name`, `title`, `content`, `order`, `css_classes`. -- Columns sort by `order`, then object number. -- `name` is normalized to word characters for CSS/fallback lookup. +Normal XObject input names follow `Space.Class_0_field`. Request values override stored values. -Table renderers: +Object number resolution checks request parameters, execution context, `computed_obj_nb`, the first matching object, then a request-cached negative number for a new object. Inspect rendered field names and object-number parameters before changing Java. -- `DOC`: search documents and render each result. -- `OBJ`: stream matching XObjects on the current document and render the current doc once per object while setting the execution object number. -- `OBJLINK`: stream matching link objects, read the first non-empty `reference`, `ref`, or `link` field as a document reference, expose `$srcdoc` and `$rowdoc`, and render linked rows. +## Filters And Field Types -With explicit columns, cell content is resolved in this order: +`Classes.KeyValueClass` objects on a cell define Velocity-evaluated filters: -1. `StructTableColumnClass.content` evaluated as Velocity with `$rowdoc` and `$colcfg`. -2. A disk template named `col_.vm`, resolved through layout/table naming fallbacks. -3. The matching XObject field display value. -4. An XDocument pseudo-field value. +- `struct-obj-filter` and `struct-obj-filter-and`: AND +- `struct-obj-filter-or`: OR -Without columns, the table uses `row_layout` / `header_layout` through `LayoutServiceRole`, with local-wiki and central-wiki fallback. Edit/inline actions add create/delete links and a template row for client-side insertion. +Filters affect object selection, object streams, request keys, and new-object numbers. A CelTag filter named `type` may also select its tag type. -## Client Behavior +Current page types include `FormField`, `InputTag`, `NumberTag`, `TextAreaTag`, `SelectTag`, `OptionTag`, `SelectTagAutocomplete`, `DateTime`, `HiddenTag`, `DisplayField`, `LabelTag`, `LanguageSelector`, `SubmitLink`, `ObjectList`, and `Table`. -Struct editor JS is legacy-page JS. Expect Prototype/YUI/jQuery/Select2 integration rather than a modern isolated frontend. +Prefer current names over legacy `InputField`, `TextAreaField`, and `DateTimePicker` unless existing DB layouts depend on them. Field configuration lives in matching `*EditorClass` classes and `celTemplates/fieldTypeEdit/` templates. -- `StructEditor.mjs` manages dirty editors, close/save buttons, save-and-continue, and events such as `structEdit:finishedInitialize`, `structEdit:finishedLoading`, `structEdit:saveAndContinueButtonSuccessful`, and `structEdit:saveAndContinueButtonFailed`. -- `cel-table.mjs` defines the `cel-table` custom element for table row create/delete behavior. -- New client-created objects get negative object numbers. Existing objects are deleted by prefixing the object number in form field names with `^`. -- `StructObjectListEdit.mjs` handles similar repeated-object editing for `ObjectList`. +## Autocomplete And Tables -When debugging browser behavior, inspect the rendered form field names and data attributes before changing Java code. +`SelectTagAutocomplete` delegates search and selected-value behavior to an `AutocompleteRole`. Custom implementations need a distinct component hint and may provide extra JS through `getJsFilePath()`. -## Change Workflow +`Celements.StructTableClass` supports: -For layout/content issues: +- `DOC`: query documents. +- `OBJ`: iterate matching XObjects on the current document. +- `OBJLINK`: resolve linked documents from `reference`, `ref`, or `link`. -1. Identify the current document page type and resolved struct layout space. -2. Inspect the relevant layout cell documents, their page types, parent/menu order, `Celements.CellClass`, `Celements.StructEditFieldClass`, field-type-specific objects, and `Classes.KeyValueClass` filters. -3. Map the cell page type to its Java page type and Velocity view template. -4. Check the rendered field name, object number source, current document context, request language, and request parameters. -5. Check rights: both script services hide output when view rights are missing. +Optional `StructTableColumnClass` objects sort by `order`, then object number. Column content resolves from configured Velocity, `col_.vm`, an XObject field, then an XDocument pseudo-field. Without columns, `header_layout` and `row_layout` render through `LayoutServiceRole` with wiki fallback. -For code changes: +Client-created objects use negative numbers. Deletion marks form names with a `^`-prefixed object number. `StructEditor.mjs`, `cel-table.mjs`, and `StructObjectListEdit.mjs` manage this client behavior. -1. Add or update the class definition if new configuration is stored on XWiki objects. -2. Add or update the Java page type when a new field type or tag needs attribute collection. -3. Add the view template under `celTemplates/` and the edit-config template under `celTemplates/fieldTypeEdit/` when editors must configure it. -4. Register legacy components in `META-INF/components.txt` unless the module has been deliberately migrated to Spring scanning. -5. Update JS/CSS only where the rendered templates actually include it. -6. Run focused tests in `component/src/test/java`, plus a whitespace check on changed files. +## Debugging And Changes -## Pitfalls +For a layout issue, identify the document page type and resolved layout, inspect the cell page type and objects, map it to Java and Velocity, then check field names, object number, language, request values, rights, and central-wiki fallback. -- Do not confuse layout space resolution with skin/resource resolution. -- Do not assume a layout cell document is the data document. The cell config is read from the layout; field values are read from the current document. -- Do not ignore request values. They intentionally override stored values while rendering validation failures or partial saves. -- Do not delete `components.txt` entries as cleanup unless component registration has been revalidated. -- Be precise with `struct-obj-filter` labels; a spelling or label-family change alters object selection. -- Check central-wiki fallback before declaring a layout missing. -- Check old page type names before renaming layouts; existing DB content may still use deprecated aliases. +For a new stored option or field type, update the XClass definition, Java page type, view template, and field configuration template as needed. Register legacy components in `META-INF/components.txt` unless Spring scanning deliberately replaces registration. Run focused component tests and relevant client checks. diff --git a/skills/celements-struct/agents/openai.yaml b/skills/celements-struct/agents/openai.yaml deleted file mode 100644 index e078e68..0000000 --- a/skills/celements-struct/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Celements Struct" - short_description: "Structured editor and table layouts in Celements" - default_prompt: "Use $celements-struct when working with Celements structured editor layouts, struct field page types, object filters, tables, or autocomplete." diff --git a/skills/celements-testing/SKILL.md b/skills/celements-testing/SKILL.md index 28824fc..2794fec 100644 --- a/skills/celements-testing/SKILL.md +++ b/skills/celements-testing/SKILL.md @@ -1,113 +1,43 @@ --- name: celements-testing -description: Use when writing or refactoring tests in Celements Java codebases, including Celements-based Progon code, especially JUnit and EasyMock tests built on com.celements.common.test.AbstractComponentTest for components, Spring beans, controllers, listeners, and services. Covers registerComponentMocks, getMock, createDefaultMock, replayDefault and verifyDefault, bean lookup, and unwrapping Spring AOP proxies when the goal is to test method logic rather than annotations. +description: Use when writing Celements Java tests with AbstractComponentTest and EasyMock. --- # Celements Testing -## Overview +Use `AbstractComponentTest` when the unit depends on Celements component wiring, XWiki APIs, Spring beans, or registered component mocks. Test public method behavior directly; use MVC tooling only when the web layer itself is under test. -Prefer Celements component-style tests when the code under test depends on the Celements component container, XWiki-derived APIs, Spring wiring, or existing component mocks. Progon is Celements-based, so the same test guidance applies there. Keep the test focused on method behavior: search params, rights checks, conversion, exception mapping, and returned DTOs. +## Setup -## When To Use +1. Extend `com.celements.common.test.AbstractComponentTest`. +2. Register common collaborators with `registerComponentMocks(...)`. +3. Use `registerComponentMock(...)` for a hinted or prebuilt component. +4. Load the real unit with the inherited `getBeanFactory().getBean(...)`. +5. Set expectations, call `replayDefault()`, invoke the unit, assert, then call `verifyDefault()`. -Use this skill when: +Use the parent POM's managed test dependencies. Add `celements-shared-tests`, JUnit, or EasyMock to the module only when they are not inherited. -- the test should extend `com.celements.common.test.AbstractComponentTest` -- collaborators should be registered with `registerComponentMocks(...)` or `registerComponentMock(...)` -- the class under test is loaded from the Celements or Spring container -- the code uses EasyMock and should rely on `replayDefault()` and `verifyDefault()` -- the goal is direct method testing, not HTTP annotation or MVC-layer testing +## Mock Selection -Do not default to Spring's `MockMvc` or annotation tests unless the user explicitly asks to test the web layer. +- Use `getMock(Type.class)` for registered collaborators. +- Use `createDefaultMock(...)` for shared fixture mocks handled by `replayDefault()` and `verifyDefault()`. +- Use EasyMock `createMock(...)` for scenario-local or multiple same-type mocks; pass them explicitly to `replayDefault(mock)` and `verifyDefault(mock)`. +- Keep expectations local to the test unless shared setup removes meaningful duplication. -## Dependencies And Libraries +Do not replace core harness infrastructure such as `Execution` with standalone mocks unless the test requires it. -The standard stack for these tests is: +## Bean Lookup And Proxies -- `junit:junit` for JUnit style `@Before` and `@Test` -- `org.easymock:easymock` for EasyMock expectations and mocks -- `com.celements:celements-shared-tests` for `AbstractComponentTest` +Use inherited container helpers, not static `SpringContextProvider` or `Utils.getComponent(...)` lookup. For hinted components: -In many Celements modules and Celements-based Progon modules these arrive from the shared parent pom already. If `AbstractComponentTest`, EasyMock helpers, or the test harness classes are missing, first inspect the parent pom before adding duplicate module-level dependencies. - -If the module does need explicit test dependencies, the minimal set is usually: - -```xml - - com.celements - celements-shared-tests - ... - test - - - junit - junit - test - - - org.easymock - easymock - test - +```java +ClassDefinition classDef = getBeanFactory() + .getBean(MyClass.CLASS_DEF_HINT, ClassDefinition.class); ``` -Use the version managed by the parent pom when available instead of duplicating it in the module. - -Additional test-scope dependencies are module-specific. Inspect the parent pom and neighboring -tests before adding or duplicating them. - -## Default Workflow - -1. Extend `AbstractComponentTest`. -2. In `@Before`, register collaborators with `registerComponentMocks(...)`. -3. Register hinted or special components individually with `registerComponentMock(...)` when needed. -4. Load the real class under test from the container. -5. Access registered collaborators through `getMock(...)` inside each test. -6. Use `createDefaultMock(...)` for fixture/shared mocks and plain EasyMock `createMock(...)` for scenario-local mocks, especially multiple same-type instances. -7. Use `replayDefault()` before invoking the method and `verifyDefault()` after assertions; pass explicit `createMock(...)` instances to these calls. - -## Core Patterns - -### Registering dependencies - -- Prefer `registerComponentMocks(...)` for the common set of collaborators. -- Use `registerComponentMock(...)` only when a test needs a single special registration, a named hint, or a prebuilt instance. -- Avoid a large block of private mock fields. Use `getMock(TheClass.class)` at the call site unless the same mock is reused heavily in helper methods. - -### Loading the unit under test - -- In tests extending `AbstractComponentTest`, use the parent-class helpers directly: - `getBeanFactory()`, `registerComponentMocks(...)`, `getMock(...)`, `createDefaultMock(...)`, - `replayDefault()`, and `verifyDefault()`. -- For controllers, listeners, beans, roles, and hinted components in `AbstractComponentTest`-based tests, use `getBeanFactory().getBean(...)`. -- For hinted lookups such as class definitions, use the bean-factory form, e.g. `getBeanFactory().getBean(MyClass.CLASS_DEF_HINT, ClassDefinition.class)`. -- Avoid static lookups in tests that extend `AbstractComponentTest`: neither - `SpringContextProvider.getBeanFactory().getBean(...)` nor legacy - `com.xpn.xwiki.web.Utils.getComponent(...)`. -- If the bean is returned as a Spring AOP proxy and the user wants unit tests for the method body, unwrap the proxy target before invoking methods. - -Example: +When a Spring AOP proxy would test interceptors rather than the requested method logic, unwrap its target: ```java -private ActorController controller; - -@Before -public void prepareTest() throws Exception { - registerComponentMocks( - UserService.class, - IRightsAccessFacadeRole.class, - ExportUtilService.class, - IModelAccessFacade.class, - IOrgServiceRole.class, - IOrgObjectServiceRole.class, - ActorDtoConverter.class, - ActorSearcher.class, - SearchParamsBuilder.class, - ProgonApiUtils.class); - controller = getBeanTarget(ActorController.class); -} - @SuppressWarnings("unchecked") private T getBeanTarget(Class beanClass) throws Exception { T bean = getBeanFactory().getBean(beanClass); @@ -118,60 +48,14 @@ private T getBeanTarget(Class beanClass) throws Exception { } ``` -### Writing expectations +Keep the proxy when annotation or interceptor behavior is part of the test. -- Use `getMock(...)` for registered collaborators. -- Use `createDefaultMock(...)` for fixture or shared collaborators that naturally belong to the test setup and should be replayed and verified by plain `replayDefault()` and `verifyDefault()`. -- Use plain EasyMock `createMock(...)` for scenario-local mocks, especially when a test needs multiple instances of the same type or the mock is only meaningful inside one test method. Pass those mocks explicitly to `replayDefault(mock1, mock2)` and `verifyDefault(mock1, mock2)`. -- Do not convert shared collaborators to explicit mocks just because they are manually registered as Spring beans; if they are part of the fixture, keeping them in the default mock set is clearer. -- Keep expectations local to the scenario. Avoid a large shared fixture unless it removes real duplication. - -Example: - -```java -expect(getMock(SearchParamsBuilder.class).buildSearchParams(same(dto))) - .andReturn(params); -expect(getMock(IOrgServiceRole.class).getOrgSpaceRef(eq(OrgType.COMPANY), isNull())) - .andReturn(companySpace); -expect(getMock(IRightsAccessFacadeRole.class).hasAccessLevel(companySpace, EAccessLevel.VIEW)) - .andReturn(false); - -replayDefault(); -assertThrows(ForbiddenException.class, () -> controller.searchActors(dto)); -verifyDefault(); -``` +## Conventions -## Best Practices - -- Name JUnit setup methods `prepareTest`. -- Name test methods with the `test_...` convention. -- Test the public method directly. Do not add framework setup that is unrelated to the behavior under test. -- Keep assertions on observable outcomes: returned DTOs, response status, thrown exceptions, or interactions that define behavior. -- For controller tests, cover both the happy path and the main denial or not-found branches. -- If the code under test depends on named components such as configuration sources, register the hinted component explicitly. -- In `AbstractComponentTest`-based tests, use parent helpers such as `getBeanFactory()` before any - direct container utility. -- Reuse real infrastructure only where the test harness expects it. In `AbstractComponentTest`, core infrastructure like `org.xwiki.context.Execution` may be safer left as the registered component than replaced with a standalone mock. -- When the project already has similar Celements tests, follow the local house style before introducing a new pattern. - -## Common Pitfalls - -- Loading a proxied Spring bean and then accidentally testing security interceptors instead of the method body. -- Keeping one private field per mock instead of using `registerComponentMocks(...)` plus `getMock(...)`. -- Mixing plain `replay(...)` and `verify(...)` calls with `replayDefault()` and `verifyDefault()` without a reason. -- Manually constructing the class under test when container wiring is part of what should be exercised. -- Testing annotations or framework metadata when the user only asked for unit tests of the method logic. - -## Fast Checklist - -- `AbstractComponentTest` -- `registerComponentMocks(...)` first -- prefer parent helpers from `AbstractComponentTest` -- use the parent `getBeanFactory().getBean(...)` helper for all component lookups -- unwrap `Advised` beans when avoiding annotation testing -- `getMock(...)` for registered collaborators -- `createDefaultMock(...)` for fixture/shared collaborators -- `createMock(...)` for multiple same-type or purely scenario-local mocks -- pass explicit mocks to `replayDefault(...)` and `verifyDefault(...)` -- name setup methods `prepareTest` -- name test methods `test_...` +- Name setup methods `prepareTest` and tests `test_...`. +- Assert observable results and contract-defining interactions. +- Cover the happy path and primary denial, not-found, or exception branch. +- Follow nearby Celements tests before introducing a new pattern. +- Do not mix plain EasyMock replay/verify calls with default helpers without a reason. +- Do not construct the unit manually when container wiring is relevant. +- Separate the replay/verify block with empty lines from the arrange and assert blocks for visual clarity. diff --git a/skills/celements-testing/agents/openai.yaml b/skills/celements-testing/agents/openai.yaml deleted file mode 100644 index 3509135..0000000 --- a/skills/celements-testing/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Celements Testing" - short_description: "Patterns and guidance for Celements unit tests" - default_prompt: "Use this skill when writing or refactoring Celements tests based on AbstractComponentTest, including in Progon." diff --git a/skills/celements-velocity/SKILL.md b/skills/celements-velocity/SKILL.md index 9abf92a..b47fbe2 100644 --- a/skills/celements-velocity/SKILL.md +++ b/skills/celements-velocity/SKILL.md @@ -1,100 +1,37 @@ --- name: celements-velocity -description: Use when maintaining, reviewing, or adding Apache Velocity 1.7 templates in Celements or Celements-based Progon projects, especially choosing between Velocity server-side rendering, Spring MVC, and Vue.js; working with file-based `.vm` templates, `#parse`, Celements `$services` APIs, quiet references, output encoding, or authorization and CSRF checks for request-triggered mutations. +description: Use when writing or reviewing Celements Apache Velocity 1.7 templates. --- # Celements Velocity -## Choose the Technology by Context +Use Velocity for server-rendered markup, template composition, configuration, and light orchestration. Prefer Java/Spring MVC for complex request or domain logic and Vue for substantial client interaction. Do not rewrite working templates only because they use Velocity. -Use Velocity for server-side rendering where it fits the surrounding Celements architecture. Velocity remains an established part of Celements and is not generally considered legacy or scheduled for replacement. +## Compatibility And Structure -Treat many existing Velocity scripts as legacy because Velocity was historically overused in this codebase. Do not assume an existing template is a good example without checking its age, structure, and use of modern Celements APIs. +Write Velocity 1.7-compatible VTL. Use the official [VTL reference](https://velocity.apache.org/engine/1.7/vtl-reference.html) and [user guide](https://velocity.apache.org/engine/1.7/user-guide.html); Velocity 2.x and current XWiki behavior may differ from Celements. -Choose the technology according to the responsibility: +Keep substantial VTL in version-controlled `.vm` files and compose it with `#parse`. Keep Wiki/XAR content limited to integration. Reuse shared templates; do not use `#evaluate` as a substitute for a disk template. -- Use Velocity for server-rendered markup and template composition. -- Prefer Java and Spring MVC for request handling, domain logic, and complex server-side behavior. -- Prefer Vue.js or an existing frontend component for substantial client-side interaction. +## Celements Services -Do not rewrite working templates merely because they use Velocity. Apply these choices to new work and substantial rewrites. +Prefer verified `$services.` APIs over new `$xwiki.xxx` calls. Before replacing or adding an API call: -## Use Velocity 1.7 +1. Search current templates for local usage. +2. Inspect the Java `ScriptService` and focused tests. +3. Follow a modern example from the same module when possible. -Write Apache Velocity 1.7-compatible VTL. Consult only the official Apache documentation for the language: +Common services include `$services.reference`, `$services.modelAccess`, `$services.url`, `$services.rightsAccess`, `$services.date`, `$services.json`, and `$services.celementsweb`. This is not a complete API list. -- [Velocity 1.7 VTL reference](https://velocity.apache.org/engine/1.7/vtl-reference.html) -- [Velocity 1.7 user guide](https://velocity.apache.org/engine/1.7/user-guide.html) +## Rendering Safety -Do not copy Velocity 2.x syntax or behavior without verifying compatibility. Do not use XWiki documentation as Celements documentation: Celements forked XWiki long ago, so current XWiki behavior and examples may not apply. +- Use `$!value` or `$!{value}` only to suppress legitimately unresolved output; it does not escape or provide a fallback. +- Escape dynamic HTML/XML, JavaScript, and URL data for its output context with the local `$escapetool` conventions. +- Also HTML/XML-escape a URL when placing it in an HTML attribute. +- Build JSON with `$services.json.newBuilder()`, not string concatenation. -## Keep Velocity Code on Disk +For request-triggered mutations, check authorization through `$services.rightsAccess` and validate `$services.csrf.isTokenValid(...)`. UI visibility is not authorization. Delegate complex mutations to secured Java code. -Store substantial Velocity code in version-controlled `.vm` files. Use `#parse('path/to/template.vm')` to invoke and compose disk templates. Keep Velocity embedded in Wiki or XAR content to the minimal integration needed to parse the disk template. +## Review -Prefer existing shared disk templates over copying their implementation. When Java extraction is not practical, split a large implementation into focused `.vm` files. Do not use `#evaluate` as a substitute for a version-controlled template. - -## Prefer Celements ScriptServices - -Treat `$xwiki.xxx` calls as a legacy-code smell. For new or substantially rewritten code, look for an existing Celements ScriptService exposed through `$services.xxx`. - -Do not mechanically replace `$xwiki` calls. Verify the available Celements API and its behavior in local Java code, tests, and known-modern templates: - -1. Search current `.vm` files for relevant `$services.` usage. -2. Find and inspect the corresponding Java `ScriptService` implementation. -3. Read its public methods and focused tests. -4. Prefer examples from the same module or another known-modern project. -5. If no suitable service exists, report that before introducing a new legacy `$xwiki` call. - -Common modern Celements services include `$services.reference`, `$services.modelAccess`, `$services.url`, `$services.rightsAccess`, `$services.date`, `$services.json`, and `$services.celementsweb`. Verify each service locally rather than treating this list as a complete API reference. - -## Use Quiet References - -Use quiet references such as `$!value` and `$!{value}` when a value may legitimately resolve to `null`. Velocity 1.7 otherwise renders the unresolved Velocity expression, which is not useful to customers. - -Use `$!{value}` when braces are needed to separate the reference from adjacent text. Quiet references only suppress unresolved output. They do not provide a fallback value, change control flow, or escape the resolved value. Use an explicit `#if` when absence changes behavior. - -## Encode Output for Its Context - -Encode request data, document data, and other dynamic values for the context where they are rendered: - -- Use `$escapetool.html(...)` or `$escapetool.xml(...)` for HTML text and attributes, following the convention of the surrounding template. -- Use `$escapetool.javascript(...)` for values embedded in JavaScript. -- Use `$escapetool.url(...)` for dynamic URL values or components. Also HTML/XML-encode the resulting value when rendering it into an HTML attribute. -- Use `$services.json.newBuilder()` for JSON instead of manually concatenating or escaping JSON strings. - -Do not treat a quiet reference as output encoding. - -## Guard Request-Triggered Mutations - -Before a request-triggered save, delete, or other mutation, verify that the current user has the required authorization through `$services.rightsAccess` and validate the CSRF token with `$services.csrf.isTokenValid(...)`. - -Do not rely on hiding an action in the UI. For complex mutations, delegate to a secured Java controller or service instead of implementing the mutation in an AppScript or `celAjax` template. - -## Keep Templates Focused - -Use Velocity for server-rendered markup, template composition, configuration, and small amounts of orchestration. During substantial rewrites, move domain logic and complex processing into Java where practical. If Java extraction is not practical, divide the implementation among focused disk-based templates. - -Prefer configuring an existing frontend or Celements component over reimplementing its behavior with Velocity loops and logic. Keep AppScript and setup templates small and delegate work to services or dedicated templates. - -## Follow Established Modern Patterns - -- Resolve and serialize references through `$services.reference` where the target service supports reference objects. Do not manually assemble or split document names without first checking the service API. -- Use `$services.modelAccess`, `$services.url`, and other verified local ScriptServices instead of introducing new legacy `$xwiki` usage. -- Use `$services.json.newBuilder()` for JSON produced by Velocity instead of manually concatenating JSON strings. -- Use Velocity to configure existing frontend components when those components already provide the required rendering or interaction. - -Treat these as patterns established by current Celements code, not instructions to mechanically refactor unrelated legacy templates. - -## Review Checklist - -- Confirm that Velocity fits the responsibility; consider Spring MVC for complex server-side behavior and Vue.js for substantial client-side interaction. -- Confirm compatibility with Apache Velocity 1.7. -- Keep substantial VTL in a version-controlled `.vm` file invoked with `#parse`. -- Investigate every new or changed `$xwiki.xxx` call for a verified Celements ScriptService alternative. -- Use quiet references for legitimately nullable rendered values. -- Encode every dynamic output for its HTML/XML, JavaScript, URL, or JSON context; do not treat quiet references as escaping. -- Require `$services.rightsAccess` authorization and `$services.csrf.isTokenValid(...)` before request-triggered mutations, or delegate complex mutations to secured Java code. -- Keep orchestration small and move substantial logic to Java where practical. -- Reuse existing disk templates and frontend or Celements components. -- Use the Celements JSON builder when producing JSON in Velocity. +Check Velocity 1.7 compatibility, disk-based template composition, nullable references, contextual encoding, verified Celements service usage, authorization, CSRF protection, and whether substantial logic belongs in Java or an existing frontend component. diff --git a/skills/celements-vue/SKILL.md b/skills/celements-vue/SKILL.md deleted file mode 100644 index acbc679..0000000 --- a/skills/celements-vue/SKILL.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -name: celements-vue -description: Use when adding, reviewing, or debugging Vue islands in Celements or Celements-based Progon legacy pages, especially Vite frontend entrypoints, Velocity addExtJSfileOnce integration, Vue SFCs, reka-ui/headless components, Tailwind v4 with prefixed no-preflight utilities, Vite manifest JS/CSS resolution, and CSS cascade conflicts with legacy application.css. ---- - -# Celements Vue - -## Overview - -Build Vue as small page-level islands inside the legacy Celements page, not as a global SPA replacement. Keep the integration manifest-driven, CSS-conscious, and reversible: Velocity includes one stable frontend source path, Vite emits hashed JS/CSS, and the Celements frontend resource resolver maps the source path to the emitted assets. - -Prefer Vue + reka-ui + locally styled Tailwind utilities for new interactive islands. Avoid copying full app setups from standalone frontends unless the page is truly isolated. - -## Default Workflow - -1. Inspect the existing frontend structure: - - `src/main/frontend//index.ts` for Vite entrypoint names. - - `vite.config.ts` for plugin setup, `build.manifest`, `outDir`, and Rollup input. - - existing Velocity/appscript includes that call `services.javascript.addExtJSfileOnce`. -2. Add a standalone entry for each island or related page surface. -3. Mount into a stable DOM root from the legacy page. Pass server-rendered data through `data-*` attributes or JSON script tags. -4. Include the source entry from Velocity: - -```velocity -$!services.javascript.addExtJSfileOnce(':frontend///main.ts', 'file') -``` - -5. Let the frontend resolver and Vite manifest handle hashed output. Do not hard-code `dist/*.mjs` or `assets/*.css` names in Velocity. -6. Run focused verification: - - `rtk npm run format` - - `rtk npm run type-check` - - `rtk npm run build` - - `rtk git diff --check -- ` - -## Vue Island Shape - -Use `src/main/frontend/shared/vue/mount.ts` instead of redefining mount logic in island entries. Pass props according to the island's needs. - -```ts -import { mountVueApp } from "@/shared/vue/mount"; -import Island from "@/progon/my-island/Island.vue"; - -mountVueApp("my-island", Island); -``` - -Keep the island root owned by Vue. Do not let legacy scripts mutate the same subtree after mount. If mixed ownership is unavoidable, split the DOM into separate roots. - -## Reka UI Guidance - -Use `reka-ui` for accessible headless primitives when a component needs behavior such as popovers, collapsibles, dialogs, tabs, menus, or selects. - -For component-specific API details and examples, consult Reka UI's LLM-friendly docs at https://reka-ui.com/llms.txt. - -Good defaults: - -- import only the primitives needed by the island -- style them locally with prefixed Tailwind utilities -- check portal/overlay behavior on the actual Celements page -- keep popover/dialog z-index explicit when legacy overlays exist -- avoid global theme CSS from component libraries - -## Tailwind v4 Setup - -When Tailwind is wanted in a Vue island, import `src/main/frontend/shared/vue/tailwind.css` from the island entry: - -```ts -import "@/shared/vue/tailwind.css"; -``` - -That stylesheet must keep the legacy-safe Tailwind setup: prefix enabled, preflight disabled, utilities important. Keep `important` because legacy Celements CSS is unlayered and often targets base elements, so layered Tailwind utilities may otherwise lose even with class selectors. - -Do not use a plain global Tailwind import: - -```css -@import "tailwindcss"; -``` - -That includes preflight/base behavior and is too broad for normal Celements legacy pages. - -Use Tailwind v4 prefix syntax in templates: - -```vue - -``` - -The prefix is a variant-style prefix (`tw:text-white`), not Tailwind v3-style `tw-text-white`. - -Avoid constructing class names dynamically with string concatenation unless the generated classes are safelisted or otherwise discoverable. - -## Manifest CSS Requirements - -Vite extracts CSS from Vue SFCs and imported stylesheets. The Celements frontend resource path must include both the emitted JS file and the manifest `css` entries for the source entrypoint. - -When styling appears missing at runtime but the build is green: - -- inspect `src/main/webapp/resources/dist/.vite/manifest*.json` -- confirm the entry has a `css` array -- confirm the Celements include path loads those CSS files when registering the frontend JS -- confirm the Velocity code uses the stable source path, such as `:frontend///main.ts` - -If CSS manifest inclusion is not available in the target app, fix that integration before leaning on Tailwind or SFC CSS. Green Vite builds alone do not prove runtime styling. - -## Legacy Page Limits - -Keep these constraints in mind: - -- No global SPA assumptions: the page may already have YUI, jQuery, Prototype-era behavior, Select2, or custom scripts. -- No global CSS resets unless the whole page is isolated. -- Avoid sharing a DOM subtree between Vue and legacy scripts. -- Treat overlays, focus trapping, scroll locking, and z-index as integration risks. -- Do not rely on router ownership of the page unless the app was designed as a full Vue surface. -- Prefer data passed at mount time over scraping legacy DOM after mount. -- Keep bundle scope intentional; avoid loading island-only dependencies on unrelated pages. - -## Review Checklist - -- Velocity uses `:frontend/.../main.ts`, not hashed output files. -- The Vite entry is registered in the local entry map. -- Vue islands use `src/main/frontend/shared/vue/mount.ts`. -- Vue islands that need Tailwind import `src/main/frontend/shared/vue/tailwind.css`. -- SFC support has `*.vue` typing, usually via `src/main/frontend/env.d.ts`. -- Reka primitives are directly imported and locally styled. -- Tailwind uses `@tailwindcss/vite`, `prefix(tw)`, no preflight, and important utilities. -- Generated manifest CSS is included by the Celements frontend resolver. -- The build, type-check, formatter, and diff whitespace checks pass. diff --git a/skills/celements-vue/agents/openai.yaml b/skills/celements-vue/agents/openai.yaml deleted file mode 100644 index 3f6d246..0000000 --- a/skills/celements-vue/agents/openai.yaml +++ /dev/null @@ -1,4 +0,0 @@ -interface: - display_name: "Celements Vue" - short_description: "Vue islands with Reka and Tailwind in Celements" - default_prompt: "Use $celements-vue when adding or reviewing Vue islands with Reka UI and Tailwind in a Celements or Progon legacy page." diff --git a/skills/celements-webapp-vue-islands/SKILL.md b/skills/celements-webapp-vue-islands/SKILL.md new file mode 100644 index 0000000..31ce654 --- /dev/null +++ b/skills/celements-webapp-vue-islands/SKILL.md @@ -0,0 +1,61 @@ +--- +name: celements-webapp-vue-islands +description: Use when integrating Vue islands, Vite assets, or scoped Tailwind into legacy Celements pages. +--- + +# Celements Webapp Vue Islands + +Build page-level Vue islands, not a global SPA. Keep Vue and legacy scripts from owning the same DOM subtree. + +## Integration + +1. Inspect `vite.config.ts`, existing `src/main/frontend` entries, and nearby Velocity includes. +2. Add an entry for the island and mount it into a stable root. +3. Pass server data through `data-*` attributes or a JSON script element. +4. Include the stable source entry from Velocity: + +```velocity +$!services.javascript.addExtJSfileOnce(':frontend///main.ts', 'file') +``` + +5. Let the Vite manifest resolve hashed JS and CSS. Never hard-code `dist` asset names in Velocity. + +Use the shared mount helper when available: + +```ts +import { mountVueApp } from "@/shared/vue/mount"; +import Island from "@/progon/my-island/Island.vue"; + +mountVueApp("my-island", Island); +``` + +## Reka UI And Tailwind + +Use `reka-ui` for accessible dialogs, popovers, tabs, menus, and similar behavior. Import only needed primitives, style them locally, and verify portals, focus handling, scroll locking, and z-index on the legacy page. API reference: . + +For Tailwind, import the shared legacy-safe stylesheet: + +```ts +import "@/shared/vue/tailwind.css"; +``` + +It must retain the project prefix, disabled preflight, and important utilities. Use Tailwind v4 prefix syntax such as `tw:text-white`, not `tw-text-white`. Avoid dynamically constructed classes unless Vite can discover or safelist them. + +Do not use a global `@import "tailwindcss"`; its reset is too broad for legacy pages. + +## Manifest CSS + +Vite extracts imported and SFC CSS. The Celements resource resolver must load the manifest entry's `css` files as well as its JS file. + +If runtime styles are missing despite a green build: + +1. Inspect `src/main/webapp/resources/dist/.vite/manifest*.json`. +2. Confirm the source entry has a `css` array. +3. Confirm the Celements include resolves those CSS files. +4. Confirm Velocity references `:frontend/.../main.ts`. + +Fix manifest integration rather than hard-coding generated CSS names. + +## Verify + +Use the scripts defined by the project, including formatting, type checking, linting, and build checks. Also verify the island on the real page for legacy CSS conflicts, overlays, and unrelated-page bundle loading. diff --git a/skills/lambda-exception-util/SKILL.md b/skills/lambda-exception-util/SKILL.md index f3136e1..9f60025 100644 --- a/skills/lambda-exception-util/SKILL.md +++ b/skills/lambda-exception-util/SKILL.md @@ -1,128 +1,42 @@ --- name: lambda-exception-util -description: Use when handling checked exceptions within Java streams, optionals, or lambda expressions in Celements or Progon code, using com.celements.common.lambda.LambdaExceptionUtil to sneakily throw checked exceptions while still enforcing compile-time checked exception handling at the caller/outer scope. +description: Use in Celements/Progon when a checked exception must pass through a Java lambda using LambdaExceptionUtil. --- # Lambda Exception Utility -## Overview +`com.celements.common.lambda.LambdaExceptionUtil` adapts checked-exception lambdas to standard Java functional interfaces. It sneaky-throws inside the lambda while requiring the caller to catch or declare the checked exception. -`com.celements.common.lambda.LambdaExceptionUtil` is a utility designed to simplify handling checked exceptions inside Java lambdas. Standard Java functional interfaces (like `Function`, `Predicate`, `Consumer`, etc.) do not allow throwing checked exceptions. `LambdaExceptionUtil` resolves this by wrapping throwing lambdas and using a generics-based "sneaky throw" pattern to bypass the compiler check inside the lambda, while still declaring the throws signature on the wrapper method to enforce compile-time exception handling at the caller's outer scope. - -## Core API & Throwing Interfaces - -`LambdaExceptionUtil` defines several `@FunctionalInterface` matching Java's standard functional interfaces, but allowing exceptions: - -| Standard Interface | Throwing Variant | Utility Wrapper Method | -| :--- | :--- | :--- | -| `java.util.function.Function` | `ThrowingFunction` | `rethrowFunction` or `rethrow` | -| `java.util.function.Predicate` | `ThrowingPredicate` | `rethrowPredicate` or `rethrow` | -| `java.util.function.Consumer` | `ThrowingConsumer` | `rethrowConsumer` or `rethrow` | -| `java.util.function.BiConsumer` | `ThrowingBiConsumer` | `rethrowBiConsumer` or `rethrow` | -| `java.util.function.Supplier` | `ThrowingSupplier` | `rethrowSupplier` or `rethrow` | -| `java.lang.Runnable` | `ThrowingRunnable` | `rethrowRunnable` or `rethrow` | - -## How to Import - -Always use static imports for concise code: +Use static imports: ```java import static com.celements.common.lambda.LambdaExceptionUtil.*; ``` -## Detailed Usage Patterns +## Wrappers -### 1. Mapping with Checked Exceptions (`Function`) -When using `Stream.map` or `Optional.map` with a method that throws a checked exception: +| Lambda | Wrapper | +| --- | --- | +| `ThrowingFunction` | `rethrowFunction` | +| `ThrowingPredicate` | `rethrowPredicate` | +| `ThrowingConsumer` | `rethrowConsumer` | +| `ThrowingBiConsumer` | `rethrowBiConsumer` | +| `ThrowingSupplier` | `rethrowSupplier` | +| `ThrowingRunnable` | `rethrowRunnable` | -**Standard Java (Verbose/Boilerplate):** -```java -List results = documents.stream() - .map(doc -> { - try { - return evaluateVelocityText(doc, text); // Throws XWikiVelocityException - } catch (XWikiVelocityException e) { - throw new RuntimeException(e); - } - }) - .collect(toList()); -``` +`rethrow(...)` is overloaded for all variants. Prefer the explicit name when type inference is ambiguous. -**With LambdaExceptionUtil:** ```java -// The enclosing method MUST declare: throws XWikiVelocityException List results = documents.stream() .map(rethrowFunction(doc -> evaluateVelocityText(doc, text))) - .collect(toList()); -``` - -### 2. Filtering with Checked Exceptions (`Predicate`) -When using `Stream.filter` with a predicate that checks a condition throwing a checked exception: - -```java -// The enclosing method MUST declare: throws ClassNotFoundException -List classes = classNames.stream() - .filter(rethrowPredicate(className -> Class.forName(className) != null)) - .collect(toList()); -``` - -### 3. Iterating/Consuming with Checked Exceptions (`Consumer`) -When executing side-effects inside `Stream.forEach` or `Iterable.forEach`: - -```java -// The enclosing method MUST declare: throws ClassNotFoundException -classNames.forEach(rethrowConsumer(Class::forName)); + .toList(); ``` -### 4. Supplies and Computations (`Supplier`) -When loading resource values inside a container or context fallback (e.g. `computeIfAbsent`): - -```java -public void initialize(ExecutionContext context) throws ExecutionContextException { - try { - // The supplier throws ExecutionException, caught by the enclosing try-catch - context.computeIfAbsent(XWIKI, rethrow(() -> context.get(NO_AWAIT).orElse(false) - ? wikiProvider.get().orElse(null) - : wikiProvider.await(Duration.ofHours(1)))); - } catch (ExecutionException xwe) { - throw new ExecutionContextException("failed initializing XWiki", xwe); - } -} -``` - -### 5. Running Code Blocks (`Runnable`) -For throwing runnable blocks (e.g. background threads or custom runner closures): - -```java -// The enclosing method MUST declare: throws ClassNotFoundException -rethrowRunnable(() -> Class.forName(Object.class.getName())).run(); -``` - ---- - -## Best Practices - -### 1. Resolve Compiler Ambiguity by Using Specific Names -While `rethrow(...)` is overloaded for all functional types, the Java compiler's type inference may occasionally fail or resolve to the wrong overload if the compiler cannot deduce the argument type. -* **Tip:** If the compiler complains about types, use the specific named wrapper methods: - * Use `rethrowFunction(...)` instead of `rethrow(...)` for mappings. - * Use `rethrowPredicate(...)` instead of `rethrow(...)` for filters. - * Use `rethrowConsumer(...)` instead of `rethrow(...)` for iteration. - * Use `rethrowSupplier(...)` instead of `rethrow(...)` for suppliers. - * Use `rethrowRunnable(...)` instead of `rethrow(...)` for runnables. - -### 2. Declare Exceptions on the Enclosing Method -Because `LambdaExceptionUtil` wrapper methods declare `throws E`, they enforce static compiler checks on the calling block. You **must** either: -- Add the thrown checked exception to your enclosing method's `throws` clause. -- Catch the specific checked exception at the caller scope (outside the stream/lambda). - -### 3. Do Not Wrap in RuntimeException Manually -Avoid wrapping exceptions in `RuntimeException` just to bypass lambda limits when `LambdaExceptionUtil` can preserve the exact class of the checked exception, leading to much cleaner error handling and stack traces. - -## Common Pitfalls - -- **Separating wrapper creation from execution:** The checked-exception contract exists only at the rethrow* call. Do not return or store the functional interface for later execution. E.g. with streams, keep the terminal operation inside the same try-block or method. -- **Compiler type resolution failure:** Using generic `rethrow(...)` with an implicit parameter list that makes it hard for the Java compiler to infer the functional interface type. Prefer `rethrowFunction` or other explicit wrappers. -- **Forgetting caller-level exception handling:** Assuming that since it's a lambda, you don't need to handle the exception. You still must declare or catch it at the enclosing method scope. +The enclosing method must declare or catch the exception thrown by `evaluateVelocityText`. +## Rules +- Catch or declare the checked exception outside the lambda or stream. +- Keep wrapper creation and execution in the same exception-handling scope; do not store the adapted functional interface for later execution. +- Do not wrap the exception in `RuntimeException` merely to satisfy a functional interface. +- Keep a stream terminal operation inside the surrounding `try` block when it can trigger the exception.