Repository navigation
Conversation
Books on the public shelves of the shared libraries (Wolf's, for now) are offered ahead of Google Books and Open Library while a title is typed in the Add book wizard. A member row carries title, author, date, source URL and the owner's own cover through the cover proxy, never the notes, rating or difficulty. It stands in a soft blue ring and says on hover whose library it comes from. Provider rows that repeat a member title are dropped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…napshot Additive, idempotent: shelves by name, books by slug, covers by production hash through the staging upload API, links by SQL through keepsimple-ctl. Journals every run. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The owner writes a description of up to 180 characters in the Add shelf and Edit shelf forms. When one is set, a (?) mark after the shelf name says it on hover through the shared Tooltip, for owner and visitor alike; with nothing written there is no mark. Saving sends only what changed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Its description lives on the library, saved the way its privacy is. The owner opens it from the shelf name; the form shows the description alone, and the same (?) mark answers on hover once it is set. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A lamp beside the search turns the light down and up. The choice lives in a cookie the pages read on the server, so a dark reader never sees a light flash, and is mirrored onto <html> for surfaces portaled to <body>. Every paper token gets a night value in themes.scss; the hardcoded colors that would have ignored it become tokens; the wood plank photograph is dimmed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The rename modal reads name and description back from the update response and reports a failed save when the CMS kept neither, as the Favorites path already did. The (?) glyph takes its face from the font token. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…#218) Library: shared memory, shelf descriptions, staging mirror
The edit form draws one slice per shelf in the tag's own colour, biggest share first, with the percentage beside each shelf name and the total in the ring's eye. Resting on a slice or its legend row names the books that shelf holds under this tag, in the shelf's own sequence. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A share under a percent is spoken as it is drawn, the legend steps back as far as the ring does, and the slices are laid out inside the room the gaps leave, so a shelf too thin to see can never be drawn over its neighbour. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…eared useAnimatedList held a leaver until its node finished fading, but a list put aside by a tag filter has no node: the leaver was never dropped and came back as a live shelf, in full, the moment the filter cleared, while the toolbar still counted the search. The hook now refuses to queue a departure while its container is off the page, and lets go of any leaver whose container leaves. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The name field was held to Strapi's uid character class, a leftover from when the client stamped its own slug: it refused "To begin" and every other name with a space. The CMS derives the address from the name itself, so the name only has to be a word the owner recognises. Spaces, accents, punctuation and any script now pass; the name is trimmed and the field stops at the shared cap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
keepsimple already carries a dark and light switch in its navbar; the Library had grown a second one in its toolbar, a second answer to the same question. The lamp, its cookie and the theme state are gone: the night tokens now key off the `darkTheme` class the site's switch puts on <body>, which also covers the surfaces portaled there. The passport says the Library ships no switch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The class list uses the shared helper, and the global entry no longer describes a mechanism the Library does not have any more. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Library: a tag says where it is used as a ring
Every label, counter, hint and error in the Library is ink that was black at a fraction, or a signal colour tuned for paper: on night paper they sat at 1.1:1 to 3.7:1, which is what the tag form, the rating box, the About editor and the object forms all showed. The night sheet now carries light ink at a fraction, lifted reds, green and purple, a stronger dim behind a dialog, and one error colour that pointed at a token this project never had. The Libraries page kept its daylight paper because the sheet is a canvas tile handed to the element inline, with washes that multiply: at night the tile is put away and the flat night sheet carries the page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Answers the review: --gray-medium was set twice in the night sheet, so the first line was dead weight. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Library: the night sheet reads everywhere it is written on
…222) * feat(library): the library is written into the page, not fetched after 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> * docs: the Library charter moves to LIBRARY.md 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> * fix(library): the server reads a note in the editor's own dialect 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> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(library): five tools an agent may hold a library by The backend already knew how to do this: a tag has a name, a description, a colour and its own order of books, and a book has a note and a rating. What was missing was a key a machine may hold and a wrapper that reads as tools. Books and tags are named the way a person names them, by title or by word, and a word that matches two books is refused with both rather than guessed at. A partial order is enough: what is named takes the front and the rest keep their sequence behind it. Nothing here deletes a book, a shelf or a tag; that asks the owner first, in the Library. The key buys the owner's ordinary session and no exemption: the same ownership checks, feature flag and limits decide every write, and a call that would reach another library fails here before it is sent. Every call leaves one line in logs/library-mcp.jsonl, the key and the session never in it. probe.mjs runs all five against a live library and takes back every write it makes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(library): the journal names the caller and carries the instruction A production write through the MCP is the owner's word relayed. Each write tool now takes an optional instruction, the words the change was asked in, and every journal line names the client that asked, from the handshake. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(library): the MCP answers the review The limits are read from the constants the forms read, so they cannot drift; a new tag is refused locally at the thirteen-tag cap; tagging many books answers for each by name and fails as a whole only when nothing changed; the post-write check folds whitespace before comparing a note and puts no value in its message, and the journal cuts details as it cuts arguments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
Claude finished @manager's task in 5m 13s —— View job Review complete
SummaryLarge, well-scoped release batch (shared-memory title search, tag usage ring, free-text tag names, dark theme tokens, object-level SSR pages + sitemap, Library MCP). Overall quality is high — SSR seeding via lazy Inline comment posted:
Not blocking, worth a look:
No violations found of App Router patterns, Tailwind/CSS-in-JS, new state libraries, global CSS import location, |
| {sourceUrl && ( | ||
| <a | ||
| className={styles.link} | ||
| href={sourceUrl} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| > | ||
| Source | ||
| </a> | ||
| )} |
There was a problem hiding this comment.
Unsanitized sourceUrl used as href on a publicly crawlable page.
sourceUrl is rendered directly as an anchor href with no scheme check. For books, the field isn't reachable through the manual add form (addObjectSchema.ts has no sourceUrl for books, and AddObjectModal.config.ts omits it from book.fields), so today it can only be populated by the trusted autofill providers or, now, the new "member library" search (src/pages/api/library/autofill/book.ts), which forwards o.attributes?.sourceUrl from another owner's stored object as‑is. Anything that can be written directly through the CMS admin/API (bypassing the frontend zod schema) — e.g. a javascript: URL — would render as a real link on this new server-rendered, crawler-facing object page (/library/[username]/[[...object]]), and any visitor who clicks "Source" would execute it in the page's origin.
Video/audio already have the same pattern in ObjectOverviewModal.tsx (titleHref), gated by a URL_REGEX at input time — so this is pre-existing exposure being extended to a new, more widely reachable surface (public SSR page vs. an authenticated dialog).
This PR already ships a safeHref-style scheme allowlist in src/lib/library/noteHtml.ts (/^https?:\/\//i check) — worth reusing the same guard here (and possibly in ObjectOverviewModal) before rendering sourceUrl as a live href, rather than trusting upstream validation that a CMS-side edit can bypass.
Authorized by Wolf on 2026-09-10.
Depends on keepsimple-cms-new dev → main landing first (description columns). Merged only after staging serves this commit and the CMS release is live on production.
🤖 Generated with Claude Code