Compare commits

...

2 Commits

Author SHA1 Message Date
Ben Stull 018e323ed4 Release 0.2.2: Philosophy.jsx now renders mermaid (uses MarkdownPreview)
Philosophy.jsx parsed PHILOSOPHY.md with bare marked + dangerouslySet
InnerHTML, so ```mermaid fences fell through as <pre> blocks. Swapped
to MarkdownPreview, which already carries the lazy mermaid loader +
SVG render path used by the RFC body view. Pre-existing gap, surfaced
when an OHM deployment authored a mermaid block in its PHILOSOPHY_PATH
override.

PATCH per SPEC §20.2 — additive, no operator action required beyond
the routine rebuild + restart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 13:31:49 -07:00
Ben Stull 0e1805b8ce 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.
2026-05-26 08:50:48 -07:00
6 changed files with 60 additions and 19 deletions
+41
View File
@@ -23,6 +23,47 @@ skip versions are the composition of each intervening adjacent
release's steps in order — no A-to-B path is pre-computed beyond release's steps in order — no A-to-B path is pre-computed beyond
that. that.
## 0.2.2 — 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 beyond the fix below.
### Fixed
- **Mermaid blocks rendered as raw code on `/philosophy`.**
`frontend/src/components/Philosophy.jsx` parsed PHILOSOPHY.md with
the bare `marked` import and `dangerouslySetInnerHTML`, so
```` ```mermaid ```` fences fell through as `<pre>` blocks rather
than rendered diagrams. Swapped to `MarkdownPreview`, which already
carries the lazy mermaid loader + SVG render path used by the RFC
body view, so the philosophy surface now renders mermaid the same
way RFC bodies do. Pre-existing gap, surfaced when a deployment
authored a mermaid block in its `PHILOSOPHY_PATH` override.
## 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 ## 0.2.0 — 2026-05-26
**Breaking config change.** The frontend now requires **Breaking config change.** The frontend now requires
+1 -1
View File
@@ -1 +1 @@
0.2.0 0.2.2
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "rfc-app-frontend", "name": "rfc-app-frontend",
"version": "0.1.0", "version": "0.2.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "rfc-app-frontend", "name": "rfc-app-frontend",
"version": "0.1.0", "version": "0.2.1",
"dependencies": { "dependencies": {
"@codemirror/commands": "^6.10.3", "@codemirror/commands": "^6.10.3",
"@codemirror/lang-markdown": "^6.5.0", "@codemirror/lang-markdown": "^6.5.0",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "rfc-app-frontend", "name": "rfc-app-frontend",
"private": true, "private": true,
"version": "0.2.0", "version": "0.2.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+13 -11
View File
@@ -143,6 +143,19 @@ export default function PRView({ viewer }) {
} }
}, [slug, prNumber, reviewText, reviewDraft, refresh]) }, [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 ───────────────────────────────────────────────────── // ── render ─────────────────────────────────────────────────────
if (error) return <article className="entry-view"><p>Error: {error}</p></article> if (error) return <article className="entry-view"><p>Error: {error}</p></article>
if (!pr) return <article className="entry-view">Loading PR</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 supersededBy = pr.superseded_by_pr_number
const supersedes = pr.supersedes_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 seenSha = pr.seen?.last_seen_commit_sha
const seenMsgId = pr.seen?.last_seen_message_id || 0 const seenMsgId = pr.seen?.last_seen_message_id || 0
+2 -4
View File
@@ -15,7 +15,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { marked } from 'marked' import MarkdownPreview from './MarkdownPreview.jsx'
import { getPhilosophy } from '../api.js' import { getPhilosophy } from '../api.js'
export default function Philosophy({ authenticated }) { export default function Philosophy({ authenticated }) {
@@ -33,8 +33,6 @@ export default function Philosophy({ authenticated }) {
return () => { active = false } return () => { active = false }
}, []) }, [])
const html = body ? marked.parse(body) : ''
return ( return (
<div className="philosophy-page"> <div className="philosophy-page">
<header className="philosophy-header"> <header className="philosophy-header">
@@ -53,7 +51,7 @@ export default function Philosophy({ authenticated }) {
{loading && <p className="muted">Loading</p>} {loading && <p className="muted">Loading</p>}
{error && <p className="error">Could not load the philosophy: {error}</p>} {error && <p className="error">Could not load the philosophy: {error}</p>}
{!loading && !error && ( {!loading && !error && (
<div dangerouslySetInnerHTML={{ __html: html }} /> <MarkdownPreview content={body} />
)} )}
</article> </article>
</div> </div>