Repository navigation
Description
Pressing Enter in the search box of the bulk remote-assignment subject picker, or of the Delete Assignments table, reloads the whole app and signs the user out. libui's DataTable renders its search box as libui's SearchBar, which is a bare <form> with no submit handler. A form with a single text input submits implicitly on Enter, and the default action reloads the current URL. The access token and the current session live only in memory (they are not in PERSISTED_KEYS), so the reload sends the user to the login page and discards everything the wizard was holding: picked subjects, parsed file, timepoints. The repo already has SearchSubmitGuard for exactly this (added in 66d794d for the subject datahub), but neither component wraps its table in it. Typing a search term and pressing Enter is a natural thing to do, so users will hit this.
Where
apps/web/src/components/BulkRemoteAssignmentWizard/SourceStep.tsx:231-300:
<div data-testid="bulk-subject-picker">
<DataTable
columns={[ /* ... */ ]}
data={rows}
// ...
/>
</div>apps/web/src/components/DeleteRemoteAssignments/DeleteRemoteAssignments.tsx:136-226:
<DataTable
columns={[ /* ... */ ]}
data={rows}
rootStyle={{ '--color-background': 'var(--color-card)' }}
onRowClick={(row) => toggle(row.id)}
/>apps/web/src/components/SearchSubmitGuard/SearchSubmitGuard.tsx:7-15 documents the mechanism. The only call site is routes/_app/datahub/subjects/index.tsx. The searchable tables in routes/_app/admin/users/index.tsx, admin/groups/index.tsx, admin/instruments.tsx and admin/instrument-repos/index.tsx are unguarded in the same way.
Reproduce
- As a group manager, open Group → Remote Assignments → Create Assignments.
- On "Select Subjects", tick a few subjects, type part of an identifier in the table's search box, and press Enter.
Actual: the page reloads, the user is sent to the login page, and the selection is lost. The same happens on the Delete Assignments table.
Expected: Enter does nothing beyond the as-you-type filtering, and the user stays signed in with the wizard state intact.
Tests
apps/web/src/components/BulkRemoteAssignmentWizard/__tests__/SourceStep.test.tsx:it('should cancel the submit when Enter is pressed in the subject search, so the app does not reload and sign the user out')(assertfireEvent.submiton the search form is default-prevented).apps/web/src/components/DeleteRemoteAssignments/__tests__/DeleteRemoteAssignments.test.tsx: the same assertion for its search box.testing/src/specs/remote-assignments-wizard.spec.ts: type in the picker search, press Enter, and assert the wizard is still on the subjects step with the selection kept.
Suggested fix
Wrap both DataTables in <SearchSubmitGuard>, and do the same for the admin route tables listed above. Fixing libui's SearchBar to preventDefault on submit would cover every call site at once.
Activity
joshunrau commentedon Oct 26, 2026
Still present on
main(d86740e). Neither table has been wrapped since this was filed, and no open PR wraps them.More unguarded search bars
admin/instruments.tsxhas two searchable tables::146(instruments) and:274(series).SearchBars outside anyDataTablesubmit the same way:group/manage.tsx:423(the Create Series dialog),group/manage.tsx:615andupload/index.tsx:68. Ongroup/manage.tsx:615the reload also throws away unsaved instrument ticks and settings edits, becauseselectedIds(:558) is only sent when the form is submitted (:821).InstrumentShowcase.tsx:68-79is the only safe one: its ownonKeyDowncallspreventDefaulton Enter.InstrumentTable.tsxrenders a searchableDataTablewith no guard, and the new/datahub/instrumentspages use it.That makes 10 sites on
mainand 11 with #1807. Wrapping each call site has already been missed twice:DeleteRemoteAssignmentslanded in e2e30f0 two days after the guard (66d794d), and #1807 misses it again.Fix
Layout(Layout.tsx:15) inSearchSubmitGuard, and update its doc comment to say where it sits. This is safe because every other form already cancels its own submit. libuiFormdoes it inhandleSubmit, and the four native forms callpreventDefaultfirst:CreateTemplateForm.tsx:42,EditTemplateDialog.tsx:39,LoginPageEditor/hooks.ts:323andadmin/instrument-repos/index.tsx:47. React propagatesonSubmitthrough portals, so the Create Series dialog is covered too./auth/*and/setuprender outsideLayout, but they have no search bar. The change does not touchSourceStep.tsx,DeleteRemoteAssignments.tsxorInstrumentTable.tsx, so it does not conflict with fix(web): align remote assignment table colors with datahub subject table #1811 or feat: add the instrument hub and record provenance #1807. The datahub wrapper (datahub/subjects/index.tsx:493,:652) becomes redundant. feat: add the instrument hub and record provenance #1807 edits that file, so remove the wrapper after feat: add the instrument hub and record provenance #1807 merges.SearchBar.tsx:47still renders<form className={cn('relative', className)} {...props}>with noonSubmit. No libui issue tracks this.DataTableControlspasses fixed props toSearchBar, so a caller cannot add the handler throughDataTable. The bump has a blocker: libui 6.18.0 breaks typing inapps/web(libui **6.18.0** publishes type declarations that reference a globalReactnamespace without importing React. libui#126). Its declarations rely on a globalReactthat our vendoredreactdoes not declare. WithskipLibCheck, the props ofCheckbox,Select,Tableand others silently becomeany, andapps/webgets 17TS7006errors. The fix, fix(types): importReactin files whose emitted declarations useReact.*libui#128, is still open. Ship theSearchBarfix in the same patch release or a later one. Once the bump lands,SearchSubmitGuard.test.tsx:39-41fails. That is the signal to delete the guard.Tests
apps/web/src/components/Layout/__tests__/Layout.test.tsx:it('should cancel a search bar submit anywhere in the app, so Enter does not reload the page and sign the user out'). Make theOutletmock render a real libuiSearchBar(import@/services/i18n), and add a second case that renders the bar throughcreatePortal. The per-component unit tests in the issue no longer apply, because the components themselves cancel nothing.remote-assignments-wizard.spec.tscase from the issue (fix(web): align remote assignment table colors with datahub subject table #1811 also edits that spec). Add a case toupload.spec.tsfor the bare-SearchBarpath, using the existingUploadPage.searchInput. Nothing intesting/opens?mode=deleteyet, so the delete-mode spec that Bulk-deleting remote assignments reports success when the server could not delete them #1685 needs can carry the same Enter check.datahub.spec.ts:88already presses Enter in the subject table and should keep passing once that wrapper is gone.Triage note written with Claude Code. Code references point at
main@d86740e.