Repository navigation
Library: the library is written into the page, not fetched after it - #222
Conversation
…r it /library/wolf answered with a shell. The title, the description and the schema.org list were in the first HTML; every word a reader could read arrived later by script. A crawler that runs no scripts, which is most of them and every AI one, indexed "Loading", "0 Books" and "No tags yet" on a page elea.co, the CV and LinkedIn link to as proof of 165 read books. The read the browser made is now made once on the request, anonymously, so the shelves, the books, the About text and the tags leave the server in the response. A private shelf never reaches the page source, even on the owner's own request, and the browser still re-reads on mount, silently, so an owner's own view arrives without the shelves blinking out. A book's address is now that book: its own title, a description drawn from the owner's note, its cover as the shared image, the note itself in the page, and a Book entry whose review carries the note and the rating. Before this 165 addresses answered with the library's own title and picture. A dialog cannot be server-rendered, so Modal stands down where there is no document and the browser opens it on arrival; notes render through an escape first, allow after sanitizer that needs no DOM. /library-sitemap.xml lists every public library and every object on a public shelf, none of which Strapi's own sitemap knows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 8m 56s —— View job Review: Library server-rendered page (#222)Overall: solid, well-scoped fix for the stated crawler problem. The anonymous, single-read Found one substantive issue (posted inline on The new hand-rolled server-side sanitizer's Minor, non-blocking observations (not posted inline):
Not verified: I wasn't able to run |
| const SIMPLE_TAGS = [ | ||
| 'strong', | ||
| 'b', | ||
| 'em', | ||
| 'i', | ||
| 's', | ||
| 'p', | ||
| 'br', | ||
| 'ul', | ||
| 'ol', | ||
| 'li', | ||
| ]; |
There was a problem hiding this comment.
SIMPLE_TAGS doesn't actually match the note dialect it claims to implement.
The client renders the same description field via toEditorHtml (src/lib/library/richText.ts), whose EDITOR_TAGS is ['p','br','strong','em','s','b','i','strike','del','a']. This list is missing strike and del but adds ul/ol/li (which aren't in EDITOR_TAGS, and ObjectArticle.module.scss has no list styling for them either).
Concretely: a note that still contains <strike> or <del> (e.g. legacy data saved before toEditorHtml's normalization pass, or any value written some other way) will render as strikethrough in the client dialog (ObjectOverviewModal) but show up as escaped, visible <strike>text</strike> literal text on this server-rendered book page — the two renders of the same note disagree, which undercuts the PR's stated goal that the object page renders exactly what the owner wrote.
Also worth double-checking: isomorphic-dompurify is already a dependency and is already called from plain (non-'use client') lib code in this same folder — descriptionLinks.ts's linkDescriptionUrls calls DOMPurify.sanitize(html, { RETURN_DOM_FRAGMENT: true, ... }), which requires DOM support and evidently works in that context. If it also works from getServerSideProps, reusing it here (with ALLOWED_TAGS sourced from richText.ts's EDITOR_TAGS) would remove the need for a second, hand-maintained regex-based sanitizer for the same security-sensitive content, and the two renders could never drift apart again. I didn't find an actual escape in the regex approach as written, but duplicating an allowlist that's meant to be exhaustive (src/lib/library/richText.ts's comment: "Everything that reaches the editor or leaves it passes through here") is exactly the kind of drift this codebase's own conventions warn about elsewhere (e.g. the tag/description character caps are deliberately read from one shared constant "so the counter and the validator cannot drift apart").
CLAUDE.md had grown to 624 lines, about 430 of them Library passports, contracts and release gates that every session on this repo paid for whether or not it touched the Library. They now live in LIBRARY.md at the root, in their original order and wording, and CLAUDE.md keeps a seven-line pointer that says when to read it. Not imported on purpose: the file is read when the Library is the work. Every non-blank line of the old file is in one of the two new ones; the split was checked line by line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 2m 46s —— View job Review: Library server-rendered page (#222) — updated after new commit
What changed since the last review: one new commit, Split fidelity: diffed the pre-split New finding (posted inline on Prior findings — still open, unaffected by this commit: since no code files changed, my earlier review stands as-is. In particular, Not run: |
|
|
||
| Everything about the Library lives in `LIBRARY.md` at the repo root: every | ||
| passport, contract, release gate and the MCP. Read it before touching anything | ||
| under `src/*/library`, `src/pages/library`, `mcp/library` or the Library CMS |
There was a problem hiding this comment.
This pointer paragraph references mcp/library and "the MCP" — neither exists in this repo. git ls-files | grep '^mcp/' returns nothing, and searching the pre-split CLAUDE.md (commit 6e03ebb) and the new LIBRARY.md for "mcp" turns up only the unrelated CodeGraph MCP server mention. If this is describing a path that hasn't landed yet, it'll send a future agent looking for a directory that isn't there; if it's a leftover from another repo's layout, it should be dropped from the pointer.
The allowlist now equals EDITOR_TAGS in richText.ts less the anchor, which is rebuilt with its address checked: strike and del pass, lists do not, so the dialog and the book page never read one note two ways. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 1m 10s —— View job Review: Library server-rendered page (#222) — updated after new commit
What changed since the last review: one new commit, This resolves my earlier finding. The rest of the sanitizer is unchanged: escape-everything-first then allowlist-restore, Prior finding — still open, unaffected by this commit (docs-only, not re-flagged inline since nothing changed): Not run: |
|
Review dispositions, commit 3f8480f:
|
/library/wolfanswered with a shell: the title, the description and the schema.org list were in the first HTML, and everything a reader could actually read arrived later by script. A crawler that runs no scripts, which is most of them and every AI one, indexedLoading,0 BooksandNo tags yeton a page elea.co, the CV and LinkedIn link to as proof of 165 read books. Reported by The Order, 2026-09-10.What changed
getPublicLibraryView), and seeds the providers and the template. Shelves, books, About, Author, counts and tags leave the server in the response. A private shelf never reaches the page source, even on the owner's own request; the browser still re-reads on mount, silently.objectSeo): its own title, a description drawn from the owner's note, its cover as the shared image, and aBook/VideoObject/AudioObjectentry whosereviewholds the note and the rating. Before this, 165 addresses answered with the library's own title and picture.ObjectArticle, with a passport in CLAUDE.md.Modalstands down where there is no document. An object address opened its dialog mid-render once the shelves arrived server-side, andcreatePortal(…, document.body)took the whole response down with it. Notes render through an escape-first, allow-after sanitizer that needs no DOM./library-sitemap.xml: every public library and every object on a public shelf. Strapi's sitemap plugin knows none of these URLs; on 2026-09-10 not one was insitemap.xml.Proof
Built and served locally (
next start), read with curl against the staging CMS:/library/<user>Loading,0 Books,No tags yet/library/<user>/<book>Book+review+ rating, the note in the page/library-sitemap.xmlNo server errors across five routes including a library that does not exist. Weight: Wolf's page goes from 95 kB / 22.5 kB gzip to 800 kB / 77 kB gzip, which is what 165 server-rendered cards cost. Covers are trimmed to the fields the cards read and notes travel only with the object the URL names, which is what keeps it to that.
NOT DONE: the
robots.txtserved on keepsimple.io is not this repo's file (it carries aSitemap:line the repo never had), so the new sitemap reaches production's robots only through The Order. Both lines are inpublic/robots.txthere. Google Search Console and Bing still need the sitemap submitted once it is live.🤖 Generated with Claude Code