Close public-libraries Stage + Multi-library critical path
Status note (Aug 2026)
Multi-library app path is implemented and SQL has been applied. Library chrome lives on libraries; user_profiles keeps author fields only. Stage close does not require a public_library_index table.
Gemini auto-fill (title if default + description if empty) ships via library-ai Function. Public listing is available to MCP, site search, and the chatbot proxy in read mode.
A. Close Stage (Phase 1–2) — before /library/[username]
Do not start username aliases until this checklist is done.
Already done
/l/{token}SEO/agent document (JS handoff to Flow for humans)- Static
/og-library.png - Sitemap, anonymous
/librarylisting (directory UI also at/debug/libraries) - Search
type: "library", public MCP tools - Hide empty shared libraries from public lists
Remaining to mark Stage closed
| Item | Critical path decision |
|---|---|
| Branded per-library OG | Leave deferred. Closing the stage does not require uink-brand-cli / Blobs. Static OG is enough. |
public_library_index | Do not add a table. Keep listing via live user_profiles + bookmarks (already shipped). A SQL VIEW is optional sugar only — not required to close the stage. |
| Token regen cleanup | Old share_token naturally 404s on /l/ and drops out of live lists. Mark acceptance done. No purge job until Blobs OG returns. |
libraries-index.json snapshot | Skip for Stage close. Optional later if Function/CDN cost hurts. |
| Docs | Update PRD status to “Stage closed”; keep OG + username as later. |
Stage-close backend
Nothing new required for “index.” Directory, search, MCP, and sitemap already read share-enabled profiles and filter empty libraries in app/Function code.
Optional later (only if list queries get painful): a VIEW over the same tables — still not a second stored copy:
-- Optional convenience only — not a Stage-close blocker
create or replace view public.public_libraries_list as
select
up.share_token,
up.user_id,
coalesce(nullif(trim(up.library_title), ''), 'My Pattern Library') as title,
coalesce(nullif(trim(up.library_description), ''), 'A curated pattern library on PATTTTERNS.') as description,
case when up.public_show_author then up.public_display_name else null end as author_name,
up.public_show_author as show_author,
(select count(*)::int from public.bookmarks b where b.user_id = up.user_id) as pattern_count
from public.user_profiles up
where up.share_enabled = true
and up.share_token is not null
and exists (select 1 from public.bookmarks b where b.user_id = up.user_id);
Prefer no VIEW until multi-library cutover — then list from libraries the same live way.
Stage-close frontend
- No marketplace redesign required.
- Optional polish only: card titles already fall back; encourage owners to rename “My Pattern Library” (product copy, not a blocker).
Exit: PRD acceptance boxes for index + token regen checked; OG stays deferred; username still blocked.
B. Multi-library without refactoring everything
Constraint (from your screenshot)
Today one user ↔ one library on user_profiles. Directory shows generic “My Pattern Library” per person. You need N folders per user soon, without rewriting bookmarks, Flow, share, or discovery from scratch.
What NOT to do first
- Username URLs (
/library/[username]) — needs stable library identity + default/list semantics. - Full Prisma / new API layer — keep Supabase REST + RLS.
- Moving every consumer to M2M in one PR.
- Rebuilding Library Flow as a multi-root IA before
librariesexists.
Target model (same as MULTILIBRARY Phase 2 — keep it)
auth.users
│ 1:N
▼
libraries (title, description, share_token, share_enabled, is_default, position)
│
│ M:N via bookmark_on_library
▼
bookmarks (pattern payload — keep as today)
Author display fields (public_display_name, avatar, show_author) can stay on user_profiles (user-level), not duplicated per library.
Critical path — ordered
Step 1 — Schema + default migration (backend only)
- Create
libraries+bookmark_on_librarywith RLS (owner CRUD; anon read whenshare_enabled). - Backfill: one
is_defaultlibrary per existinguser_profiles, copy title/description/share_*. - Backfill join: every existing bookmark → default library.
- Do not drop
user_profileslibrary columns yet — dual-read possible.
Step 2 — Compatibility shim (backend + thin FE)
Change library-cloud.ts only (and Function readers: /l/, sitemap, MCP):
| API | Behavior after shim |
|---|---|
getMyLibraryProfile() | Load default libraries row |
updateMyLibraryProfile() | Update default library |
getPublicProfile(token) | libraries by share_token (+ join user_profiles for author) |
getPublicBookmarks | Via bookmark_on_library for that library’s user_id+library_id |
| Bookmark save/delete | Write bookmarks and ensure row on active library (default until switcher exists) |
Frontend /library keeps working unchanged: still “one library” UX = the default.
Exit: Zero UX change for users; data lives on libraries.
Step 3 — Multi-folder MVP (frontend + small API)
Minimum product:
- Library switcher on owner Flow (list my libraries; select active).
- Create library (title only → new
librariesrow, empty join). - Active library id in URL:
/library?lib={uuid}(owner) or keep session state; public stays/library?token=. - Save bookmark → active library’s
bookmark_on_library(pattern can later be added to multiple libs). - Share toggle / token remain per library (already fits
/l/{token}+ directory).
Avoid for MVP: drag between folders, nested folders, username routes, per-library avatars.
Step 4 — Cut over + cleanup
- Stop reading
user_profiles.library_*/share_*. - Drop or deprecate those columns.
- Recreate public lists on
libraries+ counts viabookmark_on_library(still live queries — no index table). - Then (later) username:
/library/[username]→ default library or directory of that user’s public libs.
C. Suggested sequencing (calendar sense)
Week A Close Stage: docs + mark live-list index/token-regen done (no new SQL table)
Week B libraries table + backfill + shim library-cloud + /l/ + MCP
Week C Switcher + create library + ?lib= + bookmark attach to active
Later Username aliases, branded OG, view counters, AI context
Why this is the best critical path
- Closes discovery Stage without waiting on OG, usernames, or a denormalized index table.
- Live RLS lists stay the listing surface; after cutover, same pattern on
libraries. - Shim-first means Flow/share/marketplace keep working while you get a second library for yourself.
- Token identity stays — multi-lib doesn’t force SEO URL redesign; each folder gets its own
share_token. - Username comes after
librariesexist so/library/pablohas a clear default (or list).
Risk to watch
- Bookmark save paths that bypass
library-cloud/bookmark-cloudmust also attach to active library. - Directory cards must key by
share_token/library_id, notuser_id(two cards from same author become normal). - Empty-library rule stays:
pattern_count > 0for public lists.