Skip to content

UX Rescue Phase 2A: rebuild the Form Designer as a unified authoring studio - #153

Merged
henter36 merged 18 commits into
mainfrom
ux-rescue-phase2a-unified-form-designer-studio
Jul 29, 2026
Merged

UX Rescue Phase 2A: rebuild the Form Designer as a unified authoring studio#153
henter36 merged 18 commits into
mainfrom
ux-rescue-phase2a-unified-form-designer-studio

Conversation

@henter36

@henter36 henter36 commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replaces the 7-route form creation/design/review flow with one unified studio (/forms/designer/new, /forms/designer/:formId) that merges creation, page/section/field design, condition/formula authoring, validation, preview, autosave, and review into a single place.
  • Closes the critical gap already documented in docs/ux-rescue/form-designer-gap-analysis.md: Condition Builder and Formula Builder did not exist anywhere in the UI despite the domain model and evaluation engine already supporting typed conditions/formulas server-side. Both are new, typed, safe (no free-text expressions), with client-side cycle/self-reference detection backed by the server's validateVersion as the final authority.
  • Reuses existing infrastructure unchanged: autosave (useFormDesignerAutosave), undo/redo (historyStore), preview/evaluation engine (previewLogic), and the server's FormSchemaValidator/FormDependencyGraph. No parallel form engine was built.
  • Two small, justified backend additions only: copy-schema-from-an-existing-form (POST /api/v1/forms/copy-from/{sourceFormId}/{sourceVersionId}) and template preview-before-use (GET /api/v1/form-templates/{id}/schema) — both reuse existing FormVersionService/FormTemplateService logic and scope/permission checks, no new services, no migrations.
  • Desktop (3-pane), Tablet (openable side panels, no stacked modals), and Mobile (explicit review/simple-edit-only mode, no false drag-and-drop claim) layouts, all tested.
  • Legacy designer route (/forms/:formId/versions/:versionId/edit) and the confirmed-dead /forms/:formId/versions/new now redirect into the studio (capability moved first, redirects tested) rather than being deleted outright; every other route disposition is documented in docs/ux-rescue/phase2a-form-designer-route-transition.md.
  • Does not close [UX Rescue] Rebuild the Form Designer into a guided, low-complexity authoring studio #144. See docs/ux-rescue/phase2a-form-designer-compliance-ledger.md for the full Verified/Partial/Missing breakdown across all 95 checklist items from the task brief — 8 explicit Missing items remain (multi-select in canvas, institutional context in preview, formula result-type display, a documented 200-field performance budget, an automated query-count test, an automated accessibility audit, manual screen-reader testing, and a migration policy for renaming a published option key). None are data-integrity or security risks.

Test plan

  • Backend unit tests: dotnet test Baseera.UnitTests — 990 passed, 0 failed, 0 skipped
  • Backend integration tests: dotnet test Baseera.IntegrationTests (real SQL Server) — 263 passed, 0 failed, 0 skipped
  • Frontend tests: npm run test — 343 passed (63 files), including new coverage for cycle detection, condition/formula builders, validation panel, the full new-form→studio flow, autosave states, the 409 conflict UX (all three recovery options), mobile review-only mode, and route redirects
  • npm run typecheck, npm run lint — clean (only pre-existing warnings in unrelated files)
  • npm run build — succeeds
  • npm run check:ux-routes — passes (65 routes, up from 62, fully reconciled with the inventory doc)
  • npm audit --audit-level=high — 0 vulnerabilities
  • bash scripts/check-nuget-vulnerabilities.sh — no High/Critical
  • gitleaks detect — no leaks
  • git diff --check — clean

Full details, numbers, and the remaining Phase 2B scope are in docs/ux-rescue/phase2a-form-designer-completion-report.md.

🤖 Generated with Claude Code

Summary by Sourcery

Introduce a unified Form Designer studio that consolidates form creation, editing, validation, preview, and review into new /forms/designer routes, while adding backend support to copy existing forms and preview templates, updating routes, permissions docs, tests, and UX documentation accordingly.

New Features:

  • Add a unified Form Designer Studio UI for creating and editing forms at /forms/designer/new and /forms/designer/:formId with desktop/tablet/mobile layouts.
  • Introduce Condition Builder and Formula Builder components for typed, UI-driven condition and formula authoring within the form designer.
  • Add a form version comparison page and diff view to compare schemas between two versions.
  • Enable starting a new form from templates or by copying an existing form via a guided start flow in the studio.

Bug Fixes:

  • Align form template preview visibility rules with template listing to prevent mismatched access between list and preview endpoints.
  • Fix form templates page navigation to use SPA routing instead of full page reloads, preserving app state.

Enhancements:

  • Refactor legacy form designer routes to redirect into the unified studio while keeping backward compatibility for old deep links.
  • Document and update the UX route inventory, implementation plan, permissions matrix, and compliance ledger for the new studio and routes.
  • Enhance form campaign wizard to accept prefilled formId and versionId from the designer studio for smoother handoff to publishing.
  • Add a validation panel with classified errors/warnings and navigation, plus improved autosave conflict handling with non-destructive options.
  • Improve accessibility and responsive behavior of the form designer through new layouts, sr-only helpers, and keyboard-friendly interactions.

Documentation:

  • Add detailed Phase 2A UX Rescue documentation covering the unified form designer studio architecture, scope, validation, performance, accessibility, route transitions, and completion report.
  • Update screen and route inventory to reflect new designer routes, redirects, and route counts.
  • Clarify permissions matrix for form-related permissions and document how the new studio reuses existing permissions model.

Tests:

  • Add backend integration tests for copying forms from existing forms and for template schema preview visibility and content.
  • Add frontend tests for the new studio page, condition and formula builders, validation panel, version diffing, route redirects, and mobile review-only mode.

Summary by CodeRabbit

  • New Features

    • Introduced a unified form designer studio for creating, editing, reviewing, previewing, and comparing form versions.
    • Added template preview and “copy from existing form” creation flows, with conflict-aware save/resolution and legacy route redirects.
    • Added condition/formula building, continuous validation UI, and responsive desktop/tablet/mobile experiences.
    • Added mobile-focused review controls and accessible keyboard-friendly editing.
  • Documentation

    • Updated UX rescue, permissions, accessibility, performance, schema contract, test plan, and compliance ledger documentation.
  • Tests

    • Expanded frontend and backend tests for studio flows, validation, permissions/visibility, copying, routing redirects, and conflict handling.

henter36 and others added 2 commits July 29, 2026 06:42
Replaces the 7-route form creation/design/review flow with one studio
at /forms/designer/new and /forms/designer/:formId, closing the
critical gap identified in form-designer-gap-analysis.md: condition
and formula builders now exist (previously no UI could set visibility/
required conditions or calculation formulas despite the domain model
and evaluation engine already supporting them).

Reuses existing autosave, undo/redo, and preview/evaluation logic
unchanged. Two small backend additions only: copy-schema-from-an-
existing-form, and template preview-before-use — no parallel form
engine, no new services beyond the existing FormVersionService/
FormTemplateService split.

Backend: 990 unit + 263 integration tests passing, 0 failed, 0 skipped.
Frontend: 343 tests passing; typecheck/lint/build/audit clean.

Does not close #144 — see phase2a-form-designer-compliance-ledger.md
for the full Verified/Partial/Missing breakdown (8 explicit Missing
items, none data/security risk).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@sourcery-ai sourcery-ai 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.

Sorry @henter36, your pull request is larger than the review limit of 150000 diff characters

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@henter36, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 87b455b8-b8d5-4929-9054-893bb0cd9145

📥 Commits

Reviewing files that changed from the base of the PR and between b9299ea and d8f8bf9.

📒 Files selected for processing (7)
  • docs/ux-rescue/phase2a-form-designer-compliance-ledger.md
  • src/frontend/src/forms/designer/ConditionBuilder.tsx
  • src/frontend/src/forms/designer/FormulaBuilder.tsx
  • src/frontend/src/pages/forms/studio/FormDesignerStudioPage.test.tsx
  • src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx
  • src/frontend/src/pages/forms/studio/StudioInspector.test.tsx
  • src/frontend/src/pages/forms/studio/StudioInspector.tsx
📝 Walkthrough

Walkthrough

The PR adds a unified form designer studio with schema editing, validation, comparison, responsive workspaces, migrated routes, copy/template APIs, consolidated access checks, automated coverage, and Phase 2A documentation.

Changes

Unified Form Designer Studio

Layer / File(s) Summary
Phase 2A documentation and contracts
README.md, docs/implementation-plan.md, docs/permissions-matrix.md, docs/ux-rescue/*
Documents the studio scope, routes, schema and permissions, accessibility, autosave, validation, performance, compliance status, and follow-up work.
Backend copy and template-schema APIs
src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs, src/backend/Baseera.Application/Forms/*, src/backend/tests/*
Adds copy-from and template-schema endpoints, a form-version access guard, schema DTOs, service integration, and integration/unit coverage.
Schema editing and validation primitives
src/frontend/src/forms/designer/*
Adds immutable schema operations, field/dependency analysis, condition and formula builders, validation and comparison utilities, responsive layout detection, unsaved-change protection, and tests.
Studio workspace and navigation
src/frontend/src/pages/forms/studio/*, src/frontend/src/App.tsx, src/frontend/src/index.css, src/frontend/src/pages/forms/versions/*
Adds creation flows, canvas, inspector, outline, review/conflict handling, mobile and desktop workspaces, styling, comparison routing, and legacy-route redirects.
Workflow verification and supporting navigation
src/frontend/src/pages/forms/studio/*.test.tsx, src/frontend/src/App.route-redirects.test.tsx, src/frontend/src/pages/form-campaigns/*, src/frontend/src/pages/forms/templates/*
Covers studio creation, autosave, conflicts, mobile behavior, redirects, and preserves query-driven form/version navigation.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: rebuilding the Form Designer into a unified authoring studio for Phase 2A.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ux-rescue-phase2a-unified-form-designer-studio

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@henter36

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ 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.

@sourcery-ai

sourcery-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

Rebuilds the form designer UX into a single unified studio at /forms/designer/*, introduces typed condition and formula builders with cycle detection, adds backend support for copying forms and previewing templates, and wires all existing flows, routes, permissions, and tests to the new studio without introducing a parallel engine or new services.

Sequence diagram for unified studio start flow and backend copy/template endpoints

sequenceDiagram
  actor User
  participant FormDesignerStudioPage
  participant StudioStartFlow
  participant api_forms as api.forms
  participant api_templates as api.formTemplates

  User->>FormDesignerStudioPage: open /forms/designer/new
  FormDesignerStudioPage->>StudioStartFlow: render start flow

  User->>StudioStartFlow: choose blank form
  StudioStartFlow->>api_forms: create(request) // CreateFormRequest
  api_forms-->>StudioStartFlow: FormDetail
  StudioStartFlow->>api_forms: createVersion(formId)
  api_forms-->>StudioStartFlow: FormVersionDetail
  StudioStartFlow->>FormDesignerStudioPage: onCreated(formId,versionId)
  FormDesignerStudioPage->>FormDesignerStudioPage: navigate /forms/designer/:formId?versionId=

  User->>StudioStartFlow: choose template
  StudioStartFlow->>api_templates: list()
  api_templates-->>StudioStartFlow: FormTemplateListItem[]
  User->>StudioStartFlow: preview template
  StudioStartFlow->>api_templates: getSchema(templateId)
  api_templates-->>StudioStartFlow: FormTemplateSchema
  User->>StudioStartFlow: use template
  StudioStartFlow->>api_templates: createForm(templateId,CreateFormRequest)
  api_templates-->>StudioStartFlow: FormDetail
  StudioStartFlow->>FormDesignerStudioPage: onCreated(formId,versionId)

  User->>StudioStartFlow: choose copy existing form
  StudioStartFlow->>api_forms: list({search,pageSize})
  api_forms-->>StudioStartFlow: FormsList
  User->>StudioStartFlow: select sourceFormId,sourceVersionId
  StudioStartFlow->>api_forms: copyFromExistingForm(sourceFormId,sourceVersionId,CreateFormRequest)
  api_forms-->>StudioStartFlow: FormVersionDetail
  StudioStartFlow->>FormDesignerStudioPage: onCreated(formId,versionId)
Loading

File-Level Changes

Change Details Files
Introduce unified Form Designer Studio UI and retire legacy FormDesignerPage in favor of new studio components and layout logic.
  • Add FormDesignerStudioPage orchestrating queries, autosave, undo/redo, layout mode, and wiring new studio subcomponents.
  • Implement StudioStartFlow to handle new-form flows (blank, from template, copy existing form) before entering the main studio.
  • Replace legacy designer canvas/palette/properties/toolbar with StudioCanvas + StudioOutline + StudioFieldLibrary + StudioInspector wired through shared schema operations and historyStore.
  • Add responsive layout handling and unsaved-changes guard to support desktop/tablet/mobile modes and beforeunload protection.
  • Remove the old FormDesignerPage and its supporting designer UI components now that all functionality lives in the studio.
src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx
src/frontend/src/pages/forms/studio/StudioStartFlow.tsx
src/frontend/src/pages/forms/studio/StudioCanvas.tsx
src/frontend/src/pages/forms/studio/StudioInspector.tsx
src/frontend/src/pages/forms/studio/StudioOutline.tsx
src/frontend/src/pages/forms/studio/StudioFieldLibrary.tsx
src/frontend/src/pages/forms/studio/StudioReviewPanel.tsx
src/frontend/src/pages/forms/studio/StudioTopBar.tsx
src/frontend/src/pages/forms/studio/StudioMobileReview.tsx
src/frontend/src/pages/forms/studio/StudioConflictBanner.tsx
src/frontend/src/forms/designer/useResponsiveStudioLayout.ts
src/frontend/src/forms/designer/useUnsavedChangesGuard.ts
src/frontend/src/forms/designer/studioSchemaOps.ts
src/frontend/src/forms/designer/fieldLibrary.ts
src/frontend/src/index.css
src/frontend/src/pages/forms/versions/FormDesignerPage.tsx
src/frontend/src/forms/designer/DesignerCanvas.tsx
src/frontend/src/forms/designer/DesignerPalette.tsx
src/frontend/src/forms/designer/DesignerPropertiesPanel.tsx
src/frontend/src/forms/designer/DesignerToolbar.tsx
Add typed ConditionBuilder and FormulaBuilder with client-side dependency analysis, and integrate them into the field inspector and validation pipeline.
  • Implement ConditionBuilder that restricts operators based on field type, forbids self-reference, supports nested groups, and emits strongly-typed condition trees.
  • Implement FormulaBuilder that builds typed formula ASTs (constants, field references, binary ops, functions) with numeric-type enforcement and division-by-zero warnings.
  • Add fieldDependencies utilities to flatten fields, compute per-field dependencies, find dependents, and detect dependency cycles, with unit tests.
  • Add ValidationPanel that classifies server-side validation issues into errors/warnings, decorates with locations and suggested actions, and provides navigation hooks into the studio.
  • Extend the inspector to surface advanced settings (visibility/required conditions and formulas) under a collapsible section and to apply cycle-guarded schema patches.
src/frontend/src/forms/designer/ConditionBuilder.tsx
src/frontend/src/forms/designer/FormulaBuilder.tsx
src/frontend/src/forms/designer/fieldDependencies.ts
src/frontend/src/forms/designer/fieldDependencies.test.ts
src/frontend/src/forms/designer/ValidationPanel.tsx
src/frontend/src/forms/designer/ValidationPanel.test.tsx
src/frontend/src/forms/designer/versionDiff.ts
src/frontend/src/forms/designer/versionDiff.test.ts
src/frontend/src/forms/designer/schemaTypes.ts
src/frontend/src/forms/designer/designerHelpers.ts
src/frontend/src/forms/designer/FormPreviewPanel.tsx
src/frontend/src/pages/forms/studio/StudioInspector.tsx
Wire studio into routing, navigation, and forms UX, adding redirects from legacy routes and a version comparison page.
  • Add new routes for /forms/designer/new and /forms/designer/:formId and gate them behind Forms.UpdateDraft, plus a nav link labelled "استوديو تصميم النماذج".
  • Introduce RedirectToStudioEdit and RedirectToStudioNew components so legacy /versions/:versionId/edit and /versions/new routes redirect into the studio while preserving formId/versionId.
  • Update FormsListPage and FormVersionsPage to send all "new form" and "design" actions into the studio, and add a /versions/compare entry point when multiple versions exist.
  • Add FormVersionComparePage and VersionCompare component to diff two schemas (field additions/removals/changes, options, conditions, formulas, requiredness) and expose it via /forms/:formId/versions/compare.
  • Convert template-based form creation and campaign wizard to SPA-friendly flows that preserve formId/versionId context coming from the studio.
src/frontend/src/App.tsx
src/frontend/src/pages/forms/FormsListPage.tsx
src/frontend/src/pages/forms/versions/FormVersionsPage.tsx
src/frontend/src/pages/forms/versions/FormVersionDetailPage.tsx
src/frontend/src/pages/forms/versions/FormVersionComparePage.tsx
src/frontend/src/pages/forms/templates/FormTemplatesPage.tsx
src/frontend/src/pages/form-campaigns/FormCampaignWizardPage.tsx
src/frontend/src/pages/forms/studio/FormDesignerStudioPage.test.tsx
src/frontend/src/App.route-redirects.test.tsx
docs/ux-rescue/screen-and-route-inventory.md
docs/ux-rescue/phase2a-form-designer-route-transition.md
Extend backend services and API endpoints to support copying forms from existing versions and previewing form templates, with matching integration tests and DTOs.
  • Add FormVersionService.CreateFromExistingFormAsync that enforces Forms.Create + Forms.UpdateDraft, loads a source form/version within scope, seeds a new draft form+version with the source schema, and writes an audit log entry capturing provenance.
  • Expose POST /api/v1/forms/copy-from/{sourceFormId}/{sourceVersionId} and map it to the new service method under AuthPolicies.FormsCreate.
  • Refactor FormTemplateService to centralize template visibility filtering in BuildVisibleTemplatesQueryAsync and add GetSchemaAsync + GET /api/v1/form-templates/{templateId}/schema for previewing templates with the same visibility rules as listing.
  • Extend shared DTOs and frontend API client types (FormTemplateSchema, api.forms.copyFromExistingForm, api.formTemplates.getSchema) to cover the new backend endpoints.
  • Add integration tests verifying template schema visibility consistency and copy-from-existing-form behavior, including not-found semantics when copying across scopes.
src/backend/Baseera.Application/Forms/FormVersionService.cs
src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
src/backend/Baseera.Application/Forms/FormTemplateService.cs
src/backend/Baseera.Application/Forms/FormVersionDtos.cs
src/backend/tests/Baseera.IntegrationTests/FormsVersionIntegrationTests.cs
src/frontend/src/api/client.ts
docs/permissions-matrix.md
docs/ux-rescue/phase2a-form-designer-schema-contract.md
Document Phase 2A architecture, scope, validation, performance, and compliance, and update UX route and implementation plans accordingly.
  • Add a detailed compliance ledger enumerating 95 checklist items with Verified/Partial/Missing/Not Applicable statuses for Phase 2A.
  • Write a completion report summarizing architecture, routes, tests, and remaining Phase 2B scope, and link it from README and rescue roadmap.
  • Document the schema contract, autosave behavior, validation pipeline, performance assumptions, and accessibility decisions for the unified studio.
  • Update screen-and-route inventory, implementation plan, and permissions matrix to reflect new routes, behaviors, permissions wiring, and backend additions.
  • Capture test matrices and metrics baseline deltas, including route counts, screen counts for the authoring flow, and remaining gaps.
docs/ux-rescue/phase2a-form-designer-compliance-ledger.md
docs/ux-rescue/phase2a-form-designer-completion-report.md
docs/ux-rescue/phase2a-form-designer-schema-contract.md
docs/ux-rescue/phase2a-form-designer-architecture.md
docs/ux-rescue/phase2a-form-designer-scope.md
docs/ux-rescue/phase2a-form-designer-test-matrix.md
docs/ux-rescue/phase2a-form-designer-route-transition.md
docs/ux-rescue/phase2a-form-designer-autosave.md
docs/ux-rescue/phase2a-form-designer-validation.md
docs/ux-rescue/phase2a-form-designer-performance.md
docs/ux-rescue/rescue-roadmap.md
docs/ux-rescue/task-metrics-baseline.md
docs/implementation-plan.md
docs/permissions-matrix.md
README.md

Assessment against linked issues

Issue Objective Addressed Explanation
#144 Replace the legacy multi-route form creation/design/review flow with a unified Form Designer studio that consolidates form creation, page/section/field design, validation, preview, and review in one place while preserving existing domain versioning, schema contracts, and backward-compatible routing.
#144 Implement Condition Builder and Formula Builder UIs integrated into the studio, using the existing typed condition/formula models, with safe (non–free-text) editing, cycle/self-reference detection, and placement in a clearly separated advanced section.
#144 Align the Form Designer UX with the issue’s authoring and usability criteria (guided start flow, progressive disclosure of advanced options, validation errors pointing to specific elements, keyboard-friendly and mobile/tablet behavior, and documentation of route/architecture changes) so that simple forms can be authored and sent for review entirely within the studio, without significant UX gaps. The PR substantially improves UX (guided start flow, progressive disclosure, validation panel with element navigation, mobile/tablet layouts, route/architecture docs) and allows simple forms to be created and submitted from the studio, but the compliance ledger explicitly lists remaining gaps against the brief (e.g., no multi-select operations in the canvas, no institutional context selector in preview, no formula result-type display, missing documented performance budget and query-count test, and incomplete accessibility/a11y testing). The issue is therefore only partially satisfied and intentionally not closed.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

henter36 and others added 7 commits July 29, 2026 08:44
FormVersionService's constructor had 8 parameters (Sonar S107, max 7).
formScope + effectiveAccess were always used together (load a form in
scope, then check a capability on it) across every method in the
service, so they are now a single coherent collaborator,
IFormVersionAccessGuard, instead of two separate injected services.

No transaction boundary, version numbering, optimistic concurrency, or
publish/draft lifecycle behavior changed — verified by the existing
FormsVersionIntegrationTests suite plus new unit tests for the guard
itself and a new integration test covering the "reject invalid schema
on submit" path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FormDesignerStudioPage's main workspace component had Cognitive
Complexity 30 (Sonar S3776, max 15), a 6-level-deep inline function
nest in the mobile label-rename handler (S2004), and a nested ternary
choosing between the field library / read-only notice / outline
(S3358).

- studioWorkspaceHelpers.ts: pulls the branching logic out of mutation
  callbacks and effects (version reseed sync, conflict-schema sync,
  review decision dispatch, save-as-new-version, error-message
  resolution) into named functions with their own, separate
  complexity budget — the effects/mutations in the component now each
  reduce to a single delegated call.
- useStudioFieldCommands.ts: extracts the add-field/add-page guard
  clauses into their own hook.
- StudioSidePanel.tsx: owns the field-library/outline tab switch,
  resolving the nested ternary into a single decision function.
- StudioMobileWorkspace.tsx: owns the mobile branch's rendering, and
  replaces the inline nested page->section->field map chain with the
  existing flat updateFieldInSchema/renamePageTitle helpers (the same
  ones the desktop canvas already uses), eliminating the deep nesting
  entirely rather than just relocating it.

Undo/redo, dirty-state guarding, autosave/manual-save semantics, and
URL/selection synchronization are unchanged — the hook call order and
dependencies are identical, only the callback bodies moved.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
StudioOutline had a map > map(&&) > ternary > map chain (page ->
section -> field, Sonar S2004, max 4 levels) and StudioMobileReview
had a similar page -> field nesting. Both are split into small named
components (OutlinePage/OutlineSection/OutlineField;
MobileReviewPageSection/MobileReviewPageTitleField/
MobileReviewFieldLabelField/MobileReviewStatusSummary), each with at
most one level of mapping in its own body.

MobileReviewPageTitleField/MobileReviewFieldLabelField now own their
draft text as local state instead of the parent tracking every open
input in one shared Record keyed by id — equivalent behavior (once
edited, the local override persists the same way), simpler to reason
about per-row.

Also fixes S6819: the mobile advisory banner used role="status" on a
div; it is now a semantic <output aria-live="polite"> element, which
is the accessible-by-default alternative Sonar and the task both call
for. Keyboard navigation, selection, and expand/collapse in the
outline are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- ValidationPanel.locateIssue was Cognitive Complexity 17 (Sonar
  S3776, max 15): a nested page/section scan followed by a flattened
  field scan with a compound boolean condition. Split into
  findPageOrSectionLocation / matchesIssueField / findFieldLocation,
  each independently simple; locateIssue itself is now two lines.
  Also replaces the `issue.fieldKey && ... === issue.fieldKey...`
  guard with `issue.fieldKey?.toLowerCase()` (S6582).
- versionDiff.diffField was Cognitive Complexity 16: a 7-branch
  if-chain building changedProperties. Converted to a declarative
  FIELD_PROPERTY_CHECKS table (label + comparator) filtered/mapped in
  one pass, in the exact original order, plus a hasRequiredChanged
  helper. Also adds a "قواعد التحقق" (validation rules) check to the
  same table, and covers renamed/validation/formula/condition/
  reordered/unchanged-form scenarios in versionDiff.test.ts.

Diff output order and displayed text are unchanged; verified against
the existing test suite plus the new scenarios.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- ConditionBuilder and FormulaBuilder used role="group" on a <div>
  (Sonar S6819); both are now <fieldset><legend>...</legend> — the
  native, accessible-by-default grouping element the rule asks for.
  `getByRole('group', { name })` still resolves correctly since
  <fieldset> exposes that role with its <legend> as the accessible
  name, so no test changes were needed.
- Replaced array-index React keys (S6479) with content-derived keys:
  nested condition groups and formula argument nodes have no id of
  their own (mirroring the server's shape), so their key is the
  node's own JSON content — neither component holds per-instance
  local state, so two structurally-identical siblings sharing a key
  is harmless. Condition predicates, field-issue rows, and recent-
  field-type buttons use natural composite/unique identifiers
  (predicate content, code+path, and the type itself, which is
  deduplicated before insertion) instead of array position.
- StudioInspector: replaced the `field.number ?? {}` spread fallback
  (S7744, "the empty object is useless") with `...field.number`
  directly — spreading undefined/null into an object literal is a
  no-op in JS, so the empty-object literal added nothing. Choice
  option rows now key off `option.value`, which the existing
  duplicate-value guard already keeps unique.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Removed `event.returnValue = ''` (Sonar S1874, deprecated).
`event.preventDefault()` alone is the modern, spec-compliant way to
trigger the browser's generic beforeunload prompt; the project has no
documented legacy-browser target (no browserslist/legacy build
config) that would require the old fallback.

Added useUnsavedChangesGuard.test.ts covering the hook's actual
observable behavior (whether a dispatched beforeunload event ends up
defaultPrevented) rather than mocking addEventListener: no-op when
clean, blocks when dirty, stops blocking after unmount, and reacts to
the dirty flag changing across re-renders.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaced `await waitFor(() => expect(screen.getByText(...)))` with
`expect(await screen.findByText(...))` (Sonar S9020) — findBy already
retries until the element appears or times out, so the outer waitFor
was redundant. waitFor is kept only for non-DOM assertions (mock call
checks) where findBy doesn't apply.

Also gives the three tests that combine userEvent typing, the 800ms
autosave debounce, and a waitFor an explicit 10s test timeout instead
of vitest's 5s default — these were already timing-marginal before
this change and became visibly flaky under concurrent system load
while the backend integration suite was running; not a regression
introduced by the findBy swap itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@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: 14

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (9)
docs/ux-rescue/phase2a-form-designer-completion-report.md-26-27 (1)

26-27: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the component inventory counts.

Line 26 lists 10 studio components, not 8; line 27 lists 10 designer modules/components, not 7. Update the counts or the lists so the completion report accurately reflects the delivered scope.

🤖 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 `@docs/ux-rescue/phase2a-form-designer-completion-report.md` around lines 26 -
27, Correct the inventory counts in the completion report: update the studio
component count to 10 and the designer module/component count to 10, while
preserving the listed names.
docs/ux-rescue/phase2a-form-designer-compliance-ledger.md-98-98 (1)

98-98: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Count Max among the verified formula operations.

The schema contract documents Max as a supported backend function, so “Verified للستة الأولى” is inconsistent with the seven supported requested operations through الحد الأعلى. Change this to “للسبعة الأولى” or clarify the intended subset.

🤖 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 `@docs/ux-rescue/phase2a-form-designer-compliance-ledger.md` at line 98, Update
the formula-operations verification entry in the compliance ledger to count
“Max” among the supported verified operations, changing “Verified للستة الأولى”
to “Verified للسبعة الأولى” unless the schema contract confirms a narrower
intended subset.
docs/ux-rescue/phase2a-form-designer-compliance-ledger.md-90-90 (1)

90-90: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the malformed compliance table rows.

These rows have only three cells in a four-column table, so Markdown rendering drops the evidence column. Add the missing fourth cell or split the status/evidence text correctly on Lines 90, 164, 203, and 205.

Also applies to: 164-164, 203-203, 205-205

🤖 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 `@docs/ux-rescue/phase2a-form-designer-compliance-ledger.md` at line 90, The
compliance table rows at the entries labeled 40, 164, 203, and 205 have only
three cells instead of the required four. Update each row to include a separate
fourth evidence cell, preserving the existing status and evidence content while
ensuring every row matches the table’s column structure.

Source: Linters/SAST tools

src/frontend/src/forms/designer/ConditionBuilder.test.tsx-8-24 (1)

8-24: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Self-reference exclusion test never actually exercises the exclusion logic.

excludeFieldKey is hardcoded to 'self_field' (Line 21), but FIELDS never contains a field keyed self_field (Lines 8-12). The assertion that no option has value === 'self_field' is trivially true whether or not wouldCreateSelfReference filtering works — a regression here would go undetected.

🧪 Proposed fix — make `self_field` an actual candidate so exclusion is meaningfully tested
 const FIELDS: ConditionableField[] = [
+  { key: 'self_field', labelAr: 'الحقل الحالي', type: 0 },
   { key: 'text_field', labelAr: 'حقل نصي', type: 0 },
   { key: 'number_field', labelAr: 'حقل رقمي', type: 2 },
   { key: 'choice_field', labelAr: 'حقل اختيار', type: 7, choiceOptions: [{ value: 'a', labelAr: 'أ', order: 0, isActive: true }, { value: 'b', labelAr: 'ب', order: 1, isActive: true }] },
 ]

Also applies to: 45-53

🤖 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 `@src/frontend/src/forms/designer/ConditionBuilder.test.tsx` around lines 8 -
24, Add a field keyed "self_field" to the FIELDS fixture used by Harness, with
valid field metadata, while keeping Harness.excludeFieldKey set to "self_field".
Ensure the self-reference exclusion test can observe and verify that this
candidate is omitted rather than passing trivially.
src/frontend/src/App.route-redirects.test.tsx-20-20 (1)

20-20: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the preserved versionId, not just the destination route.

The probe matches regardless of query string, so an implementation that drops ?versionId=v1 still passes.

Proposed test update
-import { MemoryRouter, Route, Routes } from 'react-router'
+import { MemoryRouter, Route, Routes, useLocation } from 'react-router'

 function TargetProbe({ label }: Readonly<{ label: string }>) {
-  return <div>{label}</div>
+  const location = useLocation()
+  return <div data-location={`${location.pathname}${location.search}`}>{label}</div>
 }

-    expect(screen.getByText('landed-in-studio')).toBeInTheDocument()
+    expect(screen.getByText('landed-in-studio')).toHaveAttribute(
+      'data-location',
+      '/forms/designer/f1?versionId=v1',
+    )
🤖 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 `@src/frontend/src/App.route-redirects.test.tsx` at line 20, Update the route
redirect assertion in the test to verify the destination includes the preserved
versionId query parameter, such as versionId=v1, rather than only matching the
landed-in-studio text. Keep the existing destination assertion while adding an
exact URL or query-string check that fails when the redirect drops versionId.
src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx-326-339 (1)

326-339: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

canValidate is hardcoded true here but gated on canEdit for mobile.

Read-only viewers get an enabled “التحقق” button on desktop/tablet that will likely fail server-side. Consider canValidate={canEdit} (or hasAllowedAction(allowedActions, 'Validate')) for consistency with StudioMobileWorkspace.

🤖 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 `@src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx` around lines
326 - 339, Update the FormDesignerStudioPage validation capability passed to the
studio component so canValidate is restricted to users who can edit, matching
the mobile workspace behavior; replace the hardcoded true value while preserving
the existing validation handler.
src/frontend/src/pages/forms/studio/StudioInspector.tsx-116-116 (1)

116-116: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Substring matching attributes issues to the wrong field.

i.path.includes(field.key) misfires whenever one key is a substring of another (e.g. age matching manager_age), so unrelated validation issues appear under this field. Match on a parsed path segment instead of a raw substring.

🤖 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 `@src/frontend/src/pages/forms/studio/StudioInspector.tsx` at line 116, Update
the fieldIssues filtering in StudioInspector to parse each issue path into
segments and match field.key against an exact segment, rather than using
substring includes. Preserve case-insensitive matching while preventing keys
such as “age” from matching “manager_age”.
src/frontend/src/pages/forms/studio/StudioConflictBanner.tsx-29-31 (1)

29-31: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Compare stays permanently disabled if the server schema fetch fails.

The caller derives isLoadingServerSchema from !conflictServerSchema (FormDesignerStudioPage.tsx line 345) and syncConflictServerSchema swallows fetch errors, so a failed load is indistinguishable from loading and the button never re-enables. Consider an explicit loading/error flag from the caller and a retry affordance.

🤖 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 `@src/frontend/src/pages/forms/studio/StudioConflictBanner.tsx` around lines 29
- 31, Update StudioConflictBanner and its FormDesignerStudioPage caller to track
server-schema loading explicitly rather than deriving isLoadingServerSchema from
!conflictServerSchema. Ensure syncConflictServerSchema exposes or propagates
fetch failure state, allowing the compare button to re-enable after errors, and
add a retry affordance for failed schema loads.
src/frontend/src/pages/forms/studio/StudioCanvas.tsx-216-230 (1)

216-230: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Incomplete ARIA tab pattern in the studio shell. Both tablists declare role="tablist"/role="tab" without an associated role="tabpanel", aria-controls, or arrow-key roving focus, so screen-reader users get a tab widget that doesn't behave like one.

  • src/frontend/src/pages/forms/studio/StudioCanvas.tsx#L216-L230: give the page tabs id/aria-controls pointing at the page content region (marked role="tabpanel") and add arrow-key navigation, or drop the roles and keep aria-pressed buttons.
  • src/frontend/src/pages/forms/studio/StudioSidePanel.tsx#L58-L66: apply the same fix to the library/outline tabs, wiring aria-controls to the panel rendered by resolveSidePanelContent.
🤖 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 `@src/frontend/src/pages/forms/studio/StudioCanvas.tsx` around lines 216 - 230,
The studio’s ARIA tab widgets are incomplete because their tabs lack associated
panels, controls, and keyboard navigation. In
src/frontend/src/pages/forms/studio/StudioCanvas.tsx lines 216-230, either
implement the full tab pattern by adding tab IDs, aria-controls, arrow-key
roving focus, and a role="tabpanel" page content region, or remove tab roles and
use aria-pressed buttons; in
src/frontend/src/pages/forms/studio/StudioSidePanel.tsx lines 58-66, apply the
same chosen approach and wire the library/outline tabs to the panel rendered by
resolveSidePanelContent.
🧹 Nitpick comments (11)
src/frontend/src/forms/designer/versionDiff.ts (1)

18-20: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

stableJson is not actually order-stable.

JSON.stringify preserves each object's own key-insertion order, so two semantically-equal objects built via different code paths (e.g., one deserialized from a server response, another freshly constructed with a different literal property order) can serialize differently and be flagged as "changed" even though nothing meaningfully differs. This affects every stableJson-based comparison here (text/number/file/validationRules settings, options, visibilityCondition, formula, requiredCondition), and would surface as spurious entries in the version-compare UI.

Consider a canonical comparison (recursively sort object keys before stringifying, or use a proper deep-equal utility) instead of raw JSON.stringify.

Since this depends on how before/after schema objects are actually constructed elsewhere (e.g. studioSchemaOps.ts, server DTO serialization), please confirm whether key ordering can actually diverge between the two.

Also applies to: 52-59, 77-79

🤖 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 `@src/frontend/src/forms/designer/versionDiff.ts` around lines 18 - 20, Update
stableJson to canonicalize values before serialization by recursively sorting
object keys, while preserving array order and the existing null handling. Ensure
all stableJson-based comparisons produce identical results for semantically
equal objects with different key insertion orders.
src/frontend/src/pages/forms/FormsListPage.tsx (1)

104-106: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Avoid nesting a button inside the navigation link.

Use the Link itself as the interactive control; nested interactive elements are invalid and may behave inconsistently for keyboard users.

Proposed update
-          <Link to="/forms/designer/new">
-            <button type="button">نموذج جديد (استوديو التصميم)</button>
-          </Link>
+          <Link to="/forms/designer/new">نموذج جديد (استوديو التصميم)</Link>
🤖 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 `@src/frontend/src/pages/forms/FormsListPage.tsx` around lines 104 - 106,
Update the new-form navigation control in FormsListPage to use the Link directly
as the interactive element, removing the nested button while preserving its
destination and displayed label.
src/frontend/src/pages/forms/studio/StudioOutline.tsx (1)

4-18: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Selected field isn't exposed to assistive tech.

Selection is communicated only through a CSS class swap; add aria-current so screen-reader users can tell which field is selected.

♻️ Proposed fix
     <button
       type="button"
       className={isSelected ? 'studio-outline-field' : 'studio-outline-field secondary'}
+      aria-current={isSelected ? 'true' : undefined}
       onClick={onSelect}
     >
🤖 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 `@src/frontend/src/pages/forms/studio/StudioOutline.tsx` around lines 4 - 18,
Update the OutlineField button to expose its selection state through
aria-current, setting it to the selected state when isSelected is true and
leaving it unset or false otherwise, while preserving the existing className and
onSelect behavior.
src/frontend/src/pages/forms/studio/StudioStartFlow.tsx (1)

251-261: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Search fires a request on every keystroke.

queryKey includes the raw search value with no debounce, so once the 2-character threshold is met, every keystroke issues a new api.forms.list call.

♻️ Proposed fix: debounce the search term
+  const [debouncedSearch, setDebouncedSearch] = useState('')
+  useEffect(() => {
+    const id = setTimeout(() => setDebouncedSearch(search), 300)
+    return () => clearTimeout(id)
+  }, [search])
+
   const searchQuery = useQuery({
-    queryKey: ['forms-search', search],
-    queryFn: () => api.forms.list({ search, pageSize: 10 }),
-    enabled: search.trim().length >= 2,
+    queryKey: ['forms-search', debouncedSearch],
+    queryFn: () => api.forms.list({ search: debouncedSearch, pageSize: 10 }),
+    enabled: debouncedSearch.trim().length >= 2,
   })
🤖 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 `@src/frontend/src/pages/forms/studio/StudioStartFlow.tsx` around lines 251 -
261, Debounce the form search term used by the searchQuery in StudioStartFlow,
so api.forms.list is not called for every keystroke once the two-character
threshold is reached. Introduce or reuse a debounced search value, use it in
both queryKey and queryFn, and keep the existing minimum-length enabled
condition applied to the debounced value.
src/frontend/src/index.css (1)

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

Deprecated clip property flagged by stylelint.

♻️ Proposed fix
 .sr-only {
   position: absolute;
   width: 1px;
   height: 1px;
   padding: 0;
   margin: -1px;
   overflow: hidden;
-  clip: rect(0, 0, 0, 0);
+  clip-path: inset(50%);
   white-space: nowrap;
   border: 0;
 }
🤖 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 `@src/frontend/src/index.css` around lines 2200 - 2210, Update the .sr-only
accessibility utility to replace the deprecated clip declaration with the
supported clip-path equivalent, preserving the existing visually-hidden behavior
and stylelint compliance.

Source: Linters/SAST tools

src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx (2)

84-91: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid mutating the previous URLSearchParams and prefer named version statuses.

Mutating and returning prev relies on React Router re-serializing the same instance; a fresh copy is the safer, idiomatic form. Also, status === 0 || status === 2 is opaque — a named constant/enum for draft/changes-requested would read better here and in the rest of the studio.

♻️ Suggested change
-    if (resolved) {
-      setSearchParams((prev) => { prev.set('versionId', resolved.id); return prev }, { replace: true })
-    }
+    if (resolved) {
+      setSearchParams((prev) => {
+        const next = new URLSearchParams(prev)
+        next.set('versionId', resolved.id)
+        return next
+      }, { replace: true })
+    }
🤖 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 `@src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx` around lines
84 - 91, Update the setSearchParams callback in FormDesignerStudioPage to return
a new URLSearchParams copy after setting versionId, rather than mutating and
returning prev. Replace the numeric status checks in the editable-version
selection with the existing named constants or enum values for draft and
changes-requested statuses, reusing those symbols consistently in the studio.

234-239: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

isSavingAsNewVersion duplicates saveAsNewVersionMutation.isPending.

The extra state (plus onMutate/onSettled) can be dropped in favour of the mutation's own pending flag passed to StudioConflictBanner.

🤖 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 `@src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx` around lines
234 - 239, Remove the redundant isSavingAsNewVersion state and its
onMutate/onSettled handlers from saveAsNewVersionMutation. Use
saveAsNewVersionMutation.isPending directly when passing the saving status to
StudioConflictBanner, preserving the existing mutation and success navigation
behavior.
src/frontend/src/pages/forms/studio/StudioInspector.tsx (1)

46-54: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Effect keyed on the selectedField object identity resets in-progress drafts.

selectedField is a fresh object after any schema change (e.g. toggling “إلزامي”), so unrelated edits re-run this effect and overwrite drafts the user is still typing. Depending on selectedField?.id would scope the reset to actual selection changes.

🤖 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 `@src/frontend/src/pages/forms/studio/StudioInspector.tsx` around lines 46 -
54, Update the draft-reset useEffect associated with selectedField to depend on
selectedField?.id instead of the selectedField object identity, so schema
updates do not overwrite in-progress edits while switching fields still resets
all draft values and errors.
src/frontend/src/pages/forms/studio/StudioCanvas.tsx (1)

91-135: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

FieldRow reimplements InlineEditableText.

The label draft/commit/Escape logic duplicates the component defined above at lines 16-62; reuse it (passing the trigger label) instead of a second copy.

🤖 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 `@src/frontend/src/pages/forms/studio/StudioCanvas.tsx` around lines 91 - 135,
Replace the duplicated label editing state and commit/Escape handling in
FieldRow with the existing InlineEditableText component defined above. Pass
field.labelAr as the displayed value and trigger label, and connect its save
callback to onRenameLabel while preserving trimming and unchanged-value
behavior.
src/frontend/src/pages/forms/studio/FormDesignerStudioPage.test.tsx (1)

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

validateVersion is mocked but never exercised.

Consider a case covering the validate → ValidationPanel path (and a read-only version where canEdit is false), which are the branches most likely to regress.

Also applies to: 152-192

🤖 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 `@src/frontend/src/pages/forms/studio/FormDesignerStudioPage.test.tsx` at line
26, Add tests in FormDesignerStudioPage.test.tsx that exercise the mocked
validateVersion flow through ValidationPanel, including the read-only
canEdit=false case. Assert validation is triggered and the resulting panel or
read-only behavior is rendered, while preserving existing editable-path
coverage.
src/frontend/src/pages/forms/studio/StudioFieldLibrary.tsx (1)

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

aria-label on a role-less div is ignored by assistive tech.

Use a landmark/region element so the label is exposed, e.g. <section aria-label="مكتبة الحقول"> (or add role="group").

🤖 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 `@src/frontend/src/pages/forms/studio/StudioFieldLibrary.tsx` at line 16,
Update the role-less container in StudioFieldLibrary to use a semantic section
or an explicit group/region role so the existing Arabic aria-label is exposed to
assistive technologies, while preserving the current styling class and contents.
🤖 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 `@docs/permissions-matrix.md`:
- Line 417: Update the Forms.ManageTemplates row in the permissions matrix to
align with CreateFormFromTemplateAsync: clearly state that template-based form
creation requires both Forms.ManageTemplates and Forms.Create, and remove the
contradictory claim that Forms.ManageTemplates is unnecessary.

In `@docs/ux-rescue/phase2a-form-designer-autosave.md`:
- Around line 23-25: Correct the autosave concurrency documentation for
`inFlightRef` and `abortRef`: do not claim that debounced saves are serialized
or that aborting prevents the server from processing an outdated request. Either
update the hook’s debounced `saveNow` flow to await the existing `inFlightRef`
before sending the next save, or revise the documentation to describe possible
concurrent writes, 409 responses, and retry handling accurately.

In `@docs/ux-rescue/phase2a-form-designer-compliance-ledger.md`:
- Line 214: Revise the risk statement in the compliance ledger to classify
published option-key changes as a data-integrity risk, consistent with the
migration-policy gap recorded on line 73. Alternatively, add an explicit
migration or immutability safeguard for keys used by published schemas, then
retain the existing statement only if that safeguard is documented.
- Line 105: Update row 55 in the compliance ledger from Verified to Partial
because previewLogic.ts and the server evaluators are separate implementations.
Describe the outcome as sharing the same tested rules rather than the same
engine, unless the implementation adds a shared evaluator or automated parity
contract.

In `@docs/ux-rescue/phase2a-form-designer-performance.md`:
- Line 5: Update the “لا N+1 عند تحميل الاستوديو” statement to describe exactly
two API requests, not exactly two database or EF Core queries. Remove the
unsupported claim that these requests prove query count or absence of N+1
behavior, and avoid asserting query-count verification until the
interceptor-based test referenced later exists.

In `@src/frontend/src/forms/designer/ConditionBuilder.tsx`:
- Around line 172-193: Replace the value-derived keys in the predicate and
nested-group renderers with stable array-index keys, using the existing map
indices in the top-level and nested collections. Update the key on the predicate
row and the key generated by conditionGroupKey for nested groups; preserve the
existing append and filter-removal behavior.

In `@src/frontend/src/forms/designer/FormulaBuilder.tsx`:
- Around line 41-43: Update formulaNodeKey and the function-argument rendering
in FormulaNodeEditor to use each argument’s stable array index as its React key
instead of JSON.stringify(node). Preserve the controlled input behavior while
ensuring keys remain unchanged during edits and are unique for duplicate
newly-created arguments.

In `@src/frontend/src/forms/designer/studioSchemaOps.ts`:
- Around line 3-14: The cloning flow must remap internal field-key references
when duplicating groups. Update cloneWithNewIds and the
duplicateSection/duplicatePage paths to build an old-key-to-new-key map for all
copied fields, then rewrite visibilityCondition, requiredCondition, and formula
fieldReference nodes targeting keys in that map; leave duplicateField behavior
unchanged because its siblings are not copied.

In `@src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx`:
- Around line 268-274: Reorder the guards in FormDesignerStudioPage so the
versionQuery.isError branch runs before the loading condition that checks
!history and !schema. Preserve the existing formatted API error response and
loading behavior for non-error states.
- Around line 97-110: Handle versionsQuery failures and the
no-version/no-permission state in FormDesignerStudioPage before the existing
version-selection spinner: render an alert using formatApiError for
versionsQuery.isError, and render a non-loading terminal message or error state
when versionsQuery.data is empty and canDesign is false. Preserve
CreateFirstVersion for empty versions when canDesign is true and keep the normal
version-selection flow for available versions.

In `@src/frontend/src/pages/forms/studio/StudioCanvas.tsx`:
- Around line 273-291: Update the field rendering in StudioCanvas so it passes a
lazy resolveDependents callback to FieldRow instead of eagerly evaluating
fieldDependents(field.key) for every row. Update FieldRow to invoke
resolveDependents only while confirmingDelete is true, preserving the existing
dependent data used by delete confirmation.

In `@src/frontend/src/pages/forms/studio/StudioInspector.tsx`:
- Around line 315-319: Update the options mapping in the choice editor to use a
stable key instead of option.value, so editing the “مفتاح الخيار” input does not
remount the row or lose focus. Modify the key on the mapped div while preserving
the existing updateOption behavior and validation display.

In `@src/frontend/src/pages/forms/studio/StudioMobileReview.tsx`:
- Around line 5-21: Synchronize the local draft state with updated source values
in both MobileReviewPageTitleField and MobileReviewFieldLabelField: add an
effect that updates draft when titleAr or labelAr changes, respectively.
Preserve the existing editing and trimmed onBlur commit behavior.

In `@src/frontend/src/pages/forms/studio/studioWorkspaceHelpers.ts`:
- Around line 39-47: The JSON parsing fallback logic is duplicated across the
studio flow. Keep parseSchema in
src/frontend/src/pages/forms/studio/studioWorkspaceHelpers.ts:39-47 as the
single exported implementation, and in
src/frontend/src/pages/forms/studio/StudioStartFlow.tsx:29-36 remove the local
copy and import parseSchema from './studioWorkspaceHelpers'.

---

Minor comments:
In `@docs/ux-rescue/phase2a-form-designer-completion-report.md`:
- Around line 26-27: Correct the inventory counts in the completion report:
update the studio component count to 10 and the designer module/component count
to 10, while preserving the listed names.

In `@docs/ux-rescue/phase2a-form-designer-compliance-ledger.md`:
- Line 98: Update the formula-operations verification entry in the compliance
ledger to count “Max” among the supported verified operations, changing
“Verified للستة الأولى” to “Verified للسبعة الأولى” unless the schema contract
confirms a narrower intended subset.
- Line 90: The compliance table rows at the entries labeled 40, 164, 203, and
205 have only three cells instead of the required four. Update each row to
include a separate fourth evidence cell, preserving the existing status and
evidence content while ensuring every row matches the table’s column structure.

In `@src/frontend/src/App.route-redirects.test.tsx`:
- Line 20: Update the route redirect assertion in the test to verify the
destination includes the preserved versionId query parameter, such as
versionId=v1, rather than only matching the landed-in-studio text. Keep the
existing destination assertion while adding an exact URL or query-string check
that fails when the redirect drops versionId.

In `@src/frontend/src/forms/designer/ConditionBuilder.test.tsx`:
- Around line 8-24: Add a field keyed "self_field" to the FIELDS fixture used by
Harness, with valid field metadata, while keeping Harness.excludeFieldKey set to
"self_field". Ensure the self-reference exclusion test can observe and verify
that this candidate is omitted rather than passing trivially.

In `@src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx`:
- Around line 326-339: Update the FormDesignerStudioPage validation capability
passed to the studio component so canValidate is restricted to users who can
edit, matching the mobile workspace behavior; replace the hardcoded true value
while preserving the existing validation handler.

In `@src/frontend/src/pages/forms/studio/StudioCanvas.tsx`:
- Around line 216-230: The studio’s ARIA tab widgets are incomplete because
their tabs lack associated panels, controls, and keyboard navigation. In
src/frontend/src/pages/forms/studio/StudioCanvas.tsx lines 216-230, either
implement the full tab pattern by adding tab IDs, aria-controls, arrow-key
roving focus, and a role="tabpanel" page content region, or remove tab roles and
use aria-pressed buttons; in
src/frontend/src/pages/forms/studio/StudioSidePanel.tsx lines 58-66, apply the
same chosen approach and wire the library/outline tabs to the panel rendered by
resolveSidePanelContent.

In `@src/frontend/src/pages/forms/studio/StudioConflictBanner.tsx`:
- Around line 29-31: Update StudioConflictBanner and its FormDesignerStudioPage
caller to track server-schema loading explicitly rather than deriving
isLoadingServerSchema from !conflictServerSchema. Ensure
syncConflictServerSchema exposes or propagates fetch failure state, allowing the
compare button to re-enable after errors, and add a retry affordance for failed
schema loads.

In `@src/frontend/src/pages/forms/studio/StudioInspector.tsx`:
- Line 116: Update the fieldIssues filtering in StudioInspector to parse each
issue path into segments and match field.key against an exact segment, rather
than using substring includes. Preserve case-insensitive matching while
preventing keys such as “age” from matching “manager_age”.

---

Nitpick comments:
In `@src/frontend/src/forms/designer/versionDiff.ts`:
- Around line 18-20: Update stableJson to canonicalize values before
serialization by recursively sorting object keys, while preserving array order
and the existing null handling. Ensure all stableJson-based comparisons produce
identical results for semantically equal objects with different key insertion
orders.

In `@src/frontend/src/index.css`:
- Around line 2200-2210: Update the .sr-only accessibility utility to replace
the deprecated clip declaration with the supported clip-path equivalent,
preserving the existing visually-hidden behavior and stylelint compliance.

In `@src/frontend/src/pages/forms/FormsListPage.tsx`:
- Around line 104-106: Update the new-form navigation control in FormsListPage
to use the Link directly as the interactive element, removing the nested button
while preserving its destination and displayed label.

In `@src/frontend/src/pages/forms/studio/FormDesignerStudioPage.test.tsx`:
- Line 26: Add tests in FormDesignerStudioPage.test.tsx that exercise the mocked
validateVersion flow through ValidationPanel, including the read-only
canEdit=false case. Assert validation is triggered and the resulting panel or
read-only behavior is rendered, while preserving existing editable-path
coverage.

In `@src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx`:
- Around line 84-91: Update the setSearchParams callback in
FormDesignerStudioPage to return a new URLSearchParams copy after setting
versionId, rather than mutating and returning prev. Replace the numeric status
checks in the editable-version selection with the existing named constants or
enum values for draft and changes-requested statuses, reusing those symbols
consistently in the studio.
- Around line 234-239: Remove the redundant isSavingAsNewVersion state and its
onMutate/onSettled handlers from saveAsNewVersionMutation. Use
saveAsNewVersionMutation.isPending directly when passing the saving status to
StudioConflictBanner, preserving the existing mutation and success navigation
behavior.

In `@src/frontend/src/pages/forms/studio/StudioCanvas.tsx`:
- Around line 91-135: Replace the duplicated label editing state and
commit/Escape handling in FieldRow with the existing InlineEditableText
component defined above. Pass field.labelAr as the displayed value and trigger
label, and connect its save callback to onRenameLabel while preserving trimming
and unchanged-value behavior.

In `@src/frontend/src/pages/forms/studio/StudioFieldLibrary.tsx`:
- Line 16: Update the role-less container in StudioFieldLibrary to use a
semantic section or an explicit group/region role so the existing Arabic
aria-label is exposed to assistive technologies, while preserving the current
styling class and contents.

In `@src/frontend/src/pages/forms/studio/StudioInspector.tsx`:
- Around line 46-54: Update the draft-reset useEffect associated with
selectedField to depend on selectedField?.id instead of the selectedField object
identity, so schema updates do not overwrite in-progress edits while switching
fields still resets all draft values and errors.

In `@src/frontend/src/pages/forms/studio/StudioOutline.tsx`:
- Around line 4-18: Update the OutlineField button to expose its selection state
through aria-current, setting it to the selected state when isSelected is true
and leaving it unset or false otherwise, while preserving the existing className
and onSelect behavior.

In `@src/frontend/src/pages/forms/studio/StudioStartFlow.tsx`:
- Around line 251-261: Debounce the form search term used by the searchQuery in
StudioStartFlow, so api.forms.list is not called for every keystroke once the
two-character threshold is reached. Introduce or reuse a debounced search value,
use it in both queryKey and queryFn, and keep the existing minimum-length
enabled condition applied to the debounced value.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 77d8107e-919e-4b57-9afe-3fe0aaf76e6b

📥 Commits

Reviewing files that changed from the base of the PR and between 7adc122 and cc6aeb6.

📒 Files selected for processing (72)
  • README.md
  • docs/implementation-plan.md
  • docs/permissions-matrix.md
  • docs/ux-rescue/phase2a-form-designer-accessibility.md
  • docs/ux-rescue/phase2a-form-designer-architecture.md
  • docs/ux-rescue/phase2a-form-designer-autosave.md
  • docs/ux-rescue/phase2a-form-designer-completion-report.md
  • docs/ux-rescue/phase2a-form-designer-compliance-ledger.md
  • docs/ux-rescue/phase2a-form-designer-performance.md
  • docs/ux-rescue/phase2a-form-designer-route-transition.md
  • docs/ux-rescue/phase2a-form-designer-schema-contract.md
  • docs/ux-rescue/phase2a-form-designer-scope.md
  • docs/ux-rescue/phase2a-form-designer-test-matrix.md
  • docs/ux-rescue/phase2a-form-designer-validation.md
  • docs/ux-rescue/rescue-roadmap.md
  • docs/ux-rescue/screen-and-route-inventory.md
  • docs/ux-rescue/task-metrics-baseline.md
  • src/backend/Baseera.Api/Endpoints/ApiEndpoints.cs
  • src/backend/Baseera.Application/DependencyInjection/ApplicationServiceCollectionExtensions.cs
  • src/backend/Baseera.Application/Forms/FormTemplateService.cs
  • src/backend/Baseera.Application/Forms/FormVersionAccessGuard.cs
  • src/backend/Baseera.Application/Forms/FormVersionDtos.cs
  • src/backend/Baseera.Application/Forms/FormVersionService.cs
  • src/backend/tests/Baseera.IntegrationTests/FormsVersionIntegrationTests.cs
  • src/backend/tests/Baseera.UnitTests/Forms/Versions/FormVersionAccessGuardTests.cs
  • src/frontend/src/App.route-redirects.test.tsx
  • src/frontend/src/App.tsx
  • src/frontend/src/api/client.ts
  • src/frontend/src/forms/designer/ConditionBuilder.test.tsx
  • src/frontend/src/forms/designer/ConditionBuilder.tsx
  • src/frontend/src/forms/designer/DesignerCanvas.tsx
  • src/frontend/src/forms/designer/DesignerPalette.tsx
  • src/frontend/src/forms/designer/DesignerPropertiesPanel.tsx
  • src/frontend/src/forms/designer/DesignerToolbar.tsx
  • src/frontend/src/forms/designer/FormulaBuilder.test.tsx
  • src/frontend/src/forms/designer/FormulaBuilder.tsx
  • src/frontend/src/forms/designer/ValidationPanel.test.tsx
  • src/frontend/src/forms/designer/ValidationPanel.tsx
  • src/frontend/src/forms/designer/VersionCompare.tsx
  • src/frontend/src/forms/designer/fieldDependencies.test.ts
  • src/frontend/src/forms/designer/fieldDependencies.ts
  • src/frontend/src/forms/designer/fieldLibrary.ts
  • src/frontend/src/forms/designer/studioSchemaOps.ts
  • src/frontend/src/forms/designer/useResponsiveStudioLayout.ts
  • src/frontend/src/forms/designer/useUnsavedChangesGuard.test.ts
  • src/frontend/src/forms/designer/useUnsavedChangesGuard.ts
  • src/frontend/src/forms/designer/versionDiff.test.ts
  • src/frontend/src/forms/designer/versionDiff.ts
  • src/frontend/src/index.css
  • src/frontend/src/pages/form-campaigns/FormCampaignWizardPage.tsx
  • src/frontend/src/pages/forms/FormsListPage.tsx
  • src/frontend/src/pages/forms/studio/FormDesignerStudioPage.test.tsx
  • src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx
  • src/frontend/src/pages/forms/studio/StudioCanvas.tsx
  • src/frontend/src/pages/forms/studio/StudioConflictBanner.tsx
  • src/frontend/src/pages/forms/studio/StudioFieldLibrary.tsx
  • src/frontend/src/pages/forms/studio/StudioInspector.tsx
  • src/frontend/src/pages/forms/studio/StudioMobileReview.tsx
  • src/frontend/src/pages/forms/studio/StudioMobileWorkspace.tsx
  • src/frontend/src/pages/forms/studio/StudioOutline.tsx
  • src/frontend/src/pages/forms/studio/StudioReviewPanel.tsx
  • src/frontend/src/pages/forms/studio/StudioSidePanel.tsx
  • src/frontend/src/pages/forms/studio/StudioStartFlow.tsx
  • src/frontend/src/pages/forms/studio/StudioTopBar.tsx
  • src/frontend/src/pages/forms/studio/studioWorkspaceHelpers.ts
  • src/frontend/src/pages/forms/studio/useStudioFieldCommands.ts
  • src/frontend/src/pages/forms/templates/FormTemplatesPage.tsx
  • src/frontend/src/pages/forms/versions/FormDesignerPage.test.tsx
  • src/frontend/src/pages/forms/versions/FormDesignerPage.tsx
  • src/frontend/src/pages/forms/versions/FormVersionComparePage.tsx
  • src/frontend/src/pages/forms/versions/FormVersionDetailPage.tsx
  • src/frontend/src/pages/forms/versions/FormVersionsPage.tsx
💤 Files with no reviewable changes (6)
  • src/frontend/src/forms/designer/DesignerToolbar.tsx
  • src/frontend/src/forms/designer/DesignerCanvas.tsx
  • src/frontend/src/pages/forms/versions/FormDesignerPage.test.tsx
  • src/frontend/src/forms/designer/DesignerPalette.tsx
  • src/frontend/src/pages/forms/versions/FormDesignerPage.tsx
  • src/frontend/src/forms/designer/DesignerPropertiesPanel.tsx

Comment thread docs/permissions-matrix.md Outdated
Comment thread docs/ux-rescue/phase2a-form-designer-autosave.md Outdated
Comment thread docs/ux-rescue/phase2a-form-designer-compliance-ledger.md Outdated
Comment thread docs/ux-rescue/phase2a-form-designer-compliance-ledger.md Outdated
Comment thread docs/ux-rescue/phase2a-form-designer-performance.md Outdated
Comment thread src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx
Comment thread src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx Outdated
Comment thread src/frontend/src/pages/forms/studio/StudioCanvas.tsx
Comment thread src/frontend/src/pages/forms/studio/StudioMobileReview.tsx
Comment thread src/frontend/src/pages/forms/studio/studioWorkspaceHelpers.ts
henter36 and others added 5 commits July 29, 2026 09:18
SonarCloud re-analysis of the previous commits surfaced two new
findings from the refactor itself:

- FormVersionService.ValidateAsync: 'form' was assigned from
  LoadViewableAsync but never used afterward (the old code needed it
  to call the now-inlined view-capability check separately) — S1481,
  unused local variable. Stopped capturing it.
- StudioWorkspace was still Cognitive Complexity 19 (down from 30, but
  above the 15 limit) — the desktop/tablet JSX tree's many small
  ternaries and && checks (preview toggle, tablet panel toggles,
  undo/redo availability, conflict banner, validation/review panel
  visibility) were still counted in the orchestrator. Extracted
  StudioDesktopWorkspace (+ its StudioDesktopEditor sub-component) the
  same way StudioMobileWorkspace already was, so that render tree's
  complexity is attributed to it instead. StudioWorkspace now only
  wires up state/hooks and picks mobile vs. desktop.

Also moved classifyIssues() out of StudioWorkspace entirely — both
StudioMobileWorkspace and StudioDesktopWorkspace now compute
errors/warnings themselves from the raw issues list, removing that
call (and its complexity contribution) from the orchestrator too.

No behavior change: same props, same hook order, same guards.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CodeRabbit flagged that the earlier Sonar remediation pass introduced a
regression: ConditionBuilder/FormulaBuilder/StudioInspector switched
array-index keys to content-derived keys (JSON.stringify / option.value) to
satisfy a lint rule, but those lists hold editable text inputs — a key that
changes on every keystroke forces React to remount the row and drop focus,
so a user could not type more than one character into a predicate value,
formula argument, or choice-option field. Revert to index keys, which are
safe here (append/filter-only or swap-based reorder, no per-row local
state), and add regression tests that type into each affected input and
assert focus is retained.

Also fixes a related regression in StudioMobileReview: the page-title/
field-label draft inputs seeded local state once and never resynced when
the underlying schema changed externally (e.g. after a conflict reload),
so a blur could commit stale text over freshly reloaded data.
…ions/pages

duplicateSection/duplicatePage assign every duplicated field a new key
(cloneWithNewIds) but left visibilityCondition/requiredCondition predicates
and formula fieldReference nodes pointing at the original keys, so a
condition or formula referencing a sibling field inside the duplicated
group silently kept referencing the untouched original after duplication
instead of its own copy. Build an old-key -> new-key map up front for the
group being duplicated (including nested repeating-table columns) and
rewrite condition/formula references through it; duplicateField gets the
same treatment for its own repeating-table columns, since those undergo
the same key-changing clone. References to fields outside the duplicated
group are left untouched, matching existing behavior.
FormDesignerStudioPage never checked versionsQuery.isError, so a failed
version-list request (or zero versions with no create permission) left the
user on a permanent "جاري تحديد الإصدار…" spinner with no way out. Handle
both cases explicitly with an error message.

StudioCanvas called fieldDependents(field.key) — a full-schema scan — for
every field on every render, even though the result is only shown once a
row enters delete-confirmation. Compute it lazily inside FieldRow, gated on
confirmingDelete, instead of eagerly for the whole list.

StudioStartFlow had its own copy of the JSON-parse-with-fallback schema
logic already exported from studioWorkspaceHelpers; import the shared one
instead of maintaining two copies that can drift.
Five accuracy fixes raised in review, each a doc overclaiming a guarantee
the code doesn't actually provide:

- permissions-matrix.md: "using a template doesn't require
  Forms.ManageTemplates" contradicted CreateFormFromTemplateAsync, which
  checks both Forms.ManageTemplates and Forms.Create.
- autosave.md: the debounced autosave path doesn't await inFlightRef
  (only flush() does) and abortRef only cancels the client-side request —
  the server may still process an "aborted" save, so parallel saves are
  not strictly prevented, just resolved via an occasional 409 rather than
  silent data loss.
- compliance-ledger.md #55: preview and runtime use separate evaluator
  implementations tested for parity manually, not literally "the same
  engine" with an automated parity contract — downgraded to Partial.
- compliance-ledger.md summary: one of the eight "Missing" items (no
  migration policy for renaming a published option key) is a real data-
  integrity risk, not just a UX/test/perf gap — called out explicitly.
- performance.md: "no N+1" was verified at the HTTP-request level (two
  requests, no per-field loop), not at the EF Core query level, which
  was never measured.

@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

Caution

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

⚠️ Outside diff range comments (4)
src/frontend/src/pages/forms/studio/StudioInspector.tsx (4)

297-300: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Generate collision-free option values.

choice.options.length + 1 can reuse an existing value after deletion. For example, removing option_1 from [option_1, option_2] and adding an option creates a second option_2, producing an invalid choice schema.

🤖 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 `@src/frontend/src/pages/forms/studio/StudioInspector.tsx` around lines 297 -
300, Update addOption to derive the new option value from existing option values
rather than choice.options.length + 1, selecting the next collision-free
option_N identifier after deletions. Preserve the current label, order, and
active-state behavior.

116-116: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Match validation paths by field segments, not substrings.

includes(field.key) misattributes issues when one key is contained in another, such as name and surname. Parse the path or compare the exact field-key segment.

🤖 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 `@src/frontend/src/pages/forms/studio/StudioInspector.tsx` at line 116, Update
the fieldIssues filter in StudioInspector to match field.key against an exact
segment of each validation path rather than using substring includes, preventing
keys such as name from matching surname. Parse or split each path using the
established path format, compare the relevant segment exactly, and preserve
case-insensitive matching.

21-25: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid committing twice when Enter is pressed.

blur() invokes the input’s onBlur commit, then commit() runs again immediately. This can create duplicate schema/history updates and duplicate autosave work. Use either the blur path or the explicit commit, not both.

🤖 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 `@src/frontend/src/pages/forms/studio/StudioInspector.tsx` around lines 21 -
25, Update commitOnEnter so pressing Enter triggers only one commit: either rely
on event.currentTarget.blur() and remove the explicit commit() call, or commit
explicitly without blurring. Preserve the existing Enter-key guard and avoid
duplicate onBlur/schema history updates.

46-54: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not reset drafts on every selected-field object change.

Any schema edit creates a new selectedField object; while the user is typing an unblurred label, key, description, or default value, changing another inspector control can overwrite that draft with the previous schema value. Reset on field identity changes or preserve dirty draft state.

🤖 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 `@src/frontend/src/pages/forms/studio/StudioInspector.tsx` around lines 46 -
54, Update the selectedField synchronization effect in StudioInspector so drafts
are reset only when the selected field identity changes, not whenever the
selectedField object is recreated by schema edits. Preserve unblurred user edits
to the label, key, description, and default value while other inspector controls
modify the same field, while still initializing drafts for a newly selected
field.
🤖 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 `@docs/ux-rescue/phase2a-form-designer-compliance-ledger.md`:
- Line 105: Update the ledger entry for preview/runtime parity to remove the
claim that the cited tests are manual. State that the separate preview and
runtime implementations have automated test coverage, while no automated
cross-engine parity contract exists; mention manual comparison only if it is
explicitly documented elsewhere.

In `@src/frontend/src/pages/forms/studio/StudioInspector.tsx`:
- Around line 315-319: Add an editor-only stable ID to each option in the
StudioInspector editor state, preserve it across reorder and removal operations,
and assign one when options are created or loaded. Update the choice.options
rendering to use that ID for the React key and keyErrors lookup, and adjust
duplicate-key validation/update logic to track errors by stable option ID rather
than array index.

---

Outside diff comments:
In `@src/frontend/src/pages/forms/studio/StudioInspector.tsx`:
- Around line 297-300: Update addOption to derive the new option value from
existing option values rather than choice.options.length + 1, selecting the next
collision-free option_N identifier after deletions. Preserve the current label,
order, and active-state behavior.
- Line 116: Update the fieldIssues filter in StudioInspector to match field.key
against an exact segment of each validation path rather than using substring
includes, preventing keys such as name from matching surname. Parse or split
each path using the established path format, compare the relevant segment
exactly, and preserve case-insensitive matching.
- Around line 21-25: Update commitOnEnter so pressing Enter triggers only one
commit: either rely on event.currentTarget.blur() and remove the explicit
commit() call, or commit explicitly without blurring. Preserve the existing
Enter-key guard and avoid duplicate onBlur/schema history updates.
- Around line 46-54: Update the selectedField synchronization effect in
StudioInspector so drafts are reset only when the selected field identity
changes, not whenever the selectedField object is recreated by schema edits.
Preserve unblurred user edits to the label, key, description, and default value
while other inspector controls modify the same field, while still initializing
drafts for a newly selected field.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 81df7ddb-6c8c-4475-957d-0c8946a0537a

📥 Commits

Reviewing files that changed from the base of the PR and between cc6aeb6 and b9299ea.

📒 Files selected for processing (21)
  • docs/permissions-matrix.md
  • docs/ux-rescue/phase2a-form-designer-autosave.md
  • docs/ux-rescue/phase2a-form-designer-compliance-ledger.md
  • docs/ux-rescue/phase2a-form-designer-performance.md
  • src/backend/Baseera.Application/Forms/FormVersionService.cs
  • src/frontend/src/forms/designer/ConditionBuilder.test.tsx
  • src/frontend/src/forms/designer/ConditionBuilder.tsx
  • src/frontend/src/forms/designer/FormulaBuilder.test.tsx
  • src/frontend/src/forms/designer/FormulaBuilder.tsx
  • src/frontend/src/forms/designer/studioSchemaOps.test.ts
  • src/frontend/src/forms/designer/studioSchemaOps.ts
  • src/frontend/src/pages/forms/studio/FormDesignerStudioPage.test.tsx
  • src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx
  • src/frontend/src/pages/forms/studio/StudioCanvas.tsx
  • src/frontend/src/pages/forms/studio/StudioDesktopWorkspace.tsx
  • src/frontend/src/pages/forms/studio/StudioInspector.test.tsx
  • src/frontend/src/pages/forms/studio/StudioInspector.tsx
  • src/frontend/src/pages/forms/studio/StudioMobileReview.test.tsx
  • src/frontend/src/pages/forms/studio/StudioMobileReview.tsx
  • src/frontend/src/pages/forms/studio/StudioMobileWorkspace.tsx
  • src/frontend/src/pages/forms/studio/StudioStartFlow.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/frontend/src/forms/designer/FormulaBuilder.test.tsx
  • src/frontend/src/forms/designer/ConditionBuilder.test.tsx
  • src/frontend/src/pages/forms/studio/StudioMobileWorkspace.tsx
  • src/frontend/src/pages/forms/studio/StudioCanvas.tsx
  • src/frontend/src/pages/forms/studio/FormDesignerStudioPage.tsx
  • src/frontend/src/pages/forms/studio/StudioStartFlow.tsx
  • src/backend/Baseera.Application/Forms/FormVersionService.cs
  • src/frontend/src/pages/forms/studio/StudioMobileReview.tsx
  • src/frontend/src/forms/designer/studioSchemaOps.ts

Comment thread docs/ux-rescue/phase2a-form-designer-compliance-ledger.md Outdated
Comment thread src/frontend/src/pages/forms/studio/StudioInspector.tsx Outdated
henter36 added 3 commits July 29, 2026 10:07
…re correct

Reverting to array-index keys (previous commit) to fix the CodeRabbit-caught
focus-loss regression brought SonarCloud's "no array index in keys" rule
(S6479) back on these exact 4 lines. It's a false positive in each case:
every affected list only supports append and filter-based removal (or, for
StudioInspector's options, an index-preserving swap), holds no per-row local
component state, and a content-derived key was the thing that actually broke
user-facing behavior. Suppress with NOSONAR plus an explanation pointing at
the regression test that would catch a re-break, rather than reintroducing
the bug to satisfy the linter.
CodeRabbit's second look at StudioInspector's choice-option list found that
index-as-key (the previous commit's fix for the focus-loss bug) still has a
correctness problem: keyErrors is also indexed by array position, so
removing or reordering an option can leave a duplicate-key error message
attached to the wrong row once positions shift. Fix it properly instead of
re-suppressing the linter: generate a stable id per option at creation time
(not derived from editable content, so it doesn't cause the earlier focus
bug either), keep it in lockstep with add/remove/move, and key both the row
and its error message off that id. Reset the id set when the selected field
itself changes. Drops the now-unneeded NOSONAR suppression on this file —
the key is genuinely stable, not index-based, so S6479 no longer applies.

Also fixes wording in phase2a-form-designer-compliance-ledger.md #55: the
referenced *EvaluatorTests.cs files are automated test suites, not a manual
comparison — reworded to "separate automated coverage, no automated parity
contract" instead of implying a manual check was performed.
A separate CodeRabbit finding on StudioWorkspace's own versionQuery (not the
version-list query already fixed): the isLoading/!history/!schema guard ran
before the isError check, but history is only seeded from a successful
query, so on failure !history stays permanently true and the earlier guard
always wins — the isError branch was dead code and a failed version load
showed an endless spinner instead of the error message. Swap the order.
@sonarqubecloud

Copy link
Copy Markdown

@henter36
henter36 merged commit 22fa746 into main Jul 29, 2026
11 checks passed
@henter36
henter36 deleted the ux-rescue-phase2a-unified-form-designer-studio branch July 29, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[UX Rescue] Rebuild the Form Designer into a guided, low-complexity authoring studio

1 participant