Skip to content

library: source right panel by ownership and edit before a library exsts#141

Merged
MaryWylde merged 7 commits into
devfrom
feat/library
Jun 10, 2026
Merged

library: source right panel by ownership and edit before a library exsts#141
MaryWylde merged 7 commits into
devfrom
feat/library

Conversation

@MaryWylde

Copy link
Copy Markdown
Contributor

Decide the Sidebar panel by whether the viewed library is mine: my own shows my account identity and is editable, anyone else's (or logged out) shows that library's public data only. Ownership now matches the loaded owner's account id/username, and stale owner data is dropped on navigation so my identity never bleeds onto someone else's page.

Let a permitted owner edit their About panel before any library row exists — the row is bootstrapped lazily on first save (like adding the first shelf), and the page reloads by direct id instead of the restricted owner relation-filter.

MaryWylde and others added 5 commits June 9, 2026 21:15
…ists

Decide the Sidebar panel by whether the viewed library is mine: my own
shows my account identity and is editable, anyone else's (or logged out)
shows that library's public data only. Ownership now matches the loaded
owner's account id/username, and stale owner data is dropped on navigation
so my identity never bleeds onto someone else's page.

Let a permitted owner edit their About panel before any library row
exists — the row is bootstrapped lazily on first save (like adding the
first shelf), and the page reloads by direct id instead of the restricted
owner relation-filter.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Owner-only flow to mint a share link for a curated set of public-shelf
objects: a context-backed selection (one link spans the whole library,
capped at 21 objects), a Select chip on the cards, a ShareSelectionPanel
to reorder/remove/clear, and a SharedWithYou modal for the recipient.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the single `[username].tsx` page with an optional catch-all so
`/library/[username]` and `/library/[username]/[slug]` share one module.
The opened object is addressed by the URL (slugified title + id), and
open/close are shallow `router.push` transitions so the overview modal
appears over the already-loaded library with no refetch or remount.
The shelf that actually holds the object resolves it from the slug's
trailing id, keeping its real shelf context.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`/library/[username]/share/[token]` is a literal sibling of the object
catch-all (and wins for that path). It resolves the token via
getShareLink and renders the curated objects in the SharedWithYou view.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The dropdown sourced options from a global `/single-shelves` fetch, so
it offered every user's public shelves (and the backend then 403s the
move). Drive it from the viewed library's own shelves (currentShelves
in GlobalState), filtered to the object's type, and exclude the object's
current shelf — falling back to defaultShelfId since a PUT response
doesn't populate the shelf relation. Disable the dropdown when no
eligible shelf remains.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@MaryWylde

Copy link
Copy Markdown
Contributor Author

@claude, review this PR.

@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown

Claude finished @MaryWylde's task in 3m 8s —— View job


PR Review — library: source right panel by ownership and edit before a library exists

Tasks

  • Load tools and set up
  • Read changed files and analyze PR
  • Review core layout and sidebar logic
  • Review new API functions
  • Review new components (ShareSelectionPanel, SharedWithYouModal)
  • Review routing changes
  • Post final review

Summary

Solid, well-scoped PR. The ownership model is clearly reasoned, the lazy-bootstrap pattern is clean, and the share-link architecture (discriminated-union status, reused panel component for owner vs. recipient) is good design. One item must be removed before merge; a few things are worth addressing.


Must Fix

Debug effect left in Sidebar — src/components/library/organisms/Sidebar/Sidebar.tsx:311–326

// TEMP DEBUG: verify whether the authenticated `filters[user][id]` relation
// query actually resolves the owner's library in the browser...
useEffect(() => {
  if (!accountData?.id) {
    console.log('[myLibrary debug] no accountData.id yet', { accountData });
    return;
  }
  getMyLibrary(accountData.id).then(lib => {
    console.log('[myLibrary debug] getMyLibrary result', { ... });
  });
}, [accountData?.id, accountData?.username, accountData]);

The comment says "Remove once the backend exposes user on /api/libraries." This fires on every auth'd page load, makes an extra network request, and logs full account data to the console. Remove it now — the decision point it was probing (whether the slug-based or id-based ownership check is used) has already been made in the code above. Fix this →


Important

New utilities placed in src/utils/ instead of src/lib/

src/utils/library/mapSharedObject.ts and src/utils/library/objectSlug.ts are both new files placed in the legacy src/utils/ tree. Per project conventions (AGENTS.md), new utilities go in src/lib/. These should live at src/lib/library/mapSharedObject.ts and src/lib/library/objectSlug.ts respectively. Fix this →

Stale selection when a shelf changes to private

In Shelf.tsx:469, the Select chip is gated on visibility === 'public'. But when an owner changes a shelf from public → private, already-selected objects from that shelf stay in ShareSelectionContext and will be silently rejected by the backend's "non-public objects" guard when createShareLink is called. The ShareSelectionPanel will either show the link generation fail state with no explanation, or succeed with fewer items than shown. Consider purging objects belonging to the shelf from selectedObjects in handleSettingsChange when switching to private. Fix this →


Minor / Suggestions

Two independent ownership checks that could diverge

LibraryTemplate computes isOwner (username-only match) and Sidebar computes isMyLibrary (id + username + URL-slug fallback). These gate different UI surfaces but describe the same concept. If the backend ever returns an id that doesn't match accountData.id (the comment notes the id-space concern), the two checks will disagree — the library content area shows owner controls while the sidebar doesn't (or vice versa). Centralizing the ownership decision into GlobalStateContext (a single isOwner boolean published by LibraryTemplate) would make it impossible for them to diverge.

currentLibraryId URL-slug fallback is unreliable on nested routes — Sidebar.tsx:77,140

const currentLibraryId = pathname?.split('/').pop() || '';

On the share route /library/[username]/share/[token], pop() returns the token, not the username. The isMyLibrary check then falls through to:

currentLibraryId.toLowerCase() === viewerUsername

…which compares the token against the username. The match will nearly always be false, which is the correct outcome, but only by accident. The id/username branches in the same check are reliable; the URL-slug fallback on this route is a no-op that reads as intentional logic. At minimum, add a comment; ideally, scope this fallback to only apply when pathname matches the base library route pattern.

"Select shelf" button is a no-op — Shelf.tsx:401–409

<Button
  label="Select shelf"
  ariaLabel="Select shelf"
  onClick={() => {}}
  ...
/>

This button is visible to all users (owner and visitor alike) but does nothing. If the feature isn't ready, either hide it behind isOwner with a TODO, or remove it entirely to avoid user confusion.

getServerSideProps vs getStaticProps[[...object]].tsx and share/[token].tsx

The page handlers extract only URL params from context (no auth check, no data fetch). AGENTS.md recommends getStaticProps with revalidate: 10 for new pages. For these two pages, static props + catch-all fallback: 'blocking' would be equally correct and cheaper. That said, SSR isn't wrong here — just noting it for awareness.

TODO(backend) comments in createShareLink.ts and getShareLink.ts

Both files carry // TODO(backend): pin the exact success shape comments that gate removing the multi-fallback probe logic. These should be tracked in the issue tracker rather than left as inline TODOs — inline TODOs in library feature code have a habit of outliving their context.


What's Good

  • The setLibrary(null) before loading a new library (Library.tsx:122) is the right fix for identity bleed — previous owner is cleared before the new page's data arrives.
  • useMemo-stabilizing the owner object (Library.tsx:260–271) is correct; without it the setCurrentOwner effect would fire on every render even when the owner hasn't changed.
  • Reusing ShareSelectionPanel for both owner (interactive, with reorder/remove/share) and recipient (read-only, with sequence numbers) via the readOnly prop is clean.
  • The IShareLinkView discriminated union for status makes the recipient page's error/happy-path branching explicit and exhaustive.
  • objectSlug appending the id is the right call — title edits can't break existing open-object URLs.
  • The optional catch-all route [[...object]].tsx enabling shallow push for the object overview (no library refetch, no flicker) is well-reasoned.
  • ShareSelectionContext.toggle silently ignoring appends past the cap is a correct belt-and-braces guard.

MaryWylde and others added 2 commits June 10, 2026 14:45
`currentLibraryId` was the last path segment of asPath, which on the new
object (`/[slug]`) and share (`/share/[token]`) routes is the slug/token,
not the username — breaking the sidebar's share-library URL and the
ownership fallback. Read `[username]` from the router params instead.
Also remove the temporary getMyLibrary debug effect that logged account
data and fired an extra request on every authenticated page load.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wire the previously no-op "Select shelf" button to bulk-toggle the whole
shelf into the share selection (new selectMany/removeMany context
helpers, capped at 21). It's owner- and public-only like the per-card
chip, and disabled when the shelf is empty or at the cap. Switching a
shelf to private now strips its objects from the selection, so a link is
never minted with objects the backend would reject.

Also relocate the new objectSlug/mapSharedObject utils from the legacy
src/utils tree to src/lib per AGENTS.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@MaryWylde MaryWylde merged commit 8ccf440 into dev Jun 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant