Skip to content

refactor(ui): Show all toggle and JSON paste for the key/value field (#37191) - #37631

Open
adrianjm-dotCMS wants to merge 7 commits into
mainfrom
issue-37191-key-value-show-all
Open

adrianjm-dotCMS wants to merge 7 commits into
mainfrom
issue-37191-key-value-show-all

Conversation

@adrianjm-dotCMS

@adrianjm-dotCMS adrianjm-dotCMS commented Sep 18, 2026

Copy link
Copy Markdown
Member

Parent Issue

#37191 — follow-up to the review feedback on #37284 (merged).

1- PASTE JSON

Screen.Recording.2026-09-18.at.4.30.01.PM.mov

2- SHOW ALL - SHOW LESS

Screen.Recording.2026-09-18.at.4.04.34.PM.mov

Proposed Changes

Two changes to the shared Key/Value editor, both reaching its three consumers: the Edit Content key/value field, Content Type → Field Variables, and the Apps custom-properties panel.

1. Show all, in place of Load more

The field shipped with Load more: 40 rows at a time, one click per page, and the control vanishing once the last page was revealed. The feedback asked for Show all instead.

dot-key-value-ng now renders the same two-state toggle dot-relationship-field already ships:

  • ⊕ Show all (N)⊖ Show less, in the existing footer row next to Clear All
  • N is the whole list, and only on expand — collapsing always returns to the same first page
  • aria-expanded on the button
  • Same 40-row threshold and same icons as the relationship field, so the affordance is learned once
  • Below 40 pairs there is no control at all, as before

Internally $visibleCount stops being a growing signal and becomes a computed over a $showingAll flag, with $canToggleAll replacing $remaining, and loadMore() giving way to toggleShowAll().

$showingAll is deliberately state, not derived from the variables input: Field Variables and Apps hand back a fresh array on every edit, so deriving it collapsed the table the moment anything changed. Same reasoning, and same comment, as RelationshipFieldStore.showingAll.

Language keys: keyValue.action.load_morekeyValue.action.show_all + keyValue.action.show_less.

2. Pasting a JSON object

The paste shortcut only understood KEY=VALUE, so a JSON object matched nothing and the whole block landed in the Key input as one string.

parseKeyValueBlock now tries JSON first — the text as given, then the text wrapped in braces with a trailing comma dropped, so a selection taken out of the middle of a file ("id": 1, and the lines under it) reads as well as a whole {...}. Anything else falls through to the KEY=VALUE reader exactly as before.

How values are written:

  • a number or boolean keeps the form the JSON gave it (1, true);
  • a nested object or array keeps its JSON text, so the data survives the paste and stays visible and editable rather than being dropped in silence;
  • null and blank are skipped, for the same reason KEY= is — the entry row refuses a blank value and a paste is not a way around that.

A top-level array, string or number is rejected (no keys in it to make pairs from), and so is text too malformed to parse, such as a selection that cut a string in half. Both fall through to the browser's own paste, where the user can see the text and fix it.

Checklist

  • Tests
  • Translations
  • Security Implications Contemplated (not applicable — a rendering limit and a clipboard parser, no new data paths)

Additional Info

The whole list stays bound to the table and only the DOM is limited, exactly as before — PrimeNG reorders the array it is given, so a shortened one would silently drop a drag. The e2e coverage for that (drag and delete with rows withheld) is kept.

136 unit tests across the four dot-key-value-ng specs, 11 of them new for the JSON paste.

Screenshots

Verified locally against a field with 56 pairs: collapsed at 40 rows with Show all (56), one click reveals all 56 and offers Show less.

🤖 Generated with Claude Code

)

Review feedback on #37284: revealing a long list 40 rows at a time makes an
editor click repeatedly to reach the end, and the control disappears once the
last page is out, leaving no way back to a short table. A two-state toggle does
it in one click either way.

`dot-key-value-ng` now holds a `$showingAll` flag instead of a growing
`$visibleCount`, and renders "Show all (N)" / "Show less" in the same footer
row. The flag stays state rather than being derived from the `variables` input:
Field Variables and Apps hand back a fresh array on every edit, which would
collapse the table on any change.

This is the control `dot-relationship-field` already ships, down to the icons,
the count on expand only and the 40-row threshold — the two fields now teach the
same affordance once.

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

claude Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @adrianjm-dotCMS's task in 1m 43s —— View job


Code Review — Show all toggle + JSON paste (#37631)

Reviewed the diff against origin/main. Read all four dot-key-value-ng source changes, the header-row and Language.properties changes.

  • Analyze dot-key-value-ng.component.ts (Show all toggle logic)
  • Analyze dot-key-value-paste.util.ts (JSON parsing)
  • Analyze template, header-row, language keys
  • Verify no dangling load-more / $remaining references

New Issues

  • 🟡 Medium: dot-key-value-paste.util.ts:81jsonValueToString renders numbers with String(value) after JSON.parse has already coerced them to JS number. This silently corrupts values the doc comment promises to preserve ("a number ... keeps the form the JSON gave it"): a large integer id such as "id": 12345678901234567890 comes out as "12345678901234568000" (beyond Number.MAX_SAFE_INTEGER), "x": 1.10 becomes "1.1", and "y": 1e21 becomes "1e+21". For KEY=VALUE pastes the raw string is kept verbatim, so this is a behavior gap unique to the new JSON path.
    Assumption: users may paste config containing large numeric IDs or version-like decimals. What to verify: whether any real key/value consumer (Apps props, Field Variables) stores numeric IDs where the reformatting would matter. If not, this is acceptable and the doc claim could just be softened. Preserving the exact source text would require capturing the raw token rather than the parsed value. Fix this →

Everything else checks out:

  • Show all toggle$showingAll as signal + $visibleCount/$canToggleAll as computed is correct. The "state, not derived from $variables" reasoning holds, and the stale-true case (list shrinks below PAGE_SIZE after expanding) is harmless: $visibleCount still renders the full short list and $canToggleAll hides the now-pointless toggle. Footer condition $variableList().length && ($canToggleAll() || !readOnly) correctly keeps the Show all control in read-only mode while dropping the whole foot when there's nothing to expand or clear.
  • JSON parsing — top-level array/string/number and malformed text are all correctly rejected and fall through to the KEY=VALUE reader. The two-candidate strategy (raw, then brace-wrapped with trailing comma stripped) does not false-positive on .env blocks ({KEY="value"} isn't valid JSON). {} and all-null/blank objects return [], so the caller falls back to browser paste — consistent with the single-key contract.
  • DeduppairsFromJson guards existingKeys[key]; JSON.parse collapses duplicate keys itself, so the lack of a seen set (unlike the KEY=VALUE path) is fine.
  • null/blank skipping in jsonValueToString matches the existing KEY= handling.
  • No dangling load-more / load_more / $remaining references remain; language keys renamed cleanly.

Overall this is clean, well-documented work. The one Medium is non-blocking — decide based on whether numeric-ID pastes are a real scenario for these consumers.

· branch issue-37191-key-value-show-all

@github-actions github-actions Bot added the Area : Frontend PR changes Angular/TypeScript frontend code label Sep 18, 2026
@adrianjm-dotCMS
adrianjm-dotCMS marked this pull request as ready for review September 18, 2026 20:07
adrianjm-dotCMS and others added 2 commits September 18, 2026 16:25
The paste shortcut only understood `KEY=VALUE`, so a JSON object matched
nothing, and the whole block landed in the Key input as one string.

`parseKeyValueBlock` now tries JSON first: the text as given, then the text
wrapped in braces with a trailing comma dropped, so a selection taken out of the
middle of a file — `"id": 1,` and the lines under it — reads as well as a whole
`{...}`. Anything else falls through to the `KEY=VALUE` reader exactly as before.

How values are written:

- a number or boolean keeps the form the JSON gave it (`1`, `true`);
- a nested object or array keeps its JSON text, so the data survives the paste
  and stays visible and editable rather than being dropped in silence;
- `null` and blank are skipped, for the same reason `KEY=` is: the entry row
  refuses a blank value and a paste is not a way around that.

A top-level array, string or number is rejected — no keys in it to make pairs
from — and so is text too malformed to parse, such as a selection that cut a
string in half. Both fall through to the browser's own paste, where the user can
see the text and fix it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adrianjm-dotCMS adrianjm-dotCMS changed the title refactor(ui): swap the key/value Load more for a Show all toggle (#37191) refactor(ui): Show all toggle and JSON paste for the key/value field (#37191) Sep 18, 2026
fmontes
fmontes previously approved these changes Sep 18, 2026
…ish (#37191)

`format-test` failed on this file: Prettier wanted one of the calls on a single
line. The pre-commit hook's `nx format:write` reported success without applying
it, so it reached CI unformatted.

The sample data went to English at the same time — it was written in Spanish
while working through the shape of the feature, which is not what the rest of
these specs read like.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oidacra
oidacra previously approved these changes Sep 18, 2026
`spectator.query()` returns `T | null`, so the six new assertions that read
`textContent` and `aria-expanded` straight off it are strict-mode violations on
lines this branch wrote. Narrowed with `?.`, which keeps each assertion failing
the same way if the element is ever missing.

Verified with the harness the gate itself runs:
tools/scripts/strict-gate/run.mjs --base origin/main --flags strict
--granularity line --scope core-web → PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area : Frontend PR changes Angular/TypeScript frontend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants