fix(content-drive): folder dialog overlays clipped and file extensions not persisting (#35578) - #37011
Merged
Merged
Conversation
…s not persisting (#35578)
9 tasks
Contributor
|
Claude finished @zJaaal's task in 34s —— View job SDK Compatibility Analysis
Result: No SDK breaking change detected. All 16 changed files are confined to:
None of this touches any surface tracked by
This matches the doc's "Non-Breaking Examples" bucket directly: admin-UI ( |
…ns field Committing the pending text on blur was never previous behavior and is not needed to fix the reported bug. Extensions are added with Enter or by picking a suggestion, as before.
zJaaal
approved these changes
Aug 12, 2026
…rive-folder-dialog-fe-fixes
…not suggested The allowed-file-extensions field starts its suggestion list empty instead of preloading the suggested extensions. PrimeNG's AutoComplete rebuilds its chips from `suggestions` on every write to the form control, keeping only the values it can find there, and the folder is loaded through exactly such a write. A saved extension the suggested list does not carry (`*.svg`) was therefore dropped from the chips while staying in the form value: the user could not see it or remove it, and it was sent straight back on save. With nothing to match against, PrimeNG keeps the written value verbatim and the chips mirror the control. Nothing is lost visually: the suggestion panel only opens once a keystroke has produced suggestions (`handleSuggestionsChange` is gated on `loading`), and ArrowDown returns early unless that panel is already open, so the preloaded list was never reachable before typing. Also dispatch Enter with `code` in the spec. PrimeNG's key handler switches on `code` while our `(keydown.enter)` binding matches on `key`, so the previous events only exercised our handler and skipped the real interleaving, where PrimeNG handles the same press first on the inner input. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tension
Removing all extensions from an existing folder could not be persisted: the
dialog omitted `fileMasks` when the list was empty, and `WebAssetHelper` guards
the write with `UtilMethods.isSet(meta.fileMasks())`, whose Collection overload
is false for an empty list too. Absent and empty were therefore both ignored and
the previously saved extensions came back on the next load.
Editing a folder down to no extensions now sends a single blank mask, which
`String.join(",", ...)` turns into an empty string server-side. That is how a
folder with no restrictions is already stored: `FolderFactoryImpl.matchFilter`
treats a blank value as "allow everything", the upsert persists it verbatim, and
the dialog reads it back as no extensions.
On create the field stays omitted, since there is nothing to clear.
This is a workaround for the payload contract, not the contract itself: an empty
array is what should mean "clear", which needs the backend guard to distinguish
null (absent, leave untouched) from an explicit empty list. `fileMasks` is
`@Nullable`, so Immutables keeps absent as null and the two are distinguishable
whenever that change is made.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The toggler drew a custom `tree-folder.svg` sprite. It now uses the same `left_panel_open` glyph UVE uses to toggle the palette and the quick-edit panel, rendered from the self-hosted Material Symbols font rather than UVE's exported SVG, which keeps it consistent with the rest of Content Drive's icons. UVE's asset hardcodes the primary blue in both panel states, so the color is set on the glyph instead of inherited from the button: the old sprite drew with `stroke="currentColor"`, which left the icon whatever tone the text button happened to resolve to. Template-only change; the open/close mechanism is untouched, and the icon keeps its `tree-toggle-icon` test id. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… UVE's panel icons The toggle now behaves the way UVE's palette and quick-edit toggles do: a single button that stays beside the search input whether the tree is open or closed, and whose icon reacts to that state. Until now there were two buttons pretending to be one. The toolbar's copy collapsed to zero width once the tree expanded, and a second copy inside the sidebar header took over the same screen position. Both drew a custom `tree-folder.svg` sprite that never changed with state. - The toolbar's toggle is always visible, so `$togglerStyles` and the conditional grid gaps are gone, along with the now-orphaned `$treeExpanded`. - The sidebar's duplicate is removed, and a site icon takes its slot. It carries the toggle button's own classes rather than a hardcoded box, so the footprint stays identical as the theme moves and the site name keeps the indent that lines it up with the folder rows underneath. - The glyphs are UVE's own `left_panel_open`/`left_panel_close` assets rather than Material Symbols ligatures: the pair carries the primary blue and the two files differ in how much of the panel is filled, which is the state cue a single font glyph cannot express. Both render and toggle with `hidden`, so neither is fetched mid-interaction. The icon renders from `isTreeVisuallyExpanded`, not `isTreeExpanded`: the Edit Content side panel can force the tree collapsed on a narrow viewport without touching the user's stored preference, and the glyph has to follow what is actually on screen. Clicking still toggles the preference itself. The unreferenced `tree-folder.svg` is deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… dead signal
Two follow-ups from reviewing the toggler consolidation.
`contentDrive.page.ts#toggleTree` picked the sidebar's toggler whenever the tree
measured wider than 100px. That copy no longer exists, so the expanded branch
resolved to nothing and `content-drive-tree.spec.ts` ("toggles sidebar tree
collapsed and expanded", @critical) would have timed out clicking it. It now
always targets the toolbar's, which is the only one.
`$treeExpanded` on the toolbar was left behind when `$togglerStyles` and the
conditional grid gaps went: nothing in the template or the class referenced it,
and its doc comment had drifted onto `$showWorkflowActions`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a forced collapse The toggle was a custom element wearing button classes, so it could never take focus: the tree could not be opened or closed from the keyboard at all, and a screen reader announced an unnamed control. It is now a real `p-button` with an accessible name that states the action it performs, since both glyphs are decorative. The name goes through PrimeNG's `[ariaLabel]` input rather than `[attr.aria-label]`, which would land on the non-focusable `<p-button>` host and leave the actual `<button>` unnamed. The button also disables itself while the Edit Content side panel holds the tree collapsed. `isTreeVisuallyExpanded` ands the stored preference with that override, so clicking through a forced collapse flipped the preference and moved nothing — the button looked live and did nothing. Honoring the override means refusing the interaction until the panel releases it, rather than clearing a collapse the panel owns. `p-button-icon-only` is applied by hand: PrimeNG only derives it from the `icon` input, and the glyphs are projected content here, so without it the button takes label padding and renders as a padded oval rather than a circle. Adds `content-drive.tree.collapse` / `content-drive.tree.expand`, and the new signal to the store mocks of the specs that render this component. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n chips Loading a folder waits on the content-type request, so the patch that fills the form can land after the user has typed into Allowed File Extensions. PrimeNG rebuilds its chips from the current `suggestions` on every write to the control, keeping only the values it can match there, so any saved extension outside the active filter lost its chip while staying in the form value. That was not merely cosmetic. Adding a chip goes through the AutoComplete's own model, so the next add rebuilt the control from the pruned chips and deleted the unseen extension for good: opening a folder saved as `*.jpg,*.svg`, typing `*.j` while it loaded, then adding `*.png` sent `["*.jpg","*.png"]` and dropped `*.svg` permanently, with nothing on screen to notice. The loaded value is now restated straight into the AutoComplete's model after the patch, which skips the matching entirely. This complements the empty starting suggestion list: that covers the ordinary open, where the write happens before the component can filter anything, and this covers the write that arrives after. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dialog rendered an empty form immediately and patched the values in whenever the file-asset-type request answered. Anything the user typed before that landed was silently overwritten, on every field, not just the extensions one. It now shows a spinner until those types arrive and renders the form already populated, so nothing is ever patched under the user. That also removes the reason for the previous commit's workaround: the extension chips were being pruned because PrimeNG rebuilds them from the current `suggestions` on each write to the control, and that write could land after the user had typed and narrowed the list. With the form gated on its data, there is no write to race, so the loaded value no longer has to be restated into the AutoComplete's model by hand. The regression test now covers the guarantee that replaces it: a spinner while loading, and a populated, in-sync field once loaded. Drops the hardcoded `[loading]="false"` on the file-type select, which the gate makes honest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…loading The loading test only checked the extensions field, but the guarantee is broader: no input may exist before the values that belong in it, or the load patches over whatever was typed. It now also asserts the name and title inputs are absent, plus the save button, since submitting mid-load would persist an empty form over the folder's real data. Verified load-bearing: forcing the readiness gate open fails this test and nothing else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
zJaaal
enabled auto-merge
August 12, 2026 19:58
Member
|
I just polished some accessibility issues and added a feedback of the icons open/close sidebar pattern. Ivan made a great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes two front-end defects reported by QA on the Content Drive folder dialog (Create Folder / Folder Settings → Upload Behavior tab).
Proposed Changes
1.
Default File Asset Typedropdown rendered inside the dialogThe
p-select(and thep-autoCompletenext to it) used PrimeNG's defaultappendTo="self", so their overlay panels were laid out inside the dialog's scrolling content: the panel was clipped at the dialog edge, and the wheel events over the options list scrolled the dialog instead of the list — which closed the panel.appendTo="body"on bothp-selectandp-autoComplete, matching what the rest of Content Drive already does for overlays (toolbar menus, field filters, context menus).2.
Allowed File Extensionskept coming back to the previously saved valueAdding an extension wrote the whole array back through the form control (
control.setValue([...current, value])). PrimeNG'sAutoComplete.writeControlValue()re-derives its displayed model from the currently filteredsuggestions, so every already-selected extension outside the active filter was dropped from the chips while staying in the form value. With*.jpgsaved and*.pngtyped, the chips showed*.pngbut the payload still carried*.jpg— so the old extension reappeared on the next load.The value is now added through the AutoComplete's own model, which keeps the control and the chips in sync.
3. A saved extension the suggested list does not carry was invisible and unremovable
AutoComplete.writeControlValue()rebuilds the chips from the currentsuggestionson every write to the control, keeping only the values it can find there — and loading a folder is exactly such a write. With*.jpg,*.svgsaved, the field rendered only*.jpg:*.svgstayed in the form value but had no chip, so the user could neither see it nor remove it, and it was sent straight back on save. Same class of desync as defect 2, reached through the load path rather than the add path.SUGGESTED_ALLOWED_FILE_EXTENSIONS;onCompleteMethodfills it per keystroke. With nothing to match against, PrimeNG keeps the written value verbatim and the chips always mirror the control.handleSuggestionsChangeis gated onloading), andonArrowDownKeyreturns early unless the panel is already open — so the preloaded list was never reachable before typing.4. Removing all extensions could not be persisted
The dialog omitted
fileMaskswhen the list was empty, andWebAssetHelperguards the write withUtilMethods.isSet(meta.fileMasks())— whoseCollectionoverload is false for an empty list too. Absent and empty were both ignored, so the saved extensions came back on the next load and an explicit "clear" was impossible.String.join(",", …)turns into an empty string server-side. That is how a folder with no restrictions is already stored:FolderFactoryImpl.matchFiltertreats a blank value as "allow everything", the upsert persists it verbatim, and the dialog reads it back as no extensions.5. Folder-tree toggle now follows the UVE pattern
Not part of the QA failure — picked up in the same pass because the toggle was drawing a bespoke icon and behaving unlike every other panel toggle in the product.
There were two buttons pretending to be one: the toolbar's copy collapsed to zero width once the tree expanded, and a second copy inside the sidebar header took over the same screen position. Both drew a custom
tree-folder.svgsprite that never changed with state.$togglerStylesand the conditional grid gaps are gone, along with the orphaned$treeExpanded.left_panel_open/left_panel_closeassets, not Material Symbols ligatures: that pair carries the primary blue, and the two files differ in how much of the panel is filled, which is the state cue a single font glyph cannot express.isTreeVisuallyExpanded, notisTreeExpanded— the Edit Content side panel can force the tree collapsed on a narrow viewport without touching the user's stored preference, and the glyph has to follow what is on screen. Clicking still toggles the preference.tree-folder.svgis deleted.Checklist
Additional Info
Unit tests in
dot-content-drive-dialog-folder.component.spec.tsnow drive the real PrimeNG input (typing +keydown) instead of synthetic handler calls, and assert that the rendered chips match the form value.Enter is now dispatched with
code: 'Enter', not justkey. PrimeNG's key handler switches oncodewhile the(keydown.enter)binding matches onkey, so the earlier events exercised only our handler and skipped the real interleaving, where PrimeNG handles the same press first on the inner input.The regression test for defect 3 binds
folderat creation time because that is how the shell opens this dialog, and the defect depends on that ordering: the effect that loads the folder runs before PrimeNG's initial control write, so a fix applied inside the effect gets clobbered.Out of scope / follow-up:
addOnBluronly applies whentypeaheadis off, which is not the case here — so adding it would be new behavior rather than a fix for the reported defect. Extensions are added with Enter or by picking a suggestion, exactly as before; the bug being fixed is what happened after the extension was added.mainis merged in (through 4d49cfe).Related to #35578
🤖 Generated with Claude Code
This PR fixes: #35578
This PR fixes: #35578