Skip to content

chore: clear strictNullChecks errors in remaining feature modules#1010

Merged
igboyes merged 3 commits into
mainfrom
igboyes/vir-2404-fix-strictnullchecks-errors-in-remaining-modules-subtraction
Jun 3, 2026
Merged

chore: clear strictNullChecks errors in remaining feature modules#1010
igboyes merged 3 commits into
mainfrom
igboyes/vir-2404-fix-strictnullchecks-errors-in-remaining-modules-subtraction

Conversation

@igboyes
Copy link
Copy Markdown
Member

@igboyes igboyes commented Jun 2, 2026

Summary

  • Fixes all strictNullChecks TypeScript errors across administration, analyses, app, base, groups, hmm, indexes, jobs, labels, nav, quality, subtraction, uploads, and users modules
  • Adds null guards and narrows types throughout (e.g. AdministratorRoleName | null, GroupMinimal | null, proper !data loading guards) to make existing runtime behaviour explicit under strict null checking
  • Expands tsconfig.strictNullChecks.json to include all newly fixed files, enabling the CI gate to enforce strictNullChecks for these modules going forward

Test plan

  • pnpm typecheck passes
  • pnpm check passes with no lint errors
  • pnpm test passes (subtraction, uploads, jobs, and users test suites in particular)

Clears strict-null errors across subtraction, hmm, jobs, uploads, users,
groups, labels, nav, and quality (plus index.tsx) and ratchets each green
file into the tsconfig.strictNullChecks.json allowlist (96 files). The main
tsconfig.json flag stays off until cutover.

The allowlist gate type-checks the full transitive program, so shared
foundation files that the feature components import had to be fixed first:
app/utils.ts, app/hooks.tsx, base/Alert.tsx, base/InputGroup.tsx,
indexes/queries.ts, and administration/{queries,utils,hooks}.ts.

Dominant fix is React Query narrowing (`if (isPending || !data) return
<LoadingPlaceholder/>`), preserving loading-on-error behaviour. Notable
root-cause fixes: server mutation handlers returned `T | undefined` because
their catch blocks ended in a bare `rethrowAsHttp(err)` expression (added
`return`); User.administrator_role was mistyped as non-null (the server type
is already `| null`) and now flows through a null-tolerant
hasSufficientAdminRole and the Nav/Sidebar/AdministrationTabs props.

routes/ files are intentionally left out of the allowlist: they
transitively pull in the samples, references, otus, and analyses feature
modules (owned by separate issues) plus app-wide createFileRoute loader
typing, none of which are null-clean yet. Listed escape hatches are
untouched and no new `as any`/`as never`/`@ts-expect-error` was introduced.
@linear-code
Copy link
Copy Markdown

linear-code Bot commented Jun 2, 2026

VIR-2404

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 85c52147-1578-4d00-955c-33a8a8700e38

📥 Commits

Reviewing files that changed from the base of the PR and between 2a62b18 and fb74dd0.

📒 Files selected for processing (1)
  • apps/web/src/app/hooks.tsx

Summary by CodeRabbit

  • Bug Fixes

    • Cancelled jobs now show orange highlighting.
    • Loading indicators consistently appear when data is absent or pending across lists, dialogs and detail views.
  • Refactor

    • Broadened null-safety for user/admin role and related props to prevent runtime errors.
    • Various type and component adjustments for more robust handling of missing data and improved UI stability.

Walkthrough

This PR widens several prop and type definitions to allow null, tightens loading guards to require both pending flags and data presence, and adds defensive null checks in hooks, utilities, validators, and components. Server label handlers now return mapped HTTP rethrows. Tests and small UI behaviors were adjusted to match the nullable-safe contracts. The tsconfig for the web app was expanded to enable strictNullChecks across the listed sources.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 5 issues, and left some high level feedback:

  • In JobSteps, the steps prop is now typed as JobStep[] | null but the component body still assumes an array; either keep the type non-nullable and normalize upstream, or add a null/empty guard before iterating or rendering.
  • In UploadItem, user is now UserNested | null but the component still appears to assume a non-null user (eg. accessing user.handle); either keep user required or add a fallback rendering path when it is null.
  • In Groups, passing selectedGroupId ?? 0 into useFetchGroup may trigger a fetch for an invalid group ID 0; consider updating the hook to accept undefined/null and skip the query or handling this case explicitly instead of using a sentinel value.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `JobSteps`, the `steps` prop is now typed as `JobStep[] | null` but the component body still assumes an array; either keep the type non-nullable and normalize upstream, or add a null/empty guard before iterating or rendering.
- In `UploadItem`, `user` is now `UserNested | null` but the component still appears to assume a non-null user (eg. accessing `user.handle`); either keep `user` required or add a fallback rendering path when it is null.
- In `Groups`, passing `selectedGroupId ?? 0` into `useFetchGroup` may trigger a fetch for an invalid group ID `0`; consider updating the hook to accept `undefined`/`null` and skip the query or handling this case explicitly instead of using a sentinel value.

## Individual Comments

### Comment 1
<location path="apps/web/src/base/Alert.tsx" line_range="38" />
<code_context>
 			className={cn(
 				"rounded-md shadow-sm font-medium mb-4 overflow-hidden",
-				alertColorStyles[color] ?? defaultColorStyle,
+				(color && alertColorStyles[color]) ?? defaultColorStyle,
 				outerClassName,
 			)}
</code_context>
<issue_to_address>
**issue (bug_risk):** The new `color && alertColorStyles[color]` check changes behavior for falsy `color` values like an empty string.

This means an empty string (or other falsy invalid value) will now evaluate as a valid result, giving `""` instead of `defaultColorStyle`, so no alert classes are applied. If `color` can ever be empty or otherwise invalid, prefer a nullish check:

```ts
alertColorStyles[color as keyof typeof alertColorStyles] ?? defaultColorStyle
```

or explicitly guard against `undefined`/invalid keys while still treating `""` as invalid.
</issue_to_address>

### Comment 2
<location path="apps/web/src/groups/components/Groups.tsx" line_range="38" />
<code_context>
 	}

-	const { data: selectedGroup } = useFetchGroup(selectedGroupId);
+	const { data: selectedGroup } = useFetchGroup(selectedGroupId ?? 0);

-	if (isPendingGroups || (groups.length && !selectedGroup)) {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Using `0` as a fallback group id may trigger an unnecessary or invalid fetch when `selectedGroupId` is `null`.

Passing `0` means `useFetchGroup` will still fire when `selectedGroupId` is `null`, likely requesting a non-existent group and causing a failed/extra request. Since rendering is already guarded on `selectedGroup`, it’d be better to avoid calling `useFetchGroup` at all in this case (e.g., via an `enabled` flag or by passing `undefined` when `selectedGroupId` is `null`).

Suggested implementation:

```typescript
	const { data: selectedGroup } = useFetchGroup(selectedGroupId ?? undefined, {
		enabled: !!selectedGroupId,
	});

```

This change assumes `useFetchGroup` accepts a second options argument with an `enabled` flag (common with React Query-style hooks). If `useFetchGroup` has a different signature, you should instead:
1. Update `useFetchGroup`’s implementation to accept an `options?: { enabled?: boolean }` parameter and wire that into the underlying query hook, or
2. If it already accepts an options object but with a different shape, adapt the second argument accordingly (e.g., `{ query: { enabled: !!selectedGroupId } }`).
</issue_to_address>

### Comment 3
<location path="apps/web/src/subtraction/components/__tests__/SubtractionItem.test.tsx" line_range="71" />
<code_context>

 	it("should correctly render subtractions where jobs=null", async () => {
-		props.job = null;
+		props.job = undefined;
 		props.ready = false;

</code_context>
<issue_to_address>
**suggestion (testing):** Align test description and setup for `jobs=null` vs `undefined`, and consider covering both cases explicitly

The test name still refers to `jobs=null`, but the setup now uses `props.job = undefined`. If `null` and `undefined` are treated differently at runtime, this could hide an edge case. Please either rename the test to match `undefined` or add a separate test that sets `props.job = null` so both values are exercised.

```suggestion
	it("should correctly render subtractions where jobs=undefined", async () => {
```
</issue_to_address>

### Comment 4
<location path="apps/web/src/uploads/components/__tests__/UploadItem.test.tsx" line_range="26-29" />
<code_context>

 	it("should render", () => {
-		renderWithProviders(<UploadItem {...props} />);
+		const user = { id: 1, handle: "bill" };
+		renderWithProviders(<UploadItem {...props} user={user} />);

-		expect(screen.getByText(new RegExp(props.user.handle))).toBeInTheDocument();
+		expect(screen.getByText(new RegExp(user.handle))).toBeInTheDocument();
 		expect(screen.getByText(new RegExp(props.name))).toBeInTheDocument();
 		expect(screen.getByText("10.0 B")).toBeInTheDocument();
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test for `UploadItem` when `user` is `null` to verify the new nullable type handling

Since `user` can now be `UserNested | null`, please add a test where `user={null}` (or the prop is omitted, if supported) and assert the expected UI in that case (e.g., no handle or a fallback label) to cover the nullable path in `UploadItem`.

Suggested implementation:

```typescript
	});

	it("should render", () => {
		const user = { id: 1, handle: "bill" };
		renderWithProviders(<UploadItem {...props} user={user} />);

		expect(screen.getByText(new RegExp(user.handle))).toBeInTheDocument();
		expect(screen.getByText(new RegExp(props.name))).toBeInTheDocument();
		expect(screen.getByText("10.0 B")).toBeInTheDocument();
		expect(screen.getByRole("button", { name: "remove" })).toBeInTheDocument();
	});

	it("should render when user is null", () => {
		renderWithProviders(<UploadItem {...props} user={null} />);

		// The user handle should not be rendered when user is null
		expect(screen.queryByText(new RegExp(props.user.handle))).not.toBeInTheDocument();

		// Other upload data should still be rendered
		expect(screen.getByText(new RegExp(props.name))).toBeInTheDocument();
		expect(screen.getByText("10.0 B")).toBeInTheDocument();
		expect(screen.getByRole("button", { name: "remove" })).toBeInTheDocument();
	});

```

If `UploadItem` renders a specific fallback label when `user` is null (for example, "Unknown user" or "You"), you should update the new test accordingly:
1. Replace the `queryByText`/`not.toBeInTheDocument` assertion with an expectation for that fallback, e.g.:
   `expect(screen.getByText("Unknown user")).toBeInTheDocument();`
2. Ensure the `props` fixture used in this test includes a `user` with a `handle` so that `props.user.handle` is defined for the negative assertion (or inline the string if preferable).
</issue_to_address>

### Comment 5
<location path="apps/web/src/subtraction/components/Detail/__tests__/SubtractionDetail.test.tsx" line_range="93-94" />
<code_context>

 	it("should render file id when name not defined", async () => {
 		const subtraction = createFakeSubtraction({
-			file: { id: 999, name: null },
+			file: { id: 999, name: "" },
 		});
 		const scope = mockApiGetSubtractionDetail(subtraction);
</code_context>
<issue_to_address>
**suggestion (testing):** Clarify and/or extend the test for missing file name now that `name` is an empty string instead of `null`

Since the fixture now uses `name: ""` instead of `null`, the current test name no longer matches precisely. If the code should treat `null` and empty string the same, either adjust the description to mention an empty name, or add a separate test (e.g. `file: { id: 999 }` or `name: null` via cast) so both cases are covered explicitly.

Suggested implementation:

```typescript
	it("should render file id when file name is empty", async () => {
		const subtraction = createFakeSubtraction({
			file: { id: 999, name: "" },
		});
		const scope = mockApiGetSubtractionDetail(subtraction);
		await renderRoute(formatSubtractionPath(subtraction));
		await screen.findByText("999");
	});

	it("should render file id when file name is missing", async () => {
		const subtraction = createFakeSubtraction({
			file: { id: 999 } as any,
		});
		const scope = mockApiGetSubtractionDetail(subtraction);
		await renderRoute(formatSubtractionPath(subtraction));
		await screen.findByText("999");

```

1. Ensure `screen` is imported from `@testing-library/react` at the top of this test file if it is not already present.
2. If the existing test previously had a different assertion style (e.g. `getByText`, `findByText` with additional matchers, or snapshot testing), mirror that same assertion pattern in both tests instead of the `await screen.findByText("999");` shown here.
3. If `createFakeSubtraction` or the `file` type does not accept `name` being omitted, adjust the `as any` cast to the appropriate type or use a `name: null as unknown as string` pattern to represent a `null` name explicitly.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread apps/web/src/base/Alert.tsx
Comment thread apps/web/src/groups/components/Groups.tsx
Comment thread apps/web/src/subtraction/components/__tests__/SubtractionItem.test.tsx Outdated
Comment thread apps/web/src/uploads/components/__tests__/UploadItem.test.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/src/uploads/hooks.ts (1)

37-43: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix useEffect dependencies in apps/web/src/uploads/hooks.ts to track selected-ID changes

The useEffect depends on selected.length and selected.every; selected.every is stable, so the effect won’t rerun when selected IDs swap but the length stays the same, which can leave validation using stale selection state.

Suggested fix
 	}, [
 		data,
-		selected.length,
+		selected,
 		hasNextPage,
 		setSelected,
-		selected.every,
 		isPending,
 		fetchNextPage,
 	]);
apps/web/src/app/utils.ts (1)

141-149: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle malformed sessionStorage payloads defensively.

JSON.parse(item) can throw and crash the read path if storage is corrupted or manually edited. Since this API now models absence with null, parse failures should also return null safely.

Suggested fix
 export function getSessionStorage(key: string): object | null {
 	const item = window.sessionStorage.getItem(key);

 	if (item === null) {
 		return null;
 	}

-	return JSON.parse(item);
+	try {
+		const parsed: unknown = JSON.parse(item);
+		return typeof parsed === "object" && parsed !== null ? (parsed as object) : null;
+	} catch {
+		return null;
+	}
 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8584cd52-f2f1-4258-aa87-a8883126f695

📥 Commits

Reviewing files that changed from the base of the PR and between 5140f53 and dd98394.

📒 Files selected for processing (41)
  • apps/web/src/administration/components/AdministrationTabs.tsx
  • apps/web/src/administration/queries.ts
  • apps/web/src/administration/utils.ts
  • apps/web/src/analyses/components/Pathoscope/PathoscopeOtuCoverage.tsx
  • apps/web/src/app/hooks.tsx
  • apps/web/src/app/utils.ts
  • apps/web/src/base/Alert.tsx
  • apps/web/src/base/InputGroup.tsx
  • apps/web/src/groups/components/Groups.tsx
  • apps/web/src/hmm/components/HmmInstall.tsx
  • apps/web/src/hmm/components/HmmList.tsx
  • apps/web/src/hmm/queries.ts
  • apps/web/src/indexes/queries.ts
  • apps/web/src/jobs/components/JobDetail.tsx
  • apps/web/src/jobs/components/JobStep.tsx
  • apps/web/src/jobs/components/JobSteps.tsx
  • apps/web/src/jobs/components/__tests__/JobDetail.test.tsx
  • apps/web/src/labels/components/Labels.tsx
  • apps/web/src/nav/components/Nav.tsx
  • apps/web/src/nav/components/Sidebar.tsx
  • apps/web/src/quality/components/Sequences.ts
  • apps/web/src/server/labels/functions.ts
  • apps/web/src/subtraction/components/Detail/SubtractionDetail.tsx
  • apps/web/src/subtraction/components/Detail/__tests__/SubtractionDetail.test.tsx
  • apps/web/src/subtraction/components/SubtractionCreate.tsx
  • apps/web/src/subtraction/components/SubtractionItem.tsx
  • apps/web/src/subtraction/components/SubtractionList.tsx
  • apps/web/src/subtraction/components/__tests__/SubtractionItem.test.tsx
  • apps/web/src/uploads/components/FileManager.tsx
  • apps/web/src/uploads/components/UploadBar.tsx
  • apps/web/src/uploads/components/UploadItem.tsx
  • apps/web/src/uploads/components/UploaderDialog.tsx
  • apps/web/src/uploads/components/__tests__/UploadItem.test.tsx
  • apps/web/src/uploads/hooks.ts
  • apps/web/src/users/components/PrimaryGroup.tsx
  • apps/web/src/users/components/UserDetail.tsx
  • apps/web/src/users/components/UserGroups.tsx
  • apps/web/src/users/components/UserItem.tsx
  • apps/web/src/users/components/UsersList.tsx
  • apps/web/src/users/types.ts
  • apps/web/tsconfig.strictNullChecks.json

Comment thread apps/web/src/base/Alert.tsx
Comment thread apps/web/src/subtraction/components/SubtractionCreate.tsx
Comment thread apps/web/src/subtraction/components/SubtractionList.tsx
Comment thread apps/web/src/uploads/components/FileManager.tsx
Comment thread apps/web/src/users/components/UserDetail.tsx
Comment thread apps/web/src/users/components/UserGroups.tsx
Comment thread apps/web/src/users/components/UsersList.tsx
igboyes added 2 commits June 2, 2026 16:37
- narrow Alert color prop to the shared palette union and add gray/purple
  styles per the base-component color convention
- guard getSessionStorage JSON.parse against corrupted storage
- fix useValidateFiles effect deps (use selected, not selected.length /
  selected.every)
- correct test names and assert the UploadItem null-user fallback
@igboyes igboyes merged commit 8c3055a into main Jun 3, 2026
11 checks passed
@igboyes igboyes deleted the igboyes/vir-2404-fix-strictnullchecks-errors-in-remaining-modules-subtraction branch June 3, 2026 16:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant