Repository navigation
Add keyboard-first review with roving focus, browser tests and WCAG checks - #2585
Merged
Merged
Conversation
# Conflicts: # CONTRIBUTING.md
4 tasks done
pournasserian
added a commit
that referenced
this pull request
Oct 19, 2026
Closes #2621 ## What The third slice of F-FRM (#2426): pickers for reference and media fields (FRM-01), backed by search (`ISearch`, #2566, #2606) and the media library (#2543). - **`PickerField`** (`Components/Forms`) is an interactive island inside the static generated form. `FieldWidget` renders it for reference and media fields. - Over the circuit it shows the current choice, a Clear button and a search box. The choice is a hidden input named `Form.Values[field]`, so the form posts and saves exactly as before. - **Entries**: as the person types, it searches the reference's target type (at most 10 direct hits). Each hit shows its first text value, or an entity's first text fact, with its state and a short id. - **Media**: it filters the library by file name or folder, newest first, with a preview for images. - The matches are a `listbox` of buttons that rove with the module from #2585. ArrowDown moves from the search box into the list, the arrows move through it, Enter chooses and Escape clears the matches. - Before the circuit attaches, or when it is lost, the prerendered island is the plain select from #2613, so a form posted then still saves. - **`admin.js`**: a `data-ui="rove-input"` box moves focus to the first item of the list its `aria-controls` names on ArrowDown. ## Why - **An island, not a page in interactive mode.** Only the search needs the circuit. The form, its other fields and the save stay static, so the save path from #2613 does not change, and a lost circuit costs only the search. - **A hidden input in the island.** The static form collects it with the other fields, and no server state has to survive between the choice and the save. - **`FieldOption[]` rather than an interface.** An island's parameters cross into the circuit as JSON. With `IReadOnlyList<FieldOption>`, the browser refused the root components with "The list of component operations is not valid". The parameter says why in a comment. - **Not in this PR**: field filters in the picker (`SearchQuery.Fields` exists for it once a field's settings name a filter), list-of-reference widgets, and rule hints (FRM-03, needs F-RUL #2419). ## Tests - bUnit (`PickerFieldTests`): - Before the circuit, a picker is the select with the stored choice. - An entry picker searches its target type and names hits by their first text fact. It wires the listbox to the search box and the rove module, and choosing posts the id and shows the name. Clear empties the choice. - A search with no match says so, and Escape clears the matches. - A media picker finds an asset by file name, case-insensitively, with its preview, and posts its id. - `GeneratedFormTests` renders the reference and media widgets before the circuit, as selects. - Browser (`FormPickerTests`), keyboard alone: type the title, Tab to the picker, type part of a product's name, press ArrowDown and Enter, then write the rationale and save. The stored entry references the product. `dotnet build -warnaserror`, the unit and bUnit suite, the browser tests and the SQLite integration suite pass locally. The local PostgreSQL run on a loaded machine had nine failures in untouched classes, the same pattern as the last three PRs. CI runs the full PostgreSQL suite. No new package and no new project reference. ## Checklist - [x] Linked to an issue a maintainer has confirmed - [x] Targets `v2` - [x] Tests or docs updated where behaviour changed - [x] No tool-generated co-author trailers or footers
This was referenced Oct 19, 2026
pournasserian
added a commit
that referenced
this pull request
Oct 20, 2026
…properly (#2661) Closes #2626 ## What Follow-ups from the independent review of #2585 (F-DSK, #2422). - **A "Single-key shortcuts" switch in the shortcuts dialog, on by default** (WCAG 2.1 SC 2.1.4, which UX-04 targets). - Off turns off every shortcut without Control, Alt or Meta: the action letters, the queue numbers, J and K, and ?. The help stays one click away. - The choice is kept in this browser's local storage. If storage refuses it, it lasts for the page. - `admin.js` shows the stored choice when the switch is rendered, after each enhanced navigation (`enhancedload`), and when the switch takes focus. - **J and K also match their physical keys,** as the other letters do, so they work on Persian, Hebrew and other layouts. - **After an approve or reject from a queue, the page goes to the set that followed this one before the decision.** Skipping a set and approving the next no longer lands back on the skipped one. - **The shortcut dialogs list every key:** the arrow keys, Home and End on both views, and Space beside Enter for folding an item. The island's readiness (finding 7) stays as documented, as the issue says. A letter that types another Latin letter on a non-QWERTY layout can still fire that letter's shortcut. That is existing behaviour, filed as #2662. ## Tests Browser: - **Switch off:** - With single-key shortcuts off, A leaves the set awaiting review and J moves nothing. - After a reload, J and ? still do nothing. A neither approves nor leaves the page, which is checked after a pause, not at once. The switch is still off. - After an enhanced navigation to the desk, the switch still shows off. - With `admin.js` made to ignore the stored choice, this test fails. - **Physical keys:** a keydown with the J key's code and a Persian character moves to the next item. - **Arrow keys:** ArrowDown, ArrowUp, Home and End move through the desk's rows and the set's items. - **Field check:** "Big bold jab" typed into a generated form's title stays in the field, and B doesn't go back. This is the field check outside any dialog, which the old test couldn't reach. - **Queue order:** skipping the first set with N and approving the second lands on the third. bUnit: the dialogs list the new keys, and the switch is rendered on. `dotnet build -warnaserror`, the unit and bUnit suite and the keyboard browser tests pass locally. No new package and no new project reference. ## Checklist - [x] Linked to an issue a maintainer has confirmed - [x] Targets `v2` - [x] Tests or docs updated where behaviour changed - [x] No tool-generated co-author trailers or footers
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.
Closes #2582
What
The fourth slice of F-DSK (#2422): keyboard-first batch review (DSK-04, UX-02) on the merged interop module (#2500). It also adds the Playwright browser tests and the automated WCAG 2.1 AA checks (UX-04) that #2434 left for this epic.
admin.jsroving lists: a[data-ui="rove"]list of[data-ui="rove-item"]elements uses the existingrovehelper.aria-keyshortcuts.sets/{id}?queue=awaiting-me).DiffViewgets an opt-inRovingparameter, used by the island./uiand other uses keep every summary in the tab order.aria-keyshortcuts.ShortcutHelp(Components/Review): a dialog opened with?that lists the page's keys withKbd, on the desk and the set view.TableBodypasses extra attributes through, fordata-ui="rove"./ui:CommandSeparatorisaria-hiddeninstead ofrole="separator", which a listbox may not hold.Kbdtext usestext-foreground, becausetext-muted-foregroundonbg-mutedwas 4.3:1 in the light theme.~/.cache/ms-playwright, installs Chromium withplaywright.ps1 install --with-deps chromiumafter the build, and setsFLUENTCMS_TEST_BROWSER. CONTRIBUTING says how to run the browser tests locally.New test packages
Microsoft.Playwright1.63.0 (MIT), inFluentCMS.IntegrationTestsonly.Deque.AxeCore.Playwright4.14.0 (MIT), which runs axe-core in the page, inFluentCMS.IntegrationTestsonly. It bringsDeque.AxeCore.Commons(MPL-2.0), which carries axe-core itself.Neither ships with the product. MPL-2.0 is file-level copyleft and applies only if the package's own files are changed and distributed, which this repository does not do.
Why
AdminHostand the same data setup as the screen tests. Skipping withoutFLUENTCMS_TEST_BROWSERmatches the PostgreSQL tests, sodotnet teststill needs only the .NET SDK.ChangeSetItemsmarks itselfdata-interactive="true", and the keyboard test waits for it. A full run under heavy local load lost focus that way before the marker was added. On a reviewer's screen this window is the circuit's start, usually well under a second.Tests
Browser (Playwright, headless Chromium, against the host):
?opens the help, and A does nothing while it is open. B goes back to the queue.wcag2a,wcag2aa,wcag21a,wcag21aa) reports no violation on/ui, the desk, the stale facts queue and the set view, in LTR and RTL, light and dark.admin.jsis served by the test host.bUnit:
DiffViewwithRovingmarks the list and summaries, and only the first summary is tabbable. Without it, nothing changes.Integration: approving from a queue redirects to the next set with
done=approved, and rejecting the last one redirects to the queue withdone=rejected, which the desk shows.dotnet build -warnaserror, the full suite with the browser tests, and markdownlint pass locally against apostgres:17container as well as SQLite. One loaded full run hadEntityRelationsTests(PostgreSQL) fail once; it passed three reruns, and this PR does not touch it.Checklist
v2