Skip to content

FE2 DS modules & single load path for DS and control plugins - #8852

Merged
sumerjabri merged 7 commits into
craftersoftware:developfrom
rart:feature/fe2-data-sources
Aug 4, 2026
Merged

FE2 DS modules & single load path for DS and control plugins#8852
sumerjabri merged 7 commits into
craftersoftware:developfrom
rart:feature/fe2-data-sources

Conversation

@rart

@rart rart commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Replaces FE2’s hard-coded data-source type switches with a pluggable module + actions/capabilities runtime, and consolidates FE control/DS plugins onto the existing Studio PluginDescriptor model (importPluginregisterPlugin).

Data-source runtime

  • New platform under FormsEngine/dataSources/: versioned DataSourceModule, instance factory, capabilities (browse / search / upload / create / list / …), declarative actions, and ControlProps.dataSources injection via useFieldDataSources.
  • Built-in modules for all TB DS descriptors (item, media, options/list; remote S3/WebDAV as visible stubs where host services don’t exist yet).
  • Closed host API DataSourceServices (browseFiles / search / upload / create/push form).
  • Controls consume actions, not ds.type:
    • NodeSelector — item actions + create/type/path picker
    • Image / Video / Transcoded — media action groups
    • Dropdown / CheckboxGroup / LinkedDropdowninstance.list via useDataSourceListOptions
    • RTE — file picker from resolved manager actions
  • Removed legacy consolidators / dataSourceMap as the primary path; kept adapters only where NodeSelector still needs a presentation summary.
  • Ordered field DS resolution, interface matching, and field-level dataSources.errors for unknown ids/types/plugin failures.

Single plugin model

  • One packaging format: plugins export a PluginDescriptor; the host registers contributions (no self-register side effects).
  • PluginDescriptor.dataSources → installed into the DS module registry on registerPlugin.
  • PluginDescriptor.controls → installed into the control contribution registry; controlPluginLoader demand-loads via importPlugin and looks up by field.type (same pattern as DS record.type).
  • Form-definition <plugin> locators unchanged; TB still copies coords onto fields/DS records.

Correctness fixes along the way

  • Stopped infinite get_content_by_commit_id loops by stabilizing useFieldDataSources deps (content-types keyed by value, not identity churn).
  • Restored Create on NodeSelector for shared-content with empty Default Type (allowedCreatePaths + create-picker path attribution / null-safe loading).
  • Re-run resolution when content types load so wildcard contentTypes: '*' can emit create targets.

Docs & samples

  • Living docs: docs/type-builder-forms-engine.md, docs/type-builder-forms-engine-plugins.md, control handoff docs/fe2-control-plugin-single-model-implementation.md.
  • Cursor rule for TB/FE work + authoring site paths.

Still open (not in this PR)

  • Eager lib-only plugin boot (no dummy widget) and a stable host path for PluginDescriptor.utils.
  • Plugin validators / serializers / retrievers; FE form-controller lifecycle.
  • Persisting FE1-style item datasource provenance into FE2/XB XML.

Test plan

  • Editorial: open Category Landing (and similar); NodeSelector shows Browse / Search / Create; Create opens type picker when Default Type is empty
  • Image / video pickers: browse/upload/search menus from configured DSs; no console type-switch warnings
  • Dropdown / checkbox-group: options load from list-capable DSs (e.g. taxonomy) without request storms
  • RTE media insert uses bound managers
  • DevTools: no repeating get_content_by_commit_id loops on form open
  • Plugin sample shapes: descriptor with dataSources / controls loads via form-definition plugin coords (if a test plugin is installed)
  • yarn compile in ui/app

Notes

This PR includes:

Which correspond to:

Summary by CodeRabbit

  • New Features
    • Added a unified Forms Engine plugin and data-source system.
    • Added configurable browsing, searching, uploading, creating, and selecting content.
    • Added video, transcoded-video, page navigation, and enhanced media picker controls.
    • Added timezone support, additional-field handling, richer list options, and data-source actions in rich text editing.
  • Documentation
    • Added architecture, migration, workflow, and implementation guidance for Forms Engine plugins and data sources.
  • Bug Fixes
    • Improved validation, error handling, loading states, and stale-data protection across form controls and content selection.

…or load path for DS and control plugins.

Controls consume resolved actions/capabilities instead of switching on DS type; plugin bundles register via importPlugin/registerPlugin. Also fixes Create on path-only shared-content and content-types resolution loops.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The Forms Engine now uses shared plugin descriptors, data-source registries, typed action resolution, built-in modules, plugin control loading, descriptor-defined additional fields, and specialized controls.

Changes

Forms Engine plugin and data-source convergence

Layer / File(s) Summary
Plugin contracts and registration
studio-ui/ui/app/src/models/*, studio-ui/ui/app/src/services/plugin.ts, studio-ui/ui/app/src/state/reducers/uiConfig.ts, studio-ui/ui/app/src/components/FormsEngine/dataSources/*
Plugin descriptors support controls and data-source modules. Registration validates contributions, bindings, component types, duplicate types, and plugin identities.
Data-source framework and resolution
studio-ui/ui/app/src/components/FormsEngine/dataSources/*, studio-ui/ui/app/src/components/FormsEngine/dataSourceHooks/*
Typed contracts, registries, loaders, bindings, services, action adapters, list-option loading, and asynchronous field resolution are added.
Built-in data-source modules
studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/*
Repository, upload, content, list, taxonomy, transcoding, and remote-stub modules are defined and registered.
Control rendering and actions
studio-ui/ui/app/src/components/FormsEngine/lib/*, studio-ui/ui/app/src/components/FormsEngine/components/*, studio-ui/ui/app/src/components/FormsEngine/controls/*
Controls receive resolved data sources and services. Grouped action menus replace legacy picker flows. Plugin controls use shared loading and registration.
Additional fields and content models
studio-ui/ui/app/src/components/ContentTypeManagement/*, studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx, studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx, studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts
Descriptor-defined fields, plugin locators, timezone atoms, and transcoded-video array values are persisted, parsed, initialized, and rendered.
Specialized controls and services
studio-ui/ui/app/src/components/FormsEngine/controls/*, studio-ui/ui/app/src/hooks/useVideoInfo.ts, studio-ui/ui/app/src/services/content.ts, studio-ui/ui/app/src/components/FormsEngine/lib/rteUtils.ts
Navigation ordering, video metadata, video selection, transcoded-video selection, and rich-text data-source picking are added.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: jmendeza

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.72% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The pull request description does not follow the required template structure. The repository requires a 'Ticket reference or the full description' section, but the author provided a custom multi-section format instead. Reorganize the description to match the repository template: provide a ticket reference and move detailed sections into a single coherent description block.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the primary change: introducing FE2 data-source modules and consolidating plugin loading to a single path.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx (1)

605-621: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add customControls to the effect dependency array.

When fetchSiteUiConfigComplete replaces state.uiConfig.controls after mount, rerun the effect. Otherwise, custom-control additional fields can lack their atoms. importPlugin and registerPlugin update plugin registries, not state.uiConfig.controls.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx` around lines 605
- 621, Add customControls to the dependency array of the surrounding FormsEngine
effect so it reruns when state.uiConfig.controls is replaced after mount.
Preserve the existing dependencies and use the customControls value already
derived in the component; do not rely on importPlugin or registerPlugin registry
updates.
🧹 Nitpick comments (11)
studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts (1)

121-122: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the duplicated customControls/controlDescriptors merge into a shared helper. Both files independently compute { ...customControls, ...controlDescriptors } to resolve the descriptor lookup table, including an unresolved TODO about merge priority in one of them. One shared helper (e.g. resolveControlDescriptors(customControls)) keeps the merge order consistent if it ever needs to change.

  • studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts#L121-L122: replace the inline { ...customControls, ...controlDescriptors } and its TODO comment with a call to the shared helper.
  • studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx#L880-L897: replace the inline { ...customControls, ...controlDescriptors } with the same shared helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts` around
lines 121 - 122, Extract the duplicated customControls/controlDescriptors merge
into a shared resolveControlDescriptors helper, preserving the current merge
order. In studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts
lines 121-122, replace the inline merge and TODO with the helper call; make the
corresponding replacement in
studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx lines 880-897,
importing or defining the shared helper as appropriate.
studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioBrowseRepo.ts (1)

26-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated repo-path fallback logic across three built-in data-source modules. The propString(record, 'repoPath') || propString(record, 'path', '/static-assets/') fallback is copied verbatim in three files; extracting a shared helper in moduleHelpers.ts removes the duplication and keeps future fallback changes in one place.

  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioBrowseRepo.ts#L26-L41: replace the inline fallback on Line 27 with a shared resolveRepoPath(record, '/static-assets/') helper.
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioDesktopUpload.ts#L26-L41: replace the inline fallback on Line 27 with the same shared helper.
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoDesktopUpload.ts#L26-L41: replace the inline fallback on Line 27 with the same shared helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioBrowseRepo.ts`
around lines 26 - 41, Extract the shared repo-path fallback into resolveRepoPath
in moduleHelpers.ts, preserving repoPath, path, and the '/static-assets/'
default precedence. Replace the inline fallback in create in
studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioBrowseRepo.ts
lines 26-41, audioDesktopUpload.ts lines 26-41, and videoDesktopUpload.ts lines
26-41 with resolveRepoPath(record, '/static-assets/').
studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgDesktopUpload.ts (1)

27-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated repo-path fallback into moduleHelpers.ts. All three sites resolve a module's repository path with the identical expression propString(record, 'repoPath') || propString(record, 'path', '/static-assets/'); the shared root cause is that this fallback logic has not been extracted into a helper alongside propString/propBoolean.

  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgDesktopUpload.ts#L27: replace the inline expression with a shared helper, e.g. resolveRepoPath(record, '/static-assets/').
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgRepositoryUpload.ts#L27: replace the inline expression with the same shared helper.
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoBrowseRepo.ts#L27: replace the inline expression with the same shared helper.
♻️ Proposed helper addition
// in moduleHelpers.ts
+export function resolveRepoPath(record: DataSourceRecord, fallback: string): string {
+	return propString(record, 'repoPath') || propString(record, 'path', fallback);
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgDesktopUpload.ts`
at line 27, Extract the repeated repository-path fallback into a shared
resolveRepoPath helper in moduleHelpers.ts alongside propString and propBoolean.
Update imgDesktopUpload.ts (27-27), imgRepositoryUpload.ts (27-27), and
videoBrowseRepo.ts (27-27) to use the helper with the existing '/static-assets/'
fallback.
studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/sharedContent.ts (1)

85-103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse expandPathOrRaw instead of reimplementing path expansion.

The run() function manually chains ctx.expandPath?.(...) ?? options.target.path ?? repoPath. The expandPathOrRaw helper (already used in simpleTaxonomy.ts and referenced from pathUtils.ts) does the same thing. Import it here for consistency with the rest of the data-source modules.

♻️ Proposed refactor
-import {
-	capabilitiesFromActions,
-	createBrowseAction,
-	createCreateAction,
-	createSearchAction,
-	type CreateTarget,
-	propBoolean,
-	propString
-} from '../moduleHelpers';
+import {
+	capabilitiesFromActions,
+	createBrowseAction,
+	createCreateAction,
+	createSearchAction,
+	type CreateTarget,
+	propBoolean,
+	propString
+} from '../moduleHelpers';
+import { expandPathOrRaw } from '../pathUtils';
 								return ctx.services.createContent({
-									path: ctx.expandPath?.(options.target.path ?? repoPath) ?? options.target.path ?? repoPath,
+									path: expandPathOrRaw(ctx, options.target.path ?? repoPath),
 									contentTypeId: options.target.contentTypeId,
 									embedded: options.target.strategy === 'embedded'
 								});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/sharedContent.ts`
around lines 85 - 103, Update the create action’s run method to import and use
the existing expandPathOrRaw helper instead of manually chaining ctx.expandPath,
options.target.path, and repoPath. Preserve the current path precedence and pass
the resolved path to ctx.services.createContent.
studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/keyValueList.ts (1)

22-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated key/value normalization logic into moduleHelpers.ts. keyValueList.ts's parseKeyValueOptions and simpleTaxonomy.ts's itemsFromTaxonomyDoc both coerce key/value to strings, throw when both are empty, and spread the remaining properties — the shared root cause is that this DataSourceListItem normalization step has not been factored into a common helper.

  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/keyValueList.ts#L33-L38: replace the inline per-item normalization with a shared helper, e.g. normalizeListItem(item, index, sourceLabel).
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/simpleTaxonomy.ts#L38-L43: replace the inline per-item normalization with the same shared helper.
♻️ Proposed helper addition
// in moduleHelpers.ts
+export function normalizeListItem(item: unknown, index: number, sourceLabel: string): DataSourceListItem {
+	if (!item || typeof item !== 'object') {
+		throw new Error(`${sourceLabel} entry at index ${index} is not an object.`);
+	}
+	const record = item as Record<string, unknown>;
+	const key = String(record.key ?? '');
+	const value = String(record.value ?? '');
+	if (!key && !value) {
+		throw new Error(`${sourceLabel} entry at index ${index} is missing key/value.`);
+	}
+	return { ...record, key, value };
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/keyValueList.ts`
around lines 22 - 40, Extract the duplicated DataSourceListItem normalization
from parseKeyValueOptions into a shared normalizeListItem helper in
moduleHelpers.ts, preserving string coercion, empty key/value validation,
property spreading, and indexed error messages. Update keyValueList.ts lines
22-40 and simpleTaxonomy.ts lines 26-45 to use the helper with the appropriate
source label; both sites require direct changes.
studio-ui/ui/app/src/components/FormsEngine/controls/ImagePicker.tsx (1)

229-235: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an empty state when data sources resolve with no actions. All three pickers compute actionsReady as Boolean(context) && actions.length > 0 && !loading. When the status is ready but the field has no bound actions, the final branch renders an action container with no children and no message, so the author sees a blank area. NodeSelector handles the same condition with EmptyState.

  • studio-ui/ui/app/src/components/FormsEngine/controls/ImagePicker.tsx#L229-L235: add a branch before the action box that renders EmptyState when !actionsReady and the status is not loading or error.
  • studio-ui/ui/app/src/components/FormsEngine/controls/VideoPicker.tsx#L177-L186: add the same branch before the Box that renders menuOptions.
  • studio-ui/ui/app/src/components/FormsEngine/controls/TranscodedVideoPicker.tsx#L140-L149: add the same branch before the Box that renders actionMenuItems.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio-ui/ui/app/src/components/FormsEngine/controls/ImagePicker.tsx` around
lines 229 - 235, Add an EmptyState branch before the action container in
ImagePicker.tsx (lines 229-235), VideoPicker.tsx (lines 177-186), and
TranscodedVideoPicker.tsx (lines 140-149). Render it when actionsReady is false
while the corresponding data-source status is neither loading nor error, so
ready fields with no bound actions show an empty-state message instead of a
blank container.
studio-ui/ui/app/src/components/FormsEngine/components/GroupedDataSourceActionMenuItems.tsx (1)

152-180: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Forward MUI focus props to a direct MenuItem.

MenuList sees GroupedDataSourceActionMenuItems as one child. It injects autoFocus and tabIndex={0} into that child, but this component drops both props. Its MenuItem elements therefore keep tabIndex={-1}, so the host menu cannot assign initial focus or keyboard navigation correctly.

Lift the item rendering into the host menu, or forward these props to the intended MenuItem. The nested Menu and Dialog are not direct MenuList children.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@studio-ui/ui/app/src/components/FormsEngine/components/GroupedDataSourceActionMenuItems.tsx`
around lines 152 - 180, Update GroupedDataSourceActionMenuItems to accept and
forward the MUI autoFocus and tabIndex props from MenuList to the intended
direct MenuItem, preserving the existing choice rendering and handlers. Do not
apply these props to the nested Menu or Dialog; ensure the host menu can assign
initial focus and keyboard navigation.
studio-ui/.cursor/rules/type-builder-forms-engine.mdc (1)

22-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Generalize the hardcoded personal path.

Line 26 hardcodes an absolute path under a specific user's home directory (/Users/rart/Workspace/...), and lines 32-33 hardcode sandbox subpaths under it. This rules file guides any contributor or AI agent doing Type Builder / Forms Engine work, but the path only resolves on one developer's machine. Replace it with a placeholder that each contributor can set locally, or move the value out of the shared rules file.

📝 Proposed generalization
-The CrafterCMS authoring instance used for this work is:
-
-`/Users/rart/Workspace/craftercms/develop-2026.07.29.11.28`
-
-Site sandboxes (source of truth for form-definitions / site config — **not** the sample XML under `ui/app/src/components/ContentTypeManagement/` or `samples/`):
-
-| Site | Path |
-|------|------|
-| editorial | `/Users/rart/Workspace/craftercms/develop-2026.07.29.11.28/crafter-authoring/data/repos/sites/editorial/sandbox` |
-| craftercms | `/Users/rart/Workspace/craftercms/develop-2026.07.29.11.28/crafter-authoring/data/repos/sites/craftercmscom/sandbox` |
+The CrafterCMS authoring instance used for this work is `<AUTHORING_INSTANCE_ROOT>` (set this per developer machine).
+
+Site sandboxes (source of truth for form-definitions / site config — **not** the sample XML under `ui/app/src/components/ContentTypeManagement/` or `samples/`):
+
+| Site | Path |
+|------|------|
+| editorial | `<AUTHORING_INSTANCE_ROOT>/crafter-authoring/data/repos/sites/editorial/sandbox` |
+| craftercms | `<AUTHORING_INSTANCE_ROOT>/crafter-authoring/data/repos/sites/craftercmscom/sandbox` |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio-ui/.cursor/rules/type-builder-forms-engine.mdc` around lines 22 - 34,
Generalize the hardcoded authoring-instance path in the “Runtime environment”
section by replacing the user-specific absolute path and derived sandbox paths
with a contributor-configurable placeholder or external local configuration
value. Preserve the editorial and craftercms site entries while ensuring the
shared rules file no longer depends on `/Users/rart` or any other
machine-specific directory.
studio-ui/docs/type-builder-forms-engine-plugins.md (1)

33-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language labels to fenced code blocks.

Add text to these structural and pseudocode blocks. This fixes the reported markdownlint warnings.

Also applies to: 172-183, 481-485, 491-495

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio-ui/docs/type-builder-forms-engine-plugins.md` around lines 33 - 39,
Add the text language label to the fenced code blocks containing the plugin
source and installed site path examples, as well as the additional blocks at the
referenced sections. Preserve their existing structural and pseudocode content
while ensuring every affected fence uses the labeled form to satisfy
markdownlint.

Source: Linters/SAST tools

studio-ui/docs/type-builder-forms-engine.md (1)

20-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language labels to fenced code blocks.

Add text to these structural diagrams. This fixes the reported markdownlint warnings.

Also applies to: 72-88, 94-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio-ui/docs/type-builder-forms-engine.md` around lines 20 - 29, Add the
`text` language label to the fenced code blocks containing the structural
diagrams in this document, including the sections around the referenced ranges,
while leaving the diagram content unchanged.

Source: Linters/SAST tools

studio-ui/ui/app/src/components/FormsEngine/dataSources/actionAdapters.ts (1)

44-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Localize the group labels.

actionKindLabel derives a user-visible label by capitalizing the raw kind id, so the menu always shows English text. Map the built-in kinds to react-intl messages and fall back to the action label for custom kinds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio-ui/ui/app/src/components/FormsEngine/dataSources/actionAdapters.ts`
around lines 44 - 46, Update actionKindLabel to resolve built-in
DataSourceActionKind values through the existing react-intl message definitions,
returning the localized label for the current locale. Preserve a fallback to the
existing capitalization logic for custom or unmapped kinds, and use the
appropriate intl access available in actionAdapters.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@studio-ui/docs/type-builder-forms-engine.md`:
- Around line 391-407: Update studio-ui/docs/type-builder-forms-engine.md:
revise Section 5.8 to describe FE control and data-source plugins using
PluginDescriptor.controls and PluginDescriptor.dataSources, removing the
obsolete default-React-component and no-executable-data-source claims; update
the document’s last-updated date at line 5 to 2026-07-31; and incorporate the
durable findings into the Progress and Open decisions sections.

In
`@studio-ui/ui/app/src/components/ContentTypeManagement/components/EditTypeView.tsx`:
- Around line 579-592: Mark both insertion flows as pending changes by calling
onUpdateHasPendingChanges(true) after the type is updated in handleInsertField
and after the data source is added in the sibling insertion path, preserving the
existing update behavior.

In
`@studio-ui/ui/app/src/components/ContentTypeManagement/descriptors/controls/pageNavOrder.ts`:
- Around line 40-44: Update the page-nav-order descriptor’s metadata and
validation around the page-nav-order control so it is available only as a root
field, excluding it from repeat-group or other nested field insertion paths that
inspect type.fields. Preserve the existing placeInNav and orderDefault_f
identifiers for the root-field configuration.

In `@studio-ui/ui/app/src/components/FormsEngine/controls/DateTime.tsx`:
- Around line 39-46: Update the expiredDateDescriptor metadata to include the
required {id}_tz field used by the DateTime control. Ensure bootstrap creates
this timezone atom so DateTime can initialize without throwing, while preserving
the descriptor’s existing metadata.

In `@studio-ui/ui/app/src/components/FormsEngine/controls/ImagePicker.tsx`:
- Around line 96-109: Update the promise chain in the ImagePicker change handler
around validateImageRestrictions so rejected inspections are handled with a
catch branch. Provide user feedback through the existing image-picker error
mechanism and preserve the current setValue/crop-dialog behavior for successful
validation.

In `@studio-ui/ui/app/src/components/FormsEngine/controls/PageNavOrder.tsx`:
- Around line 220-222: Remove the development draft Alert from the PageNavOrder
control so users are not shown an unimplemented-feature notice. If an interim
message must remain, replace the raw text with a localized FormattedMessage
consistent with the other labels in PageNavOrder.

In `@studio-ui/ui/app/src/components/FormsEngine/controls/VideoPicker.tsx`:
- Around line 126-134: Update the dimension display branch in VideoPicker’s
render to safely access videoInfo.width and videoInfo.height when videoInfo may
be nullish, matching ImagePicker’s optional-access pattern. Preserve the
existing loading and error branches and ensure rendering does not throw when
dimensions are unavailable.

In
`@studio-ui/ui/app/src/components/FormsEngine/dataSourceHooks/useDataSourceListOptions.ts`:
- Around line 57-86: Update the early-return branch in the useEffect of
useDataSourceListOptions so that when status is no longer 'loading' and context
is absent, it sets loaded for the current key with an empty groups list before
returning. Preserve the existing cleanup behavior and continue the normal list
resolution path when context is available.

In `@studio-ui/ui/app/src/components/FormsEngine/dataSources/moduleHelpers.ts`:
- Around line 155-168: Update the upload-result handling branch in the candidate
mapping function to reject results when both meta.path and meta.name are absent,
rather than returning an asset with an empty relativeUrl. Throw the existing
mapping error used for invalid candidates, while preserving the current URL
construction when either value is available.

In `@studio-ui/ui/app/src/components/FormsEngine/dataSources/types.ts`:
- Around line 24-28: Update the DataSourceCustomSelection type to use a fixed
kind discriminant of 'custom' and add a separate customKind: string field,
preventing overlap with built-in selection kinds. Keep DataSourceInterface and
DataSourceActionKind open-ended, using string & {} only where autocomplete is
required, and ensure DataSourceSelection can narrow reliably on kind checks.

In `@studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx`:
- Around line 392-423: Clone repeat.values when initializing values in the
FormsEngine repeat-field flow, so subsequent additions of missing
additionalFieldIds mutate only a local working copy. Preserve the existing
createParsedValuesObject path when repeat.values is absent and keep
atomValueCreator processing unchanged.

In `@studio-ui/ui/app/src/components/FormsEngine/lib/controlHelpers.tsx`:
- Around line 82-94: Update useControlPluginModule and the controlPluginCache
flow so the complete promise returned by loadControlPluginModule is cached,
keyed by plugin URL and control type. Reuse that cached final promise across
renders instead of creating a new loading.then(...) promise on each call,
ensuring PluginControlRenderer receives stable async state.

In `@studio-ui/ui/app/src/components/FormsEngine/lib/controlPluginLoader.ts`:
- Around line 111-118: Update useControlPluginModule and the
loadControlPluginModule flow to cache the final Promise<LoadedControlPlugin> by
plugin URL and controlType before passing it to use(). Reuse the cached promise
for both the Promise.resolve fast path and loading.then path, ensuring repeated
renders do not create uncached promises.

In `@studio-ui/ui/app/src/components/FormsEngine/lib/rteUtils.ts`:
- Around line 151-164: Update the media selection callback around the
actions/dataSources.context guard to handle the empty-actions or missing-context
branch by notifying the author and invoking cb with the appropriate empty
result, following the existing noDatasourcesConfigured notification pattern in
paste_postprocess. Preserve the current invokeActionChoice and
openDataSourcePicker behavior when a valid media data source is available.

In `@studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts`:
- Around line 123-134: Update the additional-field handling in the content-type
iteration to parse each field using its own value type rather than the parent
field’s type. In particular, ensure orderDefault_f uses numberFieldExtractor so
numeric values such as -1 and 9000 remain numbers before assigning values and
invoking fieldCallback.

In `@studio-ui/ui/app/src/hooks/useVideoInfo.ts`:
- Around line 82-99: In the metadata fetch success path inside the asynchronous
flow, check abortController.signal.aborted immediately before applying results
with setVideoInfo and setIsFetchingMetadata. Return without updating state when
the request was cancelled, matching the existing cancellation handling in the
catch branch and the dimensions flow.

In `@studio-ui/ui/app/src/services/plugin.ts`:
- Around line 208-211: Update the plugin control registration flow around
registerControlContribution and registerControlDataSourceBindings to prevent
plugin typeKey values from overriding built-in control types. Either reject
collisions with the built-in control registry before registering plugin
contributions, or ensure getControlDataSourceBindings always resolves built-in
bindings before runtime plugin registrations.

In `@studio-ui/ui/app/src/state/reducers/uiConfig.ts`:
- Around line 110-134: Update the ContentTypeManagement configuration parsing in
the controls iteration to convert the deserialized descriptor value with asArray
and process each descriptor independently. Use each descriptor’s own id for both
the controls key and the normalized id field, while preserving the existing
fields, sections, and metadata normalization for each entry; do not use the
container tag.id.
- Line 119: Update the fields assignment in the descriptor mapping to explicitly
detect the empty-array result from deserializing <fields /> and replace it with
an empty object map; preserve valid object-shaped descriptor.fields values
unchanged.

---

Outside diff comments:
In `@studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx`:
- Around line 605-621: Add customControls to the dependency array of the
surrounding FormsEngine effect so it reruns when state.uiConfig.controls is
replaced after mount. Preserve the existing dependencies and use the
customControls value already derived in the component; do not rely on
importPlugin or registerPlugin registry updates.

---

Nitpick comments:
In `@studio-ui/.cursor/rules/type-builder-forms-engine.mdc`:
- Around line 22-34: Generalize the hardcoded authoring-instance path in the
“Runtime environment” section by replacing the user-specific absolute path and
derived sandbox paths with a contributor-configurable placeholder or external
local configuration value. Preserve the editorial and craftercms site entries
while ensuring the shared rules file no longer depends on `/Users/rart` or any
other machine-specific directory.

In `@studio-ui/docs/type-builder-forms-engine-plugins.md`:
- Around line 33-39: Add the text language label to the fenced code blocks
containing the plugin source and installed site path examples, as well as the
additional blocks at the referenced sections. Preserve their existing structural
and pseudocode content while ensuring every affected fence uses the labeled form
to satisfy markdownlint.

In `@studio-ui/docs/type-builder-forms-engine.md`:
- Around line 20-29: Add the `text` language label to the fenced code blocks
containing the structural diagrams in this document, including the sections
around the referenced ranges, while leaving the diagram content unchanged.

In
`@studio-ui/ui/app/src/components/FormsEngine/components/GroupedDataSourceActionMenuItems.tsx`:
- Around line 152-180: Update GroupedDataSourceActionMenuItems to accept and
forward the MUI autoFocus and tabIndex props from MenuList to the intended
direct MenuItem, preserving the existing choice rendering and handlers. Do not
apply these props to the nested Menu or Dialog; ensure the host menu can assign
initial focus and keyboard navigation.

In `@studio-ui/ui/app/src/components/FormsEngine/controls/ImagePicker.tsx`:
- Around line 229-235: Add an EmptyState branch before the action container in
ImagePicker.tsx (lines 229-235), VideoPicker.tsx (lines 177-186), and
TranscodedVideoPicker.tsx (lines 140-149). Render it when actionsReady is false
while the corresponding data-source status is neither loading nor error, so
ready fields with no bound actions show an empty-state message instead of a
blank container.

In `@studio-ui/ui/app/src/components/FormsEngine/dataSources/actionAdapters.ts`:
- Around line 44-46: Update actionKindLabel to resolve built-in
DataSourceActionKind values through the existing react-intl message definitions,
returning the localized label for the current locale. Preserve a fallback to the
existing capitalization logic for custom or unmapped kinds, and use the
appropriate intl access available in actionAdapters.ts.

In
`@studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioBrowseRepo.ts`:
- Around line 26-41: Extract the shared repo-path fallback into resolveRepoPath
in moduleHelpers.ts, preserving repoPath, path, and the '/static-assets/'
default precedence. Replace the inline fallback in create in
studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioBrowseRepo.ts
lines 26-41, audioDesktopUpload.ts lines 26-41, and videoDesktopUpload.ts lines
26-41 with resolveRepoPath(record, '/static-assets/').

In
`@studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgDesktopUpload.ts`:
- Line 27: Extract the repeated repository-path fallback into a shared
resolveRepoPath helper in moduleHelpers.ts alongside propString and propBoolean.
Update imgDesktopUpload.ts (27-27), imgRepositoryUpload.ts (27-27), and
videoBrowseRepo.ts (27-27) to use the helper with the existing '/static-assets/'
fallback.

In
`@studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/keyValueList.ts`:
- Around line 22-40: Extract the duplicated DataSourceListItem normalization
from parseKeyValueOptions into a shared normalizeListItem helper in
moduleHelpers.ts, preserving string coercion, empty key/value validation,
property spreading, and indexed error messages. Update keyValueList.ts lines
22-40 and simpleTaxonomy.ts lines 26-45 to use the helper with the appropriate
source label; both sites require direct changes.

In
`@studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/sharedContent.ts`:
- Around line 85-103: Update the create action’s run method to import and use
the existing expandPathOrRaw helper instead of manually chaining ctx.expandPath,
options.target.path, and repoPath. Preserve the current path precedence and pass
the resolved path to ctx.services.createContent.

In `@studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts`:
- Around line 121-122: Extract the duplicated customControls/controlDescriptors
merge into a shared resolveControlDescriptors helper, preserving the current
merge order. In
studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts lines
121-122, replace the inline merge and TODO with the helper call; make the
corresponding replacement in
studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx lines 880-897,
importing or defining the shared helper as appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1188c527-b5a4-4f9f-98f3-9c2dc839607b

📥 Commits

Reviewing files that changed from the base of the PR and between 57c06c7 and ff59461.

📒 Files selected for processing (93)
  • studio-ui/.cursor/rules/type-builder-forms-engine.mdc
  • studio-ui/AGENTS.md
  • studio-ui/docs/fe2-control-plugin-single-model-implementation.md
  • studio-ui/docs/type-builder-forms-engine-plugins.md
  • studio-ui/docs/type-builder-forms-engine.md
  • studio-ui/ui/app/src/components/ContentTypeManagement/components/AdditionalFieldChip.tsx
  • studio-ui/ui/app/src/components/ContentTypeManagement/components/EditTypeView.tsx
  • studio-ui/ui/app/src/components/ContentTypeManagement/components/FieldChip.tsx
  • studio-ui/ui/app/src/components/ContentTypeManagement/descriptors/controls/dateTime.ts
  • studio-ui/ui/app/src/components/ContentTypeManagement/descriptors/controls/pageNavOrder.ts
  • studio-ui/ui/app/src/components/ContentTypeManagement/utils.ts
  • studio-ui/ui/app/src/components/DateTimeTimezonePicker/DateTimeTimezonePicker.tsx
  • studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx
  • studio-ui/ui/app/src/components/FormsEngine/components/GroupedDataSourceActionMenuItems.tsx
  • studio-ui/ui/app/src/components/FormsEngine/components/SortableList.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/CheckboxGroup.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/DateTime.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/Dropdown.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/ImagePicker.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/NodeSelector.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/PageNavOrder.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/RichTextEditor.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/TranscodedVideoPicker.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/VideoPicker.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/registry.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSourceHooks/useConsolidatedImagePickerData.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSourceHooks/useConsolidatedItemPickerData.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSourceHooks/useDataSourceListOptions.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSourceHooks/useExtractDataSources.tsx
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/actionAdapters.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/bindings.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/completeness.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/defineModule.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/host.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/index.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/loader.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/moduleHelpers.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioBrowseRepo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioDesktopUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/components.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/componentsPagesShared.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/configuredList.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/embeddedContent.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/fileBrowseRepo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/fileDesktopUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/flashDesktopUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgDesktopUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgRepositoryUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgS3Repo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgS3Upload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgWebDAVRepo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgWebDAVUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/index.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/keyValueList.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/pages.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/remoteStubs.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/s3Repo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/s3Upload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/sharedContent.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/simpleTaxonomy.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoBrowseRepo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoDesktopUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoS3Repo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoS3Transcoding.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoS3Upload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoWebDAVRepo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoWebDAVUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/webDavRepo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/webDavUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/pathUtils.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/registry.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/services.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/types.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/useFieldDataSources.ts
  • studio-ui/ui/app/src/components/FormsEngine/index.ts
  • studio-ui/ui/app/src/components/FormsEngine/lib/controlHelpers.tsx
  • studio-ui/ui/app/src/components/FormsEngine/lib/controlMap.ts
  • studio-ui/ui/app/src/components/FormsEngine/lib/controlPluginLoader.ts
  • studio-ui/ui/app/src/components/FormsEngine/lib/dataSourceMap.ts
  • studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx
  • studio-ui/ui/app/src/components/FormsEngine/lib/rteUtils.ts
  • studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts
  • studio-ui/ui/app/src/components/FormsEngine/lib/valueSerializers.ts
  • studio-ui/ui/app/src/components/FormsEngine/types.ts
  • studio-ui/ui/app/src/env/craftercms.ts
  • studio-ui/ui/app/src/hooks/useVideoInfo.ts
  • studio-ui/ui/app/src/models/ContentType.ts
  • studio-ui/ui/app/src/models/GlobalState.ts
  • studio-ui/ui/app/src/models/PluginDescriptor.ts
  • studio-ui/ui/app/src/services/content.ts
  • studio-ui/ui/app/src/services/contentTypes.ts
  • studio-ui/ui/app/src/services/plugin.ts
  • studio-ui/ui/app/src/state/reducers/uiConfig.ts
💤 Files with no reviewable changes (3)
  • studio-ui/ui/app/src/components/FormsEngine/lib/dataSourceMap.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSourceHooks/useConsolidatedItemPickerData.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSourceHooks/useConsolidatedImagePickerData.ts

Comment thread studio-ui/docs/type-builder-forms-engine.md Outdated
Comment thread studio-ui/ui/app/src/components/FormsEngine/controls/DateTime.tsx
Comment thread studio-ui/ui/app/src/components/FormsEngine/controls/ImagePicker.tsx Outdated
Comment thread studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts
Comment thread studio-ui/ui/app/src/hooks/useVideoInfo.ts
Comment thread studio-ui/ui/app/src/services/plugin.ts Outdated
Comment thread studio-ui/ui/app/src/state/reducers/uiConfig.ts
Comment thread studio-ui/ui/app/src/state/reducers/uiConfig.ts Outdated
@rart

rart commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai FE2 implemented data sources in a way that didn't support plugins and the implementation was coupled to the existing data sources — code would be to be written to incorporate new DS. There was no shared, durable map of how Type Builder and Forms Engine load controls, data sources, plugins, and form controllers—especially what legacy did well versus what the new React stack still couldn’t do dynamically.

See docs on this PR (i.e. studio-ui/.cursor/rules/type-builder-forms-engine.mdc, studio-ui/docs/*) to understand how we framed and solved the problems.

Based on this changes on this PR, did we solve these issues? Did we do it with the right architecture? Does the code successfully implement the design?

Do you see any issues? What comments do you have?

Perform a comprehensive architectural review and report.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== TARGET FILES ==='
fd -t f . studio-ui | rg '(\.cursor/rules/type-builder-forms-engine\.mdc|docs/(type-builder-forms-engine|fe2-control-plugin-single-model-implementation)|ui/app/src/(services/plugin|models/PluginDescriptor|components/FormsEngine/(dataSources/(loader|registry|bindings|useFieldDataSources|index)|lib/(controlHelpers|controlPluginLoader)|controls/registry)|env/craftercms)\.ts)'

echo '=== OUTLINES ==='
for f in \
  studio-ui/ui/app/src/services/plugin.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/loader.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/useFieldDataSources.ts \
  studio-ui/ui/app/src/components/FormsEngine/lib/controlPluginLoader.ts \
  studio-ui/ui/app/src/components/FormsEngine/lib/controlHelpers.tsx \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/bindings.ts \
  studio-ui/ui/app/src/components/FormsEngine/controls/registry.ts \
  studio-ui/ui/app/src/models/PluginDescriptor.ts; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    ast-grep outline "$f" --items all --view compact || true
  fi
done

echo '=== DESIGN KEYWORDS ==='
rg -n -C 2 --glob '*.md' --glob '*.mdc' \
  'PluginDescriptor|importPlugin|registerPlugin|control|data.source|form.controller|dynamic|legacy|non.goal|acceptance' \
  studio-ui/.cursor/rules/type-builder-forms-engine.mdc studio-ui/docs 2>/dev/null | head -n 500

Length of output: 63110


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== SOURCE OUTLINES ==='
for f in \
  studio-ui/ui/app/src/services/plugin.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/loader.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/useFieldDataSources.ts \
  studio-ui/ui/app/src/components/FormsEngine/lib/controlPluginLoader.ts \
  studio-ui/ui/app/src/components/FormsEngine/lib/controlHelpers.tsx \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/bindings.ts \
  studio-ui/ui/app/src/components/FormsEngine/controls/registry.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/registry.ts \
  studio-ui/ui/app/src/models/PluginDescriptor.ts \
  studio-ui/ui/app/src/components/ContentTypeManagement/components/EditTypeView.tsx \
  studio-ui/ui/app/src/components/ContentTypeManagement/utils.ts; do
  echo "--- $f ---"
  ast-grep outline "$f" --items all --view signatures || true
done

echo '=== RUNTIME IMPLEMENTATIONS ==='
for f in \
  studio-ui/ui/app/src/services/plugin.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/loader.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/useFieldDataSources.ts \
  studio-ui/ui/app/src/components/FormsEngine/lib/controlPluginLoader.ts \
  studio-ui/ui/app/src/components/FormsEngine/lib/controlHelpers.tsx \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/bindings.ts \
  studio-ui/ui/app/src/components/FormsEngine/controls/registry.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/registry.ts \
  studio-ui/ui/app/src/models/PluginDescriptor.ts; do
  echo "--- $f ---"
  cat -n "$f"
done

echo '=== INTEGRATION CALL SITES ==='
rg -n -C 4 --type ts --type tsx \
  '\b(importPlugin|registerPlugin|loadDataSourceModule|useFieldDataSources|useControlPluginModule|registerControlDataSourceBindings|extractPluginLocatorFromConfigEntry|plugin:\s*|hasJsController)\b' \
  studio-ui/ui/app/src | head -n 1200

Length of output: 104596


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== LOCATOR MODEL, TYPE BUILDER, AND HOSTS ==='
for spec in \
  'studio-ui/ui/app/src/models/ContentType.ts:1:240' \
  'studio-ui/ui/app/src/components/ContentTypeManagement/components/EditTypeView.tsx:1230:1365' \
  'studio-ui/ui/app/src/components/ContentTypeManagement/utils.ts:570:750' \
  'studio-ui/ui/app/src/components/FormsEngine/dataSources/host.ts:1:180' \
  'studio-ui/ui/app/src/env/craftercms.ts:1:180' \
  'studio-ui/samples/fe2-control-plugin.example.mjs:1:260'; do
  file="${spec%%:*}"
  rest="${spec#*:}"; start="${rest%%:*}"; end="${rest##*:}"
  if [ -f "$file" ]; then
    echo "--- $file lines $start-$end ---"
    sed -n "${start},${end}p" "$file" | nl -ba -v "$start"
  fi
done

echo '=== PLUGIN IDENTITY AND LOCATOR DOCUMENTATION ==='
rg -n -C 4 --glob '*.md' --glob '*.mdc' \
  'pluginId|PluginDescriptor\.id|runtime.*identity|asset locator|identity|filename|file.*filename' \
  studio-ui/docs studio-ui/.cursor/rules/type-builder-forms-engine.mdc | head -n 700

echo '=== TESTS AND SPECIMENS ==='
fd -t f . studio-ui | rg -i '(test|spec|sample).*?(plugin|datasource|forms|control)|(plugin|datasource|forms|control).*?(test|spec|sample)' | head -n 500
rg -n -C 3 \
  '(loadControlPluginModule|loadDataSourceModule|registerPlugin\(|registerControlContribution|resolveFieldDataSources|FormDefinitionPlugin|pluginId)' \
  studio-ui --glob '*.{test,spec}.{ts,tsx,js,jsx}' --glob '*.mjs' 2>/dev/null | head -n 1000

echo '=== FORM CONTROLLER IMPLEMENTATION CHECK ==='
rg -n -C 3 \
  '(hasJsController|form-controller|onBeforeSave|isFieldRelevant|initialize\(form)' \
  studio-ui/ui/app/src/components/FormsEngine studio-ui/ui/app/src/models/ContentType.ts | head -n 500

Length of output: 2183


🏁 Script executed:

#!/bin/bash
set -euo pipefail

print_range () {
  file="$1"; start="$2"; end="$3"
  if [ -f "$file" ]; then
    echo "--- $file lines $start-$end ---"
    sed -n "${start},${end}{=;p;}" "$file"
  fi
}

echo '=== LOCATOR MODEL, TYPE BUILDER, AND HOSTS ==='
print_range studio-ui/ui/app/src/models/ContentType.ts 1 240
print_range studio-ui/ui/app/src/components/ContentTypeManagement/components/EditTypeView.tsx 1230 1365
print_range studio-ui/ui/app/src/components/ContentTypeManagement/utils.ts 570 750
print_range studio-ui/ui/app/src/components/FormsEngine/dataSources/host.ts 1 180
print_range studio-ui/ui/app/src/env/craftercms.ts 1 180
print_range studio-ui/samples/fe2-control-plugin.example.mjs 1 260

echo '=== PLUGIN IDENTITY AND LOCATOR DOCUMENTATION ==='
rg -n -C 4 --glob '*.md' --glob '*.mdc' \
  'pluginId|PluginDescriptor\.id|runtime.*identity|asset locator|identity|filename|file.*filename' \
  studio-ui/docs studio-ui/.cursor/rules/type-builder-forms-engine.mdc | head -n 700 || true

echo '=== TEST AND SAMPLE FILES ==='
fd -t f . studio-ui | rg -i '(test|spec|sample).*?(plugin|datasource|forms|control)|(plugin|datasource|forms|control).*?(test|spec|sample)' | head -n 500 || true

echo '=== TEST REFERENCES ==='
rg -n -C 3 \
  '(loadControlPluginModule|loadDataSourceModule|registerPlugin\(|registerControlContribution|resolveFieldDataSources|FormDefinitionPlugin|pluginId)' \
  studio-ui -g '*test*' -g '*spec*' -g '*.mjs' | head -n 1000 || true

echo '=== FORM CONTROLLER IMPLEMENTATION CHECK ==='
rg -n -C 3 \
  '(hasJsController|form-controller|onBeforeSave|isFieldRelevant|initialize\(form)' \
  studio-ui/ui/app/src/components/FormsEngine studio-ui/ui/app/src/models/ContentType.ts | head -n 500 || true

Length of output: 54288


@rart, the PR solves the central FE2 data-source and plugin-loading problem. The architecture is substantially better than the prior type-coupled model.

Overall assessment

Architecture: sound, with two important runtime defects.

The implementation now has a clear runtime model:

  1. Type Builder persists the selected plugin locator in form-definition.xml.
  2. Forms Engine resolves a data source by its persisted type.
  3. importPlugin() loads and validates a PluginDescriptor.
  4. registerPlugin() installs descriptor contributions into shared registries.
  5. Controls receive resolved data-source instances and actions through ControlProps.dataSources.
  6. Controls select actions by binding, interface, capability, and intent. They do not switch on data-source type.

This is the right direction. It replaces central data-source-type branching with typed modules, instance creation, interface checks, capability checks, owner-bound actions, and error isolation.

The code also implements the documented single descriptor path for both plugin kinds:

  • Data sources use PluginDescriptor.dataSources.
  • Controls use PluginDescriptor.controls.
  • Both use importPlugin() and registerPlugin().
  • Type Builder preserves plugin coordinates for new controls and data sources.
  • XML serialization preserves the plugin locator.
  • ControlWrapper injects ResolvedDataSources into both built-in and plugin controls.

The documentation is also useful and unusually complete. It distinguishes legacy behavior, current FE2 behavior, completed work, and planned work.

Important issue: control plugins compare two unrelated identities

Severity: high.

studio-ui/ui/app/src/components/FormsEngine/lib/controlPluginLoader.ts stores PluginDescriptor.id as RegisteredControlContribution.pluginId.

After a plugin loads, Lines 95-101 compare that value with field.properties.plugin.pluginId.

These values have different meanings:

  • FormDefinitionPlugin.pluginId identifies the installed plugin asset in the locator.
  • PluginDescriptor.id identifies the runtime descriptor registration.

The documentation explicitly states that package identity, asset locator identity, and PluginDescriptor.id are separate identities. See studio-ui/docs/type-builder-forms-engine-plugins.md, Lines 141-152.

As a result, a valid control plugin can load, register its control contribution, and then be rejected because its descriptor ID does not equal the asset locator ID.

The loader should validate ownership against the descriptor returned by the current importPlugin() call:

if (contribution.pluginId !== descriptor.id) {
  // reject
}

The fast path at Lines 66-69 cannot safely compare a locator ID with a descriptor ID. It should either:

  • cache the loaded descriptor ID with the locator URL, then compare against that descriptor ID; or
  • remove the fast path and reuse the URL-keyed importPlugin() promise.

The registry should also treat a second contribution for the same control type from a different descriptor ID as a conflict. The current same-component exception permits a different plugin to overwrite binding metadata while the control registry retains the first plugin owner.

Important issue: plugin registration is not atomic

Severity: medium.

registerPlugin() registers data sources first and controls second at Lines 227-228 in studio-ui/ui/app/src/services/plugin.ts.

If control registration fails after data-source registration succeeds, the data-source modules remain in the process-wide registry. The descriptor itself is not registered. A retry can therefore execute against partial global state.

The control path can also register some controls before a later control contribution fails. registerPluginControls() validates all entries first, but it registers each entry sequentially.

This behavior is risky because registry state is global and has no unregister operation.

Use a two-phase registration process:

  1. Validate all descriptor contributions.
  2. Preflight all registry conflicts.
  3. Commit all data-source modules, controls, and bindings only after the complete preflight succeeds.

Alternatively, add rollback support to the registries and roll back all contributions when a later registration step fails.

What this PR completes

The PR completes these design goals:

  • A typed data-source module contract.
  • Plugin-defined data-source modules.
  • Plugin-defined React control contributions.
  • A single PluginDescriptor loading and registration path.
  • Runtime data-source resolution from persisted form-definition records.
  • Data-source binding metadata for built-in and plugin controls.
  • Interface validation and per-data-source error reporting.
  • Action-driven controls rather than data-source-type branching.
  • Plugin locator propagation from Type Builder and XML round-trip support.
  • Public FE2 registry host surfaces.

The design is a meaningful improvement over the previous FE2 implementation.

What remains incomplete by design

The PR documentation correctly identifies these as open work. They should not be described as solved capabilities:

  • Form controllers are not implemented in FE2.
    hasJsController remains a model property, but Forms Engine does not load form-controller.js or provide initialize, field-relevance, or save-veto lifecycle hooks.

  • Novel controls are not fully extensible through the descriptor alone.
    Validators, value retrievers, and value serializers remain largely static. A new control that has a non-standard value shape can still require host code changes.

  • TB2 catalog and package installation are not aligned.
    Existing installers can wire legacy site-config-tools.xml, while TB2 reads ui.xml.

  • Descriptor precedence remains inconsistent or undecided.
    The documentation records different code-versus-configuration lookup orders for insert, edit, and serialization.

  • Lib-style plugin loading remains incomplete.
    PluginDescriptor.utils can be stored, but there is no complete eager-loading and stable utility-host path.

These limitations do not invalidate the completed data-source and control-plugin work. They define the boundary of this PR.

Verification and test coverage

I found no focused test or spec references for:

  • loadControlPluginModule
  • loadDataSourceModule
  • registerPlugin
  • resolveFieldDataSources
  • control contribution registration

The two issues above need targeted tests before this architecture is treated as stable. At minimum, add tests for:

  1. A locator pluginId that differs from PluginDescriptor.id.
  2. A plugin that contributes multiple controls from one URL.
  3. Duplicate control type conflicts across descriptor IDs.
  4. A registration failure after valid data-source contributions, with no partial registry state left behind.
  5. Plugin data-source load failure that leaves other field data sources usable.
  6. Type Builder parse → edit → serialize preservation of control and data-source plugin locators.

Conclusion

The PR does solve the main extensibility and coupling issue for FE2 data sources. The shared PluginDescriptor architecture is the correct model. The implementation follows that model through the main runtime paths.

However, the control-plugin identity comparison currently contradicts the documented identity model and can prevent valid control plugins from rendering. The registration process also needs atomicity before broad plugin adoption.

The work should be considered a strong architectural foundation, not the completion of all Forms Engine modernization work.


✏️ Learnings added
Learnt from: rart
URL: https://github.com/craftersoftware/craftercms/pull/8852

Timestamp: 2026-08-03T13:44:16.696Z
Learning: For CrafterCMS Studio FE2 plugins, `FormDefinitionPlugin.pluginId` is an asset-locator identity and `PluginDescriptor.id` is a runtime descriptor-registration identity. They are documented as separate identities and must not be compared as if they are equivalent.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@rart

rart commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai, perform a review of coverage of the migration of the legacy controls & data sources vs the new:

  • studio-ui/static-assets/components/cstudio-forms/data-sources vs ui/app/src/components/FormsEngine/dataSources/modules
  • studio-ui/static-assets/components/cstudio-forms/controls vs ui/app/src/components/FormsEngine/controls

Do you see any gaps or issues?

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

legacy_ds='studio-ui/static-assets/components/cstudio-forms/data-sources'
legacy_controls='studio-ui/static-assets/components/cstudio-forms/controls'
fe2_ds='studio-ui/ui/app/src/components/FormsEngine/dataSources/modules'
fe2_controls='studio-ui/ui/app/src/components/FormsEngine/controls'

echo '=== INVENTORY ==='
for dir in "$legacy_ds" "$legacy_controls" "$fe2_ds" "$fe2_controls"; do
  echo "--- $dir ---"
  if [ -d "$dir" ]; then
    fd -t f . "$dir" | sort
  else
    echo 'DIRECTORY NOT FOUND'
  fi
done

echo '=== LEGACY DATA-SOURCE OUTLINES ==='
if [ -d "$legacy_ds" ]; then
  find "$legacy_ds" -type f \( -name '*.js' -o -name '*.ts' \) -print0 |
    xargs -0 -r -n1 sh -c 'echo "--- $0 ---"; ast-grep outline "$0" --items all --view signatures || true'
fi

echo '=== LEGACY CONTROL OUTLINES ==='
if [ -d "$legacy_controls" ]; then
  find "$legacy_controls" -type f \( -name '*.js' -o -name '*.ts' \) -print0 |
    xargs -0 -r -n1 sh -c 'echo "--- $0 ---"; ast-grep outline "$0" --items all --view signatures || true'
fi

echo '=== FE2 DATA-SOURCE MODULE OUTLINES AND REGISTRATION ==='
ast-grep outline "$fe2_ds" --items all --view signatures || true
if [ -f "$fe2_ds/index.ts" ]; then
  cat -n "$fe2_ds/index.ts"
fi

echo '=== FE2 CONTROL OUTLINES ==='
ast-grep outline "$fe2_controls" --items all --view signatures || true

echo '=== LEGACY TYPE AND CONTROL IDENTIFIERS ==='
rg -n -C 3 --glob '*.{js,ts,tsx,xml}' \
  '(dataSourceType|datasourceType|data-source|controlType|fieldType|typeName|register.*[Cc]ontrol|register.*[Dd]ata)' \
  "$legacy_ds" "$legacy_controls" 2>/dev/null | head -n 2500 || true

echo '=== FE2 TYPE, CONTROL, AND LEGACY-MIGRATION REFERENCES ==='
rg -n -C 3 --type ts --type tsx \
  '(type:\s*['\''"][^'\''"]+|controlMap|controlDataSourceBindings|builtInDataSourceModules|legacy|flash-desktop-upload|linked-dropdown)' \
  studio-ui/ui/app/src/components/FormsEngine \
  studio-ui/ui/app/src/components/ContentTypeManagement 2>/dev/null | head -n 3500 || true

echo '=== LEGACY FORM-DEFINITION CONFIGURATION REFERENCES ==='
rg -n -C 3 --glob '*.{xml,js,ts,tsx}' \
  '(img-repository-upload|img-desktop-upload|file-browse-repo|file-desktop-upload|video-|audio-|shared-content|embedded-content|configured-list|key-value-list|simple-taxonomy|components|pages|linked-dropdown|transcoded-video-picker|page-nav-order)' \
  studio-ui/static-assets studio-ui/ui/app/src 2>/dev/null | head -n 3500 || true

echo '=== TEST COVERAGE ==='
rg -n -C 2 --glob '*.{test,spec}.{js,jsx,ts,tsx}' \
  '(data[Ss]ource|FormsEngine|Dropdown|CheckboxGroup|ImagePicker|NodeSelector|VideoPicker|DateTime|PageNavOrder)' \
  studio-ui 2>/dev/null | head -n 1600 || true

Length of output: 203243


🏁 Script executed:

#!/bin/bash
set -euo pipefail

legacy_ds='studio-ui/static-assets/components/cstudio-forms/data-sources'
legacy_controls='studio-ui/static-assets/components/cstudio-forms/controls'
fe2_ds='studio-ui/ui/app/src/components/FormsEngine/dataSources/modules'
fe2_controls='studio-ui/ui/app/src/components/FormsEngine/controls'

python3 - <<'PY'
from pathlib import Path
import re

roots = {
    "legacy_ds": Path("studio-ui/static-assets/components/cstudio-forms/data-sources"),
    "legacy_controls": Path("studio-ui/static-assets/components/cstudio-forms/controls"),
    "fe2_ds": Path("studio-ui/ui/app/src/components/FormsEngine/dataSources/modules"),
    "fe2_controls": Path("studio-ui/ui/app/src/components/FormsEngine/controls"),
}

def files(root, extensions):
    return sorted(p for p in root.rglob("*") if p.is_file() and p.suffix in extensions)

for key, root in roots.items():
    print(f"\n=== {key} FILES ({root}) ===")
    ext = {".js"} if key.startswith("legacy") else {".ts", ".tsx"}
    for p in files(root, ext):
        print(p.as_posix())

print("\n=== LEGACY DATA-SOURCE getName() IDENTIFIERS ===")
for p in files(roots["legacy_ds"], {".js"}):
    text = p.read_text(errors="replace")
    names = re.findall(r"getName\s*:\s*function\s*\(\s*\)\s*\{\s*return\s*['\"]([^'\"]+)", text)
    print(f"{p.name}: {', '.join(names) if names else '-'}")

print("\n=== LEGACY CONTROL REGISTRATION IDENTIFIERS ===")
patterns = [
    r"getName\s*:\s*function\s*\(\s*\)\s*\{\s*return\s*['\"]([^'\"]+)",
    r"register(?:Control)?\s*\(\s*['\"]([^'\"]+)",
    r"moduleLoaded\s*\(\s*['\"]([^'\"]+)",
]
for p in files(roots["legacy_controls"], {".js"}):
    text = p.read_text(errors="replace")
    values = []
    for pattern in patterns:
        values.extend(re.findall(pattern, text))
    print(f"{p.name}: {', '.join(dict.fromkeys(values)) if values else '-'}")

print("\n=== FE2 DATA-SOURCE MODULE type IDENTIFIERS ===")
for p in files(roots["fe2_ds"], {".ts", ".tsx"}):
    text = p.read_text(errors="replace")
    types = re.findall(r"\btype\s*:\s*['\"]([^'\"]+)", text)
    if types:
        print(f"{p.name}: {', '.join(dict.fromkeys(types))}")

print("\n=== FE2 CONTROL MAP IDENTIFIERS ===")
p = Path("studio-ui/ui/app/src/components/FormsEngine/lib/controlMap.ts")
if p.exists():
    text = p.read_text(errors="replace")
    for key in re.findall(r"^\s*['\"]?([A-Za-z0-9_-]+)['\"]?\s*:\s*(?:lazy|null)", text, re.M):
        print(key)
PY

echo '=== DATA-SOURCE BINDINGS ==='
cat -n studio-ui/ui/app/src/components/FormsEngine/dataSources/bindings.ts | sed -n '1,260p'

echo '=== FE2 MODULE REGISTRATION ==='
cat -n "$fe2_ds/index.ts" | sed -n '1,260p'

echo '=== LEGACY AND FE2 REMOTE/STUB IMPLEMENTATIONS ==='
for f in \
  "$legacy_ds/img-s3-repo.js" \
  "$legacy_ds/img-s3-upload.js" \
  "$legacy_ds/img-webdav-repo.js" \
  "$legacy_ds/img-webdav-upload.js" \
  "$legacy_ds/video-s3-repo.js" \
  "$legacy_ds/video-s3-upload.js" \
  "$legacy_ds/video-webdav-repo.js" \
  "$legacy_ds/video-webdav-upload.js" \
  "$legacy_ds/s3-repo.js" \
  "$legacy_ds/s3-upload.js" \
  "$legacy_ds/webdav-repo.js" \
  "$legacy_ds/webdav-upload.js" \
  "$fe2_ds/remoteStubs.ts"; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n -C 3 '(getName|execute|show|upload|browse|search|unsupported|throw|type:|create:|actions)' "$f" | head -n 180 || true
  fi
done

echo '=== LEGACY CONTROL NAMES AND FE2 CONTROL MAP ==='
for f in "$legacy_controls"/*.js; do
  [ -f "$f" ] || continue
  echo "--- $f ---"
  rg -n -C 2 '(getName|register|moduleLoaded|render|create|onChange|dataSource|datasource)' "$f" | head -n 100 || true
done

echo '=== DATA-SOURCE AND CONTROL TEST FILES ==='
fd -t f . studio-ui | rg '(/|^).*\.(test|spec)\.(js|jsx|ts|tsx)$' | rg -i '(forms|datasource|data-source|control|picker|dropdown|checkbox|video|taxonomy)' || true

Length of output: 107805


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== CONTROL MAP AND NULL-CONTROL RUNTIME PATH ==='
sed -n '1,180p' studio-ui/ui/app/src/components/FormsEngine/lib/controlMap.ts | nl -ba
rg -n -C 5 --type ts --type tsx \
  '(controlMap\[|UnknownControl|link-input|link-textarea|forcehttps|internal-name)' \
  studio-ui/ui/app/src/components/FormsEngine studio-ui/ui/app/src/components/ContentTypeManagement

echo '=== FE2 LINK, TEXT, AND COMPATIBILITY CONTROL IMPLEMENTATIONS ==='
for f in \
  studio-ui/ui/app/src/components/FormsEngine/controls/Text.tsx \
  studio-ui/ui/app/src/components/FormsEngine/controls/Textarea.tsx \
  studio-ui/ui/app/src/components/FormsEngine/controls/AutoFileName.tsx \
  studio-ui/ui/app/src/components/FormsEngine/controls/FileName.tsx \
  studio-ui/ui/app/src/components/FormsEngine/controls/Checkbox.tsx \
  studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts \
  studio-ui/ui/app/src/components/FormsEngine/lib/valueSerializers.ts \
  studio-ui/ui/app/src/components/FormsEngine/lib/validators.ts; do
  echo "--- $f ---"
  cat -n "$f" | sed -n '1,280p'
done

echo '=== REMOTE DATA-SOURCE ACTION IMPLEMENTATION ==='
for f in \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/moduleHelpers.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/services.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/remoteStubs.ts; do
  echo "--- $f ---"
  rg -n -C 8 '(createRemoteStubAction|unsupportedRemoteError|remote|browseFiles|search|upload|DataSourceServices)' "$f"
done

echo '=== LEGACY REMOTE DATA-SOURCE OPERATION PATHS ==='
for f in studio-ui/static-assets/components/cstudio-forms/data-sources/{S3,WebDAV,img-S3,img-WebDAV,video-S3,video-WebDAV}-{repo,upload}.js; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n -C 5 '(getName|insertImageAction|insertVideoAction|browse|upload|S3|WebDAV|dialog|picker|repository)' "$f" | head -n 260
  fi
done

echo '=== RTE FEATURE PARITY ==='
echo '--- legacy RTE plugins ---'
for f in studio-ui/static-assets/components/cstudio-forms/controls/rte-plugins/*.js; do
  echo "--- $f ---"
  rg -n -C 3 '(PluginManager|add\(|button|menu|command|datasource|image|component|layout|link)' "$f" | head -n 160
done
echo '--- FE2 RTE utility and component ---'
for f in \
  studio-ui/ui/app/src/components/FormsEngine/controls/RichTextEditor.tsx \
  studio-ui/ui/app/src/components/FormsEngine/lib/rteUtils.ts; do
  echo "--- $f ---"
  cat -n "$f" | sed -n '1,420p'
done

echo '=== FOCUSED TEST/COMPATIBILITY REFERENCES ==='
rg -n -C 3 --glob '*.{test,spec}.{js,jsx,ts,tsx}' \
  '(link-input|link-textarea|S3-repo|WebDAV|remote|rte|date-time|forcehttps|internal-name)' \
  studio-ui 2>/dev/null || true

Length of output: 3112


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== CONTROL MAP AND NULL-CONTROL RUNTIME PATH ==='
cat -n studio-ui/ui/app/src/components/FormsEngine/lib/controlMap.ts | sed -n '1,180p'
rg -n -C 5 --type ts --type tsx \
  '(controlMap\[|UnknownControl|link-input|link-textarea|forcehttps|internal-name)' \
  studio-ui/ui/app/src/components/FormsEngine studio-ui/ui/app/src/components/ContentTypeManagement || true

echo '=== FE2 LINK, TEXT, AND COMPATIBILITY CONTROL IMPLEMENTATIONS ==='
for f in \
  studio-ui/ui/app/src/components/FormsEngine/controls/Text.tsx \
  studio-ui/ui/app/src/components/FormsEngine/controls/Textarea.tsx \
  studio-ui/ui/app/src/components/FormsEngine/controls/AutoFileName.tsx \
  studio-ui/ui/app/src/components/FormsEngine/controls/FileName.tsx \
  studio-ui/ui/app/src/components/FormsEngine/controls/Checkbox.tsx \
  studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts \
  studio-ui/ui/app/src/components/FormsEngine/lib/valueSerializers.ts \
  studio-ui/ui/app/src/components/FormsEngine/lib/validators.ts; do
  echo "--- $f ---"
  cat -n "$f" | sed -n '1,280p'
done

echo '=== REMOTE DATA-SOURCE ACTION IMPLEMENTATION ==='
for f in \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/moduleHelpers.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/services.ts \
  studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/remoteStubs.ts; do
  echo "--- $f ---"
  rg -n -C 8 '(createRemoteStubAction|unsupportedRemoteError|remote|browseFiles|search|upload|DataSourceServices)' "$f" || true
done

echo '=== LEGACY REMOTE DATA-SOURCE OPERATION PATHS ==='
for f in studio-ui/static-assets/components/cstudio-forms/data-sources/{S3,WebDAV,img-S3,img-WebDAV,video-S3,video-WebDAV}-{repo,upload}.js; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n -C 5 '(getName|insertImageAction|insertVideoAction|browse|upload|S3|WebDAV|dialog|picker|repository)' "$f" | head -n 260 || true
  fi
done

echo '=== RTE FEATURE PARITY ==='
echo '--- legacy RTE plugins ---'
for f in studio-ui/static-assets/components/cstudio-forms/controls/rte-plugins/*.js; do
  echo "--- $f ---"
  rg -n -C 3 '(PluginManager|add\(|button|menu|command|datasource|image|component|layout|link)' "$f" | head -n 160 || true
done
echo '--- FE2 RTE utility and component ---'
for f in \
  studio-ui/ui/app/src/components/FormsEngine/controls/RichTextEditor.tsx \
  studio-ui/ui/app/src/components/FormsEngine/lib/rteUtils.ts; do
  echo "--- $f ---"
  cat -n "$f" | sed -n '1,420p'
done

echo '=== FOCUSED TEST/COMPATIBILITY REFERENCES ==='
rg -n -C 3 --glob '*.{test,spec}.{js,jsx,ts,tsx}' \
  '(link-input|link-textarea|S3-repo|WebDAV|remote|rte|date-time|forcehttps|internal-name)' \
  studio-ui 2>/dev/null || true

Length of output: 150939


@rart, the data-source migration has complete type coverage, but it does not have complete functional coverage. The control migration has several direct runtime gaps.

Data sources

Covered by FE2

All 27 legacy data-source types have a corresponding FE2 module registration in studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/index.ts.

This includes:

  • Local repository and desktop-upload sources.
  • Image, video, and audio sources.
  • shared-content and embedded-content.
  • key-value-list, configured-list, and simpleTaxonomy.
  • flash-desktop-upload.
  • S3 and WebDAV source identifiers.

The FE2 module set also adds components and pages. These do not have matching files in the legacy data-source directory. They are FE2 additions, not omissions.

Functional gap: all remote S3 and WebDAV data sources fail at action execution

studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/remoteStubs.ts registers all 12 remote types:

  • img-S3-repo, img-S3-upload
  • img-WebDAV-repo, img-WebDAV-upload
  • video-S3-repo, video-S3-upload
  • video-WebDAV-repo, video-WebDAV-upload
  • S3-repo, S3-upload
  • WebDAV-repo, WebDAV-upload

However, each module creates its browse or upload action with createRemoteStubAction().

studio-ui/ui/app/src/components/FormsEngine/dataSources/moduleHelpers.ts, Lines 211-218, defines the action as unsupported. Lines 382-397 show that the action always throws:

Remote S3/WebDAV ... requires dedicated platform services.

This is a functional regression from legacy behavior. The legacy sources call the working platform operations:

  • CStudioAuthoring.Operations.openS3Browse
  • CStudioAuthoring.Operations.uploadS3Asset
  • CStudioAuthoring.Operations.openWebDAVBrowse
  • CStudioAuthoring.Operations.uploadWebDAVAsset

The FE2 controls will show these actions because the modules advertise browse or upload capabilities. The user then gets an error after selection.

This affects NodeSelector, ImagePicker, VideoPicker, and RTE media selection.

Recommendation: Do not advertise these actions until FE2 services support remote operations, or implement remote browse/upload methods in DataSourceServices and map their results to FE2 selections.

Controls

Complete control-map identifiers do not mean complete runtime support

The FE2 BuiltInControlType union includes the legacy control identifiers. However, studio-ui/ui/app/src/components/FormsEngine/lib/controlMap.ts maps four legacy controls to null:

Legacy control FE2 control map Result
disabled null at Line 62 Does not render
internal-name null at Line 68 Does not render
link-input null at Line 70 Does not render
link-textarea null at Line 71 Does not render

Each type has a legacy implementation under studio-ui/static-assets/components/cstudio-forms/controls.

The last two are not safe to treat as ordinary text fields. The legacy link-input and link-textarea controls also change required-state behavior for related fields. Mapping them to Text or Textarea alone would not preserve that behavior.

The FE2 code has value retrievers and serializers for these types. Therefore, existing content can parse and serialize, but Forms Engine cannot render the fields for editing.

Recommendation: Either implement the four controls, map them to documented compatible controls, or reject these control types before a form opens with a clear migration error. Leaving them in BuiltInControlType with a null component creates a runtime failure path.

Controls with adequate migration coverage

The following legacy controls have FE2 replacements:

  • auto-filename
  • aws-file-upload
  • checkbox
  • checkbox-group
  • date-time
  • dropdown
  • file-name
  • forcehttps
  • image-picker
  • input
  • label
  • linked-dropdown
  • node-selector
  • numeric-input
  • page-nav-order
  • rte
  • textarea
  • time
  • transcoded-video-picker
  • uuid
  • video-picker

forcehttps maps to Checkbox. That is reasonable because the legacy implementation reports its control name as disabled and behaves as a boolean checkbox.

Rich Text Editor gap

The FE2 RTE now supports action-based selection from image, video, audio, and file data sources. This is a substantial improvement.

However, I found no FE2 equivalents for several legacy Crafter-specific RTE plugins:

  • channel
  • edit-image
  • insert-component
  • insert-layout
  • insert-linkBrowse
  • insert-predefined-table
  • insert-stub

For example, legacy insert-component supports widget and widget-library menus, component placeholders, and component preview rendering. Legacy edit-image supports image size, alignment, margins, and alt text.

studio-ui/ui/app/src/components/FormsEngine/lib/rteUtils.ts preserves configured TinyMCE external_plugins. That can support external replacements. It does not load or adapt these legacy modules by itself.

This is a migration gap if current RTE configurations reference these legacy features. It needs a per-plugin compatibility decision:

  1. Migrate the feature to a TinyMCE 6 compatible plugin.
  2. Keep it as a supported external plugin.
  3. Mark the feature as unsupported in FE2.

Test coverage

I found no focused compatibility tests for:

  • Legacy-to-FE2 data-source type coverage.
  • Remote S3 or WebDAV actions.
  • disabled, internal-name, link-input, or link-textarea.
  • Legacy RTE plugin compatibility.

Add a table-driven test that compares the legacy type list with builtInDataSourceModules. Add action tests for every module. Remote modules should either execute a configured remote service or be absent from the available action list.

Conclusion

The migration is strong for local data sources and the main controls. The module architecture correctly removes central type switching.

The key gaps are:

  1. S3 and WebDAV data sources are registered but non-functional.
  2. Four legacy controls are declared but non-renderable in FE2.
  3. Legacy Crafter-specific RTE plugin behavior has no demonstrated FE2 replacement.
  4. Compatibility coverage has no focused tests.

These gaps should be explicit release constraints if they are intentionally out of scope.

You are interacting with an AI system.

…ities, and enforce root-only control restrictions.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
studio-ui/ui/app/src/components/ContentTypeManagement/descriptors/controls/expiredDate.ts (1)

142-144: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add a descriptor bootstrap regression test.

Verify that an expiredDate field with id expired_dt creates the expired_dt_tz atom during normal and repeat/embedded initialization. Verify that the timezone value is preserved through value parsing and form updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@studio-ui/ui/app/src/components/ContentTypeManagement/descriptors/controls/expiredDate.ts`
around lines 142 - 144, Add regression coverage for the expiredDate descriptor
bootstrap using field id expired_dt: verify normal and repeat/embedded
initialization create the expired_dt_tz atom, and verify the timezone value
remains intact through value parsing and subsequent form updates.
studio-ui/ui/app/src/components/ContentTypeManagement/utils.ts (1)

725-725: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add a data-source plugin serialization test.

Test a data source with a plugin locator and one without a plugin. Verify that serialization preserves the locator for plugin-backed data sources and omits it for built-in data sources.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@studio-ui/ui/app/src/components/ContentTypeManagement/utils.ts` at line 725,
Add serialization coverage for the data-source handling around the plugin field,
testing both a plugin-backed source with a plugin locator and a built-in source
without one. Assert that serialization preserves the locator in the first case
and omits the plugin field in the second.
studio-ui/ui/app/src/components/ContentTypeManagement/components/PickControlDialog.tsx (1)

82-92: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add regression coverage for root-only control filtering.

Add one test that keeps metadata.rootOnly controls at the content-type root. Add another test that removes them for nested and repeat field paths. Cover both built-in controls and configDescriptors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@studio-ui/ui/app/src/components/ContentTypeManagement/components/PickControlDialog.tsx`
around lines 82 - 92, Add regression tests for the control list construction
around typesFullList: verify metadata.rootOnly built-in controls and
configDescriptors remain available at the content-type root, and verify both are
excluded when fieldIdPath represents nested and repeat fields. Keep
non-root-only controls included in each scenario.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/keyValueList.ts`:
- Around line 29-30: Validate the JSON.parse result in the key-value-list
parsing flow before calling map, rejecting falsy primitives such as null, false,
0, and "" while preserving an empty array as a valid empty-list result. Update
the relevant parser function and remove the now-unused asArray import.

---

Nitpick comments:
In
`@studio-ui/ui/app/src/components/ContentTypeManagement/components/PickControlDialog.tsx`:
- Around line 82-92: Add regression tests for the control list construction
around typesFullList: verify metadata.rootOnly built-in controls and
configDescriptors remain available at the content-type root, and verify both are
excluded when fieldIdPath represents nested and repeat fields. Keep
non-root-only controls included in each scenario.

In
`@studio-ui/ui/app/src/components/ContentTypeManagement/descriptors/controls/expiredDate.ts`:
- Around line 142-144: Add regression coverage for the expiredDate descriptor
bootstrap using field id expired_dt: verify normal and repeat/embedded
initialization create the expired_dt_tz atom, and verify the timezone value
remains intact through value parsing and subsequent form updates.

In `@studio-ui/ui/app/src/components/ContentTypeManagement/utils.ts`:
- Line 725: Add serialization coverage for the data-source handling around the
plugin field, testing both a plugin-backed source with a plugin locator and a
built-in source without one. Assert that serialization preserves the locator in
the first case and omits the plugin field in the second.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2ece4093-7bc2-4113-a4bb-d06e3dd778b8

📥 Commits

Reviewing files that changed from the base of the PR and between ff59461 and 687abcb.

📒 Files selected for processing (35)
  • studio-ui/.cursor/rules/type-builder-forms-engine.mdc
  • studio-ui/docs/type-builder-forms-engine-plugins.md
  • studio-ui/docs/type-builder-forms-engine.md
  • studio-ui/ui/app/src/components/ContentTypeManagement/components/EditTypeView.tsx
  • studio-ui/ui/app/src/components/ContentTypeManagement/components/PickControlDialog.tsx
  • studio-ui/ui/app/src/components/ContentTypeManagement/descriptors/controls/expiredDate.ts
  • studio-ui/ui/app/src/components/ContentTypeManagement/descriptors/controls/pageNavOrder.ts
  • studio-ui/ui/app/src/components/ContentTypeManagement/utils.ts
  • studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx
  • studio-ui/ui/app/src/components/FormsEngine/components/GroupedDataSourceActionMenuItems.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/ImagePicker.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/PageNavOrder.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/TranscodedVideoPicker.tsx
  • studio-ui/ui/app/src/components/FormsEngine/controls/VideoPicker.tsx
  • studio-ui/ui/app/src/components/FormsEngine/dataSourceHooks/useDataSourceListOptions.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/actionAdapters.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/bindings.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/moduleHelpers.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioBrowseRepo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioDesktopUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgDesktopUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgRepositoryUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/keyValueList.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/sharedContent.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/simpleTaxonomy.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoBrowseRepo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoDesktopUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/types.ts
  • studio-ui/ui/app/src/components/FormsEngine/lib/controlPluginLoader.ts
  • studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx
  • studio-ui/ui/app/src/components/FormsEngine/lib/rteUtils.ts
  • studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts
  • studio-ui/ui/app/src/hooks/useVideoInfo.ts
  • studio-ui/ui/app/src/services/plugin.ts
  • studio-ui/ui/app/src/state/reducers/uiConfig.ts
🚧 Files skipped from review as they are similar to previous changes (28)
  • studio-ui/ui/app/src/components/ContentTypeManagement/descriptors/controls/pageNavOrder.ts
  • studio-ui/.cursor/rules/type-builder-forms-engine.mdc
  • studio-ui/ui/app/src/components/FormsEngine/lib/rteUtils.ts
  • studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx
  • studio-ui/ui/app/src/components/FormsEngine/lib/valueRetrievers.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoDesktopUpload.ts
  • studio-ui/ui/app/src/components/ContentTypeManagement/components/EditTypeView.tsx
  • studio-ui/docs/type-builder-forms-engine.md
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/videoBrowseRepo.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/imgDesktopUpload.ts
  • studio-ui/ui/app/src/services/plugin.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioDesktopUpload.ts
  • studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/sharedContent.ts
  • studio-ui/docs/type-builder-forms-engine-plugins.md
  • studio-ui/ui/app/src/components/FormsEngine/dataSourceHooks/useDataSourceListOptions.ts
  • studio-ui/ui/app/src/state/reducers/uiConfig.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/simpleTaxonomy.ts
  • studio-ui/ui/app/src/components/FormsEngine/components/GroupedDataSourceActionMenuItems.tsx
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/actionAdapters.ts
  • studio-ui/ui/app/src/components/FormsEngine/controls/PageNavOrder.tsx
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/bindings.ts
  • studio-ui/ui/app/src/components/FormsEngine/controls/TranscodedVideoPicker.tsx
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/types.ts
  • studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/audioBrowseRepo.ts
  • studio-ui/ui/app/src/components/FormsEngine/controls/ImagePicker.tsx
  • studio-ui/ui/app/src/components/FormsEngine/lib/controlPluginLoader.ts
  • studio-ui/ui/app/src/components/FormsEngine/controls/VideoPicker.tsx

Comment thread studio-ui/ui/app/src/components/FormsEngine/dataSources/modules/keyValueList.ts Outdated
rart added 3 commits August 3, 2026 09:50
…st path, enforce descriptor id comparison, and preflight plugin contributions before registry commit.
…stability, capabilities, and creation contracts.
…ler` lifecycle, async hook behaviour, type-local `form-controller.js` location, and authenticated loading mechanisms. Refine distinctions from legacy FE1 controllers and plugin-based controls.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@studio-ui/docs/type-builder-forms-engine.md`:
- Around line 629-633: Update the open convergence-gap checklist in the
documentation to add unchecked entries for S3/WebDAV capability stubs,
non-rendering disabled/internal-name/link-input/link-textarea controls, missing
FE2 Crafter-specific RTE plugin equivalents, focused compatibility tests,
importPlugin() descriptor ownership validation, and non-atomic plugin
registration. Add a corresponding Progress entry summarizing these findings,
while keeping completed design decisions separate from remaining implementation
and validation work.
- Around line 483-495: Add the text language tag to both fenced code blocks in
the form-controller documentation: the authoring artifact path example and the
authenticated GET request example. Leave their contents unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fecffe57-5a53-44e9-bbe1-4f3a4eccffd1

📥 Commits

Reviewing files that changed from the base of the PR and between 5f2ff22 and c508630.

📒 Files selected for processing (1)
  • studio-ui/docs/type-builder-forms-engine.md

Comment thread studio-ui/docs/type-builder-forms-engine.md Outdated
Comment thread studio-ui/docs/type-builder-forms-engine.md Outdated
…sign details, async hook definitions, loading process, and usage context. Document completed decisions, remaining implementation, and integration points.
@rart rart added the FE 2 label Aug 3, 2026
@rart
rart requested a review from sumerjabri August 3, 2026 22:20
@rart rart added this to the Next Release milestone Aug 3, 2026
@rart rart added this to v5.0.0 Aug 3, 2026
@github-project-automation github-project-automation Bot moved this to Triage in v5.0.0 Aug 3, 2026
@rart rart changed the title Introduce FE2 data-source modules/actions and a single PluginDescriptor load path for DS and control plugins FE2 DS modules & single load path for DS and control plugins Aug 3, 2026
@rart rart moved this from Triage to In Code Review in v5.0.0 Aug 3, 2026
@rart

rart commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai I'm done with this for now. Ok to hand this over for human review?
FYI: I left the "development draft" notice on PageNavOrder.tsx as it still applies.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@rart Yes. This is ready for human review.

The PR provides a sound architectural foundation for FE2 data sources and control plugins. The human review should specifically assess the known boundaries:

  • S3 and WebDAV modules are registered but their actions are stubbed.
  • disabled, internal-name, link-input, and link-textarea need a compatibility decision.
  • Legacy Crafter-specific RTE plugins need an FE2 migration or support decision.
  • Focused compatibility and failure-path tests are still needed.
  • The control-plugin ownership and atomic-registration findings should remain tracked if they are not addressed in this PR.

The development-draft notice in studio-ui/ui/app/src/components/FormsEngine/controls/PageNavOrder.tsx is intentional. I will not report it as stale.


✏️ Learnings added
Learnt from: rart
URL: https://github.com/craftersoftware/craftercms/pull/8852

Timestamp: 2026-08-03T22:27:01.675Z
Learning: In PR `#8852`, the development-draft notice in `studio-ui/ui/app/src/components/FormsEngine/controls/PageNavOrder.tsx` remains applicable and is intentional. Do not report this notice as stale during review.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@sumerjabri
sumerjabri merged commit 06498ea into craftersoftware:develop Aug 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: In Code Review

Development

Successfully merging this pull request may close these issues.

2 participants