Fix all $TSFixMe instances - #6450
Conversation
There are some slight logic changes because of issues revealed by the new type errors that surfaced. Also some type errors have now been suppressed and need a closer look later.
🦋 Changeset detectedLatest commit: 260812e The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
mifi
left a comment
There was a problem hiding this comment.
I couldn't find issues myself while reviewing manually, but I ran it through AI and I think I agree with many of the points. Could you take a look? Most important are the "blocking" points. Be sure to test those in a browser to confirm that they are not regressions.
Overview
Replaces the type $TSFixMe = any escape hatch across @uppy/dashboard, @uppy/screen-capture, and one line of @uppy/core with real types: proper props interfaces for ~12 Preact components, typed DOM utils, and a UploadState union. The PR description is upfront that some logic changed and some errors were suppressed rather than fixed.
Genuinely good parts: copyToClipboard and createSuperFocus are cleaner, getUploadingState's as const union is the right call, FOCUSABLE_ELEMENTS.join(',') is exactly equivalent to the old array→string coercion, and dropping the isSizeMD prop is correct — I grepped, nothing consumed it.
But three changes alter runtime behavior in ways the type-only framing hides, and two of them look like regressions.
Blocking
1. ignoreEvent: ev.target → ev.currentTarget breaks paste into inputs — [utils/ignoreEvent.ts](packages/@uppy/dashboard/src/utils/ignoreEvent.ts)
This handler is attached to container <div>s ([FileCard/index.tsx:114-117](packages/@uppy/dashboard/src/components/FileCard/index.tsx:114), [PickerPanelContent.tsx:22-25](packages/@uppy/dashboard/src/components/PickerPanelContent.tsx:22)), so currentTarget is always that div. The tagName === 'INPUT' || 'TEXTAREA' branch becomes dead code and every paste/drop now falls through to ev.preventDefault(). The file's own header comment states the intent — ignore events unless they're in an input — so target is the correct property. Practical effect: pasting a URL into the Url plugin's input or typing/pasting into FileCard meta fields gets cancelled. Revert to ev.target, typed as TargetedEvent<Element> with a (ev.target as Element).tagName read (or EventTarget & { tagName?: string }).
2. CopyLinkButton: event.currentTarget is null by the time the .then() runs — [FileItem/Buttons/index.tsx:105](packages/@uppy/dashboard/src/components/FileItem/Buttons/index.tsx:105)
.then(() => event.currentTarget.focus({ preventScroll: true }))Per the DOM spec currentTarget is only set during dispatch and reset to null afterwards; Preact uses native listeners with no event pooling, so this callback — a microtask after the handler returns — reads null and throws. Since it's chained after .catch(uppy.log), it surfaces as an unhandled rejection and the "avoid losing focus" behavior is lost. event.target survives dispatch, which is why the original worked. Capture it before the async chain instead:
const button = event.currentTarget
// …then: .then(() => button.focus({ preventScroll: true }))3. activePickerPanel: Target | undefined → Target makes the type lie — [Dashboard.tsx:101](packages/@uppy/dashboard/src/Dashboard.tsx:101)
The runtime still assigns undefined in two places ([:346](packages/@uppy/dashboard/src/Dashboard.tsx:346) and [:1346](packages/@uppy/dashboard/src/Dashboard.tsx:1346)). DashboardState is an exported interface, so consumers now get a non-nullable type for a value that is undefined most of the time — and the NonNullable<…> wrappers added in the consumers are silently no-ops. The narrowing at [components/Dashboard.tsx:332](packages/@uppy/dashboard/src/components/Dashboard.tsx:332) is what actually makes the render site safe. Keep | undefined on the state type; the NonNullable<> on the prop types is then meaningful and correct.
Should address
4. Duplicate file-editor:cancel module augmentation — [EditorPanel.tsx:12-16](packages/@uppy/dashboard/src/components/EditorPanel.tsx:12)
@uppy/image-editor already augments UppyEventMap with this exact event ([ImageEditor.tsx:48](packages/@uppy/image-editor/src/ImageEditor.tsx:48)). The two are structurally identical today so merging compiles, but any drift produces TS2717 ("Subsequent property declarations must have the same type") in user code that imports both plugins — a failure the monorepo's own CI won't catch, since dashboard doesn't depend on image-editor. Dashboard emits the event, so the declaration belongs in @uppy/core next to the other core events, with image-editor's entry removed.
5. Three of the eight new @ts-expect-errors are hiding real bugs with one-line fixes
- [FileCard/index.tsx:83](packages/@uppy/dashboard/src/components/FileCard/index.tsx:83) —
toggleFileCard(false)drops the requiredfileID. The implementation callsthis.uppy.getFile(fileID)before branching onshow([Dashboard.tsx:565](packages/@uppy/dashboard/src/Dashboard.tsx:565)), so cancelling emitsdashboard:file-edit-completewithundefined.fileCardForis in scope —toggleFileCard(false, fileCardFor)fixes it. - [MetaErrorMessage.tsx:9](packages/@uppy/dashboard/src/components/FileItem/MetaErrorMessage.tsx:9) — the comment says "This should not be an error", but TS is right:
metaFieldsisMetaField[] | ((file: UppyFile) => MetaField[])and it's being called with no argument, so a user's callback receivesundefined.fileis in scope here too; thread it through tometaFieldIdToName. The second suppression onfields.filterthen also goes away oncemetaFieldsoptionality is handled (andfield[0].namestill needs a guard — it throws on an unknown id). - [PickerPanelTopBar.tsx:45,53](packages/@uppy/dashboard/src/components/PickerPanelTopBar.tsx:45) — "TypeScript catches a runtime issue here" is a misread.
stateis only ever assignedWAITING/PREPROCESSING/POSTPROCESSING(the uploading casereturns early), sostate !== STATE_UPLOADINGis always true. It's dead, not buggy. Delete both clauses — behavior is identical and two suppressions disappear.
The remaining FileCard suppressions all trace to const storedMetaData = {} as M. Keeping the form state as Record<string, string> and casting once at saveFileCard(formState as M, fileCardFor) removes all three.
6. copyToClipboard now actually applies its styles — [utils/copyToClipboard.ts](packages/@uppy/dashboard/src/utils/copyToClipboard.ts)
The old setAttribute('style', {…} as string) stringified to "[object Object]", i.e. no styles: the textarea was appended full-size and select() could scroll the page. The new code is what the original author intended, so this is a fix — but it's a visible behavior change buried in a typing PR and deserves a line in the changeset and a manual check of the copy-link flow. (return resolve() → resolve() is inert; nothing follows in the try.)
7. No changeset. Beyond the runtime changes above, this touches exported types (MetaField now exported, DashboardState.activePickerPanel narrowed, getActiveOverlayEl/createSuperFocus signatures) in three published packages. A patch changeset covering @uppy/core, @uppy/dashboard, @uppy/screen-capture is warranted.
Minor
- [FileCard/index.tsx:51,55](packages/@uppy/dashboard/src/components/FileCard/index.tsx:51) —
files[fileCardFor!]keeps a!on a prop now typedfileCardFor: string. Redundant; drop it. - [EditorPanel.tsx:34](packages/@uppy/dashboard/src/components/EditorPanel.tsx:34) —
props.files[props.fileCardFor!]on aUppyFileId | nullprop. It happens to hold becauseopenFileEditorsets both together ([Dashboard.tsx:396-399](packages/@uppy/dashboard/src/Dashboard.tsx:396)), butfileCardFor: file.id || nulladmitsnull. An earlyif (!file) return nullwould be honest. - [getActiveOverlayEl.ts:11](packages/@uppy/dashboard/src/utils/getActiveOverlayEl.ts:11) — use
querySelector<HTMLElement>(…)instead of the trailingas HTMLElement, matching whatcreateSuperFocus/trapFocusnow do. - [UIPlugin.ts:48](packages/@uppy/core/src/UIPlugin.ts:48) —
el!: …→el: … = nullchanges the pre-mount value fromundefinedtonull. No=== undefinedchecks exist on it, so this is fine and arguably better, but it's an observable change on a public plugin base class. - The three new
this.el!assertions inDashboard.tsxare the direct cost of that change.if (!this.el) returnguards in the twotrapFocuscallers would be truthful rather than assumed.
Test coverage & security
No tests added, and none of the three runtime changes is covered by the existing suite — which is why CI is green despite items 1 and 2. ignoreEvent and the copy-link focus restoration would both be cheap browser-mode tests and are exactly the kind of regression this PR risks. Nothing security-relevant: no auth, network, or user-input-to-DOM paths change, and copyToClipboard still targets the same execCommand/prompt fallback.
Verdict
The direction is right and most of the file-by-file work is solid — I'd merge it happily once items 1–3 are addressed, since those are behavior regressions rather than typing preferences. Items 4–7 are worth resolving before merge too, but they're arguable. Want me to push fixes for the three blocking items to the branch, or leave this as a review comment on the PR?
|
I agree with 1-3, so I resolved them. Quite some issues come from the prop spread pattern 4 is explicitly a good feature, not a bug. 5 is a fair point which I also noticed. These are something someone should have a closer look at who is already more familiar with the code base. 6 is true. Someone more familiar with the code base should have closer look. 7 is resolved The minor issues are resolved. |
- FileCard: `toggleFileCard(false)` dropped the required `fileID`, so `dashboard:file-edit-complete` was emitted with `undefined` — the implementation looks the file up before branching on `show`. Pass `fileCardFor` and remove the suppression. - PickerPanelTopBar: `state` is only ever WAITING/PREPROCESSING/ POSTPROCESSING because the uploading branch returns early, so both `state !== STATE_UPLOADING` checks were always true. Removing them is behaviour preserving and drops two suppressions. - EditorPanel: replace the `fileCardFor!` assertion with an early return, since the prop admits `null`. - FileCard, getActiveOverlayEl: drop a redundant `!` and an `as HTMLElement`. - Mention the two user visible behaviour changes in the changeset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
pushed some more fixes. if you agree then i think this can be merged. |
There are some slight logic changes because of issues revealed by the new type errors that surfaced.
Also some type errors have now been suppressed and need a closer look later.