Skip to content

feat(feedback): missing record form improvements#836

Draft
thostetler wants to merge 8 commits intoadsabs:masterfrom
thostetler:feat/missing-record-ux
Draft

feat(feedback): missing record form improvements#836
thostetler wants to merge 8 commits intoadsabs:masterfrom
thostetler:feat/missing-record-ux

Conversation

@thostetler
Copy link
Copy Markdown
Member

@thostetler thostetler commented Apr 7, 2026

Improves the missing record submission UX.

  • Auto-save draft to localStorage (500ms debounce) with restore banner on reload
  • Live completion checklist for required fields in Standard mode
  • Trying out an idea: Step-by-step guided wizard mode (Standard/Guided toggle)
  • Form fixes: blur validation, missing error messages on Title/Publication, date input filtering

Copilot AI review requested due to automatic review settings April 7, 2026 15:24
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 7, 2026

Codecov Report

❌ Patch coverage is 82.40850% with 149 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.5%. Comparing base (006cf95) to head (905b599).

Files with missing lines Patch % Lines
...onents/FeedbackForms/MissingRecord/RecordPanel.tsx 75.4% 54 Missing and 1 partial ⚠️
...nents/FeedbackForms/MissingRecord/RecordWizard.tsx 87.3% 32 Missing ⚠️
...mponents/FeedbackForms/MissingRecord/UrlsField.tsx 60.8% 22 Missing ⚠️
...ts/FeedbackForms/MissingRecord/ReferencesField.tsx 65.3% 16 Missing ⚠️
...onents/FeedbackForms/MissingRecord/useFormDraft.ts 85.3% 13 Missing ⚠️
...nents/FeedbackForms/MissingRecord/AuthorsTable.tsx 78.1% 8 Missing and 1 partial ⚠️
...nents/FeedbackForms/MissingRecord/AuthorsField.tsx 95.0% 1 Missing ⚠️
...ents/FeedbackForms/MissingRecord/FormChecklist.tsx 98.6% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           master    #836     +/-   ##
========================================
- Coverage    62.5%   61.5%   -0.9%     
========================================
  Files         317     344     +27     
  Lines       36576   40322   +3746     
  Branches     1673    1792    +119     
========================================
+ Hits        22827   24782   +1955     
- Misses      13709   15495   +1786     
- Partials       40      45      +5     
Files with missing lines Coverage Δ
...onents/FeedbackForms/MissingRecord/DraftBanner.tsx 100.0% <100.0%> (ø)
...nents/FeedbackForms/MissingRecord/PubDateField.tsx 100.0% <100.0%> (ø)
...rc/components/FeedbackForms/MissingRecord/types.ts 100.0% <100.0%> (ø)
src/types.ts 100.0% <100.0%> (ø)
...nents/FeedbackForms/MissingRecord/AuthorsField.tsx 97.7% <95.0%> (ø)
...ents/FeedbackForms/MissingRecord/FormChecklist.tsx 98.6% <98.6%> (ø)
...nents/FeedbackForms/MissingRecord/AuthorsTable.tsx 54.6% <78.1%> (ø)
...onents/FeedbackForms/MissingRecord/useFormDraft.ts 85.3% <85.3%> (ø)
...ts/FeedbackForms/MissingRecord/ReferencesField.tsx 55.4% <65.3%> (ø)
...mponents/FeedbackForms/MissingRecord/UrlsField.tsx 56.3% <60.8%> (ø)
... and 2 more

... and 19 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Risk summary: Moderate risk. Two newly introduced no-unused-vars lint failures will likely break CI (PubDateField.tsx, RecordPanel.tsx). Draft persistence also has a couple of unhandled localStorage failure modes.

Improves the Missing Record feedback form UX by adding draft persistence, a completion checklist, and a guided step-by-step wizard.

Changes:

  • Added debounced localStorage draft auto-save/restore plumbing (with a restore banner).
  • Added Guided wizard mode + Standard/Guided toggle, plus a required-fields checklist in Standard mode.
  • Updated form validation behavior (onTouched/onBlur) and publication date input filtering.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/types.ts Adds new LocalSettings keys for feedback draft + form mode.
src/components/FeedbackForms/MissingRecord/useFormDraft.ts New hook to read/write/clear debounced drafts in localStorage.
src/components/FeedbackForms/MissingRecord/useFormDraft.test.ts Adds unit tests for draft hook behavior.
src/components/FeedbackForms/MissingRecord/types.ts Extracts COLLECTIONS constant and refines types.
src/components/FeedbackForms/MissingRecord/ReferencesField.tsx Adds imperative flush() support via forwardRef for wizard navigation.
src/components/FeedbackForms/MissingRecord/RecordWizard.tsx New guided stepper UI with per-step validation gating.
src/components/FeedbackForms/MissingRecord/RecordWizard.test.tsx Tests wizard navigation/validation with heavy component mocking.
src/components/FeedbackForms/MissingRecord/RecordPanel.tsx Integrates draft banner, form mode toggle, checklist, and guided wizard into main panel.
src/components/FeedbackForms/MissingRecord/RecordPanel.test.tsx Adds coverage for required-fields validity gating and edit-mode bibcode behavior.
src/components/FeedbackForms/MissingRecord/PubDateField.tsx Adds input filtering + setValue-driven updates for pub date.
src/components/FeedbackForms/MissingRecord/FormChecklist.tsx New live completion checklist for required fields.
src/components/FeedbackForms/MissingRecord/FormChecklist.test.tsx Tests checklist completeness/progress count behavior.
src/components/FeedbackForms/MissingRecord/DraftBanner.tsx New restore/dismiss banner component.
src/components/FeedbackForms/MissingRecord/DraftBanner.test.tsx Tests banner rendering and callbacks.
src/components/FeedbackForms/MissingRecord/AuthorsTable.tsx Adds imperative flush() support via forwardRef for wizard navigation.
src/components/FeedbackForms/MissingRecord/AuthorsField.tsx Updates field wrapper to forward refs to AuthorsTable and adjusts required UI.

Comment on lines +33 to +34
localStorage.setItem(key, JSON.stringify(values));
setHasDraft(true);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localStorage.setItem() can throw (quota exceeded, storage disabled, Safari private mode). Right now that exception would bubble and break the form. Consider wrapping the write in try/catch (and only setting hasDraft=true on success) so draft persistence degrades gracefully.

Suggested change
localStorage.setItem(key, JSON.stringify(values));
setHasDraft(true);
try {
localStorage.setItem(key, JSON.stringify(values));
setHasDraft(true);
} catch {
// Ignore storage write failures so the form continues to work without draft persistence.
}

Copilot uses AI. Check for mistakes.
if (key === null || typeof window === 'undefined') {
return;
}
localStorage.removeItem(key);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localStorage.removeItem() can throw (e.g., storage unavailable). As written, clearDraft could crash the form during submission/reset. Wrap removeItem in try/catch so clearing the draft is best-effort.

Suggested change
localStorage.removeItem(key);
try {
localStorage.removeItem(key);
} catch {
// Best-effort cleanup only; storage can be unavailable in some browsers/modes.
}

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +24
const { onChange, ...rest } = register('pubDate');

return (
<FormControl isRequired isInvalid={!!errors.pubDate}>
<FormLabel>Publication Date</FormLabel>
<Input {...register('pubDate')} placeholder="yyyy-mm-dd" />
<FormErrorMessage>{errors.pubDate && errors.pubDate.message}</FormErrorMessage>
<Input
{...rest}
placeholder="YYYY-MM"
maxLength={10}
onChange={(e) => {
e.target.value = e.target.value.replace(/[^\d-]/g, '');
setValue('pubDate', e.target.value, { shouldValidate: true, shouldDirty: true });
}}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const { onChange, ...rest } = register('pubDate') leaves an unused onChange binding, which will fail linting under @typescript-eslint/no-unused-vars. Either remove the destructuring, rename to _onChange, or incorporate it (e.g., filter the value and then call the registered onChange / use setValueAs in register options).

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +110
function getDraftKey(isNew: boolean, bibcode?: string): string | null {
if (isNew) {
return LocalSettings.FEEDBACK_DRAFT_NEW;
}
if (bibcode) {
return `feedback-draft:edit-record:${bibcode}`;
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDraftKey uses the bibcode prop to decide the edit-record draft key. In the Edit Record tab, users can type a bibcode into the form without it being present in the URL query, so bibcode prop remains undefined and draft persistence is disabled (null key). If draft persistence is intended for edit mode, consider deriving the key from the current form value (e.g., watch/getValues('bibcode')) once a bibcode is present, and be careful not to churn keys on every keystroke (only enable when the bibcode is valid/stable).

Suggested change
function getDraftKey(isNew: boolean, bibcode?: string): string | null {
if (isNew) {
return LocalSettings.FEEDBACK_DRAFT_NEW;
}
if (bibcode) {
return `feedback-draft:edit-record:${bibcode}`;
}
function getStableBibcodeDraftValue(bibcode?: string): string | null {
const normalizedBibcode = bibcode?.trim();
if (!normalizedBibcode) {
return null;
}
return normalizedBibcode;
}
function getDraftKey(isNew: boolean, bibcode?: string): string | null {
if (isNew) {
return LocalSettings.FEEDBACK_DRAFT_NEW;
}
const stableBibcode = getStableBibcodeDraftValue(bibcode);
if (stableBibcode) {
return `feedback-draft:edit-record:${stableBibcode}`;
}

Copilot uses AI. Check for mistakes.
<Controller
name="collection"
control={control}
render={({ field: { ref, ...rest } }) => (
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Controller render for collection, the destructured ref is unused. With @typescript-eslint/no-unused-vars enabled, this will fail lint. Rename it to _ref (as done elsewhere) or omit it entirely.

Suggested change
render={({ field: { ref, ...rest } }) => (
render={({ field: { ref: _ref, ...rest } }) => (

Copilot uses AI. Check for mistakes.
@thostetler thostetler changed the title feat(feedback): draft persistence, completion checklist, and guided wizard for missing record form feat(feedback): missing record form improvements Apr 7, 2026
@thostetler thostetler force-pushed the feat/missing-record-ux branch from 6cbf6a3 to da19be2 Compare April 7, 2026 15:55
@thostetler thostetler marked this pull request as draft April 7, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants