Skip to content

fix(edit-content): resolve UI inconsistencies after Lara Theme migration#35319

Open
oidacra wants to merge 15 commits intomainfrom
issue-35009-ux-edit-content-form-ui-inconsistencies-after-lara
Open

fix(edit-content): resolve UI inconsistencies after Lara Theme migration#35319
oidacra wants to merge 15 commits intomainfrom
issue-35009-ux-edit-content-form-ui-inconsistencies-after-lara

Conversation

@oidacra
Copy link
Copy Markdown
Member

@oidacra oidacra commented Apr 14, 2026

Summary

  • Fix Edit Content form UI inconsistencies introduced after the Lara Theme migration: tags field width, action button double-click, block editor borders, tab bottom borders, binary field border-radius, and focus/hover/error states across custom fields
  • Align all custom field components (WYSIWYG, block editor, calendar, category, file, relationship, JSON, monaco editor) with PrimeNG Lara Theme design tokens
  • Gate sidebar API calls behind isSidebarOpen to avoid unnecessary network requests when the sidebar is collapsed
  • Fix WYSIWYG focus ring not rendering (Chrome does not propagate :focus from TinyMCE's iframe)
  • Hide TinyMCE branding badge exposed after library upgrade

Closes #35009

Changes

File Change
tag-field.component.html Add [fluid]="true" to <p-autoComplete> for 100% width
dot-block-editor.component.css Replace hardcoded Tailwind outline classes with --p-inputtext-* tokens for border, hover, focus, error, and border-radius; exclude error state from hover/focus-within rules so invalid border is never masked
dot-edit-content-binary-field.component.scss Replace $border-radius-md with var(--p-inputtext-border-radius)
dot-edit-content-form.component.html Wire [loading] and [disabled] bindings on workflow action buttons; add [pt]="tabsPt" to tabs
dot-edit-content-form.component.ts Add tabsPt passthrough config with bottom border for main content tabs
dot-edit-content.layout.component.html Render sidebar synchronously with inert/aria-hidden toggling
dot-edit-content-sidebar.component.html Sidebar template adjustments for always-mounted pattern
dot-edit-content-sidebar.component.ts Gate getReferencePages/loadActivities effect on isSidebarOpen() signal; add softer sidebar shadow; hide shadow when collapsed
calendar-field.component.scss + .ts New SCSS with Lara focus ring on <p-datepicker> wrapper enclosing input + icon button
dot-category-field.component.{html,scss,ts} Lara border/hover/focus tokens on category field
dot-file-field.component.{html,scss} Lara border tokens on file field
dot-relationship-field.component.{html,scss,ts} Lara border tokens on relationship field
dot-edit-content-json-field.component.html Wrapper class for consistent JSON field styling
dot-edit-content-monaco-editor-control.component.scss Lara focus/hover/error tokens on Monaco editor
dot-wysiwyg-tinymce.component.{ts,html,scss} Replace :focus-within (broken on iframes) with JS-driven --focused class via TinyMCE focus/blur events; scope all ::ng-deep inside :host; hide .tox-statusbar__branding badge

Acceptance Criteria

  • Tags field input renders at 100% width, matching all other form fields
  • Action buttons are disabled immediately after click with a loading spinner; only one action fires on rapid clicks
  • After operation completes (success or error), all action buttons re-enable
  • Split-button dropdown is also disabled during the operation
  • Block Editor field border matches standard inputs in default, hover, focus, and error states
  • Block Editor error state (red border) is preserved on hover and focus — never masked by the hover/focus color
  • Block Editor border-radius matches standard input text
  • No hardcoded colors, radii, or shadows on the Block Editor wrapper — all values from Lara tokens
  • Main content tabs have a bottom border line visually connecting them to the content area
  • Sidebar tabs have a consistent bottom border line
  • Binary field containers use PrimeNG design token for border-radius
  • WYSIWYG (TinyMCE) field shows the same focus ring (border + shadow glow) as the JSON (Monaco) field when clicked
  • WYSIWYG field shows hover border on mouse-over and no focus ring when not focused
  • TinyMCE branding badge ("tiny" logo) is hidden in the statusbar
  • Calendar, category, file, and relationship fields use Lara focus/hover/error tokens consistently
  • Sidebar does not fire getReferencePages / loadActivities API calls when collapsed (persisted via localStorage)
  • Sidebar renders synchronously — no empty column flash on page load

Test Plan

  • Open Content > Add new content with a Tags field — verify the Tags input stretches to 100% width
  • Edit content and click "Publish" or "Save" rapidly — verify button disables with spinner, only one action fires
  • Verify the split-button dropdown is also disabled during save
  • After save completes (or fails), verify buttons re-enable
  • Edit content with a Block Editor field — verify border matches standard inputs in default, hover, focus, and error states
  • Trigger validation error on Block Editor, then hover over it — verify border stays red (not switching to hover color)
  • Verify Block Editor border-radius matches other inputs
  • Check main content tabs (Content/Advanced Properties) have a bottom border line
  • Check sidebar tabs (info/history/comments) have a bottom border line
  • Edit a File Asset — verify binary field drop-zone border-radius matches the container
  • Click inside a WYSIWYG (TinyMCE) field — verify focus ring (blue border + shadow glow) matches the JSON field
  • Click outside the WYSIWYG — verify focus ring disappears
  • Verify no "tiny" branding badge is visible in the WYSIWYG statusbar
  • Verify calendar, category, file, and relationship fields show correct focus ring when interacted with
  • Collapse sidebar, navigate to edit content — verify no unnecessary API calls in Network tab
  • Open sidebar — verify reference pages and activities load
  • Refresh page — verify sidebar renders immediately with no empty column flash

Visual Changes

CleanShot.2026-04-16.at.10.12.49.mp4

Notes

Atomic commits on this branch:

  1. fix(edit-content): align tags, block editor, and binary field with Lara theme tokens — Pure CSS/SCSS/template styling fixes
  2. fix(edit-content): disable action buttons during save and add tabs bottom border — Component logic wiring for button state and tabs passthrough
  3. fix(edit-content): apply Lara focus/hover tokens to custom fields and align tabs — Calendar, category, file, relationship, WYSIWYG, Monaco, and JSON field token alignment
  4. fix(edit-content): smooth sidebar open/close flow without layout pop — Always-mounted sidebar with inert/aria-hidden
  5. fix(edit-content): use a softer left-cast shadow on the sidebar — Sidebar shadow tuning
  6. fix(edit-content): hide sidebar shadow when collapsed — Conditional shadow via :not([inert])
  7. fix(edit-content): render sidebar synchronously to avoid empty column flash — Remove @defer, render sidebar eagerly
  8. fix(edit-content): fix review findings — scoped ng-deep, error hover, sidebar gating, and WYSIWYG focus ring — Code review fixes: ::ng-deep scoping, block editor error/hover specificity, sidebar API gating, TinyMCE iframe focus workaround
  9. fix(edit-content): hide TinyMCE branding badge after library upgrade — Hide .tox-statusbar__branding

@github-actions github-actions bot added the Area : Frontend PR changes Angular/TypeScript frontend code label Apr 14, 2026
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 14, 2026

Claude finished @oidacra's task in 3m 19s —— View job


PR Review

  • Read changed files
  • Analyze for bugs, risks, design problems
  • Post review findings

Findings

Functional regressions

  1. overflow-auto removed from JSON field wrapper (dot-edit-content-json-field.component.html:24). The previous class list included overflow-auto; the replacement only has px-[3px]. Monaco editor content that overflows the fixed min-h-[150px] container will now clip silently instead of scrolling. Fix this →

  2. requestAnimationFrame without cancellation (dot-edit-content-sidebar-activities.component.ts, #scrollEffect). If the component is destroyed while the rAF is queued, the callback fires against a detached container. Store the rAF id and cancel it in ngOnDestroy (or use DestroyRef). Fix this →

Accessibility

  1. [attr.aria-hidden]="!showSidebar" produces aria-hidden="false" when the sidebar is visible (dot-edit-content.layout.component.html:131). aria-hidden="false" is not universally treated the same as omitting the attribute by all screen readers. Use [attr.aria-hidden]="!showSidebar || null" to remove the attribute entirely when the sidebar should be exposed. Fix this →

Inconsistent focus/error states across the new fields

  1. Category field has no :focus-within focus ring (dot-category-field.component.scss). Every other field touched in this PR (Monaco, block-editor, WYSIWYG, calendar) shows a focus ring; the category field only has hover + invalid. Fix this →

  2. Relationship field missing both focus ring and error state (dot-relationship-field.component.scss). The table has hover but no .ng-invalid.ng-touched border-color and no :focus-within rule. Fix this →

  3. File field missing focus ring (dot-file-field.component.scss). .dot-file-field__container has hover and uses the .border-color-error class for errors, but there is no :focus-within rule. Interactive drop zones should indicate keyboard focus. Fix this →

Fragile approach

  1. ::ng-deep dot-card-field-footer:empty in dot-card-field.component.ts. The :empty pseudo-class does not match when Angular's content projection leaves whitespace text nodes inside the projected slot, which is common. For fields that don't use a conditional @if around the footer, the empty footer will still occupy space. A safer approach is to let each field control footer rendering conditionally (as the calendar field now does) rather than relying on :empty.

Calendar SCSS architecture

  1. box-shadow on p-datepicker host, border on child elements (calendar-field.component.scss). The focus glow is on the p-datepicker wrapper, while the blue border is applied to .p-datepicker-input and .p-datepicker-dropdown inside it. Since the wrapper has no border of its own, the box-shadow renders at the wrapper's edge (determined by border-radius) while the visible blue border is on the inner elements. In practice they can visually align, but if PrimeNG's internal layout adds any gap or padding between the wrapper and the input, the glow and border will be offset. Compare with Monaco/block-editor, where both box-shadow and border-color are applied to the same element.

Unfixed items from prior review comments

  1. $isFocused = signal(false) not protected readonly (dot-wysiwyg-tinymce.component.ts:63). Per ANGULAR_STANDARDS.md, state signals should be protected readonly. This was flagged in a review comment earlier but the current branch still has the unfixed form.

  2. px-[3px] in JSON field (dot-edit-content-json-field.component.html:24). Non-canonical Tailwind step; flagged in a review comment but still present. Either use px-1 (4 px) or add a comment explaining the exact pixel value needed to compensate for Monaco's internal border.

Minor

  1. min-h-[50px] vs min-h-[52px] in the action bar (dot-edit-content-form.component.html). The wrapper div was changed to min-h-[50px] but the sidebar-toggle <div> directly inside it still has min-h-[52px]. The 2 px discrepancy could cause the toggle button to push the wrapper slightly taller than intended.

  2. Both sidebar icon <img> elements always fire HTTP requests even when the other is hidden. Negligible for two small SVGs, but if these assets don't exist in a given deployment (the paths are /dotAdmin/assets/edit-uve-toolbar/left_panel_open.svg), both will produce 404s in the network tab permanently.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 14, 2026

Claude finished @oidacra's task in 53s —— View job


Rollback Safety Analysis

  • Read rollback-unsafe categories reference
  • Get full PR diff
  • Analyze diff against unsafe categories
  • Apply appropriate label

Result: Safe to Rollback ✅

All 5 changed files are pure frontend (Angular/CSS/SCSS) — no backend, database, Elasticsearch, or API changes are present.

Category Assessment
C-1 Structural Data Model ✅ No database tables, columns, or storage model touched
C-2 Elasticsearch Mapping ✅ No ES mapping changes
C-3 Content JSON Model Version ✅ No ImmutableContentlet or CURRENT_MODEL_VERSION changes
C-4 DROP TABLE / DROP COLUMN ✅ No DDL changes
H-1 One-Way Data Migration ✅ No data transformations
H-2 RENAME TABLE / COLUMN ✅ No renames
H-3 Primary Key Restructuring ✅ No PK changes
H-4 New Content Type Field Type ✅ No new field type registrations
H-5 Binary Storage Provider ✅ No storage provider changes
H-6 DROP PROCEDURE / FUNCTION ✅ No stored procedures affected
H-7 NOT NULL Column Without Default ✅ No column additions
M-1 Column Type Change ✅ No DDL
M-2 Push Publishing Bundle Format ✅ No bundler changes
M-3 REST / GraphQL API Contract ✅ No API endpoint or response shape changes
M-4 OSGi Interface Change ✅ No Java interface changes

All changes are limited to:

  • CSS custom property replacements (dot-block-editor.component.css)
  • SCSS variable substitution (dot-edit-content-binary-field.component.scss)
  • Angular template attribute additions ([fluid], [loading], [disabled], [pt])
  • A new readonly tabsPt config object in the Angular component TS

Label AI: Safe To Rollback has been applied.

@fmontes
Copy link
Copy Markdown
Member

fmontes commented Apr 14, 2026

Screenshot please :D

@semgrep-code-dotcms-test
Copy link
Copy Markdown

Legal Risk

The following dependencies were released under a license that
has been flagged by your organization for consideration.

Recommendation

While merging is not directly blocked, it's best to pause and consider what it means to use this license before continuing. If you are unsure, reach out to your security team or Semgrep admin to address this issue.

GPL-2.0

MPL-2.0

@oidacra oidacra force-pushed the issue-35009-ux-edit-content-form-ui-inconsistencies-after-lara branch from 9b20df0 to 274d0f6 Compare April 15, 2026 21:06
@oidacra oidacra marked this pull request as ready for review April 15, 2026 21:12
@oidacra
Copy link
Copy Markdown
Member Author

oidacra commented Apr 16, 2026

Screenshot please :D

Added video

@oidacra oidacra force-pushed the issue-35009-ux-edit-content-form-ui-inconsistencies-after-lara branch from a23c961 to 1a0847a Compare April 16, 2026 14:18
@oidacra
Copy link
Copy Markdown
Member Author

oidacra commented Apr 16, 2026

Thanks for the review — walking through the findings:

1. dot-category-field.component.scss missing error state — Done.
Added :host.ng-invalid.ng-touched { border-color: var(--p-inputtext-invalid-border-color) } and extended the hover guard with :not(.ng-invalid) so the error color isn't masked on hover.

2. dot-edit-content-monaco-editor-control.component.scss — Done.
Removed :not(.ng-dirty) from the hover and focus-within selectors, and dropped the standalone &.ng-dirty { red-border } rule. ng-invalid is now the sole error indicator, matching every other field.

3. dot-edit-content.layout.component.html double-gated @defer — Done.
Dropped the redundant @defer (when view === 'form'|'compare') from both branches. The outer @if is now the only condition, matching the sidebar's synchronous-render intent.

4. Sidebar toggle --gray-200 legacy variable — Done.
Replaced with var(--p-inputtext-border-color) to stay consistent with the tab-list border at line 24 of the same template.

5. aria-hidden="false" — Skipped.
Keeping [attr.aria-hidden]="!showSidebar"; functionally equivalent. Happy to revisit if the || null form is the team preference.

6. Sidebar data reloads on every open — Won't fix.
Intended behavior — fresh reference pages / activities per reopen. The signal gating in this PR is specifically about not firing when the sidebar is never opened; once it is opened we want the latest data. Store/service layer deduplicates if needed.

7. dot-file-field rounded-md hardcoded — Done.
Moved border and border-radius out of the template and into the .dot-file-field__container SCSS using var(--p-inputtext-border-radius) and var(--p-inputtext-border-color). Template now carries only layout classes.

@github-actions github-actions bot added the Area : Backend PR changes Java/Maven backend code label Apr 16, 2026
oidacra added 12 commits April 16, 2026 13:24
…ra theme tokens

- Add [fluid]="true" to tags field autocomplete for full-width rendering
- Replace hardcoded outline styles on block editor with PrimeNG Lara
  input tokens for border, hover, focus, and error states
- Replace $border-radius-md with var(--p-inputtext-border-radius)
  on binary field drop-zone elements
…ttom border

- Wire [loading] and [disabled] bindings to workflow action buttons
  using the store's isSaving signal to prevent duplicate submissions
- Add tabsPt passthrough config with bottom border on main content tabs
  to match sidebar tab styling
… align tabs

Unifies hover/focus/transition and border-radius across non-native PrimeNG
inputs (Binary, Block Editor, File/Image, JSON, WYSIWYG/TinyMCE + Monaco,
Relationship, Category, Calendar) using Lara theme tokens so they move with
the theme.

Other fixes in the same pass:
- Move date field focus/hover ring to the p-datepicker wrapper so it encloses
  the input and calendar button as one unit.
- Category field: gray border, border-radius, stable 36px min-height.
- JSON field: remove overflow-auto clipping the focus ring and reserve
  horizontal breathing room so the ring is not cut off on the sides.
- Form and sidebar p-tablist: add matching gray bottom border and align both
  tablists at the same 51px height so their baselines match.
- Form tablist: add px-8 so tabs and right-side actions align with the form
  body; drop redundant pr-4 on edit-content-actions.
- Keep sidebar always mounted with @defer (on idle) instead of @if, avoiding
  mount/unmount churn and the content squeeze during the grid transition.
- Mark the sidebar inert and aria-hidden while collapsed so it stays
  focus-/AT-transparent.
- Sync the external toggle with an Angular :enter/:leave width animation
  (150ms) so the Save group no longer snaps when the button appears or
  disappears; it now interpolates in step with the grid columns.
- Match the external toggle's wrapper, button size, icon, and right
  placement (-mr-8) to the internal one so the icon does not jump between
  the open and closed states.
- Force the sidebar tablist inner list and tabs to full height so the
  inactive tab background reaches the bottom border.
Replace shadow-md (all-sides) with a left-biased shadow so the sidebar
reads as a floating panel over the body instead of a boxed pane.
Apply the left-cast shadow only when the sidebar is not inert so it stops
bleeding into the body area while the sidebar is off-screen.
… flash

Drop the @defer (on idle) around the sidebar: the component is statically
imported, so deferring only delayed instantiation without reducing the
bundle. The grid reserves the sidebar column from the first frame, so
waiting for idle left ~200ms of empty white space on initial load.
… sidebar gating, and WYSIWYG focus ring

- Wrap ::ng-deep rules in :host in dot-wysiwyg-tinymce.component.scss
- Exclude .editor-wrapper--error from hover/focus-within rules in block
  editor so the invalid border is never masked
- Gate sidebar getReferencePages/loadActivities on isSidebarOpen to skip
  unnecessary API calls when the sidebar is collapsed
- Replace :focus-within with a JS-driven --focused class for TinyMCE
  because Chrome does not propagate :focus from an iframe to its parent,
  making the CSS pseudo-class a no-op
The TinyMCE upgrade changed the statusbar DOM structure, making the
previously hidden branding badge visible again. Hide it with a scoped
::ng-deep rule targeting .tox-statusbar__branding.
…onaco ng-dirty, defer, sidebar token

- dot-category-field.component.scss: add :host.ng-invalid.ng-touched rule; guard hover against .ng-invalid so the error color is not masked
- dot-edit-content-monaco-editor-control.component.scss: drop :not(.ng-dirty) from hover/focus suppressors and remove standalone .ng-dirty red-border rule; ng-invalid is now the sole error indicator
- dot-edit-content.layout.component.html: remove redundant @defer inside @if for both view === 'form' and view === 'compare' branches
- dot-edit-content-sidebar.component.html: sidebar-toggle border uses --p-inputtext-border-color instead of legacy --gray-200
- dot-file-field.component.{html,scss}: move border and border-radius out of the template into .dot-file-field__container SCSS using PrimeNG tokens
…m fields

- dot-card-field.component.ts: collapse empty <dot-card-field-footer> with :empty { display: none } so consumers that always project the footer element no longer push column content out of alignment via the flex gap
- dot-edit-content-calendar-field.component.html: wrap <dot-card-field-footer> in @if so the always-rendered inner wrapper div stops contributing a 7px gap when there is no error, hint, or timezone message
- dot-category-field.component.scss: match pInputText rendered height exactly with min-height: calc(var(--p-form-field-padding-y) * 2 + 1.4em + 2px) and box-sizing: border-box; previous 39px left a 0.1px sub-pixel gap

Result: every .field wrapper in the 2-column grid renders at 65.703px, so adjacent left/right rows align to the pixel.
Replace the conditional inline-SVG toggle and the tabs-embedded close button with a single always-visible button that mirrors the UVE editor's right-panel toggle.

- dot-edit-content-form.component.html: render the toggle unconditionally; swap the inline SVG for the UVE assets (left_panel_open.svg and left_panel_close.svg) flipped with -scale-x-100 and toggled via [class.hidden]; drop the border-l separator and fixed 64px wrapper, keep -mr-4 to nudge it toward the sidebar edge
- dot-edit-content-form.component.ts: remove the now-unused slideToggle animation trigger
- dot-edit-content-sidebar.component.html: remove the dotTabViewInsert reference, the appendContent template and the inline close button inside the tabs toolbar; set min-h-[53px] on <p-tablist> so its bottom border aligns with the external tabs at the pixel level, and add flex-1 + flex items-center justify-center on each p-tab so icons are evenly distributed and centered
- dot-edit-content-sidebar.component.ts: drop TabViewInsertDirective import, remove toggleButtonPt, remove justify-between from the tabs navContent (no longer needed without the embedded button)
- tab-view-insert.directive.ts: mark the directive as deprecated in JSDoc (it is still used from the form component but is now only a legacy escape hatch)
- Language.properties: add edit.content.sidebar.open / edit.content.sidebar.close i18n keys for the img alt text
- dot-edit-content-sidebar.component.spec.ts: drop the 'should have toggle-button element' assertion and the whole 'Sidebar Controls' describe block — the inline toggle button and its append-content template were removed from the sidebar in the previous commit
- dot-edit-content-form.component.spec.ts: drop the 'sidebar is closed so the toggle renders' precondition (the toggle is now always visible); add a new assertion that both sidebar-open-icon and sidebar-close-icon exist and that exactly one is hidden at any time

All 1774 tests in edit-content pass (28 skipped, 0 failed).
@oidacra oidacra force-pushed the issue-35009-ux-edit-content-form-ui-inconsistencies-after-lara branch from f689082 to 0eb6ff4 Compare April 16, 2026 17:24
oidacra added 2 commits April 16, 2026 13:52
…update size limits in project.json

- Deleted .postcssrc.json as it is no longer needed.
- Updated budget limits in project.json to allow larger initial bundle sizes (3.5mb warning, 5mb error).
- Removed Tailwind CSS imports from style.css to streamline styles.
…state detection

- Added an  input to the activities tab component to track its visibility state.
- Updated the scroll effect to trigger when the activities tab becomes active, ensuring smooth scrolling to the last activity item when the tab is displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code Area : Frontend PR changes Angular/TypeScript frontend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

UX: Edit Content form UI inconsistencies after Lara Theme migration

2 participants