Release 0.2.1: PRView Rules-of-Hooks fix (React #310 blank page)
PRView.jsx declared its threadsByKind useMemo after the early-return guard for the loading state. First mount: pr is null, early return fires, 14 hooks called. Second render: pr populated, execution reaches the useMemo, 15 hooks — React #310, page goes blank. Move the useMemo above the early returns and null-safe the pr?.threads access so the hook count stays stable across renders. Pre-existing bug in the post-Contribute-rewrite PR view; surfaced in production by 0.2.0's graduation merge race fix enabling the workflow that opens this code path. Patch per §20.2 — no operator action required beyond rebuilding the frontend and restarting.
This commit is contained in:
@@ -23,6 +23,28 @@ skip versions are the composition of each intervening adjacent
|
||||
release's steps in order — no A-to-B path is pre-computed beyond
|
||||
that.
|
||||
|
||||
## 0.2.1 — 2026-05-26
|
||||
|
||||
**Patch — no operator action required.** Rebuild the frontend and
|
||||
restart per the routine deploy steps; no `.env` changes, no schema
|
||||
changes, no behavior changes a deployment would notice in steady
|
||||
state.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **PR view blank page (React #310).** `frontend/src/components/PRView.jsx`
|
||||
declared its `threadsByKind` `useMemo` *after* the early-return
|
||||
guard for the loading state (`if (!pr) return …`). On first mount
|
||||
`pr` was null so the early return fired and 14 hooks were called;
|
||||
on the second render `pr` was populated and execution reached the
|
||||
`useMemo`, calling 15 hooks — violating the Rules of Hooks and
|
||||
unmounting the page subtree (blank screen). The `useMemo` is now
|
||||
declared above the early returns with optional-chaining on `pr`,
|
||||
keeping the hook count stable between the loading and loaded
|
||||
renders. Pre-existing bug in the post-Contribute-rewrite PR view;
|
||||
surfaced in production by 0.2.0's graduation merge race fix
|
||||
enabling the workflow that opens this code path.
|
||||
|
||||
## 0.2.0 — 2026-05-26
|
||||
|
||||
**Breaking config change.** The frontend now requires
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "rfc-app-frontend",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "rfc-app-frontend",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.1",
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "^6.10.3",
|
||||
"@codemirror/lang-markdown": "^6.5.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "rfc-app-frontend",
|
||||
"private": true,
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -143,6 +143,19 @@ export default function PRView({ viewer }) {
|
||||
}
|
||||
}, [slug, prNumber, reviewText, reviewDraft, refresh])
|
||||
|
||||
// §10.6: split messages by thread kind for the visual distinction
|
||||
// §10.4 requires. Within each kind, sort by id for chronological
|
||||
// order. Declared above the early returns below so the hook count
|
||||
// stays stable between the loading render (pr == null) and the
|
||||
// loaded render (Rules of Hooks — React #310 otherwise).
|
||||
const threadsByKind = useMemo(() => {
|
||||
const out = { chat: [], review: [], flag: [] }
|
||||
for (const t of pr?.threads || []) {
|
||||
out[t.thread_kind]?.push(t)
|
||||
}
|
||||
return out
|
||||
}, [pr?.threads])
|
||||
|
||||
// ── render ─────────────────────────────────────────────────────
|
||||
if (error) return <article className="entry-view"><p>Error: {error}</p></article>
|
||||
if (!pr) return <article className="entry-view">Loading PR…</article>
|
||||
@@ -154,17 +167,6 @@ export default function PRView({ viewer }) {
|
||||
const supersededBy = pr.superseded_by_pr_number
|
||||
const supersedes = pr.supersedes_pr_number
|
||||
|
||||
// §10.6: split messages by thread kind for the visual distinction
|
||||
// §10.4 requires. Within each kind, sort by id for chronological
|
||||
// order.
|
||||
const threadsByKind = useMemo(() => {
|
||||
const out = { chat: [], review: [], flag: [] }
|
||||
for (const t of pr.threads || []) {
|
||||
out[t.thread_kind]?.push(t)
|
||||
}
|
||||
return out
|
||||
}, [pr.threads])
|
||||
|
||||
const seenSha = pr.seen?.last_seen_commit_sha
|
||||
const seenMsgId = pr.seen?.last_seen_message_id || 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user