Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .react-doctor/false-positives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# React Doctor review predicates

These are source-backed review notes, not scanner suppressions. Recheck every
predicate when the referenced code changes. No rule is disabled by this file.

## Hydration of catalogue offsets

`react-doctor/no-hydration-branch-on-browser-global` at `ProjectListSection`
and the collections directory is a false positive only while both use
`useCatalogueOffset`. That hook must keep its `useHydrated()` gate: the initial
server and hydration snapshot use the fixed display-slot offset, and browser
storage is read after hydration. `apps/web/tests/catalogue-offset-hydration.test.tsx`
checks both catalogue identities with a conflicting stored offset and asserts
that hydration reports no recoverable error. Do not exempt direct render-time
storage access elsewhere.

## Native select names supplied by FieldLabel

`react-doctor/control-has-associated-label` on `ad-tier`, `ad-status` and
`ad-order-status` is a false positive while each select's ID matches its adjacent
`FieldLabel.htmlFor`. The shared FieldLabel renders a native label. The advertising
admin and order tests exercise these controls using their accessible labels.

## Member project form drafts

`react-doctor/no-derived-useState` in `MemberProjectEditor` is intentional only
while its caller in `src/routes/_main/managed/$id.tsx` keys the editor by both
project ID and revision. The editor owns unsaved input; a new project/revision
remounts it. A continuously mounted editor receiving a different project would
need a new review.

## Mutation cache ownership

The member editor's `react-doctor/query-mutation-missing-invalidation` finding
does not account for its `save` handler, which awaits `mutateAsync`, catalogue
invalidation, ownership-key invalidation and router invalidation. Those steps
must remain on the successful save path.

The project report mutation returns a receipt and closes the reporting viewer's
dialog; that viewer has no report-inbox query. Do not add admin-only cache reads
or invalidation solely to silence the detector. Review again if the reporting
viewer starts displaying a cached report list.

## Authentication return destination

`react-doctor/clickjacking-redirect-risk` in `src/routes/auth/route.tsx` is a false
positive only while the destination passes through `resolveReturnTo` immediately
before redirecting. That resolver rejects external origins, protocol-relative
paths, backslashes, control characters, malformed encodings and auth loops. The
malicious-destination cases in `apps/web/tests/sign-in-form.test.tsx` protect this
contract. This does not exempt unrelated redirects or framing configuration.

## JSON serialization contract test

The `JSON.parse(JSON.stringify(result))` assertion in
`apps/web/tests/public-content-prepare.test.ts` verifies that the public content
payload survives JSON serialization unchanged. It is not an application deep
clone. Replacing it with structuredClone would remove the serialization check.

## Static text line keys

Index keys on fitted OG text lines are used by a one-shot static image renderer,
with no browser reconciliation or row-local state. Commit-message lines likewise
belong to an immutable commit keyed by commit identity. These predicates do not
exempt editable, reorderable, filterable or stateful lists. The interactive
catalogue pagination and GitHub statistics now use semantic keys.
1 change: 1 addition & 0 deletions apps/web/src/components/block-note/code-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ export const BNCustomCodeBlock = createReactBlockSpec(

return {
...baseSpec.implementation,
renderFrame: undefined,
render: CustomCodeBlock,
toExternalHTML: CodeBlockExternalHTML,
}
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/block-note/schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BlockNoteSchema, SyntaxHighlightingExtension } from '@blocknote/core'
import { BlockNoteSchema } from '@blocknote/core'
import type { BlockNoteEditor, InlineContent } from '@blocknote/core'
import { SyntaxHighlightingExtension } from '@blocknote/core/extensions'

import {
BNCustomCodeBlock,
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/category-combobox-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export const CategoryComboboxView = ({
const items = categories
.filter((category) => category.isLeaf)
.map((category) => category.slug)
const stale = value.filter((slug) => !items.includes(slug))
const itemSlugs = new Set(items)
const stale = value.filter((slug) => !itemSlugs.has(slug))

return (
<div className="space-y-2">
Expand Down
Loading
Loading